@mcp-ts/sdk 2.4.1 → 2.4.3
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/dist/adapters/agui-adapter.d.mts +3 -4
- package/dist/adapters/agui-adapter.d.ts +3 -4
- package/dist/adapters/agui-middleware.d.mts +3 -4
- package/dist/adapters/agui-middleware.d.ts +3 -4
- package/dist/adapters/ai-adapter.d.mts +3 -4
- package/dist/adapters/ai-adapter.d.ts +3 -4
- package/dist/adapters/langchain-adapter.d.mts +3 -4
- package/dist/adapters/langchain-adapter.d.ts +3 -4
- package/dist/adapters/mastra-adapter.d.mts +2 -2
- package/dist/adapters/mastra-adapter.d.ts +2 -2
- package/dist/bin/mcp-ts.js +5 -5
- package/dist/bin/mcp-ts.js.map +1 -1
- package/dist/bin/mcp-ts.mjs +5 -5
- package/dist/bin/mcp-ts.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -3
- package/dist/client/index.d.ts +2 -3
- package/dist/client/react.d.mts +4 -6
- package/dist/client/react.d.ts +4 -6
- package/dist/client/vue.d.mts +4 -6
- package/dist/client/vue.d.ts +4 -6
- package/dist/{index-Ch7ouNSa.d.ts → index-B8kJSrBJ.d.ts} +1 -2
- package/dist/{index-L5XoXgsb.d.mts → index-DiJsm_lK.d.mts} +1 -2
- package/dist/index.d.mts +5 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.js +174 -110
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +174 -110
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-k-9RvWvi.d.mts → multi-session-client-B6hsYO8V.d.mts} +218 -71
- package/dist/{multi-session-client-Bwm-efqg.d.ts → multi-session-client-DOBvtaQV.d.ts} +218 -71
- package/dist/server/index.d.mts +5 -128
- package/dist/server/index.d.ts +5 -128
- package/dist/server/index.js +174 -110
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +174 -110
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +4 -5
- package/dist/shared/index.d.ts +4 -5
- package/dist/{tool-router-CuApsDiV.d.mts → tool-router-CbG4Tum6.d.mts} +1 -1
- package/dist/{tool-router-CZMrOG8J.d.ts → tool-router-ChIhPwgP.d.ts} +1 -1
- package/dist/{types-DCk_IF4L.d.mts → types-CjczQwNX.d.mts} +122 -1
- package/dist/{types-DCk_IF4L.d.ts → types-CjczQwNX.d.ts} +122 -1
- package/package.json +1 -1
- package/src/bin/mcp-ts.ts +5 -5
- package/src/server/mcp/multi-session-client.ts +190 -132
- package/src/server/mcp/oauth-client.ts +49 -7
- package/src/server/storage/index.ts +8 -8
- package/dist/events-C4m7tK1U.d.mts +0 -122
- package/dist/events-C4m7tK1U.d.ts +0 -122
package/dist/index.js
CHANGED
|
@@ -1741,12 +1741,12 @@ async function createStorage() {
|
|
|
1741
1741
|
}
|
|
1742
1742
|
if (type === "supabase") {
|
|
1743
1743
|
const url = process.env.SUPABASE_URL;
|
|
1744
|
-
const key = process.env.
|
|
1744
|
+
const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
|
|
1745
1745
|
if (!url || !key) {
|
|
1746
|
-
console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and
|
|
1746
|
+
console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SECRET_KEY.');
|
|
1747
1747
|
} else {
|
|
1748
|
-
if (!process.env.
|
|
1749
|
-
console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "
|
|
1748
|
+
if (!process.env.SUPABASE_SECRET_KEY) {
|
|
1749
|
+
console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SECRET_KEY" is recommended.');
|
|
1750
1750
|
}
|
|
1751
1751
|
try {
|
|
1752
1752
|
const { createClient } = await import('@supabase/supabase-js');
|
|
@@ -1801,13 +1801,13 @@ async function createStorage() {
|
|
|
1801
1801
|
console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
|
|
1802
1802
|
return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
|
|
1803
1803
|
}
|
|
1804
|
-
if (process.env.SUPABASE_URL && (process.env.
|
|
1804
|
+
if (process.env.SUPABASE_URL && (process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY)) {
|
|
1805
1805
|
try {
|
|
1806
1806
|
const { createClient } = await import('@supabase/supabase-js');
|
|
1807
1807
|
const url = process.env.SUPABASE_URL;
|
|
1808
|
-
const key = process.env.
|
|
1809
|
-
if (!process.env.
|
|
1810
|
-
console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "
|
|
1808
|
+
const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
|
|
1809
|
+
if (!process.env.SUPABASE_SECRET_KEY) {
|
|
1810
|
+
console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SECRET_KEY" is recommended.');
|
|
1811
1811
|
}
|
|
1812
1812
|
const client = createClient(url, key);
|
|
1813
1813
|
console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
|
|
@@ -2375,7 +2375,6 @@ var MCPClient = class {
|
|
|
2375
2375
|
const response = await fetch(url, { ...init, signal });
|
|
2376
2376
|
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2377
2377
|
if (response.status === 404 && hasSessionHeader) {
|
|
2378
|
-
this.client = null;
|
|
2379
2378
|
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2380
2379
|
}
|
|
2381
2380
|
return response;
|
|
@@ -2756,7 +2755,9 @@ var MCPClient = class {
|
|
|
2756
2755
|
method: "tools/list",
|
|
2757
2756
|
params: {}
|
|
2758
2757
|
};
|
|
2759
|
-
const result = await this.
|
|
2758
|
+
const result = await this.withRetry(
|
|
2759
|
+
() => this.client.request(request, types_js.ListToolsResultSchema)
|
|
2760
|
+
);
|
|
2760
2761
|
if (this.serverId) {
|
|
2761
2762
|
this._onConnectionEvent.fire({
|
|
2762
2763
|
type: "tools_discovered",
|
|
@@ -2796,7 +2797,9 @@ var MCPClient = class {
|
|
|
2796
2797
|
}
|
|
2797
2798
|
};
|
|
2798
2799
|
try {
|
|
2799
|
-
const result = await this.
|
|
2800
|
+
const result = await this.withRetry(
|
|
2801
|
+
() => this.client.request(request, types_js.CallToolResultSchema)
|
|
2802
|
+
);
|
|
2800
2803
|
this._onObservabilityEvent.fire({
|
|
2801
2804
|
type: "mcp:client:tool_call",
|
|
2802
2805
|
level: "info",
|
|
@@ -2848,7 +2851,9 @@ var MCPClient = class {
|
|
|
2848
2851
|
method: "prompts/list",
|
|
2849
2852
|
params: {}
|
|
2850
2853
|
};
|
|
2851
|
-
const result = await this.
|
|
2854
|
+
const result = await this.withRetry(
|
|
2855
|
+
() => this.client.request(request, types_js.ListPromptsResultSchema)
|
|
2856
|
+
);
|
|
2852
2857
|
this.emitStateChange("READY");
|
|
2853
2858
|
this.emitProgress(`Discovered ${result.prompts.length} prompts`);
|
|
2854
2859
|
return result;
|
|
@@ -2877,7 +2882,9 @@ var MCPClient = class {
|
|
|
2877
2882
|
arguments: args
|
|
2878
2883
|
}
|
|
2879
2884
|
};
|
|
2880
|
-
return await this.
|
|
2885
|
+
return await this.withRetry(
|
|
2886
|
+
() => this.client.request(request, types_js.GetPromptResultSchema)
|
|
2887
|
+
);
|
|
2881
2888
|
}
|
|
2882
2889
|
/**
|
|
2883
2890
|
* Lists all available resources from the connected MCP server
|
|
@@ -2894,7 +2901,9 @@ var MCPClient = class {
|
|
|
2894
2901
|
method: "resources/list",
|
|
2895
2902
|
params: {}
|
|
2896
2903
|
};
|
|
2897
|
-
const result = await this.
|
|
2904
|
+
const result = await this.withRetry(
|
|
2905
|
+
() => this.client.request(request, types_js.ListResourcesResultSchema)
|
|
2906
|
+
);
|
|
2898
2907
|
this.emitStateChange("READY");
|
|
2899
2908
|
this.emitProgress(`Discovered ${result.resources.length} resources`);
|
|
2900
2909
|
return result;
|
|
@@ -2921,7 +2930,36 @@ var MCPClient = class {
|
|
|
2921
2930
|
uri
|
|
2922
2931
|
}
|
|
2923
2932
|
};
|
|
2924
|
-
return await this.
|
|
2933
|
+
return await this.withRetry(
|
|
2934
|
+
() => this.client.request(request, types_js.ReadResourceResultSchema)
|
|
2935
|
+
);
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Wraps an MCP request with automatic transport-session recovery.
|
|
2939
|
+
*
|
|
2940
|
+
* When the downstream MCP server rejects the request with a 404 indicating
|
|
2941
|
+
* the transport session has expired, this method tears down the stale SDK
|
|
2942
|
+
* client and transport, calls {@link reconnect} to negotiate a fresh session,
|
|
2943
|
+
* and retries the request once.
|
|
2944
|
+
*
|
|
2945
|
+
* Non-transient errors (network failures, auth errors, etc.) propagate as-is.
|
|
2946
|
+
*/
|
|
2947
|
+
async withRetry(fn) {
|
|
2948
|
+
try {
|
|
2949
|
+
return await fn();
|
|
2950
|
+
} catch (error) {
|
|
2951
|
+
if (!(error instanceof Error && error.message.includes("MCP_SESSION_EXPIRED"))) throw error;
|
|
2952
|
+
if (this.client) {
|
|
2953
|
+
try {
|
|
2954
|
+
await this.client.close();
|
|
2955
|
+
} catch {
|
|
2956
|
+
}
|
|
2957
|
+
this.transport = null;
|
|
2958
|
+
this.client = null;
|
|
2959
|
+
}
|
|
2960
|
+
await this.reconnect();
|
|
2961
|
+
return await fn();
|
|
2962
|
+
}
|
|
2925
2963
|
}
|
|
2926
2964
|
/**
|
|
2927
2965
|
* Reconnects to MCP server using existing OAuth provider from Redis
|
|
@@ -2934,6 +2972,12 @@ var MCPClient = class {
|
|
|
2934
2972
|
if (!this.oauthProvider) {
|
|
2935
2973
|
throw new Error("OAuth provider not initialized");
|
|
2936
2974
|
}
|
|
2975
|
+
if (this.client) {
|
|
2976
|
+
try {
|
|
2977
|
+
await this.client.close();
|
|
2978
|
+
} catch {
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2937
2981
|
this.client = new index_js.Client(
|
|
2938
2982
|
{
|
|
2939
2983
|
name: MCP_CLIENT_NAME,
|
|
@@ -3070,17 +3114,14 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
3070
3114
|
var CONNECTION_BATCH_SIZE = 5;
|
|
3071
3115
|
var MultiSessionClient = class {
|
|
3072
3116
|
/**
|
|
3073
|
-
*
|
|
3074
|
-
*
|
|
3075
|
-
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
3076
|
-
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
3077
|
-
* Falls back to sensible defaults if not provided.
|
|
3117
|
+
* @param userId - Unique identifier for the user (e.g. user ID or email).
|
|
3118
|
+
* @param options - Optional tuning and lifecycle hooks.
|
|
3078
3119
|
*/
|
|
3079
3120
|
constructor(userId, options = {}) {
|
|
3080
3121
|
__publicField(this, "clients", []);
|
|
3122
|
+
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
3081
3123
|
__publicField(this, "userId");
|
|
3082
3124
|
__publicField(this, "options");
|
|
3083
|
-
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
3084
3125
|
this.userId = userId;
|
|
3085
3126
|
this.options = {
|
|
3086
3127
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
@@ -3089,28 +3130,97 @@ var MultiSessionClient = class {
|
|
|
3089
3130
|
...options
|
|
3090
3131
|
};
|
|
3091
3132
|
}
|
|
3133
|
+
// -----------------------------------------------------------------------
|
|
3134
|
+
// Public API
|
|
3135
|
+
// -----------------------------------------------------------------------
|
|
3092
3136
|
/**
|
|
3093
|
-
* Fetches
|
|
3094
|
-
* ones that are ready to connect.
|
|
3137
|
+
* Fetches active sessions and establishes connections to all of them.
|
|
3095
3138
|
*
|
|
3096
|
-
*
|
|
3097
|
-
*
|
|
3098
|
-
*
|
|
3099
|
-
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
3139
|
+
* Call this once after creating the client. On long-running servers you
|
|
3140
|
+
* can cache the `MultiSessionClient` instance and call `connect()` on
|
|
3141
|
+
* each request — already-connected sessions are skipped internally.
|
|
3100
3142
|
*/
|
|
3101
|
-
async
|
|
3102
|
-
const
|
|
3103
|
-
const
|
|
3143
|
+
async connect() {
|
|
3144
|
+
const sessions2 = await this.fetchActiveSessions();
|
|
3145
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
3146
|
+
for (const client of this.clients) {
|
|
3147
|
+
if (!activeSessionIds.has(client.getSessionId())) {
|
|
3148
|
+
this.options.onSessionEvicted?.(client.getSessionId());
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
3152
|
+
await this.connectInBatches(sessions2);
|
|
3153
|
+
}
|
|
3154
|
+
/**
|
|
3155
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
3156
|
+
*
|
|
3157
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
3158
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
3159
|
+
* "Session not found. Reconnect without session header." errors.
|
|
3160
|
+
*
|
|
3161
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
3162
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
3163
|
+
*/
|
|
3164
|
+
async reconnect() {
|
|
3165
|
+
await this.disconnect();
|
|
3166
|
+
await this.connect();
|
|
3167
|
+
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Returns all currently connected `MCPClient` instances.
|
|
3170
|
+
*
|
|
3171
|
+
* Use this to enumerate available tools across all connected servers,
|
|
3172
|
+
* or to route a tool call to the right client by `serverId`.
|
|
3173
|
+
*/
|
|
3174
|
+
getClients() {
|
|
3175
|
+
return this.clients;
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Removes and disconnects a single session by ID.
|
|
3179
|
+
*
|
|
3180
|
+
* @returns `true` if the session was found and removed, `false` if not found.
|
|
3181
|
+
*/
|
|
3182
|
+
async removeSession(sessionId) {
|
|
3183
|
+
const idx = this.clients.findIndex((c) => c.getSessionId() === sessionId);
|
|
3184
|
+
if (idx === -1) return false;
|
|
3185
|
+
const [client] = this.clients.splice(idx, 1);
|
|
3186
|
+
await client.disconnect();
|
|
3187
|
+
return true;
|
|
3188
|
+
}
|
|
3189
|
+
/**
|
|
3190
|
+
* Gracefully disconnects all active MCP clients and clears the internal list.
|
|
3191
|
+
*
|
|
3192
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
3193
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
3194
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
3195
|
+
*
|
|
3196
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
3197
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3198
|
+
*/
|
|
3199
|
+
async disconnect() {
|
|
3200
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3201
|
+
this.clients = [];
|
|
3202
|
+
}
|
|
3203
|
+
// -----------------------------------------------------------------------
|
|
3204
|
+
// Internals
|
|
3205
|
+
// -----------------------------------------------------------------------
|
|
3206
|
+
/**
|
|
3207
|
+
* Resolves the list of sessions to connect.
|
|
3208
|
+
*
|
|
3209
|
+
* Uses the custom `sessionProvider` when provided, otherwise falls back
|
|
3210
|
+
* to querying the storage backend via `sessions.list(userId)`.
|
|
3211
|
+
*/
|
|
3212
|
+
async fetchActiveSessions() {
|
|
3213
|
+
const sessionList = this.options.sessionProvider ? await this.options.sessionProvider() : await sessions.list(this.userId);
|
|
3214
|
+
return sessionList.filter(
|
|
3104
3215
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
|
|
3105
3216
|
);
|
|
3106
|
-
return valid;
|
|
3107
3217
|
}
|
|
3108
3218
|
/**
|
|
3109
|
-
* Connects
|
|
3219
|
+
* Connects a list of sessions in controlled batches.
|
|
3110
3220
|
*
|
|
3111
|
-
* Batching prevents overwhelming the event loop or external servers when
|
|
3112
|
-
* has many active MCP sessions
|
|
3113
|
-
*
|
|
3221
|
+
* Batching prevents overwhelming the event loop or external servers when
|
|
3222
|
+
* a user has many active MCP sessions. Within each batch, sessions are
|
|
3223
|
+
* connected concurrently using `Promise.all`.
|
|
3114
3224
|
*/
|
|
3115
3225
|
async connectInBatches(sessions2) {
|
|
3116
3226
|
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
@@ -3119,17 +3229,16 @@ var MultiSessionClient = class {
|
|
|
3119
3229
|
}
|
|
3120
3230
|
}
|
|
3121
3231
|
/**
|
|
3122
|
-
* Connects a single session, with
|
|
3232
|
+
* Connects a single session, with deduplication to prevent race conditions.
|
|
3123
3233
|
*
|
|
3124
|
-
* - If a client for this session already exists and is connected, returns
|
|
3125
|
-
*
|
|
3126
|
-
*
|
|
3127
|
-
*
|
|
3128
|
-
* - If a connection attempt for this session is already in-flight
|
|
3129
|
-
*
|
|
3130
|
-
*
|
|
3131
|
-
*
|
|
3132
|
-
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
3234
|
+
* - If a client for this session already exists and is connected, returns
|
|
3235
|
+
* immediately.
|
|
3236
|
+
* - If the existing client entry is no longer connected (e.g. explicit
|
|
3237
|
+
* disconnect), it is evicted so a fresh transport is created.
|
|
3238
|
+
* - If a connection attempt for this session is already in-flight, the
|
|
3239
|
+
* existing promise is reused as a per-session mutex.
|
|
3240
|
+
* - On completion (success or failure), the promise is cleaned up from
|
|
3241
|
+
* the connectionPromises map.
|
|
3133
3242
|
*/
|
|
3134
3243
|
async connectSession(session) {
|
|
3135
3244
|
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
@@ -3137,6 +3246,7 @@ var MultiSessionClient = class {
|
|
|
3137
3246
|
if (existing.isConnected()) {
|
|
3138
3247
|
return;
|
|
3139
3248
|
}
|
|
3249
|
+
this.options.onSessionEvicted?.(existing.getSessionId());
|
|
3140
3250
|
this.clients = this.clients.filter((c) => c !== existing);
|
|
3141
3251
|
}
|
|
3142
3252
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
@@ -3151,22 +3261,19 @@ var MultiSessionClient = class {
|
|
|
3151
3261
|
}
|
|
3152
3262
|
}
|
|
3153
3263
|
/**
|
|
3154
|
-
*
|
|
3264
|
+
* Core connection loop for a single session with retry logic.
|
|
3155
3265
|
*
|
|
3156
|
-
*
|
|
3157
|
-
*
|
|
3158
|
-
*
|
|
3159
|
-
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
3160
|
-
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
3161
|
-
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
3266
|
+
* 1. Creates a fresh `MCPClient` from the session data.
|
|
3267
|
+
* 2. Races `client.connect()` against a timeout.
|
|
3268
|
+
* 3. On success, replaces any stale entry and fires `onSessionConnected`.
|
|
3162
3269
|
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
3163
3270
|
*
|
|
3164
|
-
* If all attempts are exhausted, logs an error and returns silently
|
|
3165
|
-
*
|
|
3271
|
+
* If all attempts are exhausted, logs an error and returns silently so
|
|
3272
|
+
* a single bad server doesn't block the rest of the batch.
|
|
3166
3273
|
*/
|
|
3167
3274
|
async establishConnectionWithRetries(session) {
|
|
3168
|
-
const maxRetries = this.options.maxRetries
|
|
3169
|
-
const retryDelay = this.options.retryDelay
|
|
3275
|
+
const maxRetries = this.options.maxRetries;
|
|
3276
|
+
const retryDelay = this.options.retryDelay;
|
|
3170
3277
|
let lastError;
|
|
3171
3278
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3172
3279
|
try {
|
|
@@ -3180,10 +3287,13 @@ var MultiSessionClient = class {
|
|
|
3180
3287
|
transportType: session.transportType,
|
|
3181
3288
|
headers: session.headers
|
|
3182
3289
|
});
|
|
3183
|
-
const timeoutMs = this.options.timeout
|
|
3290
|
+
const timeoutMs = this.options.timeout;
|
|
3184
3291
|
let timeoutTimer;
|
|
3185
3292
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3186
|
-
timeoutTimer = setTimeout(
|
|
3293
|
+
timeoutTimer = setTimeout(
|
|
3294
|
+
() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)),
|
|
3295
|
+
timeoutMs
|
|
3296
|
+
);
|
|
3187
3297
|
});
|
|
3188
3298
|
try {
|
|
3189
3299
|
await Promise.race([client.connect(), timeoutPromise]);
|
|
@@ -3192,6 +3302,7 @@ var MultiSessionClient = class {
|
|
|
3192
3302
|
}
|
|
3193
3303
|
this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
|
|
3194
3304
|
this.clients.push(client);
|
|
3305
|
+
this.options.onSessionConnected?.(session.sessionId, client);
|
|
3195
3306
|
return;
|
|
3196
3307
|
} catch (error) {
|
|
3197
3308
|
lastError = error;
|
|
@@ -3200,58 +3311,11 @@ var MultiSessionClient = class {
|
|
|
3200
3311
|
}
|
|
3201
3312
|
}
|
|
3202
3313
|
}
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
*
|
|
3209
|
-
* Call this once after creating the client. On traditional servers, you can
|
|
3210
|
-
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
3211
|
-
* re-fetching and re-connecting on every request.
|
|
3212
|
-
*/
|
|
3213
|
-
async connect() {
|
|
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()));
|
|
3217
|
-
await this.connectInBatches(sessions2);
|
|
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
|
-
}
|
|
3233
|
-
/**
|
|
3234
|
-
* Returns all currently connected `MCPClient` instances.
|
|
3235
|
-
*
|
|
3236
|
-
* Use this to enumerate available tools across all connected servers,
|
|
3237
|
-
* or to route a tool call to the right client by `serverId`.
|
|
3238
|
-
*/
|
|
3239
|
-
getClients() {
|
|
3240
|
-
return this.clients;
|
|
3241
|
-
}
|
|
3242
|
-
/**
|
|
3243
|
-
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
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
|
-
*
|
|
3249
|
-
* Call this during server shutdown or when a user logs out to free up
|
|
3250
|
-
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3251
|
-
*/
|
|
3252
|
-
async disconnect() {
|
|
3253
|
-
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3254
|
-
this.clients = [];
|
|
3314
|
+
this.options.onSessionFailed?.(session.sessionId, lastError);
|
|
3315
|
+
console.error(
|
|
3316
|
+
`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`,
|
|
3317
|
+
lastError
|
|
3318
|
+
);
|
|
3255
3319
|
}
|
|
3256
3320
|
};
|
|
3257
3321
|
|