@mcp-ts/sdk 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +58 -19
  2. package/dist/adapters/agui-adapter.d.mts +2 -2
  3. package/dist/adapters/agui-adapter.d.ts +2 -2
  4. package/dist/adapters/agui-middleware.d.mts +2 -2
  5. package/dist/adapters/agui-middleware.d.ts +2 -2
  6. package/dist/adapters/ai-adapter.d.mts +2 -2
  7. package/dist/adapters/ai-adapter.d.ts +2 -2
  8. package/dist/adapters/langchain-adapter.d.mts +2 -2
  9. package/dist/adapters/langchain-adapter.d.ts +2 -2
  10. package/dist/adapters/mastra-adapter.d.mts +2 -2
  11. package/dist/adapters/mastra-adapter.d.ts +2 -2
  12. package/dist/client/index.d.mts +2 -2
  13. package/dist/client/index.d.ts +2 -2
  14. package/dist/client/react.d.mts +4 -4
  15. package/dist/client/react.d.ts +4 -4
  16. package/dist/client/vue.d.mts +4 -4
  17. package/dist/client/vue.d.ts +4 -4
  18. package/dist/{events-CK3N--3g.d.mts → events-C4m7tK1U.d.mts} +0 -1
  19. package/dist/{events-CK3N--3g.d.ts → events-C4m7tK1U.d.ts} +0 -1
  20. package/dist/{index-CtXvKl8N.d.ts → index-Ch7ouNSa.d.ts} +1 -1
  21. package/dist/{index-ByIjEReo.d.mts → index-L5XoXgsb.d.mts} +1 -1
  22. package/dist/index.d.mts +3 -3
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.js +71 -20
  25. package/dist/index.js.map +1 -1
  26. package/dist/index.mjs +71 -20
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/{multi-session-client-CnvZEGPY.d.ts → multi-session-client-Bwm-efqg.d.ts} +28 -5
  29. package/dist/{multi-session-client-CIMUGF8S.d.mts → multi-session-client-k-9RvWvi.d.mts} +28 -5
  30. package/dist/server/index.d.mts +4 -4
  31. package/dist/server/index.d.ts +4 -4
  32. package/dist/server/index.js +71 -20
  33. package/dist/server/index.js.map +1 -1
  34. package/dist/server/index.mjs +71 -20
  35. package/dist/server/index.mjs.map +1 -1
  36. package/dist/shared/index.d.mts +1 -1
  37. package/dist/shared/index.d.ts +1 -1
  38. package/dist/shared/index.js.map +1 -1
  39. package/dist/shared/index.mjs.map +1 -1
  40. package/migrations/v2.3.4/neon/20260513010000_install_mcp_sessions.sql +69 -0
  41. package/migrations/v2.3.4/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
  42. package/migrations/v2.3.4/supabase/20260330195700_install_mcp_sessions.sql +82 -0
  43. package/migrations/v2.3.4/supabase/20260421010000_add_session_cleanup_cron.sql +32 -0
  44. package/package.json +1 -1
  45. package/src/server/handlers/sse-handler.ts +7 -5
  46. package/src/server/mcp/multi-session-client.ts +46 -5
  47. package/src/server/mcp/oauth-client.ts +60 -10
  48. package/src/shared/events.ts +0 -1
@@ -1,4 +1,4 @@
1
- import { b as Event, M as McpConnectionEvent, d as McpObservabilityEvent, c as McpConnectionState } from './events-CK3N--3g.js';
1
+ import { b as Event, M as McpConnectionEvent, d as McpObservabilityEvent, c as McpConnectionState } from './events-C4m7tK1U.js';
2
2
  import { ListToolsResult, CallToolResult, ListPromptsResult, GetPromptResult, ListResourcesResult, ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
4
4
  import { OAuthClientMetadata, OAuthClientInformationMixed, OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
@@ -282,10 +282,15 @@ declare class MCPClient {
282
282
  */
283
283
  isConnected(): boolean;
284
284
  /**
285
- * Disconnects from the MCP server and cleans up resources
286
- * Does not remove session from Redis - use clearSession() for that
285
+ * Disconnects from the MCP server and cleans up resources.
286
+ * Does not remove session from Redis use clearSession() for that.
287
+ *
288
+ * For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
289
+ * before closing, as recommended by the MCP Streamable HTTP spec
290
+ * (section "Session Management", rule 5). This is best-effort — errors
291
+ * (e.g. server already restarted, 404/405 responses) are silently ignored.
287
292
  */
288
- disconnect(reason?: string): void;
293
+ disconnect(): Promise<void>;
289
294
  /**
290
295
  * Dispose of all event emitters
291
296
  * Call this when the client is no longer needed
@@ -387,6 +392,9 @@ declare class MultiSessionClient {
387
392
  * Connects a single session, with built-in deduplication to prevent race conditions.
388
393
  *
389
394
  * - If a client for this session already exists and is connected, returns immediately.
395
+ * - If the existing client entry is no longer connected (e.g. it was explicitly
396
+ * disconnected), it is evicted so that `establishConnectionWithRetries` creates a
397
+ * fresh transport — preventing "Client already connected" errors from the SDK.
390
398
  * - If a connection attempt for this session is already in-flight (e.g. from a
391
399
  * concurrent call), it joins the existing promise instead of starting a new one.
392
400
  * This is the key concurrency lock — the `connectionPromises` map acts as a
@@ -418,6 +426,17 @@ declare class MultiSessionClient {
418
426
  * re-fetching and re-connecting on every request.
419
427
  */
420
428
  connect(): Promise<void>;
429
+ /**
430
+ * Drops all cached `MCPClient` instances and reconnects fresh from storage.
431
+ *
432
+ * Call this when downstream MCP servers have expired their transport sessions
433
+ * (e.g. after a remote server restart) and subsequent tool calls return
434
+ * "Session not found. Reconnect without session header." errors.
435
+ *
436
+ * OAuth tokens are preserved in the storage backend — no re-authentication
437
+ * is required. Only the in-memory transport sessions are cleared.
438
+ */
439
+ reconnect(): Promise<void>;
421
440
  /**
422
441
  * Returns all currently connected `MCPClient` instances.
423
442
  *
@@ -428,10 +447,14 @@ declare class MultiSessionClient {
428
447
  /**
429
448
  * Gracefully disconnects all active MCP clients and clears the internal client list.
430
449
  *
450
+ * For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
451
+ * endpoint per the spec before closing locally. All disconnects run in
452
+ * parallel so shutdown is not serialised across many sessions.
453
+ *
431
454
  * Call this during server shutdown or when a user logs out to free up
432
455
  * underlying transport resources (SSE streams, HTTP connections, etc.).
433
456
  */
434
- disconnect(): void;
457
+ disconnect(): Promise<void>;
435
458
  }
436
459
 
437
460
  export { MCPClient as M, StorageOAuthClientProvider as S, MultiSessionClient as a };
@@ -1,4 +1,4 @@
1
- import { b as Event, M as McpConnectionEvent, d as McpObservabilityEvent, c as McpConnectionState } from './events-CK3N--3g.mjs';
1
+ import { b as Event, M as McpConnectionEvent, d as McpObservabilityEvent, c as McpConnectionState } from './events-C4m7tK1U.mjs';
2
2
  import { ListToolsResult, CallToolResult, ListPromptsResult, GetPromptResult, ListResourcesResult, ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
4
4
  import { OAuthClientMetadata, OAuthClientInformationMixed, OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
@@ -282,10 +282,15 @@ declare class MCPClient {
282
282
  */
283
283
  isConnected(): boolean;
284
284
  /**
285
- * Disconnects from the MCP server and cleans up resources
286
- * Does not remove session from Redis - use clearSession() for that
285
+ * Disconnects from the MCP server and cleans up resources.
286
+ * Does not remove session from Redis use clearSession() for that.
287
+ *
288
+ * For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
289
+ * before closing, as recommended by the MCP Streamable HTTP spec
290
+ * (section "Session Management", rule 5). This is best-effort — errors
291
+ * (e.g. server already restarted, 404/405 responses) are silently ignored.
287
292
  */
288
- disconnect(reason?: string): void;
293
+ disconnect(): Promise<void>;
289
294
  /**
290
295
  * Dispose of all event emitters
291
296
  * Call this when the client is no longer needed
@@ -387,6 +392,9 @@ declare class MultiSessionClient {
387
392
  * Connects a single session, with built-in deduplication to prevent race conditions.
388
393
  *
389
394
  * - If a client for this session already exists and is connected, returns immediately.
395
+ * - If the existing client entry is no longer connected (e.g. it was explicitly
396
+ * disconnected), it is evicted so that `establishConnectionWithRetries` creates a
397
+ * fresh transport — preventing "Client already connected" errors from the SDK.
390
398
  * - If a connection attempt for this session is already in-flight (e.g. from a
391
399
  * concurrent call), it joins the existing promise instead of starting a new one.
392
400
  * This is the key concurrency lock — the `connectionPromises` map acts as a
@@ -418,6 +426,17 @@ declare class MultiSessionClient {
418
426
  * re-fetching and re-connecting on every request.
419
427
  */
420
428
  connect(): Promise<void>;
429
+ /**
430
+ * Drops all cached `MCPClient` instances and reconnects fresh from storage.
431
+ *
432
+ * Call this when downstream MCP servers have expired their transport sessions
433
+ * (e.g. after a remote server restart) and subsequent tool calls return
434
+ * "Session not found. Reconnect without session header." errors.
435
+ *
436
+ * OAuth tokens are preserved in the storage backend — no re-authentication
437
+ * is required. Only the in-memory transport sessions are cleared.
438
+ */
439
+ reconnect(): Promise<void>;
421
440
  /**
422
441
  * Returns all currently connected `MCPClient` instances.
423
442
  *
@@ -428,10 +447,14 @@ declare class MultiSessionClient {
428
447
  /**
429
448
  * Gracefully disconnects all active MCP clients and clears the internal client list.
430
449
  *
450
+ * For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
451
+ * endpoint per the spec before closing locally. All disconnects run in
452
+ * parallel so shutdown is not serialised across many sessions.
453
+ *
431
454
  * Call this during server shutdown or when a user logs out to free up
432
455
  * underlying transport resources (SSE streams, HTTP connections, etc.).
433
456
  */
434
- disconnect(): void;
457
+ disconnect(): Promise<void>;
435
458
  }
436
459
 
437
460
  export { MCPClient as M, StorageOAuthClientProvider as S, MultiSessionClient as a };
@@ -1,9 +1,9 @@
1
- export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-CIMUGF8S.mjs';
1
+ export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-k-9RvWvi.mjs';
2
2
  export { U as UnauthorizedError, s as sanitizeServerLabel } from '../utils-DELRKQPU.mjs';
3
3
  import { OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
4
4
  export { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
5
- import { M as McpConnectionEvent, d as McpObservabilityEvent } from '../events-CK3N--3g.mjs';
6
- export { D as Disposable, E as Emitter, b as Event, c as McpConnectionState } from '../events-CK3N--3g.mjs';
5
+ import { M as McpConnectionEvent, d as McpObservabilityEvent } from '../events-C4m7tK1U.mjs';
6
+ export { D as Disposable, E as Emitter, b as Event, c as McpConnectionState } from '../events-C4m7tK1U.mjs';
7
7
  import { r as McpRpcResponse, q as McpRpcRequest } from '../types-DCk_IF4L.mjs';
8
8
  export { a as CallToolRequest, b as CallToolResponse, f as ConnectRequest, g as ConnectResponse, n as ListToolsResponse, T as ToolClient, u as ToolClientProvider, v as ToolInfo } from '../types-DCk_IF4L.mjs';
9
9
  export { CallToolResult, ListToolsResult, Tool } from '@modelcontextprotocol/sdk/types.js';
@@ -249,7 +249,7 @@ declare class SSEConnectionManager {
249
249
  /**
250
250
  * Cleanup and close all connections
251
251
  */
252
- dispose(): void;
252
+ dispose(): Promise<void>;
253
253
  }
254
254
  /**
255
255
  * Create an SSE endpoint handler compatible with Node.js HTTP frameworks.
@@ -1,9 +1,9 @@
1
- export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-CnvZEGPY.js';
1
+ export { M as MCPClient, a as MultiSessionClient, S as StorageOAuthClientProvider } from '../multi-session-client-Bwm-efqg.js';
2
2
  export { U as UnauthorizedError, s as sanitizeServerLabel } from '../utils-DELRKQPU.js';
3
3
  import { OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
4
4
  export { OAuthClientInformation, OAuthClientInformationFull, OAuthClientMetadata, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
5
- import { M as McpConnectionEvent, d as McpObservabilityEvent } from '../events-CK3N--3g.js';
6
- export { D as Disposable, E as Emitter, b as Event, c as McpConnectionState } from '../events-CK3N--3g.js';
5
+ import { M as McpConnectionEvent, d as McpObservabilityEvent } from '../events-C4m7tK1U.js';
6
+ export { D as Disposable, E as Emitter, b as Event, c as McpConnectionState } from '../events-C4m7tK1U.js';
7
7
  import { r as McpRpcResponse, q as McpRpcRequest } from '../types-DCk_IF4L.js';
8
8
  export { a as CallToolRequest, b as CallToolResponse, f as ConnectRequest, g as ConnectResponse, n as ListToolsResponse, T as ToolClient, u as ToolClientProvider, v as ToolInfo } from '../types-DCk_IF4L.js';
9
9
  export { CallToolResult, ListToolsResult, Tool } from '@modelcontextprotocol/sdk/types.js';
@@ -249,7 +249,7 @@ declare class SSEConnectionManager {
249
249
  /**
250
250
  * Cleanup and close all connections
251
251
  */
252
- dispose(): void;
252
+ dispose(): Promise<void>;
253
253
  }
254
254
  /**
255
255
  * Create an SSE endpoint handler compatible with Node.js HTTP frameworks.
@@ -2288,7 +2288,7 @@ var MCPClient = class {
2288
2288
  * Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
2289
2289
  * This wrapper enforces a timeout and properly uses AbortController to unblock the request.
2290
2290
  */
2291
- fetch: (url, init) => {
2291
+ fetch: async (url, init) => {
2292
2292
  const timeout = 3e4;
2293
2293
  const controller = new AbortController();
2294
2294
  const timeoutId = setTimeout(() => controller.abort(), timeout);
@@ -2296,7 +2296,17 @@ var MCPClient = class {
2296
2296
  // @ts-ignore: AbortSignal.any is available in Node 20+
2297
2297
  AbortSignal.any ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
2298
2298
  ) : controller.signal;
2299
- return fetch(url, { ...init, signal }).finally(() => clearTimeout(timeoutId));
2299
+ try {
2300
+ const response = await fetch(url, { ...init, signal });
2301
+ const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
2302
+ if (response.status === 404 && hasSessionHeader) {
2303
+ this.client = null;
2304
+ throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
2305
+ }
2306
+ return response;
2307
+ } finally {
2308
+ clearTimeout(timeoutId);
2309
+ }
2300
2310
  }
2301
2311
  };
2302
2312
  if (type === "sse") {
@@ -2488,6 +2498,14 @@ var MCPClient = class {
2488
2498
  * @throws {Error} When connection fails for other reasons
2489
2499
  */
2490
2500
  async connect() {
2501
+ if (this.client?.transport) {
2502
+ this.transport = null;
2503
+ try {
2504
+ await this.client.close();
2505
+ } catch {
2506
+ }
2507
+ this.client = null;
2508
+ }
2491
2509
  await this.initialize();
2492
2510
  if (!this.client || !this.oauthProvider) {
2493
2511
  const error = "Client or OAuth provider not initialized";
@@ -2866,7 +2884,7 @@ var MCPClient = class {
2866
2884
  await this.oauthProvider.invalidateCredentials("all");
2867
2885
  }
2868
2886
  await sessions.delete(this.userId, this.sessionId);
2869
- this.disconnect();
2887
+ await this.disconnect();
2870
2888
  }
2871
2889
  /**
2872
2890
  * Checks if the client is currently connected to an MCP server
@@ -2876,10 +2894,21 @@ var MCPClient = class {
2876
2894
  return this.client !== null;
2877
2895
  }
2878
2896
  /**
2879
- * Disconnects from the MCP server and cleans up resources
2880
- * Does not remove session from Redis - use clearSession() for that
2897
+ * Disconnects from the MCP server and cleans up resources.
2898
+ * Does not remove session from Redis use clearSession() for that.
2899
+ *
2900
+ * For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
2901
+ * before closing, as recommended by the MCP Streamable HTTP spec
2902
+ * (section "Session Management", rule 5). This is best-effort — errors
2903
+ * (e.g. server already restarted, 404/405 responses) are silently ignored.
2881
2904
  */
2882
- disconnect(reason) {
2905
+ async disconnect() {
2906
+ if (this.transport instanceof streamableHttp_js.StreamableHTTPClientTransport) {
2907
+ try {
2908
+ await this.transport.terminateSession();
2909
+ } catch {
2910
+ }
2911
+ }
2883
2912
  if (this.client) {
2884
2913
  this.client.close();
2885
2914
  }
@@ -2891,7 +2920,6 @@ var MCPClient = class {
2891
2920
  type: "disconnected",
2892
2921
  sessionId: this.sessionId,
2893
2922
  serverId: this.serverId,
2894
- reason,
2895
2923
  timestamp: Date.now()
2896
2924
  });
2897
2925
  this._onObservabilityEvent.fire({
@@ -2900,9 +2928,7 @@ var MCPClient = class {
2900
2928
  message: `Disconnected from ${this.serverId}`,
2901
2929
  sessionId: this.sessionId,
2902
2930
  serverId: this.serverId,
2903
- payload: {
2904
- reason: reason || "unknown"
2905
- },
2931
+ payload: {},
2906
2932
  timestamp: Date.now(),
2907
2933
  id: nanoid.nanoid()
2908
2934
  });
@@ -3021,6 +3047,9 @@ var MultiSessionClient = class {
3021
3047
  * Connects a single session, with built-in deduplication to prevent race conditions.
3022
3048
  *
3023
3049
  * - If a client for this session already exists and is connected, returns immediately.
3050
+ * - If the existing client entry is no longer connected (e.g. it was explicitly
3051
+ * disconnected), it is evicted so that `establishConnectionWithRetries` creates a
3052
+ * fresh transport — preventing "Client already connected" errors from the SDK.
3024
3053
  * - If a connection attempt for this session is already in-flight (e.g. from a
3025
3054
  * concurrent call), it joins the existing promise instead of starting a new one.
3026
3055
  * This is the key concurrency lock — the `connectionPromises` map acts as a
@@ -3028,9 +3057,12 @@ var MultiSessionClient = class {
3028
3057
  * - On completion (success or failure), the promise is cleaned up from the map.
3029
3058
  */
3030
3059
  async connectSession(session) {
3031
- const existingClient = this.clients.find((c) => c.getSessionId() === session.sessionId);
3032
- if (existingClient?.isConnected()) {
3033
- return;
3060
+ const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
3061
+ if (existing) {
3062
+ if (existing.isConnected()) {
3063
+ return;
3064
+ }
3065
+ this.clients = this.clients.filter((c) => c !== existing);
3034
3066
  }
3035
3067
  if (this.connectionPromises.has(session.sessionId)) {
3036
3068
  return this.connectionPromises.get(session.sessionId);
@@ -3105,8 +3137,24 @@ var MultiSessionClient = class {
3105
3137
  */
3106
3138
  async connect() {
3107
3139
  const sessions2 = await this.getActiveSessions();
3140
+ const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
3141
+ this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
3108
3142
  await this.connectInBatches(sessions2);
3109
3143
  }
3144
+ /**
3145
+ * Drops all cached `MCPClient` instances and reconnects fresh from storage.
3146
+ *
3147
+ * Call this when downstream MCP servers have expired their transport sessions
3148
+ * (e.g. after a remote server restart) and subsequent tool calls return
3149
+ * "Session not found. Reconnect without session header." errors.
3150
+ *
3151
+ * OAuth tokens are preserved in the storage backend — no re-authentication
3152
+ * is required. Only the in-memory transport sessions are cleared.
3153
+ */
3154
+ async reconnect() {
3155
+ await this.disconnect();
3156
+ await this.connect();
3157
+ }
3110
3158
  /**
3111
3159
  * Returns all currently connected `MCPClient` instances.
3112
3160
  *
@@ -3119,11 +3167,15 @@ var MultiSessionClient = class {
3119
3167
  /**
3120
3168
  * Gracefully disconnects all active MCP clients and clears the internal client list.
3121
3169
  *
3170
+ * For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
3171
+ * endpoint per the spec before closing locally. All disconnects run in
3172
+ * parallel so shutdown is not serialised across many sessions.
3173
+ *
3122
3174
  * Call this during server shutdown or when a user logs out to free up
3123
3175
  * underlying transport resources (SSE streams, HTTP connections, etc.).
3124
3176
  */
3125
- disconnect() {
3126
- this.clients.forEach((client) => client.disconnect());
3177
+ async disconnect() {
3178
+ await Promise.all(this.clients.map((client) => client.disconnect()));
3127
3179
  this.clients = [];
3128
3180
  }
3129
3181
  };
@@ -3357,7 +3409,6 @@ var SSEConnectionManager = class {
3357
3409
  const client = this.clients.get(sessionId);
3358
3410
  if (client) {
3359
3411
  await client.clearSession();
3360
- client.disconnect();
3361
3412
  this.clients.delete(sessionId);
3362
3413
  } else {
3363
3414
  await sessions.delete(this.userId, sessionId);
@@ -3566,14 +3617,14 @@ var SSEConnectionManager = class {
3566
3617
  /**
3567
3618
  * Cleanup and close all connections
3568
3619
  */
3569
- dispose() {
3620
+ async dispose() {
3570
3621
  this.isActive = false;
3571
3622
  if (this.heartbeatTimer) {
3572
3623
  clearInterval(this.heartbeatTimer);
3573
3624
  }
3574
- for (const client of this.clients.values()) {
3575
- client.disconnect();
3576
- }
3625
+ await Promise.all(
3626
+ Array.from(this.clients.values()).map((client) => client.disconnect())
3627
+ );
3577
3628
  this.clients.clear();
3578
3629
  }
3579
3630
  };