You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 — but custom
implementations must add token().
Key changes:
- New AuthProvider interface in auth.ts — transports only need token()
+ optional onUnauthorized(), not the full 21-member OAuth interface
- OAuthClientProvider extends AuthProvider; the 4 built-in providers
implement token() + onUnauthorized() (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
- finishAuth() and 403 upscoping gated on isOAuthClientProvider() guard
- TokenProvider type + tokenProvider option deleted — subsumed by
{ token: async () => ... } as authProvider
See migration.md for before/after. This is an alternative design
presented for discussion alongside the additive approach in the
earlier commits — the team should pick one shape before merge.
Add `TokenProvider` for simple bearer-token authentication and export composable auth primitives
5
+
Unify client auth around a minimal `AuthProvider` interface
6
6
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.
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.
117
117
118
-
### Token provider
118
+
### Bearer tokens
119
119
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 401throws {@linkcode@modelcontextprotocol/client!client/auth.UnauthorizedError | UnauthorizedError} immediately:
const transport =newStreamableHTTPClientTransport(newURL('http://localhost:3000/mcp'), { tokenProvider });
125
+
const transport =newStreamableHTTPClientTransport(newURL('http://localhost:3000/mcp'), { authProvider });
126
126
```
127
127
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.
Copy file name to clipboardExpand all lines: docs/migration.md
+94-61Lines changed: 94 additions & 61 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -252,6 +252,7 @@ server.registerTool('ping', {
252
252
```
253
253
254
254
This applies to:
255
+
255
256
-`inputSchema` in `registerTool()`
256
257
-`outputSchema` in `registerTool()`
257
258
-`argsSchema` in `registerPrompt()`
@@ -339,25 +340,21 @@ Common method string replacements:
339
340
340
341
### `Protocol.request()`, `ctx.mcpReq.send()`, and `Client.callTool()` no longer take a schema parameter
341
342
342
-
The public `Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` methods no longer accept a Zod result schema argument. The SDK now resolves the correct result schema internally based on the method name. This means you no longer need to import result schemas like `CallToolResultSchema` or `ElicitResultSchema` when making requests.
343
+
The public `Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` methods no longer accept a Zod result schema argument. The SDK now resolves the correct result schema internally based on the method name. This means you no longer need to import result schemas
344
+
like `CallToolResultSchema` or `ElicitResultSchema` when making requests.
Previously, `ErrorCode.RequestTimeout` (-32001) and `ErrorCode.ConnectionClosed` (-32000) were used for local timeout/connection errors. However, these errors never cross the wire as JSON-RPC responses - they are rejected locally. Using protocol error codes for local errors was semantically inconsistent.
643
+
Previously, `ErrorCode.RequestTimeout` (-32001) and `ErrorCode.ConnectionClosed` (-32000) were used for local timeout/connection errors. However, these errors never cross the wire as JSON-RPC responses - they are rejected locally. Using protocol error codes for local errors was
644
+
semantically inconsistent.
650
645
651
646
The new design:
652
647
653
648
-`ProtocolError` with `ProtocolErrorCode`: For errors that are serialized and sent as JSON-RPC error responses
654
649
-`SdkError` with `SdkErrorCode`: For local errors that are thrown/rejected locally and never leave the SDK
655
650
651
+
### Client `authProvider` unified around `AuthProvider`
652
+
653
+
Transport `authProvider` options now accept the minimal `AuthProvider` interface rather than being typed as `OAuthClientProvider`. `OAuthClientProvider` extends `AuthProvider`, so built-in providers and most existing code continue to work unchanged — but custom
654
+
`OAuthClientProvider` implementations must add a `token()` method.
655
+
656
+
**What changed:** transports now call `authProvider.token()` before every request (instead of `authProvider.tokens()?.access_token`), and call `authProvider.onUnauthorized()` on 401 (instead of inlining OAuth orchestration). One code path handles both simple bearer tokens and
657
+
full OAuth.
658
+
659
+
**If you implement `OAuthClientProvider` directly** (the interactive browser-redirect pattern), add:
660
+
661
+
```ts
662
+
classMyProviderimplementsOAuthClientProvider {
663
+
// ...existing 8 required members...
664
+
665
+
// Required: return the current access token
666
+
async token():Promise<string|undefined> {
667
+
return (awaitthis.tokens())?.access_token;
668
+
}
669
+
670
+
// Optional but recommended: runs the OAuth flow on 401
**If you use `ClientCredentialsProvider`, `PrivateKeyJwtProvider`, `StaticPrivateKeyJwtProvider`, or `CrossAppAccessProvider`** — no change. These already implement both methods.
678
+
679
+
**If you have simple bearer tokens** (API keys, gateway tokens, externally-managed tokens), you can now skip `OAuthClientProvider` entirely:
680
+
681
+
```ts
682
+
// Before: had to implement 8 OAuthClientProvider members with no-op stubs
683
+
// After:
684
+
const transport =newStreamableHTTPClientTransport(url, {
0 commit comments