Skip to content

Commit 2961101

Browse files
BREAKING: unify client auth around minimal AuthProvider interface
Transports now accept AuthProvider { token(), onUnauthorized() } instead of being typed as OAuthClientProvider. OAuthClientProvider extends AuthProvider, so built-in providers work unchanged — custom implementations add two methods (both TypeScript-enforced). Core changes: - New AuthProvider interface — transports only need token() + onUnauthorized(), not the full 21-member OAuth interface - OAuthClientProvider extends AuthProvider; onUnauthorized() is required (not optional) on OAuthClientProvider since OAuth providers that omit it lose all 401 recovery. The 4 built-in providers implement both methods, delegating to new handleOAuthUnauthorized helper. - Transports call authProvider.token() in _commonHeaders() — one code path, no precedence rules - Transports call authProvider.onUnauthorized() on 401, retry once — ~50 lines of inline OAuth orchestration removed per transport. Circuit breaker via _authRetryInFlight (reset in outer catch so transient onUnauthorized failures don't permanently disable retries). - Response body consumption deferred until after the onUnauthorized branch so custom implementations can read ctx.response.text() - WWW-Authenticate extraction guarded with headers.has() check (pre-existing inconsistency; the SSE connect path already did this) - finishAuth() and 403 upscoping gated on isOAuthClientProvider() - TokenProvider type + tokenProvider option deleted — subsumed by { token: async () => ... } as authProvider Simple case: { authProvider: { token: async () => apiKey } } — no class needed, TypeScript structural typing. auth() and authInternal() (227 LOC of OAuth orchestration) untouched. They still take OAuthClientProvider. Only the transport/provider boundary moved. See docs/migration.md and docs/migration-SKILL.md for before/after.
1 parent 9aea20f commit 2961101

19 files changed

+736
-553
lines changed
Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
---
2-
'@modelcontextprotocol/client': minor
2+
'@modelcontextprotocol/client': major
33
---
44

5-
Add `TokenProvider` for simple bearer-token authentication and export composable auth primitives
5+
Unify client auth around a minimal `AuthProvider` interface
66

7-
- New `TokenProvider` type — a minimal `() => Promise<string | undefined>` function interface for supplying bearer tokens. Use this instead of `OAuthClientProvider` when tokens are managed externally (gateway/proxy patterns, service accounts, upfront API tokens, or any scenario where the full OAuth redirect flow is not needed).
8-
- New `tokenProvider` option on `StreamableHTTPClientTransport` and `SSEClientTransport`. Called before every request to obtain a fresh token. If both `authProvider` and `tokenProvider` are set, `authProvider` takes precedence.
9-
- New `withBearerAuth(getToken, fetchFn?)` helper that wraps a fetch function to inject `Authorization: Bearer` headers — useful for composing with other fetch middleware.
10-
- Exported previously-internal auth helpers for building custom auth flows: `applyBasicAuth`, `applyPostAuth`, `applyPublicAuth`, `executeTokenRequest`.
7+
**Breaking:** Transport `authProvider` option now accepts the new minimal `AuthProvider` interface instead of being typed as `OAuthClientProvider`. `OAuthClientProvider` now extends `AuthProvider`, so most existing code continues to work — but custom implementations must add a `token()` method.
8+
9+
- New `AuthProvider` interface: `{ token(): Promise<string | undefined>; onUnauthorized?(ctx): Promise<void> }`. Transports call `token()` before every request and `onUnauthorized()` on 401 (then retry once).
10+
- `OAuthClientProvider` extends `AuthProvider`. Custom implementations must add `token()` (typically `return (await this.tokens())?.access_token`) and optionally `onUnauthorized()` (typically `return handleOAuthUnauthorized(this, ctx)`).
11+
- Built-in providers (`ClientCredentialsProvider`, `PrivateKeyJwtProvider`, `StaticPrivateKeyJwtProvider`, `CrossAppAccessProvider`) implement both methods — existing user code is unchanged.
12+
- New `handleOAuthUnauthorized(provider, ctx)` helper runs the standard OAuth flow from `onUnauthorized`.
13+
- New `isOAuthClientProvider()` type guard for gating OAuth-specific transport features like `finishAuth()`.
14+
- Transports no longer inline OAuth orchestration — ~50 lines of `auth()` calls, WWW-Authenticate parsing, and circuit-breaker state moved into `onUnauthorized()` implementations.
15+
- Exported previously-internal auth helpers for building custom flows: `applyBasicAuth`, `applyPostAuth`, `applyPublicAuth`, `executeTokenRequest`.
16+
17+
See `docs/migration.md` for before/after examples.

docs/client.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ A client connects to a server, discovers what it offers — tools, resources, pr
1313
The examples below use these imports. Adjust based on which features and transport you need:
1414

1515
```ts source="../examples/client/src/clientGuide.examples.ts#imports"
16-
import type { Prompt, Resource, TokenProvider, Tool } from '@modelcontextprotocol/client';
16+
import type { AuthProvider, Prompt, Resource, Tool } from '@modelcontextprotocol/client';
1717
import {
1818
applyMiddlewares,
1919
Client,
@@ -113,19 +113,19 @@ console.log(systemPrompt);
113113

114114
## Authentication
115115

116-
MCP servers can require authentication before accepting client connections (see [Authorization](https://modelcontextprotocol.io/specification/latest/basic/authorization) in the MCP specification). For servers that accept plain bearer tokens, pass a `tokenProvider` function to {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport}. For servers that require OAuth 2.0, pass an `authProvider` — the SDK provides built-in providers for common machine-to-machine flows, or you can implement the full {@linkcode @modelcontextprotocol/client!client/auth.OAuthClientProvider | OAuthClientProvider} interface for user-facing OAuth.
116+
MCP servers can require authentication before accepting client connections (see [Authorization](https://modelcontextprotocol.io/specification/latest/basic/authorization) in the MCP specification). Pass an {@linkcode @modelcontextprotocol/client!client/auth.AuthProvider | AuthProvider} to {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport}. The transport calls `token()` before every request and `onUnauthorized()` (if provided) on 401, then retries once.
117117

118-
### Token provider
118+
### Bearer tokens
119119

120-
For servers that accept bearer tokens managed outside the SDK — API keys, tokens from a gateway or proxy, service-account credentials, or tokens obtained through a separate auth flow — pass a {@linkcode @modelcontextprotocol/client!client/tokenProvider.TokenProvider | TokenProvider} function. It is called before every request, so it can handle expiry and refresh internally. If the server rejects the token with 401, the transport throws {@linkcode @modelcontextprotocol/client!client/auth.UnauthorizedError | UnauthorizedError} without retrying — catch it to invalidate any external cache and reconnect:
120+
For servers that accept bearer tokens managed outside the SDK — API keys, tokens from a gateway or proxy, service-account credentials — implement only `token()`. With no `onUnauthorized()`, a 401 throws {@linkcode @modelcontextprotocol/client!client/auth.UnauthorizedError | UnauthorizedError} immediately:
121121

122122
```ts source="../examples/client/src/clientGuide.examples.ts#auth_tokenProvider"
123-
const tokenProvider: TokenProvider = async () => getStoredToken();
123+
const authProvider: AuthProvider = { token: async () => getStoredToken() };
124124

125-
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { tokenProvider });
125+
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
126126
```
127127

128-
See [`simpleTokenProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleTokenProvider.ts) for a complete runnable example. For finer control, {@linkcode @modelcontextprotocol/client!client/tokenProvider.withBearerAuth | withBearerAuth} wraps a fetch function directly.
128+
See [`simpleTokenProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleTokenProvider.ts) for a complete runnable example.
129129

130130
### Client credentials
131131

0 commit comments

Comments
 (0)