@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.mjs
CHANGED
|
@@ -2290,7 +2290,7 @@ var MCPClient = class {
|
|
|
2290
2290
|
* Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
|
|
2291
2291
|
* This wrapper enforces a timeout and properly uses AbortController to unblock the request.
|
|
2292
2292
|
*/
|
|
2293
|
-
fetch: (url, init) => {
|
|
2293
|
+
fetch: async (url, init) => {
|
|
2294
2294
|
const timeout = 3e4;
|
|
2295
2295
|
const controller = new AbortController();
|
|
2296
2296
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -2298,7 +2298,17 @@ var MCPClient = class {
|
|
|
2298
2298
|
// @ts-ignore: AbortSignal.any is available in Node 20+
|
|
2299
2299
|
AbortSignal.any ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
|
|
2300
2300
|
) : controller.signal;
|
|
2301
|
-
|
|
2301
|
+
try {
|
|
2302
|
+
const response = await fetch(url, { ...init, signal });
|
|
2303
|
+
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2304
|
+
if (response.status === 404 && hasSessionHeader) {
|
|
2305
|
+
this.client = null;
|
|
2306
|
+
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2307
|
+
}
|
|
2308
|
+
return response;
|
|
2309
|
+
} finally {
|
|
2310
|
+
clearTimeout(timeoutId);
|
|
2311
|
+
}
|
|
2302
2312
|
}
|
|
2303
2313
|
};
|
|
2304
2314
|
if (type === "sse") {
|
|
@@ -2490,6 +2500,14 @@ var MCPClient = class {
|
|
|
2490
2500
|
* @throws {Error} When connection fails for other reasons
|
|
2491
2501
|
*/
|
|
2492
2502
|
async connect() {
|
|
2503
|
+
if (this.client?.transport) {
|
|
2504
|
+
this.transport = null;
|
|
2505
|
+
try {
|
|
2506
|
+
await this.client.close();
|
|
2507
|
+
} catch {
|
|
2508
|
+
}
|
|
2509
|
+
this.client = null;
|
|
2510
|
+
}
|
|
2493
2511
|
await this.initialize();
|
|
2494
2512
|
if (!this.client || !this.oauthProvider) {
|
|
2495
2513
|
const error = "Client or OAuth provider not initialized";
|
|
@@ -2868,7 +2886,7 @@ var MCPClient = class {
|
|
|
2868
2886
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2869
2887
|
}
|
|
2870
2888
|
await sessions.delete(this.userId, this.sessionId);
|
|
2871
|
-
this.disconnect();
|
|
2889
|
+
await this.disconnect();
|
|
2872
2890
|
}
|
|
2873
2891
|
/**
|
|
2874
2892
|
* Checks if the client is currently connected to an MCP server
|
|
@@ -2878,10 +2896,21 @@ var MCPClient = class {
|
|
|
2878
2896
|
return this.client !== null;
|
|
2879
2897
|
}
|
|
2880
2898
|
/**
|
|
2881
|
-
* Disconnects from the MCP server and cleans up resources
|
|
2882
|
-
* Does not remove session from Redis
|
|
2899
|
+
* Disconnects from the MCP server and cleans up resources.
|
|
2900
|
+
* Does not remove session from Redis — use clearSession() for that.
|
|
2901
|
+
*
|
|
2902
|
+
* For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
|
|
2903
|
+
* before closing, as recommended by the MCP Streamable HTTP spec
|
|
2904
|
+
* (section "Session Management", rule 5). This is best-effort — errors
|
|
2905
|
+
* (e.g. server already restarted, 404/405 responses) are silently ignored.
|
|
2883
2906
|
*/
|
|
2884
|
-
disconnect(
|
|
2907
|
+
async disconnect() {
|
|
2908
|
+
if (this.transport instanceof StreamableHTTPClientTransport) {
|
|
2909
|
+
try {
|
|
2910
|
+
await this.transport.terminateSession();
|
|
2911
|
+
} catch {
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2885
2914
|
if (this.client) {
|
|
2886
2915
|
this.client.close();
|
|
2887
2916
|
}
|
|
@@ -2893,7 +2922,6 @@ var MCPClient = class {
|
|
|
2893
2922
|
type: "disconnected",
|
|
2894
2923
|
sessionId: this.sessionId,
|
|
2895
2924
|
serverId: this.serverId,
|
|
2896
|
-
reason,
|
|
2897
2925
|
timestamp: Date.now()
|
|
2898
2926
|
});
|
|
2899
2927
|
this._onObservabilityEvent.fire({
|
|
@@ -2902,9 +2930,7 @@ var MCPClient = class {
|
|
|
2902
2930
|
message: `Disconnected from ${this.serverId}`,
|
|
2903
2931
|
sessionId: this.sessionId,
|
|
2904
2932
|
serverId: this.serverId,
|
|
2905
|
-
payload: {
|
|
2906
|
-
reason: reason || "unknown"
|
|
2907
|
-
},
|
|
2933
|
+
payload: {},
|
|
2908
2934
|
timestamp: Date.now(),
|
|
2909
2935
|
id: nanoid()
|
|
2910
2936
|
});
|
|
@@ -3022,6 +3048,9 @@ var MultiSessionClient = class {
|
|
|
3022
3048
|
* Connects a single session, with built-in deduplication to prevent race conditions.
|
|
3023
3049
|
*
|
|
3024
3050
|
* - If a client for this session already exists and is connected, returns immediately.
|
|
3051
|
+
* - If the existing client entry is no longer connected (e.g. it was explicitly
|
|
3052
|
+
* disconnected), it is evicted so that `establishConnectionWithRetries` creates a
|
|
3053
|
+
* fresh transport — preventing "Client already connected" errors from the SDK.
|
|
3025
3054
|
* - If a connection attempt for this session is already in-flight (e.g. from a
|
|
3026
3055
|
* concurrent call), it joins the existing promise instead of starting a new one.
|
|
3027
3056
|
* This is the key concurrency lock — the `connectionPromises` map acts as a
|
|
@@ -3029,9 +3058,12 @@ var MultiSessionClient = class {
|
|
|
3029
3058
|
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
3030
3059
|
*/
|
|
3031
3060
|
async connectSession(session) {
|
|
3032
|
-
const
|
|
3033
|
-
if (
|
|
3034
|
-
|
|
3061
|
+
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
3062
|
+
if (existing) {
|
|
3063
|
+
if (existing.isConnected()) {
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
this.clients = this.clients.filter((c) => c !== existing);
|
|
3035
3067
|
}
|
|
3036
3068
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
3037
3069
|
return this.connectionPromises.get(session.sessionId);
|
|
@@ -3106,8 +3138,24 @@ var MultiSessionClient = class {
|
|
|
3106
3138
|
*/
|
|
3107
3139
|
async connect() {
|
|
3108
3140
|
const sessions2 = await this.getActiveSessions();
|
|
3141
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
3142
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
3109
3143
|
await this.connectInBatches(sessions2);
|
|
3110
3144
|
}
|
|
3145
|
+
/**
|
|
3146
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
3147
|
+
*
|
|
3148
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
3149
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
3150
|
+
* "Session not found. Reconnect without session header." errors.
|
|
3151
|
+
*
|
|
3152
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
3153
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
3154
|
+
*/
|
|
3155
|
+
async reconnect() {
|
|
3156
|
+
await this.disconnect();
|
|
3157
|
+
await this.connect();
|
|
3158
|
+
}
|
|
3111
3159
|
/**
|
|
3112
3160
|
* Returns all currently connected `MCPClient` instances.
|
|
3113
3161
|
*
|
|
@@ -3120,11 +3168,15 @@ var MultiSessionClient = class {
|
|
|
3120
3168
|
/**
|
|
3121
3169
|
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
3122
3170
|
*
|
|
3171
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
3172
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
3173
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
3174
|
+
*
|
|
3123
3175
|
* Call this during server shutdown or when a user logs out to free up
|
|
3124
3176
|
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3125
3177
|
*/
|
|
3126
|
-
disconnect() {
|
|
3127
|
-
this.clients.
|
|
3178
|
+
async disconnect() {
|
|
3179
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3128
3180
|
this.clients = [];
|
|
3129
3181
|
}
|
|
3130
3182
|
};
|
|
@@ -3354,7 +3406,6 @@ var SSEConnectionManager = class {
|
|
|
3354
3406
|
const client = this.clients.get(sessionId);
|
|
3355
3407
|
if (client) {
|
|
3356
3408
|
await client.clearSession();
|
|
3357
|
-
client.disconnect();
|
|
3358
3409
|
this.clients.delete(sessionId);
|
|
3359
3410
|
} else {
|
|
3360
3411
|
await sessions.delete(this.userId, sessionId);
|
|
@@ -3563,14 +3614,14 @@ var SSEConnectionManager = class {
|
|
|
3563
3614
|
/**
|
|
3564
3615
|
* Cleanup and close all connections
|
|
3565
3616
|
*/
|
|
3566
|
-
dispose() {
|
|
3617
|
+
async dispose() {
|
|
3567
3618
|
this.isActive = false;
|
|
3568
3619
|
if (this.heartbeatTimer) {
|
|
3569
3620
|
clearInterval(this.heartbeatTimer);
|
|
3570
3621
|
}
|
|
3571
|
-
|
|
3572
|
-
client.disconnect()
|
|
3573
|
-
|
|
3622
|
+
await Promise.all(
|
|
3623
|
+
Array.from(this.clients.values()).map((client) => client.disconnect())
|
|
3624
|
+
);
|
|
3574
3625
|
this.clients.clear();
|
|
3575
3626
|
}
|
|
3576
3627
|
};
|