Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/client/src/client/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export function selectClientAuthMethod(clientInformation: OAuthClientInformation
* @param params - URL search parameters to modify
* @throws {Error} When required credentials are missing
*/
function applyClientAuthentication(
export function applyClientAuthentication(
method: ClientAuthMethod,
clientInformation: OAuthClientInformation,
headers: Headers,
Expand Down Expand Up @@ -333,7 +333,7 @@ function applyClientAuthentication(
/**
* Applies HTTP Basic authentication (RFC 6749 Section 2.3.1)
*/
function applyBasicAuth(clientId: string, clientSecret: string | undefined, headers: Headers): void {
export function applyBasicAuth(clientId: string, clientSecret: string | undefined, headers: Headers): void {
if (!clientSecret) {
throw new Error('client_secret_basic authentication requires a client_secret');
}
Expand All @@ -345,7 +345,7 @@ function applyBasicAuth(clientId: string, clientSecret: string | undefined, head
/**
* Applies POST body authentication (RFC 6749 Section 2.3.1)
*/
function applyPostAuth(clientId: string, clientSecret: string | undefined, params: URLSearchParams): void {
export function applyPostAuth(clientId: string, clientSecret: string | undefined, params: URLSearchParams): void {
params.set('client_id', clientId);
if (clientSecret) {
params.set('client_secret', clientSecret);
Expand All @@ -355,7 +355,7 @@ function applyPostAuth(clientId: string, clientSecret: string | undefined, param
/**
* Applies public client authentication (RFC 6749 Section 2.1)
*/
function applyPublicAuth(clientId: string, params: URLSearchParams): void {
export function applyPublicAuth(clientId: string, params: URLSearchParams): void {
params.set('client_id', clientId);
}

Expand Down Expand Up @@ -1200,7 +1200,7 @@ export function prepareAuthorizationCodeRequest(
* Internal helper to execute a token request with the given parameters.
* Used by {@linkcode exchangeAuthorization}, {@linkcode refreshAuthorization}, and {@linkcode fetchToken}.
*/
async function executeTokenRequest(
export async function executeTokenRequest(
authorizationServerUrl: string | URL,
{
metadata,
Expand Down
18 changes: 18 additions & 0 deletions packages/client/src/client/streamableHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EventSourceParserStream } from 'eventsource-parser/stream';

import type { AuthResult, OAuthClientProvider } from './auth.js';
import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js';
import type { TokenProvider } from './tokenProvider.js';

// Default reconnection options for StreamableHTTP connections
const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS: StreamableHTTPReconnectionOptions = {
Expand Down Expand Up @@ -98,6 +99,16 @@ export type StreamableHTTPClientTransportOptions = {
*/
authProvider?: OAuthClientProvider;

/**
* A simple token provider for bearer authentication.
*
* Use this instead of `authProvider` when tokens are managed externally
* (e.g., upfront auth, gateway/proxy patterns, service accounts).
*
* If both `authProvider` and `tokenProvider` are set, `authProvider` takes precedence.
*/
tokenProvider?: TokenProvider;

/**
* Customizes HTTP requests to the server.
*/
Expand Down Expand Up @@ -132,6 +143,7 @@ export class StreamableHTTPClientTransport implements Transport {
private _scope?: string;
private _requestInit?: RequestInit;
private _authProvider?: OAuthClientProvider;
private _tokenProvider?: TokenProvider;
private _fetch?: FetchLike;
private _fetchWithInit: FetchLike;
private _sessionId?: string;
Expand All @@ -152,6 +164,7 @@ export class StreamableHTTPClientTransport implements Transport {
this._scope = undefined;
this._requestInit = opts?.requestInit;
this._authProvider = opts?.authProvider;
this._tokenProvider = opts?.tokenProvider;
this._fetch = opts?.fetch;
this._fetchWithInit = createFetchWithInit(opts?.fetch, opts?.requestInit);
this._sessionId = opts?.sessionId;
Expand Down Expand Up @@ -190,6 +203,11 @@ export class StreamableHTTPClientTransport implements Transport {
if (tokens) {
headers['Authorization'] = `Bearer ${tokens.access_token}`;
}
} else if (this._tokenProvider) {
const token = await this._tokenProvider();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
}

if (this._sessionId) {
Expand Down
48 changes: 48 additions & 0 deletions packages/client/src/client/tokenProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Minimal interface for providing bearer tokens to MCP transports.
*
* Unlike `OAuthClientProvider` which assumes interactive browser-redirect OAuth,
* `TokenProvider` is a simple function that returns a token string.
* Use this for upfront auth, gateway/proxy patterns, service accounts,
* or any scenario where tokens are managed externally.
*
* @example
* ```typescript
* // Static token
* const provider: TokenProvider = async () => "my-api-token";
*
* // Token from secure storage with refresh
* const provider: TokenProvider = async () => {
* const token = await storage.getToken();
* if (isExpiringSoon(token)) {
* return (await refreshToken(token)).accessToken;
* }
* return token.accessToken;
* };
* ```
*/
export type TokenProvider = () => Promise<string | undefined>;

/**
* Wraps a fetch function to automatically inject Bearer authentication headers.
*
* @example
* ```typescript
* const authedFetch = withBearerAuth(async () => getStoredToken());
* const transport = new StreamableHTTPClientTransport(url, { fetch: authedFetch });
* ```
*/
export function withBearerAuth(
getToken: TokenProvider,
fetchFn: (url: string | URL, init?: RequestInit) => Promise<Response> = globalThis.fetch,
): (url: string | URL, init?: RequestInit) => Promise<Response> {
return async (url, init) => {
const token = await getToken();
if (token) {
const headers = new Headers(init?.headers);
headers.set("Authorization", `Bearer ${token}`);
return fetchFn(url, { ...init, headers });
}
return fetchFn(url, init);
};
}
1 change: 1 addition & 0 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './client/auth.js';
export * from './client/authExtensions.js';
export * from './client/tokenProvider.js';
export * from './client/client.js';
export * from './client/middleware.js';
export * from './client/sse.js';
Expand Down
Loading