Connect a `Client` to a server's bearer-gated streamable-HTTP app, completing OAuth in process. Yields the connected `Client` and the `HeadlessOAuth` whose `authorize_url` records what the SDK put on the authorize request. `on_request` records every HTTP request the underlying `httpx.As
(
server: Server,
*,
provider: InMemoryAuthorizationServerProvider,
settings: AuthSettings | None = None,
storage: InMemoryTokenStorage | None = None,
client_metadata: OAuthClientMetadata | None = None,
client_metadata_url: str | None = None,
headless: HeadlessOAuth | None = None,
auth: httpx.Auth | None = None,
verify_tokens: bool = True,
app_shim: Callable[[ASGIApp], ASGIApp] | None = None,
on_request: Callable[[httpx.Request], None] | None = None,
)
| 395 | |
| 396 | @asynccontextmanager |
| 397 | async def connect_with_oauth( |
| 398 | server: Server, |
| 399 | *, |
| 400 | provider: InMemoryAuthorizationServerProvider, |
| 401 | settings: AuthSettings | None = None, |
| 402 | storage: InMemoryTokenStorage | None = None, |
| 403 | client_metadata: OAuthClientMetadata | None = None, |
| 404 | client_metadata_url: str | None = None, |
| 405 | headless: HeadlessOAuth | None = None, |
| 406 | auth: httpx.Auth | None = None, |
| 407 | verify_tokens: bool = True, |
| 408 | app_shim: Callable[[ASGIApp], ASGIApp] | None = None, |
| 409 | on_request: Callable[[httpx.Request], None] | None = None, |
| 410 | ) -> AsyncIterator[tuple[Client, HeadlessOAuth]]: |
| 411 | """Connect a `Client` to a server's bearer-gated streamable-HTTP app, completing OAuth in process. |
| 412 | |
| 413 | Yields the connected `Client` and the `HeadlessOAuth` whose `authorize_url` records what the |
| 414 | SDK put on the authorize request. `on_request` records every HTTP request the underlying |
| 415 | `httpx.AsyncClient` issues, including those yielded from inside the auth flow. |
| 416 | |
| 417 | `headless`: supply a pre-configured `HeadlessOAuth` to override the callback behaviour |
| 418 | (state mismatch, error redirects). `verify_tokens=False` mounts the MCP endpoint without |
| 419 | the bearer middleware so a flow driven by a shimmed 401 completes regardless of the granted |
| 420 | scopes. `app_shim` wraps the built Starlette app before it reaches the bridge transport, |
| 421 | for tests that need to intercept or rewrite specific server responses. |
| 422 | |
| 423 | `auth`: supply a pre-built `httpx.Auth` (such as `ClientCredentialsOAuthProvider`) to use |
| 424 | instead of constructing the default `OAuthClientProvider`; in that case `storage`, |
| 425 | `client_metadata`, `client_metadata_url`, and `headless` are unused (the yielded |
| 426 | `HeadlessOAuth` is never invoked and its `authorize_url` stays None). |
| 427 | """ |
| 428 | settings = settings if settings is not None else auth_settings() |
| 429 | storage = storage if storage is not None else InMemoryTokenStorage() |
| 430 | client_metadata = client_metadata if client_metadata is not None else oauth_client_metadata() |
| 431 | headless = headless if headless is not None else HeadlessOAuth() |
| 432 | |
| 433 | oauth = ( |
| 434 | auth |
| 435 | if auth is not None |
| 436 | else OAuthClientProvider( |
| 437 | server_url=f"{BASE_URL}/mcp", |
| 438 | client_metadata=client_metadata, |
| 439 | storage=storage, |
| 440 | redirect_handler=headless.redirect_handler, |
| 441 | callback_handler=headless.callback_handler, |
| 442 | client_metadata_url=client_metadata_url, |
| 443 | ) |
| 444 | ) |
| 445 | |
| 446 | app: ASGIApp = server.streamable_http_app( |
| 447 | auth=settings, |
| 448 | token_verifier=ProviderTokenVerifier(provider) if verify_tokens else None, |
| 449 | auth_server_provider=provider, |
| 450 | transport_security=NO_DNS_REBINDING_PROTECTION, |
| 451 | ) |
| 452 | if app_shim is not None: |
| 453 | app = app_shim(app) |
| 454 |