typescript-sdk/examples/oauth/simpleOAuthClientProvider.ts
Felix Weinberger 1823aae891
Implement MCP 2026-07-28 (#2286)
Co-authored-by: Matt <77928207+mattzcarey@users.noreply.github.com>
Co-authored-by: Konstantin Konstantinov <KKonstantinov@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-26 14:01:10 +01:00

98 lines
3.2 KiB
TypeScript

import type {
OAuthClientInformationMixed,
OAuthClientMetadata,
OAuthClientProvider,
OAuthDiscoveryState,
OAuthTokens
} from '@modelcontextprotocol/client';
import { validateClientMetadataUrl } from '@modelcontextprotocol/client';
/**
* In-memory OAuth client provider for demonstration purposes.
* In production, you should persist tokens and client credentials securely.
*
* Tokens and client credentials are stored as single-slot blobs. The SDK stamps an
* `issuer` field onto every value it saves; round-tripping the blob unchanged means
* a credential issued by one authorization server is never reused at another (the
* SDK reads the stamp back as a key-not-found and re-registers / re-authorizes).
* To hold credentials for several authorization servers at once, key your storage
* on the `ctx.issuer` argument instead.
*/
export class InMemoryOAuthClientProvider implements OAuthClientProvider {
private _clientInformation?: OAuthClientInformationMixed;
private _tokens?: OAuthTokens;
private _codeVerifier?: string;
private _discoveryState?: OAuthDiscoveryState;
constructor(
private readonly _redirectUrl: string | URL,
private readonly _clientMetadata: OAuthClientMetadata,
onRedirect?: (url: URL) => void,
public readonly clientMetadataUrl?: string
) {
// Validate clientMetadataUrl at construction time (fail-fast)
validateClientMetadataUrl(clientMetadataUrl);
this._onRedirect =
onRedirect ||
(url => {
console.log(`Redirect to: ${url.toString()}`);
});
}
private _onRedirect: (url: URL) => void;
get redirectUrl(): string | URL {
return this._redirectUrl;
}
get clientMetadata(): OAuthClientMetadata {
return this._clientMetadata;
}
clientInformation(): OAuthClientInformationMixed | undefined {
return this._clientInformation;
}
saveClientInformation(clientInformation: OAuthClientInformationMixed): void {
this._clientInformation = clientInformation;
}
tokens(): OAuthTokens | undefined {
return this._tokens;
}
saveTokens(tokens: OAuthTokens): void {
this._tokens = tokens;
}
redirectToAuthorization(authorizationUrl: URL): void {
this._onRedirect(authorizationUrl);
}
saveCodeVerifier(codeVerifier: string): void {
this._codeVerifier = codeVerifier;
}
codeVerifier(): string {
if (!this._codeVerifier) {
throw new Error('No code verifier saved');
}
return this._codeVerifier;
}
saveDiscoveryState(state: OAuthDiscoveryState): void {
this._discoveryState = state;
}
discoveryState(): OAuthDiscoveryState | undefined {
return this._discoveryState;
}
invalidateCredentials(scope: 'all' | 'client' | 'tokens' | 'verifier' | 'discovery'): void {
if (scope === 'all' || scope === 'client') this._clientInformation = undefined;
if (scope === 'all' || scope === 'tokens') this._tokens = undefined;
if (scope === 'all' || scope === 'verifier') this._codeVerifier = undefined;
if (scope === 'all' || scope === 'discovery') this._discoveryState = undefined;
}
}