@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/server/index.mjs
CHANGED
|
@@ -2218,7 +2218,7 @@ var MCPClient = class {
|
|
|
2218
2218
|
* Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
|
|
2219
2219
|
* This wrapper enforces a timeout and properly uses AbortController to unblock the request.
|
|
2220
2220
|
*/
|
|
2221
|
-
fetch: (url, init) => {
|
|
2221
|
+
fetch: async (url, init) => {
|
|
2222
2222
|
const timeout = 3e4;
|
|
2223
2223
|
const controller = new AbortController();
|
|
2224
2224
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -2226,7 +2226,17 @@ var MCPClient = class {
|
|
|
2226
2226
|
// @ts-ignore: AbortSignal.any is available in Node 20+
|
|
2227
2227
|
AbortSignal.any ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
|
|
2228
2228
|
) : controller.signal;
|
|
2229
|
-
|
|
2229
|
+
try {
|
|
2230
|
+
const response = await fetch(url, { ...init, signal });
|
|
2231
|
+
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2232
|
+
if (response.status === 404 && hasSessionHeader) {
|
|
2233
|
+
this.client = null;
|
|
2234
|
+
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2235
|
+
}
|
|
2236
|
+
return response;
|
|
2237
|
+
} finally {
|
|
2238
|
+
clearTimeout(timeoutId);
|
|
2239
|
+
}
|
|
2230
2240
|
}
|
|
2231
2241
|
};
|
|
2232
2242
|
if (type === "sse") {
|
|
@@ -2418,6 +2428,14 @@ var MCPClient = class {
|
|
|
2418
2428
|
* @throws {Error} When connection fails for other reasons
|
|
2419
2429
|
*/
|
|
2420
2430
|
async connect() {
|
|
2431
|
+
if (this.client?.transport) {
|
|
2432
|
+
this.transport = null;
|
|
2433
|
+
try {
|
|
2434
|
+
await this.client.close();
|
|
2435
|
+
} catch {
|
|
2436
|
+
}
|
|
2437
|
+
this.client = null;
|
|
2438
|
+
}
|
|
2421
2439
|
await this.initialize();
|
|
2422
2440
|
if (!this.client || !this.oauthProvider) {
|
|
2423
2441
|
const error = "Client or OAuth provider not initialized";
|
|
@@ -2796,7 +2814,7 @@ var MCPClient = class {
|
|
|
2796
2814
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2797
2815
|
}
|
|
2798
2816
|
await sessions.delete(this.userId, this.sessionId);
|
|
2799
|
-
this.disconnect();
|
|
2817
|
+
await this.disconnect();
|
|
2800
2818
|
}
|
|
2801
2819
|
/**
|
|
2802
2820
|
* Checks if the client is currently connected to an MCP server
|
|
@@ -2806,10 +2824,21 @@ var MCPClient = class {
|
|
|
2806
2824
|
return this.client !== null;
|
|
2807
2825
|
}
|
|
2808
2826
|
/**
|
|
2809
|
-
* Disconnects from the MCP server and cleans up resources
|
|
2810
|
-
* Does not remove session from Redis
|
|
2827
|
+
* Disconnects from the MCP server and cleans up resources.
|
|
2828
|
+
* Does not remove session from Redis — use clearSession() for that.
|
|
2829
|
+
*
|
|
2830
|
+
* For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
|
|
2831
|
+
* before closing, as recommended by the MCP Streamable HTTP spec
|
|
2832
|
+
* (section "Session Management", rule 5). This is best-effort — errors
|
|
2833
|
+
* (e.g. server already restarted, 404/405 responses) are silently ignored.
|
|
2811
2834
|
*/
|
|
2812
|
-
disconnect(
|
|
2835
|
+
async disconnect() {
|
|
2836
|
+
if (this.transport instanceof StreamableHTTPClientTransport) {
|
|
2837
|
+
try {
|
|
2838
|
+
await this.transport.terminateSession();
|
|
2839
|
+
} catch {
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2813
2842
|
if (this.client) {
|
|
2814
2843
|
this.client.close();
|
|
2815
2844
|
}
|
|
@@ -2821,7 +2850,6 @@ var MCPClient = class {
|
|
|
2821
2850
|
type: "disconnected",
|
|
2822
2851
|
sessionId: this.sessionId,
|
|
2823
2852
|
serverId: this.serverId,
|
|
2824
|
-
reason,
|
|
2825
2853
|
timestamp: Date.now()
|
|
2826
2854
|
});
|
|
2827
2855
|
this._onObservabilityEvent.fire({
|
|
@@ -2830,9 +2858,7 @@ var MCPClient = class {
|
|
|
2830
2858
|
message: `Disconnected from ${this.serverId}`,
|
|
2831
2859
|
sessionId: this.sessionId,
|
|
2832
2860
|
serverId: this.serverId,
|
|
2833
|
-
payload: {
|
|
2834
|
-
reason: reason || "unknown"
|
|
2835
|
-
},
|
|
2861
|
+
payload: {},
|
|
2836
2862
|
timestamp: Date.now(),
|
|
2837
2863
|
id: nanoid()
|
|
2838
2864
|
});
|
|
@@ -2950,6 +2976,9 @@ var MultiSessionClient = class {
|
|
|
2950
2976
|
* Connects a single session, with built-in deduplication to prevent race conditions.
|
|
2951
2977
|
*
|
|
2952
2978
|
* - If a client for this session already exists and is connected, returns immediately.
|
|
2979
|
+
* - If the existing client entry is no longer connected (e.g. it was explicitly
|
|
2980
|
+
* disconnected), it is evicted so that `establishConnectionWithRetries` creates a
|
|
2981
|
+
* fresh transport — preventing "Client already connected" errors from the SDK.
|
|
2953
2982
|
* - If a connection attempt for this session is already in-flight (e.g. from a
|
|
2954
2983
|
* concurrent call), it joins the existing promise instead of starting a new one.
|
|
2955
2984
|
* This is the key concurrency lock — the `connectionPromises` map acts as a
|
|
@@ -2957,9 +2986,12 @@ var MultiSessionClient = class {
|
|
|
2957
2986
|
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
2958
2987
|
*/
|
|
2959
2988
|
async connectSession(session) {
|
|
2960
|
-
const
|
|
2961
|
-
if (
|
|
2962
|
-
|
|
2989
|
+
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
2990
|
+
if (existing) {
|
|
2991
|
+
if (existing.isConnected()) {
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
this.clients = this.clients.filter((c) => c !== existing);
|
|
2963
2995
|
}
|
|
2964
2996
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
2965
2997
|
return this.connectionPromises.get(session.sessionId);
|
|
@@ -3034,8 +3066,24 @@ var MultiSessionClient = class {
|
|
|
3034
3066
|
*/
|
|
3035
3067
|
async connect() {
|
|
3036
3068
|
const sessions2 = await this.getActiveSessions();
|
|
3069
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
3070
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
3037
3071
|
await this.connectInBatches(sessions2);
|
|
3038
3072
|
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
3075
|
+
*
|
|
3076
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
3077
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
3078
|
+
* "Session not found. Reconnect without session header." errors.
|
|
3079
|
+
*
|
|
3080
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
3081
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
3082
|
+
*/
|
|
3083
|
+
async reconnect() {
|
|
3084
|
+
await this.disconnect();
|
|
3085
|
+
await this.connect();
|
|
3086
|
+
}
|
|
3039
3087
|
/**
|
|
3040
3088
|
* Returns all currently connected `MCPClient` instances.
|
|
3041
3089
|
*
|
|
@@ -3048,11 +3096,15 @@ var MultiSessionClient = class {
|
|
|
3048
3096
|
/**
|
|
3049
3097
|
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
3050
3098
|
*
|
|
3099
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
3100
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
3101
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
3102
|
+
*
|
|
3051
3103
|
* Call this during server shutdown or when a user logs out to free up
|
|
3052
3104
|
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3053
3105
|
*/
|
|
3054
|
-
disconnect() {
|
|
3055
|
-
this.clients.
|
|
3106
|
+
async disconnect() {
|
|
3107
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3056
3108
|
this.clients = [];
|
|
3057
3109
|
}
|
|
3058
3110
|
};
|
|
@@ -3282,7 +3334,6 @@ var SSEConnectionManager = class {
|
|
|
3282
3334
|
const client = this.clients.get(sessionId);
|
|
3283
3335
|
if (client) {
|
|
3284
3336
|
await client.clearSession();
|
|
3285
|
-
client.disconnect();
|
|
3286
3337
|
this.clients.delete(sessionId);
|
|
3287
3338
|
} else {
|
|
3288
3339
|
await sessions.delete(this.userId, sessionId);
|
|
@@ -3491,14 +3542,14 @@ var SSEConnectionManager = class {
|
|
|
3491
3542
|
/**
|
|
3492
3543
|
* Cleanup and close all connections
|
|
3493
3544
|
*/
|
|
3494
|
-
dispose() {
|
|
3545
|
+
async dispose() {
|
|
3495
3546
|
this.isActive = false;
|
|
3496
3547
|
if (this.heartbeatTimer) {
|
|
3497
3548
|
clearInterval(this.heartbeatTimer);
|
|
3498
3549
|
}
|
|
3499
|
-
|
|
3500
|
-
client.disconnect()
|
|
3501
|
-
|
|
3550
|
+
await Promise.all(
|
|
3551
|
+
Array.from(this.clients.values()).map((client) => client.disconnect())
|
|
3552
|
+
);
|
|
3502
3553
|
this.clients.clear();
|
|
3503
3554
|
}
|
|
3504
3555
|
};
|