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 @@ -428,6 +428,8 @@ export declare class Client>; + callTool(params: CallToolRequest['params'], resultSchema: T, options?: RequestOptions): Promise>; 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 @@ -428,6 +428,8 @@ export declare class Client>; + callTool(params: CallToolRequest['params'], resultSchema: T, options?: RequestOptions): Promise>; 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 @@ -288,41 +288,16 @@ 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. @@ -330,6 +305,37 @@ 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. */ @@ -541,9 +547,11 @@ 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) { @@ -569,7 +577,7 @@ 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 @@ -290,7 +290,38 @@ 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 @@ -298,6 +329,7 @@ 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 = { @@ -310,11 +342,20 @@ 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) { @@ -335,7 +376,7 @@ 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); @@ -362,7 +403,7 @@ 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 @@ -744,7 +744,12 @@ 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 @@ -205,6 +205,15 @@ export declare function parseErrorResponse(input: Response | string): Promise; +/** + * 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 @@ -7,6 +7,7 @@ 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; @@ -186,6 +187,19 @@ 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?.(); @@ -241,12 +255,12 @@ 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) { @@ -741,7 +755,7 @@ 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 @@ -205,6 +205,15 @@ export declare function parseErrorResponse(input: Response | string): Promise; +/** + * 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 @@ -161,6 +161,19 @@ 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?.(); @@ -216,12 +229,12 @@ 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) { @@ -716,7 +729,7 @@ 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 @@ -284,41 +284,16 @@ 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. @@ -326,6 +301,37 @@ 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. */ @@ -537,9 +543,11 @@ 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) { @@ -565,7 +573,7 @@ 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 @@ -1,5 +1,5 @@ import { createFetchWithInit, normalizeHeaders } from '../shared/transport.js'; -import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js'; +import { isInitializedNotification, isInitializeRequest, 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 @@ -286,7 +286,38 @@ 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 @@ -294,6 +325,7 @@ 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 = { @@ -306,11 +338,20 @@ 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) { @@ -331,7 +372,7 @@ 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); @@ -358,7 +399,7 @@ 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 @@ -740,7 +740,12 @@ 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); });