Download patches/@modelcontextprotocol%2Fsdk@1.29.0.patch from SaylorTwift/opencode: direct link, hf CLI and curl.
- Browser
- Download file 33.2 kB
-
https://huggingface.co/SaylorTwift/opencode/resolve/main/patches/%40modelcontextprotocol%252Fsdk%401.29.0.patch
- Command line
-
hf download hf://SaylorTwift/opencode/patches/@modelcontextprotocol%2Fsdk@1.29.0.patch
-
curl -L -o @modelcontextprotocol%2Fsdk@1.29.0.patch https://huggingface.co/SaylorTwift/opencode/resolve/main/patches/%40modelcontextprotocol%252Fsdk%401.29.0.patch
33.2 kB
| diff --git a/dist/cjs/client/index.d.ts b/dist/cjs/client/index.d.ts | |
| index 1822bf749aec71d2bb295083d832114ee187bb67..58b859a7b32222fb5cb9f2011fdc5d010f3d05fb 100644 | |
| --- a/dist/cjs/client/index.d.ts | |
| +++ b/dist/cjs/client/index.d.ts | |
| export declare class Client<RequestT extends Request = Request, NotificationT ex | |
| * | |
| * For task-based execution with streaming behavior, use client.experimental.tasks.callToolStream() instead. | |
| */ | |
| + callTool(params: CallToolRequest['params'], resultSchema?: undefined, options?: RequestOptions): Promise<SchemaOutput<typeof CallToolResultSchema>>; | |
| + callTool<T extends typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema>(params: CallToolRequest['params'], resultSchema: T, options?: RequestOptions): Promise<SchemaOutput<T>>; | |
| callTool(params: CallToolRequest['params'], resultSchema?: typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema, options?: RequestOptions): Promise<{ | |
| [x: string]: unknown; | |
| content: ({ | |
| diff --git a/dist/esm/client/index.d.ts b/dist/esm/client/index.d.ts | |
| index 1822bf749aec71d2bb295083d832114ee187bb67..58b859a7b32222fb5cb9f2011fdc5d010f3d05fb 100644 | |
| --- a/dist/esm/client/index.d.ts | |
| +++ b/dist/esm/client/index.d.ts | |
| export declare class Client<RequestT extends Request = Request, NotificationT ex | |
| * | |
| * For task-based execution with streaming behavior, use client.experimental.tasks.callToolStream() instead. | |
| */ | |
| + callTool(params: CallToolRequest['params'], resultSchema?: undefined, options?: RequestOptions): Promise<SchemaOutput<typeof CallToolResultSchema>>; | |
| + callTool<T extends typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema>(params: CallToolRequest['params'], resultSchema: T, options?: RequestOptions): Promise<SchemaOutput<T>>; | |
| callTool(params: CallToolRequest['params'], resultSchema?: typeof CallToolResultSchema | typeof CompatibilityCallToolResultSchema, options?: RequestOptions): Promise<{ | |
| [x: string]: unknown; | |
| content: ({ | |
| diff --git a/dist/cjs/client/index.js b/dist/cjs/client/index.js | |
| index 6ac1da14dc7f6211ae70f7711c124b76098816d8..adb5b7bd45514a406a0f7e40b64631c101584c84 100644 | |
| --- a/dist/cjs/client/index.js | |
| +++ b/dist/cjs/client/index.js | |
| class Client extends protocol_js_1.Protocol { | |
| } | |
| async connect(transport, options) { | |
| await super.connect(transport); | |
| + transport.onsessionexpired = async () => { | |
| + await this._initialize(transport); | |
| + }; | |
| // When transport sessionId is already set this means we are trying to reconnect. | |
| // In this case we don't need to initialize again. | |
| if (transport.sessionId !== undefined) { | |
| return; | |
| } | |
| try { | |
| - const result = await this.request({ | |
| - method: 'initialize', | |
| - params: { | |
| - protocolVersion: types_js_1.LATEST_PROTOCOL_VERSION, | |
| - capabilities: this._capabilities, | |
| - clientInfo: this._clientInfo | |
| - } | |
| - }, types_js_1.InitializeResultSchema, options); | |
| - if (result === undefined) { | |
| - throw new Error(`Server sent invalid initialize result: ${result}`); | |
| - } | |
| - if (!types_js_1.SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) { | |
| - throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`); | |
| - } | |
| - this._serverCapabilities = result.capabilities; | |
| - this._serverVersion = result.serverInfo; | |
| - // HTTP transports must set the protocol version in each header after initialization. | |
| - if (transport.setProtocolVersion) { | |
| - transport.setProtocolVersion(result.protocolVersion); | |
| - } | |
| - this._instructions = result.instructions; | |
| - await this.notification({ | |
| - method: 'notifications/initialized' | |
| - }); | |
| - // Set up list changed handlers now that we know server capabilities | |
| - if (this._pendingListChangedConfig) { | |
| - this._setupListChangedHandlers(this._pendingListChangedConfig); | |
| - this._pendingListChangedConfig = undefined; | |
| - } | |
| + await this._initialize(transport, options); | |
| } | |
| catch (error) { | |
| // Disconnect if initialization fails. | |
| class Client extends protocol_js_1.Protocol { | |
| throw error; | |
| } | |
| } | |
| + async _initialize(transport, options) { | |
| + const result = await this.request({ | |
| + method: 'initialize', | |
| + params: { | |
| + protocolVersion: types_js_1.LATEST_PROTOCOL_VERSION, | |
| + capabilities: this._capabilities, | |
| + clientInfo: this._clientInfo | |
| + } | |
| + }, types_js_1.InitializeResultSchema, options); | |
| + if (result === undefined) { | |
| + throw new Error(`Server sent invalid initialize result: ${result}`); | |
| + } | |
| + if (!types_js_1.SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) { | |
| + throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`); | |
| + } | |
| + this._serverCapabilities = result.capabilities; | |
| + this._serverVersion = result.serverInfo; | |
| + // HTTP transports must set the protocol version in each header after initialization. | |
| + if (transport.setProtocolVersion) { | |
| + transport.setProtocolVersion(result.protocolVersion); | |
| + } | |
| + this._instructions = result.instructions; | |
| + await this.notification({ | |
| + method: 'notifications/initialized' | |
| + }); | |
| + // Set up list changed handlers now that we know server capabilities | |
| + if (this._pendingListChangedConfig) { | |
| + this._setupListChangedHandlers(this._pendingListChangedConfig); | |
| + this._pendingListChangedConfig = undefined; | |
| + } | |
| + } | |
| /** | |
| * After initialization has completed, this will be populated with the server's reported capabilities. | |
| */ | |
| class Client extends protocol_js_1.Protocol { | |
| * Called after listTools() to pre-compile validators for better performance. | |
| */ | |
| - cacheToolMetadata(tools) { | |
| - this._cachedToolOutputValidators.clear(); | |
| - this._cachedKnownTaskTools.clear(); | |
| - this._cachedRequiredTaskTools.clear(); | |
| + cacheToolMetadata(tools, reset = true) { | |
| + if (reset) { | |
| + this._cachedToolOutputValidators.clear(); | |
| + this._cachedKnownTaskTools.clear(); | |
| + this._cachedRequiredTaskTools.clear(); | |
| + } | |
| for (const tool of tools) { | |
| // If the tool has an outputSchema, create and cache the validator | |
| if (tool.outputSchema) { | |
| class Client extends protocol_js_1.Protocol { | |
| async listTools(params, options) { | |
| const result = await this.request({ method: 'tools/list', params }, types_js_1.ListToolsResultSchema, options); | |
| // Cache the tools and their output schemas for future validation | |
| - this.cacheToolMetadata(result.tools); | |
| + this.cacheToolMetadata(result.tools, params?.cursor === undefined); | |
| return result; | |
| } | |
| /** | |
| diff --git a/dist/cjs/client/streamableHttp.js b/dist/cjs/client/streamableHttp.js | |
| index a29a7d3a0f14d9cd800ef5b296485237350c666f..c362ae5fe6c62c8c8eae7e2e61de1eedff5443c9 100644 | |
| --- a/dist/cjs/client/streamableHttp.js | |
| +++ b/dist/cjs/client/streamableHttp.js | |
| class StreamableHTTPClientTransport { | |
| if (!event.event || event.event === 'message') { | |
| try { | |
| const message = types_js_1.JSONRPCMessageSchema.parse(JSON.parse(event.data)); | |
| - if ((0, types_js_1.isJSONRPCResultResponse)(message)) { | |
| + if ((0, types_js_1.isJSONRPCResultResponse)(message) || (0, types_js_1.isJSONRPCErrorResponse)(message)) { | |
| // Mark that we received a response - no need to reconnect for this request | |
| receivedResponse = true; | |
| if (replayMessageId !== undefined) { | |
| class StreamableHTTPClientTransport { | |
| this.onclose?.(); | |
| } | |
| async send(message, options) { | |
| + return this._send(message, options, false); | |
| + } | |
| + async _recoverSession(expiredSessionId) { | |
| + if (this._sessionRecovery) { | |
| + await this._sessionRecovery; | |
| + return true; | |
| + } | |
| + if (this._sessionId !== expiredSessionId) | |
| + return true; | |
| + this._sessionId = undefined; | |
| + this._sessionRecovery = Promise.resolve().then(() => this.onsessionexpired?.()); | |
| try { | |
| + await this._sessionRecovery; | |
| + } | |
| + catch (error) { | |
| + this._sessionId = undefined; | |
| + await this.close(); | |
| + throw error; | |
| + } | |
| + finally { | |
| + this._sessionRecovery = undefined; | |
| + } | |
| + return true; | |
| + } | |
| + async _send(message, options, isSessionRetry) { | |
| + try { | |
| + if (this._sessionRecovery && !(0, types_js_1.isInitializeRequest)(message) && !(0, types_js_1.isInitializedNotification)(message)) { | |
| + await this._sessionRecovery; | |
| + if (options?.isRequestActive?.() === false) { | |
| + throw new Error('Request is no longer active'); | |
| + } | |
| + } | |
| const { resumptionToken, onresumptiontoken } = options || {}; | |
| if (resumptionToken) { | |
| // If we have at last event ID, we need to reconnect the SSE stream | |
| class StreamableHTTPClientTransport { | |
| return; | |
| } | |
| const headers = await this._commonHeaders(); | |
| + const requestSessionId = headers.get('mcp-session-id') ?? undefined; | |
| headers.set('content-type', 'application/json'); | |
| headers.set('accept', 'application/json, text/event-stream'); | |
| const init = { | |
| class StreamableHTTPClientTransport { | |
| const response = await (this._fetch ?? fetch)(this._url, init); | |
| // Handle session ID received during initialization | |
| const sessionId = response.headers.get('mcp-session-id'); | |
| - if (sessionId) { | |
| + if (sessionId && (requestSessionId === undefined || this._sessionId === requestSessionId)) { | |
| this._sessionId = sessionId; | |
| } | |
| if (!response.ok) { | |
| const text = await response.text().catch(() => null); | |
| + if (response.status === 404 && requestSessionId && !isSessionRetry && !(0, types_js_1.isInitializedNotification)(message)) { | |
| + const recovered = await this._recoverSession(requestSessionId); | |
| + if (options?.isRequestActive?.() === false) { | |
| + throw new Error('Request is no longer active'); | |
| + } | |
| + if (recovered) { | |
| + return this._send(message, options, true); | |
| + } | |
| + } | |
| if (response.status === 401 && this._authProvider) { | |
| // Prevent infinite recursion when server returns 401 after successful auth | |
| if (this._hasCompletedAuthFlow) { | |
| class StreamableHTTPClientTransport { | |
| // Mark that we completed auth flow | |
| this._hasCompletedAuthFlow = true; | |
| // Purposely _not_ awaited, so we don't call onerror twice | |
| - return this.send(message); | |
| + return this._send(message, options, isSessionRetry); | |
| } | |
| if (response.status === 403 && this._authProvider) { | |
| const { resourceMetadataUrl, scope, error } = (0, auth_js_1.extractWWWAuthenticateParams)(response); | |
| class StreamableHTTPClientTransport { | |
| if (result !== 'AUTHORIZED') { | |
| throw new auth_js_1.UnauthorizedError(); | |
| } | |
| - return this.send(message); | |
| + return this._send(message, options, isSessionRetry); | |
| } | |
| } | |
| throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`); | |
| diff --git a/dist/cjs/shared/protocol.js b/dist/cjs/shared/protocol.js | |
| index 3617e787f0ba70447c99501aee7aa67584d89758..4a96d6a0328fa348b96f3869ab7e0bb77538182b 100644 | |
| --- a/dist/cjs/shared/protocol.js | |
| +++ b/dist/cjs/shared/protocol.js | |
| class Protocol { | |
| } | |
| else { | |
| // No related task - send through transport normally | |
| - this._transport.send(jsonrpcRequest, { relatedRequestId, resumptionToken, onresumptiontoken }).catch(error => { | |
| + this._transport.send(jsonrpcRequest, { | |
| + relatedRequestId, | |
| + resumptionToken, | |
| + onresumptiontoken, | |
| + isRequestActive: () => this._responseHandlers.has(messageId) | |
| + }).catch(error => { | |
| this._cleanupTimeout(messageId); | |
| reject(error); | |
| }); | |
| diff --git a/dist/cjs/client/auth.d.ts b/dist/cjs/client/auth.d.ts | |
| index f4363ce7c94fbddf0e1d5943b1b26682bdbaa40e..e7dd57096e4f056bcd735d5081433beea1b32f04 100644 | |
| --- a/dist/cjs/client/auth.d.ts | |
| +++ b/dist/cjs/client/auth.d.ts | |
| export declare function parseErrorResponse(input: Response | string): Promise<OA | |
| * @returns A Promise that resolves to an OAuthError instance | |
| */ | |
| export declare function parseErrorResponse(input: Response | string): Promise<OAuthError>; | |
| +/** | |
| + * Selects scopes per the MCP spec and augments them for refresh token support. | |
| + */ | |
| +export declare function determineScope(options: { | |
| + requestedScope?: string; | |
| + resourceMetadata?: OAuthProtectedResourceMetadata; | |
| + authServerMetadata?: AuthorizationServerMetadata; | |
| + clientMetadata: OAuthClientMetadata; | |
| +}): string | undefined; | |
| /** | |
| * Orchestrates the full auth flow with a server. | |
| * | |
| diff --git a/dist/cjs/client/auth.js b/dist/cjs/client/auth.js | |
| index c2e4fa91d26f5336889f6afa416147db75fc4872..178d7cfd96412d53bc14bbc13a8f76c11f727ee7 100644 | |
| --- a/dist/cjs/client/auth.js | |
| +++ b/dist/cjs/client/auth.js | |
| exports.UnauthorizedError = void 0; | |
| exports.selectClientAuthMethod = selectClientAuthMethod; | |
| exports.parseErrorResponse = parseErrorResponse; | |
| exports.auth = auth; | |
| +exports.determineScope = determineScope; | |
| exports.isHttpsUrl = isHttpsUrl; | |
| exports.selectResourceURL = selectResourceURL; | |
| exports.extractWWWAuthenticateParams = extractWWWAuthenticateParams; | |
| async function auth(provider, options) { | |
| throw error; | |
| } | |
| } | |
| +/** | |
| + * Selects scopes per the MCP spec and augments them for refresh token support. | |
| + */ | |
| +function determineScope({ requestedScope, resourceMetadata, authServerMetadata, clientMetadata }) { | |
| + let effectiveScope = requestedScope || resourceMetadata?.scopes_supported?.join(' ') || clientMetadata.scope; | |
| + if (effectiveScope && | |
| + authServerMetadata?.scopes_supported?.includes('offline_access') && | |
| + !effectiveScope.split(' ').includes('offline_access') && | |
| + clientMetadata.grant_types?.includes('refresh_token')) { | |
| + effectiveScope = `${effectiveScope} offline_access`; | |
| + } | |
| + return effectiveScope; | |
| +} | |
| async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) { | |
| // Check if the provider has cached discovery state to skip discovery | |
| const cachedState = await provider.discoveryState?.(); | |
| async function authInternal(provider, { serverUrl, authorizationCode, scope, res | |
| }); | |
| } | |
| const resource = await selectResourceURL(serverUrl, provider, resourceMetadata); | |
| - // Apply scope selection strategy (SEP-835): | |
| - // 1. WWW-Authenticate scope (passed via `scope` param) | |
| - // 2. PRM scopes_supported | |
| - // 3. Client metadata scope (user-configured fallback) | |
| - // The resolved scope is used consistently for both DCR and the authorization request. | |
| - const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(' ') || provider.clientMetadata.scope; | |
| + const resolvedScope = determineScope({ | |
| + requestedScope: scope, | |
| + resourceMetadata, | |
| + authServerMetadata: metadata, | |
| + clientMetadata: provider.clientMetadata | |
| + }); | |
| // Handle client registration if needed | |
| let clientInformation = await Promise.resolve(provider.clientInformation()); | |
| if (!clientInformation) { | |
| async function startAuthorization(authorizationServerUrl, { metadata, clientInfo | |
| if (scope) { | |
| authorizationUrl.searchParams.set('scope', scope); | |
| } | |
| - if (scope?.includes('offline_access')) { | |
| + if (scope?.split(' ').includes('offline_access')) { | |
| // if the request includes the OIDC-only "offline_access" scope, | |
| // we need to set the prompt to "consent" to ensure the user is prompted to grant offline access | |
| // https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess | |
| diff --git a/dist/esm/client/auth.d.ts b/dist/esm/client/auth.d.ts | |
| index f4363ce7c94fbddf0e1d5943b1b26682bdbaa40e..e7dd57096e4f056bcd735d5081433beea1b32f04 100644 | |
| --- a/dist/esm/client/auth.d.ts | |
| +++ b/dist/esm/client/auth.d.ts | |
| export declare function parseErrorResponse(input: Response | string): Promise<OA | |
| * @returns A Promise that resolves to an OAuthError instance | |
| */ | |
| export declare function parseErrorResponse(input: Response | string): Promise<OAuthError>; | |
| +/** | |
| + * Selects scopes per the MCP spec and augments them for refresh token support. | |
| + */ | |
| +export declare function determineScope(options: { | |
| + requestedScope?: string; | |
| + resourceMetadata?: OAuthProtectedResourceMetadata; | |
| + authServerMetadata?: AuthorizationServerMetadata; | |
| + clientMetadata: OAuthClientMetadata; | |
| +}): string | undefined; | |
| /** | |
| * Orchestrates the full auth flow with a server. | |
| * | |
| diff --git a/dist/esm/client/auth.js b/dist/esm/client/auth.js | |
| index e183040fc2bba22ca1ccc784984f3310854403b7..d367661e580ee61a96654f7af78b2af61dcad98b 100644 | |
| --- a/dist/esm/client/auth.js | |
| +++ b/dist/esm/client/auth.js | |
| export async function auth(provider, options) { | |
| throw error; | |
| } | |
| } | |
| +/** | |
| + * Selects scopes per the MCP spec and augments them for refresh token support. | |
| + */ | |
| +export function determineScope({ requestedScope, resourceMetadata, authServerMetadata, clientMetadata }) { | |
| + let effectiveScope = requestedScope || resourceMetadata?.scopes_supported?.join(' ') || clientMetadata.scope; | |
| + if (effectiveScope && | |
| + authServerMetadata?.scopes_supported?.includes('offline_access') && | |
| + !effectiveScope.split(' ').includes('offline_access') && | |
| + clientMetadata.grant_types?.includes('refresh_token')) { | |
| + effectiveScope = `${effectiveScope} offline_access`; | |
| + } | |
| + return effectiveScope; | |
| +} | |
| async function authInternal(provider, { serverUrl, authorizationCode, scope, resourceMetadataUrl, fetchFn }) { | |
| // Check if the provider has cached discovery state to skip discovery | |
| const cachedState = await provider.discoveryState?.(); | |
| async function authInternal(provider, { serverUrl, authorizationCode, scope, res | |
| }); | |
| } | |
| const resource = await selectResourceURL(serverUrl, provider, resourceMetadata); | |
| - // Apply scope selection strategy (SEP-835): | |
| - // 1. WWW-Authenticate scope (passed via `scope` param) | |
| - // 2. PRM scopes_supported | |
| - // 3. Client metadata scope (user-configured fallback) | |
| - // The resolved scope is used consistently for both DCR and the authorization request. | |
| - const resolvedScope = scope || resourceMetadata?.scopes_supported?.join(' ') || provider.clientMetadata.scope; | |
| + const resolvedScope = determineScope({ | |
| + requestedScope: scope, | |
| + resourceMetadata, | |
| + authServerMetadata: metadata, | |
| + clientMetadata: provider.clientMetadata | |
| + }); | |
| // Handle client registration if needed | |
| let clientInformation = await Promise.resolve(provider.clientInformation()); | |
| if (!clientInformation) { | |
| export async function startAuthorization(authorizationServerUrl, { metadata, cli | |
| if (scope) { | |
| authorizationUrl.searchParams.set('scope', scope); | |
| } | |
| - if (scope?.includes('offline_access')) { | |
| + if (scope?.split(' ').includes('offline_access')) { | |
| // if the request includes the OIDC-only "offline_access" scope, | |
| // we need to set the prompt to "consent" to ensure the user is prompted to grant offline access | |
| // https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess | |
| diff --git a/dist/esm/client/index.js b/dist/esm/client/index.js | |
| index 49b12c6cd918c457420fef7ad5528a9443d1a191..2afe2e22e960f26c9d516ef135d89f8eb9e4caff 100644 | |
| --- a/dist/esm/client/index.js | |
| +++ b/dist/esm/client/index.js | |
| export class Client extends Protocol { | |
| } | |
| async connect(transport, options) { | |
| await super.connect(transport); | |
| + transport.onsessionexpired = async () => { | |
| + await this._initialize(transport); | |
| + }; | |
| // When transport sessionId is already set this means we are trying to reconnect. | |
| // In this case we don't need to initialize again. | |
| if (transport.sessionId !== undefined) { | |
| return; | |
| } | |
| try { | |
| - const result = await this.request({ | |
| - method: 'initialize', | |
| - params: { | |
| - protocolVersion: LATEST_PROTOCOL_VERSION, | |
| - capabilities: this._capabilities, | |
| - clientInfo: this._clientInfo | |
| - } | |
| - }, InitializeResultSchema, options); | |
| - if (result === undefined) { | |
| - throw new Error(`Server sent invalid initialize result: ${result}`); | |
| - } | |
| - if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) { | |
| - throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`); | |
| - } | |
| - this._serverCapabilities = result.capabilities; | |
| - this._serverVersion = result.serverInfo; | |
| - // HTTP transports must set the protocol version in each header after initialization. | |
| - if (transport.setProtocolVersion) { | |
| - transport.setProtocolVersion(result.protocolVersion); | |
| - } | |
| - this._instructions = result.instructions; | |
| - await this.notification({ | |
| - method: 'notifications/initialized' | |
| - }); | |
| - // Set up list changed handlers now that we know server capabilities | |
| - if (this._pendingListChangedConfig) { | |
| - this._setupListChangedHandlers(this._pendingListChangedConfig); | |
| - this._pendingListChangedConfig = undefined; | |
| - } | |
| + await this._initialize(transport, options); | |
| } | |
| catch (error) { | |
| // Disconnect if initialization fails. | |
| export class Client extends Protocol { | |
| throw error; | |
| } | |
| } | |
| + async _initialize(transport, options) { | |
| + const result = await this.request({ | |
| + method: 'initialize', | |
| + params: { | |
| + protocolVersion: LATEST_PROTOCOL_VERSION, | |
| + capabilities: this._capabilities, | |
| + clientInfo: this._clientInfo | |
| + } | |
| + }, InitializeResultSchema, options); | |
| + if (result === undefined) { | |
| + throw new Error(`Server sent invalid initialize result: ${result}`); | |
| + } | |
| + if (!SUPPORTED_PROTOCOL_VERSIONS.includes(result.protocolVersion)) { | |
| + throw new Error(`Server's protocol version is not supported: ${result.protocolVersion}`); | |
| + } | |
| + this._serverCapabilities = result.capabilities; | |
| + this._serverVersion = result.serverInfo; | |
| + // HTTP transports must set the protocol version in each header after initialization. | |
| + if (transport.setProtocolVersion) { | |
| + transport.setProtocolVersion(result.protocolVersion); | |
| + } | |
| + this._instructions = result.instructions; | |
| + await this.notification({ | |
| + method: 'notifications/initialized' | |
| + }); | |
| + // Set up list changed handlers now that we know server capabilities | |
| + if (this._pendingListChangedConfig) { | |
| + this._setupListChangedHandlers(this._pendingListChangedConfig); | |
| + this._pendingListChangedConfig = undefined; | |
| + } | |
| + } | |
| /** | |
| * After initialization has completed, this will be populated with the server's reported capabilities. | |
| */ | |
| export class Client extends Protocol { | |
| * Called after listTools() to pre-compile validators for better performance. | |
| */ | |
| - cacheToolMetadata(tools) { | |
| - this._cachedToolOutputValidators.clear(); | |
| - this._cachedKnownTaskTools.clear(); | |
| - this._cachedRequiredTaskTools.clear(); | |
| + cacheToolMetadata(tools, reset = true) { | |
| + if (reset) { | |
| + this._cachedToolOutputValidators.clear(); | |
| + this._cachedKnownTaskTools.clear(); | |
| + this._cachedRequiredTaskTools.clear(); | |
| + } | |
| for (const tool of tools) { | |
| // If the tool has an outputSchema, create and cache the validator | |
| if (tool.outputSchema) { | |
| export class Client extends Protocol { | |
| async listTools(params, options) { | |
| const result = await this.request({ method: 'tools/list', params }, ListToolsResultSchema, options); | |
| // Cache the tools and their output schemas for future validation | |
| - this.cacheToolMetadata(result.tools); | |
| + this.cacheToolMetadata(result.tools, params?.cursor === undefined); | |
| return result; | |
| } | |
| /** | |
| diff --git a/dist/esm/client/streamableHttp.js b/dist/esm/client/streamableHttp.js | |
| index 624172aa24ae255a67c083f9c19053343e4a0581..ac75b14545fda44aff7ff4d97cc5da884fcc627a 100644 | |
| --- a/dist/esm/client/streamableHttp.js | |
| +++ b/dist/esm/client/streamableHttp.js | |
| import { createFetchWithInit, normalizeHeaders } from '../shared/transport.js'; | |
| -import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js'; | |
| +import { isInitializedNotification, isInitializeRequest, isJSONRPCErrorResponse, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js'; | |
| import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js'; | |
| import { EventSourceParserStream } from 'eventsource-parser/stream'; | |
| // Default reconnection options for StreamableHTTP connections | |
| export class StreamableHTTPClientTransport { | |
| if (!event.event || event.event === 'message') { | |
| try { | |
| const message = JSONRPCMessageSchema.parse(JSON.parse(event.data)); | |
| - if (isJSONRPCResultResponse(message)) { | |
| + if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) { | |
| // Mark that we received a response - no need to reconnect for this request | |
| receivedResponse = true; | |
| if (replayMessageId !== undefined) { | |
| export class StreamableHTTPClientTransport { | |
| this.onclose?.(); | |
| } | |
| async send(message, options) { | |
| + return this._send(message, options, false); | |
| + } | |
| + async _recoverSession(expiredSessionId) { | |
| + if (this._sessionRecovery) { | |
| + await this._sessionRecovery; | |
| + return true; | |
| + } | |
| + if (this._sessionId !== expiredSessionId) | |
| + return true; | |
| + this._sessionId = undefined; | |
| + this._sessionRecovery = Promise.resolve().then(() => this.onsessionexpired?.()); | |
| try { | |
| + await this._sessionRecovery; | |
| + } | |
| + catch (error) { | |
| + this._sessionId = undefined; | |
| + await this.close(); | |
| + throw error; | |
| + } | |
| + finally { | |
| + this._sessionRecovery = undefined; | |
| + } | |
| + return true; | |
| + } | |
| + async _send(message, options, isSessionRetry) { | |
| + try { | |
| + if (this._sessionRecovery && !isInitializeRequest(message) && !isInitializedNotification(message)) { | |
| + await this._sessionRecovery; | |
| + if (options?.isRequestActive?.() === false) { | |
| + throw new Error('Request is no longer active'); | |
| + } | |
| + } | |
| const { resumptionToken, onresumptiontoken } = options || {}; | |
| if (resumptionToken) { | |
| // If we have at last event ID, we need to reconnect the SSE stream | |
| export class StreamableHTTPClientTransport { | |
| return; | |
| } | |
| const headers = await this._commonHeaders(); | |
| + const requestSessionId = headers.get('mcp-session-id') ?? undefined; | |
| headers.set('content-type', 'application/json'); | |
| headers.set('accept', 'application/json, text/event-stream'); | |
| const init = { | |
| export class StreamableHTTPClientTransport { | |
| const response = await (this._fetch ?? fetch)(this._url, init); | |
| // Handle session ID received during initialization | |
| const sessionId = response.headers.get('mcp-session-id'); | |
| - if (sessionId) { | |
| + if (sessionId && (requestSessionId === undefined || this._sessionId === requestSessionId)) { | |
| this._sessionId = sessionId; | |
| } | |
| if (!response.ok) { | |
| const text = await response.text().catch(() => null); | |
| + if (response.status === 404 && requestSessionId && !isSessionRetry && !isInitializedNotification(message)) { | |
| + const recovered = await this._recoverSession(requestSessionId); | |
| + if (options?.isRequestActive?.() === false) { | |
| + throw new Error('Request is no longer active'); | |
| + } | |
| + if (recovered) { | |
| + return this._send(message, options, true); | |
| + } | |
| + } | |
| if (response.status === 401 && this._authProvider) { | |
| // Prevent infinite recursion when server returns 401 after successful auth | |
| if (this._hasCompletedAuthFlow) { | |
| export class StreamableHTTPClientTransport { | |
| // Mark that we completed auth flow | |
| this._hasCompletedAuthFlow = true; | |
| // Purposely _not_ awaited, so we don't call onerror twice | |
| - return this.send(message); | |
| + return this._send(message, options, isSessionRetry); | |
| } | |
| if (response.status === 403 && this._authProvider) { | |
| const { resourceMetadataUrl, scope, error } = extractWWWAuthenticateParams(response); | |
| export class StreamableHTTPClientTransport { | |
| if (result !== 'AUTHORIZED') { | |
| throw new UnauthorizedError(); | |
| } | |
| - return this.send(message); | |
| + return this._send(message, options, isSessionRetry); | |
| } | |
| } | |
| throw new StreamableHTTPError(response.status, `Error POSTing to endpoint: ${text}`); | |
| diff --git a/dist/esm/shared/protocol.js b/dist/esm/shared/protocol.js | |
| index bfa2b7120a0f50c569364ea5264e6f811076f44f..abd8dfd707c155f71dae7aeeeeaf7547368ac749 100644 | |
| --- a/dist/esm/shared/protocol.js | |
| +++ b/dist/esm/shared/protocol.js | |
| export class Protocol { | |
| } | |
| else { | |
| // No related task - send through transport normally | |
| - this._transport.send(jsonrpcRequest, { relatedRequestId, resumptionToken, onresumptiontoken }).catch(error => { | |
| + this._transport.send(jsonrpcRequest, { | |
| + relatedRequestId, | |
| + resumptionToken, | |
| + onresumptiontoken, | |
| + isRequestActive: () => this._responseHandlers.has(messageId) | |
| + }).catch(error => { | |
| this._cleanupTimeout(messageId); | |
| reject(error); | |
| }); | |