cxxmcp 1.1.6
C++ MCP SDK
Loading...
Searching...
No Matches
client_orchestrator.hpp
Go to the documentation of this file.
1// Copyright (c) 2025 [caomengxuan666]
2
3#pragma once
4
5#include <chrono>
6#include <cstdint>
7#include <functional>
8#include <memory>
9#include <optional>
10#include <string>
11#include <utility>
12#include <vector>
13
17#include "cxxmcp/auth/pkce.hpp"
18#include "cxxmcp/auth/token.hpp"
19#include "cxxmcp/auth/types.hpp"
22
30
31namespace mcp::auth {
32
39 public:
40 virtual ~OAuthClientCallback() = default;
41
46 const std::string& url) = 0;
47
56 std::chrono::seconds timeout) = 0;
57};
58
62 std::string resource_url;
64 std::string client_name = "MCP Client";
66 ScopeList scopes;
70 std::string redirect_uri;
72 std::chrono::seconds callback_timeout{300};
74 std::chrono::seconds refresh_skew{30};
76 std::optional<std::string> client_id;
77};
78
94 public:
107 OAuthMetadataEndpoint& metadata_endpoint,
108 OAuthTokenEndpoint& token_endpoint, PkceGenerator& pkce_generator,
109 OAuthClientRegistrationEndpoint* registration_endpoint = nullptr)
110 : config_(std::move(config)),
111 callback_(callback),
112 metadata_endpoint_(metadata_endpoint),
113 token_endpoint_(token_endpoint),
114 pkce_generator_(pkce_generator),
115 registration_endpoint_(registration_endpoint) {}
116
126 // Step 1: Discover metadata.
127 MetadataDiscoveryExecutor discovery(metadata_endpoint_);
128 auto discovery_result = discovery.discover(config_.resource_url);
129 if (!discovery_result.has_value()) {
130 return core::unexpected(discovery_result.error());
131 }
132
133 metadata_ = discovery_result->authorization_server;
134 protected_resource_ = discovery_result->protected_resource;
135
136 if (!metadata_.has_value()) {
137 return core::unexpected(make_oauth_error(
138 OAuthErrorCode::kMetadataDiscoveryFailed,
139 "no authorization server metadata discovered for resource",
140 config_.resource_url));
141 }
142
143 // Step 2: Configure the AuthorizationManager.
144 manager_.set_resource(config_.resource_url);
145 manager_.set_authorization_server_metadata(*metadata_);
146 manager_.set_token_endpoint(std::shared_ptr<OAuthTokenEndpoint>(
147 &token_endpoint_, [](OAuthTokenEndpoint*) {}));
148 if (registration_endpoint_ != nullptr) {
149 manager_.set_client_registration_endpoint(
150 std::shared_ptr<OAuthClientRegistrationEndpoint>(
151 registration_endpoint_, [](OAuthClientRegistrationEndpoint*) {}));
152 }
153
154 // Step 3: Configure client ID (DCR or pre-configured).
155 if (!config_.client_id.has_value()) {
156 ClientIdConfigurationOptions client_options;
157 client_options.client_name = config_.client_name;
158 client_options.redirect_uri = config_.redirect_uri;
159 client_options.scopes = config_.scopes;
160
161 auto client_config =
162 manager_.configure_client_for_authorization(client_options);
163 if (!client_config.has_value()) {
164 return core::unexpected(client_config.error());
165 }
166 } else {
167 auto client_config = manager_.configure_client_id(
168 *config_.client_id, config_.redirect_uri, config_.scopes);
169 if (!client_config.has_value()) {
170 return core::unexpected(client_config.error());
171 }
172 }
173
174 // Step 4: Generate PKCE and state.
175 auto pkce = pkce_generator_.create_s256();
176 if (!pkce.has_value()) {
177 return core::unexpected(pkce.error());
178 }
179
180 auto state = generate_state();
181
182 // Step 5: Build authorization URL and present to user.
183 AuthorizationSessionRequest session_request;
184 session_request.client.client_name = config_.client_name;
185 session_request.client.redirect_uri = manager_.client_config().redirect_uri;
186 session_request.client.scopes = config_.scopes;
187 session_request.pkce = *pkce;
188 session_request.state = state;
189 auto session_result = manager_.start_session(std::move(session_request));
190 if (!session_result.has_value()) {
191 return core::unexpected(session_result.error());
192 }
193
194 auto present_result = callback_.present_authorization_url(
195 session_result->authorization_url());
196 if (!present_result.has_value()) {
197 return core::unexpected(present_result.error());
198 }
199
200 // Step 6: Wait for authorization code.
201 auto callback_result =
202 callback_.wait_for_callback(config_.callback_timeout);
203 if (!callback_result.has_value()) {
204 return core::unexpected(callback_result.error());
205 }
206
207 auto& [code, callback_state] = *callback_result;
208
209 // Step 7: Exchange code for tokens.
210 auto tokens = manager_.exchange_authorization_code(code, callback_state);
211 if (!tokens.has_value()) {
212 return core::unexpected(tokens.error());
213 }
214
215 return *tokens;
216 }
217
225 return manager_.get_access_token(config_.refresh_skew);
226 }
227
237 const HttpResponseMetadata& response) {
238 return manager_.refresh_after_unauthorized_response(response);
239 }
240
243 return manager_.lifecycle_state();
244 }
245
248 return manager_.client_config();
249 }
250
252 const std::optional<AuthorizationServerMetadata>& metadata() const {
253 return metadata_;
254 }
255
257 const std::optional<ProtectedResourceMetadata>& protected_resource_metadata()
258 const {
259 return protected_resource_;
260 }
261
262 private:
263 static std::string generate_state() {
264 // 16 random bytes as hex = 32 characters.
265 // Uses the same approach as the lifecycle detail namespace.
266 static constexpr char kHex[] = "0123456789abcdef";
267 std::string result;
268 result.reserve(32);
269 // Use a simple PRNG seeded from system clock for state.
270 // The state is verified by comparison, not by cryptographic strength,
271 // but we still want reasonable uniqueness.
272 auto seed = static_cast<std::uint64_t>(
273 SystemClock::now().time_since_epoch().count());
274 for (int i = 0; i < 32; ++i) {
275 seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
276 result.push_back(kHex[(seed >> 16) & 0xF]);
277 }
278 return result;
279 }
280
281 OAuthClientOrchestratorConfig config_;
282 OAuthClientCallback& callback_;
283 OAuthMetadataEndpoint& metadata_endpoint_;
284 OAuthTokenEndpoint& token_endpoint_;
285 PkceGenerator& pkce_generator_;
286 OAuthClientRegistrationEndpoint* registration_endpoint_;
287
288 AuthorizationManager manager_;
289 std::optional<AuthorizationServerMetadata> metadata_;
290 std::optional<ProtectedResourceMetadata> protected_resource_;
291};
292
293class OAuthClientFlowBuilder;
294
302 public:
303 OAuthClientFlow(const OAuthClientFlow&) = delete;
304 OAuthClientFlow& operator=(const OAuthClientFlow&) = delete;
306 OAuthClientFlow& operator=(OAuthClientFlow&&) = delete;
307
309 core::Result<TokenSet> authorize() { return orchestrator_.authorize(); }
310
313 return orchestrator_.get_access_token();
314 }
315
318 const HttpResponseMetadata& response) {
319 return orchestrator_.handle_auth_response(response);
320 }
321
324 return orchestrator_.lifecycle_state();
325 }
326
329 return orchestrator_.client_config();
330 }
331
333 const std::optional<AuthorizationServerMetadata>& metadata() const {
334 return orchestrator_.metadata();
335 }
336
338 const std::optional<ProtectedResourceMetadata>& protected_resource_metadata()
339 const {
340 return orchestrator_.protected_resource_metadata();
341 }
342
343 private:
344 friend class OAuthClientFlowBuilder;
345
348 std::shared_ptr<OAuthClientCallback> callback,
349 std::shared_ptr<OAuthMetadataEndpoint> metadata_endpoint,
350 std::shared_ptr<OAuthTokenEndpoint> token_endpoint,
351 std::shared_ptr<PkceGenerator> pkce_generator,
352 std::shared_ptr<OAuthClientRegistrationEndpoint> registration_endpoint)
353 : config_(std::move(config)),
354 callback_(std::move(callback)),
355 metadata_endpoint_(std::move(metadata_endpoint)),
356 token_endpoint_(std::move(token_endpoint)),
357 pkce_generator_(std::move(pkce_generator)),
358 registration_endpoint_(std::move(registration_endpoint)),
359 orchestrator_(
360 config_, *callback_, *metadata_endpoint_, *token_endpoint_,
361 *pkce_generator_,
362 registration_endpoint_ ? registration_endpoint_.get() : nullptr) {}
363
364 OAuthClientOrchestratorConfig config_;
365 std::shared_ptr<OAuthClientCallback> callback_;
366 std::shared_ptr<OAuthMetadataEndpoint> metadata_endpoint_;
367 std::shared_ptr<OAuthTokenEndpoint> token_endpoint_;
368 std::shared_ptr<PkceGenerator> pkce_generator_;
369 std::shared_ptr<OAuthClientRegistrationEndpoint> registration_endpoint_;
370 OAuthClientOrchestrator orchestrator_;
371};
372
375 public:
376 OAuthClientFlowBuilder() = default;
377
378 explicit OAuthClientFlowBuilder(std::string resource_url) {
379 config_.resource_url = std::move(resource_url);
380 }
381
384 config_.resource_url = std::move(value);
385 return *this;
386 }
387
390 config_.client_name = std::move(value);
391 return *this;
392 }
393
395 OAuthClientFlowBuilder& scopes(ScopeList value) {
396 config_.scopes = std::move(value);
397 return *this;
398 }
399
402 config_.redirect_uri = std::move(value);
403 return *this;
404 }
405
407 OAuthClientFlowBuilder& callback_timeout(std::chrono::seconds value) {
408 config_.callback_timeout = value;
409 return *this;
410 }
411
413 OAuthClientFlowBuilder& refresh_skew(std::chrono::seconds value) {
414 config_.refresh_skew = value;
415 return *this;
416 }
417
419 OAuthClientFlowBuilder& client_id(std::string value) {
420 config_.client_id = std::move(value);
421 return *this;
422 }
423
426 callback_ = non_owning(value);
427 return *this;
428 }
429
431 OAuthClientFlowBuilder& callback(std::shared_ptr<OAuthClientCallback> value) {
432 callback_ = std::move(value);
433 return *this;
434 }
435
438 metadata_endpoint_ = non_owning(value);
439 return *this;
440 }
441
444 std::shared_ptr<OAuthMetadataEndpoint> value) {
445 metadata_endpoint_ = std::move(value);
446 return *this;
447 }
448
451 token_endpoint_ = non_owning(value);
452 return *this;
453 }
454
457 std::shared_ptr<OAuthTokenEndpoint> value) {
458 token_endpoint_ = std::move(value);
459 return *this;
460 }
461
464 metadata_endpoint_ =
465 std::make_shared<HttpOAuthMetadataEndpoint>(std::move(get));
466 token_endpoint_ = std::make_shared<HttpOAuthTokenEndpoint>(std::move(post));
467 return *this;
468 }
469
472 pkce_generator_ = non_owning(value);
473 return *this;
474 }
475
477 OAuthClientFlowBuilder& pkce_generator(std::shared_ptr<PkceGenerator> value) {
478 pkce_generator_ = std::move(value);
479 return *this;
480 }
481
485 registration_endpoint_ = non_owning(value);
486 return *this;
487 }
488
491 std::shared_ptr<OAuthClientRegistrationEndpoint> value) {
492 registration_endpoint_ = std::move(value);
493 return *this;
494 }
495
498 return std::move(*this).build();
499 }
500
503 auto validation = validate();
504 if (!validation.has_value()) {
505 return core::unexpected(validation.error());
506 }
507
508 return std::unique_ptr<OAuthClientFlow>(new OAuthClientFlow(
509 std::move(config_), std::move(callback_), std::move(metadata_endpoint_),
510 std::move(token_endpoint_), std::move(pkce_generator_),
511 std::move(registration_endpoint_)));
512 }
513
514 private:
515 template <typename T>
516 static std::shared_ptr<T> non_owning(T& value) {
517 return std::shared_ptr<T>(&value, [](T*) {});
518 }
519
520 core::Result<core::Unit> validate() const {
521 if (config_.resource_url.empty()) {
522 return core::unexpected(make_oauth_error(OAuthErrorCode::kInvalidRequest,
523 "resource_url is required"));
524 }
525 if (!callback_) {
526 return core::unexpected(
527 make_oauth_error(OAuthErrorCode::kInvalidRequest,
528 "OAuth client callback is required"));
529 }
530 if (!metadata_endpoint_) {
531 return core::unexpected(
532 make_oauth_error(OAuthErrorCode::kInvalidRequest,
533 "OAuth metadata endpoint is required"));
534 }
535 if (!token_endpoint_) {
536 return core::unexpected(make_oauth_error(
537 OAuthErrorCode::kInvalidRequest, "OAuth token endpoint is required"));
538 }
539 if (!pkce_generator_) {
540 return core::unexpected(make_oauth_error(OAuthErrorCode::kInvalidRequest,
541 "PKCE generator is required"));
542 }
543 return core::Unit{};
544 }
545
546 OAuthClientOrchestratorConfig config_;
547 std::shared_ptr<OAuthClientCallback> callback_;
548 std::shared_ptr<OAuthMetadataEndpoint> metadata_endpoint_;
549 std::shared_ptr<OAuthTokenEndpoint> token_endpoint_;
550 std::shared_ptr<PkceGenerator> pkce_generator_;
551 std::shared_ptr<OAuthClientRegistrationEndpoint> registration_endpoint_;
552};
553
558
560inline OAuthClientFlowBuilder oauth_client_flow(std::string resource_url) {
561 return OAuthClientFlowBuilder(std::move(resource_url));
562}
563
564} // namespace mcp::auth
Shared lightweight value types for cxxmcp auth contracts.
Execute protected-resource and authorization-server discovery.
Definition lifecycle.hpp:696
Callback interface for the OAuth client orchestrator.
Definition client_orchestrator.hpp:38
virtual core::Result< std::pair< std::string, std::string > > wait_for_callback(std::chrono::seconds timeout)=0
Wait for the authorization code callback.
virtual core::Result< core::Unit > present_authorization_url(const std::string &url)=0
Present the authorization URL to the user (e.g.
Fluent builder for the common OAuth browser authorization flow.
Definition client_orchestrator.hpp:374
OAuthClientFlowBuilder & registration_endpoint(std::shared_ptr< OAuthClientRegistrationEndpoint > value)
Set an owning dynamic client registration endpoint.
Definition client_orchestrator.hpp:490
core::Result< std::unique_ptr< OAuthClientFlow > > build() &
Build the owning flow wrapper.
Definition client_orchestrator.hpp:497
OAuthClientFlowBuilder & redirect_uri(std::string value)
Set the redirect URI used by the authorization code flow.
Definition client_orchestrator.hpp:401
OAuthClientFlowBuilder & resource_url(std::string value)
Set the MCP protected resource URL.
Definition client_orchestrator.hpp:383
OAuthClientFlowBuilder & metadata_endpoint(OAuthMetadataEndpoint &value)
Set a non-owning metadata endpoint reference.
Definition client_orchestrator.hpp:437
OAuthClientFlowBuilder & registration_endpoint(OAuthClientRegistrationEndpoint &value)
Set a non-owning dynamic client registration endpoint reference.
Definition client_orchestrator.hpp:483
OAuthClientFlowBuilder & callback(std::shared_ptr< OAuthClientCallback > value)
Set an owning application callback.
Definition client_orchestrator.hpp:431
OAuthClientFlowBuilder & pkce_generator(PkceGenerator &value)
Set a non-owning PKCE generator reference.
Definition client_orchestrator.hpp:471
OAuthClientFlowBuilder & client_name(std::string value)
Set the display name used during dynamic client registration.
Definition client_orchestrator.hpp:389
OAuthClientFlowBuilder & scopes(ScopeList value)
Set requested OAuth scopes.
Definition client_orchestrator.hpp:395
OAuthClientFlowBuilder & http_endpoints(OAuthHttpGet get, OAuthHttpPost post)
Set the default HTTP metadata and token endpoints.
Definition client_orchestrator.hpp:463
core::Result< std::unique_ptr< OAuthClientFlow > > build() &&
Build the owning flow wrapper.
Definition client_orchestrator.hpp:502
OAuthClientFlowBuilder & metadata_endpoint(std::shared_ptr< OAuthMetadataEndpoint > value)
Set an owning metadata endpoint.
Definition client_orchestrator.hpp:443
OAuthClientFlowBuilder & callback_timeout(std::chrono::seconds value)
Set the maximum wait time for the authorization callback.
Definition client_orchestrator.hpp:407
OAuthClientFlowBuilder & pkce_generator(std::shared_ptr< PkceGenerator > value)
Set an owning PKCE generator.
Definition client_orchestrator.hpp:477
OAuthClientFlowBuilder & refresh_skew(std::chrono::seconds value)
Set the proactive token refresh skew.
Definition client_orchestrator.hpp:413
OAuthClientFlowBuilder & token_endpoint(std::shared_ptr< OAuthTokenEndpoint > value)
Set an owning token endpoint.
Definition client_orchestrator.hpp:456
OAuthClientFlowBuilder & client_id(std::string value)
Use a preconfigured client_id and skip dynamic registration.
Definition client_orchestrator.hpp:419
OAuthClientFlowBuilder & token_endpoint(OAuthTokenEndpoint &value)
Set a non-owning token endpoint reference.
Definition client_orchestrator.hpp:450
OAuthClientFlowBuilder & callback(OAuthClientCallback &value)
Set a non-owning application callback reference.
Definition client_orchestrator.hpp:425
Built common browser + PKCE OAuth client flow.
Definition client_orchestrator.hpp:301
const std::optional< ProtectedResourceMetadata > & protected_resource_metadata() const
Get the discovered protected resource metadata.
Definition client_orchestrator.hpp:338
core::Result< TokenSet > authorize()
Execute discovery, client setup, authorization, and token exchange.
Definition client_orchestrator.hpp:309
const std::optional< AuthorizationServerMetadata > & metadata() const
Get the discovered authorization server metadata.
Definition client_orchestrator.hpp:333
core::Result< std::string > get_access_token()
Return a currently valid access token, refreshing when needed.
Definition client_orchestrator.hpp:312
core::Result< OAuthRefreshRetryResult > handle_auth_response(const HttpResponseMetadata &response)
Handle an unauthorized HTTP response and decide whether to retry.
Definition client_orchestrator.hpp:317
const OAuthClientConfig & client_config() const
Get the current client configuration.
Definition client_orchestrator.hpp:328
OAuthLifecycleState lifecycle_state() const
Get the current lifecycle state.
Definition client_orchestrator.hpp:323
High-level OAuth 2.1 client orchestrator.
Definition client_orchestrator.hpp:93
const std::optional< ProtectedResourceMetadata > & protected_resource_metadata() const
Get the discovered protected resource metadata.
Definition client_orchestrator.hpp:257
core::Result< TokenSet > authorize()
Execute the full OAuth authorization flow.
Definition client_orchestrator.hpp:125
OAuthLifecycleState lifecycle_state() const
Get the current lifecycle state.
Definition client_orchestrator.hpp:242
OAuthClientOrchestrator(OAuthClientOrchestratorConfig config, OAuthClientCallback &callback, OAuthMetadataEndpoint &metadata_endpoint, OAuthTokenEndpoint &token_endpoint, PkceGenerator &pkce_generator, OAuthClientRegistrationEndpoint *registration_endpoint=nullptr)
Construct an orchestrator with all required dependencies.
Definition client_orchestrator.hpp:105
core::Result< std::string > get_access_token()
Get a valid access token, refreshing if necessary.
Definition client_orchestrator.hpp:224
core::Result< OAuthRefreshRetryResult > handle_auth_response(const HttpResponseMetadata &response)
Handle a 401/403 response and attempt recovery.
Definition client_orchestrator.hpp:236
const OAuthClientConfig & client_config() const
Get the current client configuration.
Definition client_orchestrator.hpp:247
const std::optional< AuthorizationServerMetadata > & metadata() const
Get the discovered authorization server metadata.
Definition client_orchestrator.hpp:252
Dynamic client registration network boundary.
Definition registration.hpp:69
OAuth metadata network boundary.
Definition lifecycle.hpp:351
Token exchange and refresh network boundary.
Definition lifecycle.hpp:293
Public wrapper around the SDK's private PKCE implementation.
Definition pkce.hpp:30
OAuthClientFlowBuilder oauth_client_flow()
Start building the common OAuth browser authorization flow.
Definition client_orchestrator.hpp:555
Default HTTP metadata endpoint parser for OAuth discovery.
std::function< core::Result< OAuthHttpResponse >(const MetadataFetchRequest &)> OAuthHttpGet
Application or SDK transport adapter used for metadata GET calls.
Definition http_metadata_endpoint.hpp:27
Default OAuth token endpoint form encoder and JSON response parser.
std::function< core::Result< OAuthHttpResponse >(const OAuthHttpRequest &)> OAuthHttpPost
Application or SDK transport adapter used for token POST calls.
Definition http_token_endpoint.hpp:31
OAuth authorization lifecycle contracts and lightweight state logic.
OAuthLifecycleState
Runtime state for the interactive OAuth lifecycle.
Definition lifecycle.hpp:306
core::Error make_oauth_error(OAuthErrorCode code, std::string message, std::string detail={})
Build an auth-category lifecycle error.
Definition lifecycle.hpp:50
PKCE contracts for OAuth authorization-code flows.
Shared result and error primitives used by the public cxxmcp SDK.
tl::expected< T, Error > Result
Alias for the SDK result type.
Definition result.hpp:64
Full client-id selection plus authorization URL request.
Definition lifecycle.hpp:861
Options used when selecting a client_id before authorization.
Definition registration.hpp:98
Transport-neutral HTTP response descriptor used by auth helpers.
Definition types.hpp:35
Public OAuth client configuration used by lifecycle helpers.
Definition types.hpp:41
Configuration for the OAuth client orchestrator.
Definition client_orchestrator.hpp:60
ScopeList scopes
Scopes to request. Empty = let the server decide.
Definition client_orchestrator.hpp:66
std::chrono::seconds refresh_skew
How long before token expiry to proactively refresh.
Definition client_orchestrator.hpp:74
std::string redirect_uri
Redirect URI for the authorization code flow.
Definition client_orchestrator.hpp:70
std::string client_name
Client name for dynamic client registration.
Definition client_orchestrator.hpp:64
std::chrono::seconds callback_timeout
Maximum time to wait for the authorization code callback.
Definition client_orchestrator.hpp:72
std::optional< std::string > client_id
Pre-configured client_id. When set, DCR is skipped.
Definition client_orchestrator.hpp:76
std::string resource_url
The MCP resource URL (e.g. "https://example.com/mcp").
Definition client_orchestrator.hpp:62
OAuth token models and storage contracts.
WWW-Authenticate challenge models and parser boundary.