Client OAuth
Purpose and Scope
This page explains how to sign an end user in from an MCP client built with the v2 TypeScript SDK. It is for interactive clients that can send the user to a browser and later receive an authorization-code callback. The SDK separates this from server-side authorization and from no-user service authentication: server protection belongs in serving authorization docs, while background jobs and service accounts use the machine-auth providers. For user OAuth, the central idea is that the HTTP transport owns request retries and token attachment, while your provider owns storage, redirects, and callback completion.
Sources: docs/clients/oauth.md, docs/clients/machine-auth.md
The OAuth flow begins at connection time. Create a client, create a Streamable HTTP transport for the MCP endpoint, and pass an OAuth provider as the transport authentication provider. When the protected server rejects the unauthenticated initialize attempt, the SDK performs discovery, registers or retrieves client information, builds the authorization URL, calls your redirect hook, and then rejects the connection with an unauthorized error. That rejection is not the end of the flow; it is the point where the user has left your application and is signing in out of band.
Sources: docs/clients/oauth.md, docs/clients/connect.md
Relevant Source Files
- docs/clients/oauth.md - Primary how-to for interactive OAuth, including the transport auth provider, provider responsibilities, callback state, discovery state, and connect-time unauthorized behavior.
- docs/clients/caching.md - Explains client response caching and user partitioning concerns that matter when authenticated clients share a backing cache.
- docs/clients/calling.md - Shows the post-auth client surface for listing tools, calling tools, reading resources, and handling client-visible results.
- docs/clients/connect.md - Defines the client/transport connection model, initialize handshake, server introspection, protocol negotiation note, and clean disconnect flow.
- docs/clients/machine-auth.md - Contrasts user OAuth with no-user authentication providers, issuer pinning, bearer tokens, and retry behavior on unauthorized responses.
- docs/clients/middleware.md - Documents fetch middleware, logging, and the OAuth-as-middleware option for Streamable HTTP clients.
Core Primitives
The main primitive is OAuthClientProvider, the storage and browser hand-off surface that the transport drives. A provider exposes redirect metadata, client metadata, stored client registration information, stored tokens, PKCE verifier state, CSRF state, and discovery state. The documentation stresses that dynamically registered client credentials should be keyed by issuer, because a client identifier registered with one authorization server must never be reused with another. The same provider is also where production applications should move tokens into secure storage rather than plain files or transient process memory.
Sources: docs/clients/oauth.md
A typical provider has a redirect URL and OAuth client metadata. The example metadata includes a client name, a loopback redirect URI, and an explicit native application type when the default heuristic would not match the deployment. The provider methods shown include reading and saving client information by issuer, reading and saving tokens, creating a random state value, saving discovery state before the redirect, and returning that discovery state during callback completion. Together, those methods let the SDK keep the protocol flow generic while letting the application choose persistence, browser launching, and callback routing.
Sources: docs/clients/oauth.md
Execution Flow
Start by constructing the client with a name and version, because the client still performs the normal initialize handshake once authorization succeeds. Then create a Streamable HTTP transport aimed at the server MCP endpoint and pass the provider in the transport options. Call connect and catch only the unauthorized condition that represents a started browser flow. With protocol-version negotiation enabled, the same unauthorized condition can be wrapped in a broader SDK error as the cause, so robust code should check both the direct unauthorized error and the documented wrapped form when handling connect-time authentication.
Sources: docs/clients/oauth.md, docs/clients/connect.md
const provider = new MyOAuthProvider();
const client = new Client({ name: 'my-app', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(new URL('https://api.example.com/mcp'), {
authProvider: provider
});
try {
await client.connect(transport);
} catch (error) {
if (!(error instanceof UnauthorizedError)) throw error;
// The provider was asked to redirect the end user.
}After the user returns to your callback endpoint, complete the callback leg with the same provider state that started the flow. The important safety checks are the state value and the authorization-server binding. The state value binds the callback to the redirect that your application initiated, reducing CSRF risk. The discovery state records what authorization server discovery resolved before redirect, so the SDK can verify that the authorization code is exchanged at the same authorization server. That issuer binding is the user-OAuth counterpart to machine-auth issuer pinning with expected issuer checks.
Sources: docs/clients/oauth.md, docs/clients/machine-auth.md
API Components and Options
| Component | Role | Notes |
|---|---|---|
| Client | Holds one MCP connection | Connects through a transport and later exposes tool, resource, prompt, capability, and instruction helpers. |
| StreamableHTTPClientTransport | HTTP transport | Accepts authProvider and fetch options for OAuth, machine auth, and middleware. |
| OAuthClientProvider | User OAuth provider contract | Stores client registration, tokens, PKCE, discovery state, and redirects the user. |
| UnauthorizedError | Flow-start signal | Thrown by connect when authorization is required and the user has been redirected. |
| SdkError with cause | Negotiation-aware error shape | Protocol-version negotiation may wrap the unauthorized cause. |
| OAuthDiscoveryState | Callback binding state | Saved before redirect and reused to ensure the code is exchanged with the expected issuer. |
| withOAuth | Middleware-style OAuth | Expresses OAuth as a fetch middleware layer for Streamable HTTP requests. |
OAuth can also be composed with client middleware. Middleware wraps the fetch used by the transport, so it can see initialize, notification, and normal request traffic. The docs show custom request tagging, ordered composition, built-in logging, and an OAuth middleware helper that adds authorization, reauthenticates on a single unauthorized response, and retries once. This is useful when an application already standardizes transport fetch behavior through middleware, but it is distinct from server framework middleware packages such as Express, Hono, and Node HTTP adapters.
Sources: docs/clients/middleware.md
Auth Boundaries, Caching, and Resource Access
OAuth determines who the server believes the caller is; it does not change the post-connect client API. Once connected, the same client can list tools, call tools, read resources, fetch prompts, and use autocomplete according to the server capabilities discovered during initialize. A failed tool call can still be a normal result with an error flag, while protocol-level failures throw. This distinction matters for authenticated applications because authorization failures, validation failures, and tool-domain failures may need different user messages even when they appear during the same user task.
Sources: docs/clients/calling.md, docs/clients/connect.md
Be careful when combining authenticated clients with response caching. Every client has a response cache, and servers can mark cacheable results with freshness hints. Shared cache stores should be partitioned by a stable identity from the authorization context, such as the authenticated subject, so private results for one user are not served to another. Public cache scope is appropriate only when the server result is identical for every caller. Resource indicators, issuer binding, and cache partitioning all serve the same architectural goal: keep credentials, audiences, and cached data scoped to the correct MCP endpoint and principal.
Sources: docs/clients/caching.md, docs/clients/oauth.md
Choosing Between User OAuth and Machine Auth
Use the interactive OAuth provider when a human user is present and can consent or sign in through a browser. Use machine-auth providers when no user is present. The machine-auth guide shows client credentials, bring-your-own bearer token, private-key JWT, and cross-app access providers, all passed through the same transport authProvider option. It also documents retry behavior after a 401 and issuer pinning for secrets. That shared transport shape means an application can keep its connection and calling code similar while swapping the authentication provider according to whether the principal is a user, service account, or enterprise delegated identity.
Sources: docs/clients/machine-auth.md, docs/clients/oauth.md
Next Steps
After OAuth succeeds, read the client connection guide to inspect server capabilities and instructions, then move to the calling guide for tools, resources, and prompts. If the client shares caches across users, review caching before enabling a shared response cache store. If the application needs request tracing, custom headers, or centralized retry behavior, review client middleware and decide whether provider-based OAuth or middleware-based OAuth better fits the transport layer. For services without a browser callback, switch to the machine-auth patterns instead of forcing the user OAuth provider into a no-user environment.
Sources: docs/clients/connect.md, docs/clients/calling.md, docs/clients/caching.md, docs/clients/middleware.md, docs/clients/machine-auth.md