UNPKG

@modelcontextprotocol/sdk

Version:

Model Context Protocol implementation for TypeScript

270 lines 9.41 kB
/** * OAuth provider extensions for specialized authentication flows. * * This module provides ready-to-use OAuthClientProvider implementations * for common machine-to-machine authentication scenarios. */ /** * Helper to produce a private_key_jwt client authentication function. * * Usage: * const addClientAuth = createPrivateKeyJwtAuth({ issuer, subject, privateKey, alg, audience? }); * // pass addClientAuth as provider.addClientAuthentication implementation */ export function createPrivateKeyJwtAuth(options) { return async (_headers, params, url, metadata) => { var _a, _b, _c; // Lazy import to avoid heavy dependency unless used if (typeof globalThis.crypto === 'undefined') { throw new TypeError('crypto is not available, please ensure you add have Web Crypto API support for older Node.js versions (see https://github.com/modelcontextprotocol/typescript-sdk#nodejs-web-crypto-globalthiscrypto-compatibility)'); } const jose = await import('jose'); const audience = String((_b = (_a = options.audience) !== null && _a !== void 0 ? _a : metadata === null || metadata === void 0 ? void 0 : metadata.issuer) !== null && _b !== void 0 ? _b : url); const lifetimeSeconds = (_c = options.lifetimeSeconds) !== null && _c !== void 0 ? _c : 300; const now = Math.floor(Date.now() / 1000); const jti = `${Date.now()}-${Math.random().toString(36).slice(2)}`; const baseClaims = { iss: options.issuer, sub: options.subject, aud: audience, exp: now + lifetimeSeconds, iat: now, jti }; const claims = options.claims ? { ...baseClaims, ...options.claims } : baseClaims; // Import key for the requested algorithm const alg = options.alg; let key; if (typeof options.privateKey === 'string') { if (alg.startsWith('RS') || alg.startsWith('ES') || alg.startsWith('PS')) { key = await jose.importPKCS8(options.privateKey, alg); } else if (alg.startsWith('HS')) { key = new TextEncoder().encode(options.privateKey); } else { throw new Error(`Unsupported algorithm ${alg}`); } } else if (options.privateKey instanceof Uint8Array) { if (alg.startsWith('HS')) { key = options.privateKey; } else { // Assume PKCS#8 DER in Uint8Array for asymmetric algorithms key = await jose.importPKCS8(new TextDecoder().decode(options.privateKey), alg); } } else { // Treat as JWK key = await jose.importJWK(options.privateKey, alg); } // Sign JWT const assertion = await new jose.SignJWT(claims) .setProtectedHeader({ alg, typ: 'JWT' }) .setIssuer(options.issuer) .setSubject(options.subject) .setAudience(audience) .setIssuedAt(now) .setExpirationTime(now + lifetimeSeconds) .setJti(jti) .sign(key); params.set('client_assertion', assertion); params.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'); }; } /** * OAuth provider for client_credentials grant with client_secret_basic authentication. * * This provider is designed for machine-to-machine authentication where * the client authenticates using a client_id and client_secret. * * @example * const provider = new ClientCredentialsProvider({ * clientId: 'my-client', * clientSecret: 'my-secret' * }); * * const transport = new StreamableHTTPClientTransport(serverUrl, { * authProvider: provider * }); */ export class ClientCredentialsProvider { constructor(options) { var _a; this._clientInfo = { client_id: options.clientId, client_secret: options.clientSecret }; this._clientMetadata = { client_name: (_a = options.clientName) !== null && _a !== void 0 ? _a : 'client-credentials-client', redirect_uris: [], grant_types: ['client_credentials'], token_endpoint_auth_method: 'client_secret_basic' }; } get redirectUrl() { return undefined; } get clientMetadata() { return this._clientMetadata; } clientInformation() { return this._clientInfo; } saveClientInformation(info) { this._clientInfo = info; } tokens() { return this._tokens; } saveTokens(tokens) { this._tokens = tokens; } redirectToAuthorization() { throw new Error('redirectToAuthorization is not used for client_credentials flow'); } saveCodeVerifier() { // Not used for client_credentials } codeVerifier() { throw new Error('codeVerifier is not used for client_credentials flow'); } prepareTokenRequest(scope) { const params = new URLSearchParams({ grant_type: 'client_credentials' }); if (scope) params.set('scope', scope); return params; } } /** * OAuth provider for client_credentials grant with private_key_jwt authentication. * * This provider is designed for machine-to-machine authentication where * the client authenticates using a signed JWT assertion (RFC 7523 Section 2.2). * * @example * const provider = new PrivateKeyJwtProvider({ * clientId: 'my-client', * privateKey: pemEncodedPrivateKey, * algorithm: 'RS256' * }); * * const transport = new StreamableHTTPClientTransport(serverUrl, { * authProvider: provider * }); */ export class PrivateKeyJwtProvider { constructor(options) { var _a; this._clientInfo = { client_id: options.clientId }; this._clientMetadata = { client_name: (_a = options.clientName) !== null && _a !== void 0 ? _a : 'private-key-jwt-client', redirect_uris: [], grant_types: ['client_credentials'], token_endpoint_auth_method: 'private_key_jwt' }; this.addClientAuthentication = createPrivateKeyJwtAuth({ issuer: options.clientId, subject: options.clientId, privateKey: options.privateKey, alg: options.algorithm, lifetimeSeconds: options.jwtLifetimeSeconds }); } get redirectUrl() { return undefined; } get clientMetadata() { return this._clientMetadata; } clientInformation() { return this._clientInfo; } saveClientInformation(info) { this._clientInfo = info; } tokens() { return this._tokens; } saveTokens(tokens) { this._tokens = tokens; } redirectToAuthorization() { throw new Error('redirectToAuthorization is not used for client_credentials flow'); } saveCodeVerifier() { // Not used for client_credentials } codeVerifier() { throw new Error('codeVerifier is not used for client_credentials flow'); } prepareTokenRequest(scope) { const params = new URLSearchParams({ grant_type: 'client_credentials' }); if (scope) params.set('scope', scope); return params; } } /** * OAuth provider for client_credentials grant with a static private_key_jwt assertion. * * This provider mirrors {@link PrivateKeyJwtProvider} but instead of constructing and * signing a JWT on each request, it accepts a pre-built JWT assertion string and * uses it directly for authentication. */ export class StaticPrivateKeyJwtProvider { constructor(options) { var _a; this._clientInfo = { client_id: options.clientId }; this._clientMetadata = { client_name: (_a = options.clientName) !== null && _a !== void 0 ? _a : 'static-private-key-jwt-client', redirect_uris: [], grant_types: ['client_credentials'], token_endpoint_auth_method: 'private_key_jwt' }; const assertion = options.jwtBearerAssertion; this.addClientAuthentication = async (_headers, params) => { params.set('client_assertion', assertion); params.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'); }; } get redirectUrl() { return undefined; } get clientMetadata() { return this._clientMetadata; } clientInformation() { return this._clientInfo; } saveClientInformation(info) { this._clientInfo = info; } tokens() { return this._tokens; } saveTokens(tokens) { this._tokens = tokens; } redirectToAuthorization() { throw new Error('redirectToAuthorization is not used for client_credentials flow'); } saveCodeVerifier() { // Not used for client_credentials } codeVerifier() { throw new Error('codeVerifier is not used for client_credentials flow'); } prepareTokenRequest(scope) { const params = new URLSearchParams({ grant_type: 'client_credentials' }); if (scope) params.set('scope', scope); return params; } } //# sourceMappingURL=auth-extensions.js.map