@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.
- package/README.md +58 -19
- package/dist/adapters/agui-adapter.d.mts +2 -2
- package/dist/adapters/agui-adapter.d.ts +2 -2
- package/dist/adapters/agui-middleware.d.mts +2 -2
- package/dist/adapters/agui-middleware.d.ts +2 -2
- package/dist/adapters/ai-adapter.d.mts +2 -2
- package/dist/adapters/ai-adapter.d.ts +2 -2
- package/dist/adapters/langchain-adapter.d.mts +2 -2
- package/dist/adapters/langchain-adapter.d.ts +2 -2
- package/dist/adapters/mastra-adapter.d.mts +2 -2
- package/dist/adapters/mastra-adapter.d.ts +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react.d.mts +4 -4
- package/dist/client/react.d.ts +4 -4
- package/dist/client/vue.d.mts +4 -4
- package/dist/client/vue.d.ts +4 -4
- package/dist/{events-CK3N--3g.d.mts → events-C4m7tK1U.d.mts} +0 -1
- package/dist/{events-CK3N--3g.d.ts → events-C4m7tK1U.d.ts} +0 -1
- package/dist/{index-CtXvKl8N.d.ts → index-Ch7ouNSa.d.ts} +1 -1
- package/dist/{index-ByIjEReo.d.mts → index-L5XoXgsb.d.mts} +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +71 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +71 -20
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-CnvZEGPY.d.ts → multi-session-client-Bwm-efqg.d.ts} +28 -5
- package/dist/{multi-session-client-CIMUGF8S.d.mts → multi-session-client-k-9RvWvi.d.mts} +28 -5
- package/dist/server/index.d.mts +4 -4
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +71 -20
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +71 -20
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/migrations/v2.3.4/neon/20260513010000_install_mcp_sessions.sql +69 -0
- package/migrations/v2.3.4/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
- package/migrations/v2.3.4/supabase/20260330195700_install_mcp_sessions.sql +82 -0
- package/migrations/v2.3.4/supabase/20260421010000_add_session_cleanup_cron.sql +32 -0
- package/package.json +1 -1
- package/src/server/handlers/sse-handler.ts +7 -5
- package/src/server/mcp/multi-session-client.ts +46 -5
- package/src/server/mcp/oauth-client.ts +60 -10
- package/src/shared/events.ts +0 -1
package/dist/index.js
CHANGED
|
@@ -2363,7 +2363,7 @@ var MCPClient = class {
|
|
|
2363
2363
|
* Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
|
|
2364
2364
|
* This wrapper enforces a timeout and properly uses AbortController to unblock the request.
|
|
2365
2365
|
*/
|
|
2366
|
-
fetch: (url, init) => {
|
|
2366
|
+
fetch: async (url, init) => {
|
|
2367
2367
|
const timeout = 3e4;
|
|
2368
2368
|
const controller = new AbortController();
|
|
2369
2369
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -2371,7 +2371,17 @@ var MCPClient = class {
|
|
|
2371
2371
|
// @ts-ignore: AbortSignal.any is available in Node 20+
|
|
2372
2372
|
AbortSignal.any ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
|
|
2373
2373
|
) : controller.signal;
|
|
2374
|
-
|
|
2374
|
+
try {
|
|
2375
|
+
const response = await fetch(url, { ...init, signal });
|
|
2376
|
+
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2377
|
+
if (response.status === 404 && hasSessionHeader) {
|
|
2378
|
+
this.client = null;
|
|
2379
|
+
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2380
|
+
}
|
|
2381
|
+
return response;
|
|
2382
|
+
} finally {
|
|
2383
|
+
clearTimeout(timeoutId);
|
|
2384
|
+
}
|
|
2375
2385
|
}
|
|
2376
2386
|
};
|
|
2377
2387
|
if (type === "sse") {
|
|
@@ -2563,6 +2573,14 @@ var MCPClient = class {
|
|
|
2563
2573
|
* @throws {Error} When connection fails for other reasons
|
|
2564
2574
|
*/
|
|
2565
2575
|
async connect() {
|
|
2576
|
+
if (this.client?.transport) {
|
|
2577
|
+
this.transport = null;
|
|
2578
|
+
try {
|
|
2579
|
+
await this.client.close();
|
|
2580
|
+
} catch {
|
|
2581
|
+
}
|
|
2582
|
+
this.client = null;
|
|
2583
|
+
}
|
|
2566
2584
|
await this.initialize();
|
|
2567
2585
|
if (!this.client || !this.oauthProvider) {
|
|
2568
2586
|
const error = "Client or OAuth provider not initialized";
|
|
@@ -2941,7 +2959,7 @@ var MCPClient = class {
|
|
|
2941
2959
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2942
2960
|
}
|
|
2943
2961
|
await sessions.delete(this.userId, this.sessionId);
|
|
2944
|
-
this.disconnect();
|
|
2962
|
+
await this.disconnect();
|
|
2945
2963
|
}
|
|
2946
2964
|
/**
|
|
2947
2965
|
* Checks if the client is currently connected to an MCP server
|
|
@@ -2951,10 +2969,21 @@ var MCPClient = class {
|
|
|
2951
2969
|
return this.client !== null;
|
|
2952
2970
|
}
|
|
2953
2971
|
/**
|
|
2954
|
-
* Disconnects from the MCP server and cleans up resources
|
|
2955
|
-
* Does not remove session from Redis
|
|
2972
|
+
* Disconnects from the MCP server and cleans up resources.
|
|
2973
|
+
* Does not remove session from Redis — use clearSession() for that.
|
|
2974
|
+
*
|
|
2975
|
+
* For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
|
|
2976
|
+
* before closing, as recommended by the MCP Streamable HTTP spec
|
|
2977
|
+
* (section "Session Management", rule 5). This is best-effort — errors
|
|
2978
|
+
* (e.g. server already restarted, 404/405 responses) are silently ignored.
|
|
2956
2979
|
*/
|
|
2957
|
-
disconnect(
|
|
2980
|
+
async disconnect() {
|
|
2981
|
+
if (this.transport instanceof streamableHttp_js.StreamableHTTPClientTransport) {
|
|
2982
|
+
try {
|
|
2983
|
+
await this.transport.terminateSession();
|
|
2984
|
+
} catch {
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2958
2987
|
if (this.client) {
|
|
2959
2988
|
this.client.close();
|
|
2960
2989
|
}
|
|
@@ -2966,7 +2995,6 @@ var MCPClient = class {
|
|
|
2966
2995
|
type: "disconnected",
|
|
2967
2996
|
sessionId: this.sessionId,
|
|
2968
2997
|
serverId: this.serverId,
|
|
2969
|
-
reason,
|
|
2970
2998
|
timestamp: Date.now()
|
|
2971
2999
|
});
|
|
2972
3000
|
this._onObservabilityEvent.fire({
|
|
@@ -2975,9 +3003,7 @@ var MCPClient = class {
|
|
|
2975
3003
|
message: `Disconnected from ${this.serverId}`,
|
|
2976
3004
|
sessionId: this.sessionId,
|
|
2977
3005
|
serverId: this.serverId,
|
|
2978
|
-
payload: {
|
|
2979
|
-
reason: reason || "unknown"
|
|
2980
|
-
},
|
|
3006
|
+
payload: {},
|
|
2981
3007
|
timestamp: Date.now(),
|
|
2982
3008
|
id: nanoid.nanoid()
|
|
2983
3009
|
});
|
|
@@ -3096,6 +3122,9 @@ var MultiSessionClient = class {
|
|
|
3096
3122
|
* Connects a single session, with built-in deduplication to prevent race conditions.
|
|
3097
3123
|
*
|
|
3098
3124
|
* - If a client for this session already exists and is connected, returns immediately.
|
|
3125
|
+
* - If the existing client entry is no longer connected (e.g. it was explicitly
|
|
3126
|
+
* disconnected), it is evicted so that `establishConnectionWithRetries` creates a
|
|
3127
|
+
* fresh transport — preventing "Client already connected" errors from the SDK.
|
|
3099
3128
|
* - If a connection attempt for this session is already in-flight (e.g. from a
|
|
3100
3129
|
* concurrent call), it joins the existing promise instead of starting a new one.
|
|
3101
3130
|
* This is the key concurrency lock — the `connectionPromises` map acts as a
|
|
@@ -3103,9 +3132,12 @@ var MultiSessionClient = class {
|
|
|
3103
3132
|
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
3104
3133
|
*/
|
|
3105
3134
|
async connectSession(session) {
|
|
3106
|
-
const
|
|
3107
|
-
if (
|
|
3108
|
-
|
|
3135
|
+
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
3136
|
+
if (existing) {
|
|
3137
|
+
if (existing.isConnected()) {
|
|
3138
|
+
return;
|
|
3139
|
+
}
|
|
3140
|
+
this.clients = this.clients.filter((c) => c !== existing);
|
|
3109
3141
|
}
|
|
3110
3142
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
3111
3143
|
return this.connectionPromises.get(session.sessionId);
|
|
@@ -3180,8 +3212,24 @@ var MultiSessionClient = class {
|
|
|
3180
3212
|
*/
|
|
3181
3213
|
async connect() {
|
|
3182
3214
|
const sessions2 = await this.getActiveSessions();
|
|
3215
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
3216
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
3183
3217
|
await this.connectInBatches(sessions2);
|
|
3184
3218
|
}
|
|
3219
|
+
/**
|
|
3220
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
3221
|
+
*
|
|
3222
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
3223
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
3224
|
+
* "Session not found. Reconnect without session header." errors.
|
|
3225
|
+
*
|
|
3226
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
3227
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
3228
|
+
*/
|
|
3229
|
+
async reconnect() {
|
|
3230
|
+
await this.disconnect();
|
|
3231
|
+
await this.connect();
|
|
3232
|
+
}
|
|
3185
3233
|
/**
|
|
3186
3234
|
* Returns all currently connected `MCPClient` instances.
|
|
3187
3235
|
*
|
|
@@ -3194,11 +3242,15 @@ var MultiSessionClient = class {
|
|
|
3194
3242
|
/**
|
|
3195
3243
|
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
3196
3244
|
*
|
|
3245
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
3246
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
3247
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
3248
|
+
*
|
|
3197
3249
|
* Call this during server shutdown or when a user logs out to free up
|
|
3198
3250
|
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3199
3251
|
*/
|
|
3200
|
-
disconnect() {
|
|
3201
|
-
this.clients.
|
|
3252
|
+
async disconnect() {
|
|
3253
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3202
3254
|
this.clients = [];
|
|
3203
3255
|
}
|
|
3204
3256
|
};
|
|
@@ -3432,7 +3484,6 @@ var SSEConnectionManager = class {
|
|
|
3432
3484
|
const client = this.clients.get(sessionId);
|
|
3433
3485
|
if (client) {
|
|
3434
3486
|
await client.clearSession();
|
|
3435
|
-
client.disconnect();
|
|
3436
3487
|
this.clients.delete(sessionId);
|
|
3437
3488
|
} else {
|
|
3438
3489
|
await sessions.delete(this.userId, sessionId);
|
|
@@ -3641,14 +3692,14 @@ var SSEConnectionManager = class {
|
|
|
3641
3692
|
/**
|
|
3642
3693
|
* Cleanup and close all connections
|
|
3643
3694
|
*/
|
|
3644
|
-
dispose() {
|
|
3695
|
+
async dispose() {
|
|
3645
3696
|
this.isActive = false;
|
|
3646
3697
|
if (this.heartbeatTimer) {
|
|
3647
3698
|
clearInterval(this.heartbeatTimer);
|
|
3648
3699
|
}
|
|
3649
|
-
|
|
3650
|
-
client.disconnect()
|
|
3651
|
-
|
|
3700
|
+
await Promise.all(
|
|
3701
|
+
Array.from(this.clients.values()).map((client) => client.disconnect())
|
|
3702
|
+
);
|
|
3652
3703
|
this.clients.clear();
|
|
3653
3704
|
}
|
|
3654
3705
|
};
|