@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.mjs
CHANGED
|
@@ -1670,12 +1670,12 @@ async function createStorage() {
|
|
|
1670
1670
|
}
|
|
1671
1671
|
if (type === "supabase") {
|
|
1672
1672
|
const url = process.env.SUPABASE_URL;
|
|
1673
|
-
const key = process.env.
|
|
1673
|
+
const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
|
|
1674
1674
|
if (!url || !key) {
|
|
1675
|
-
console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and
|
|
1675
|
+
console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SECRET_KEY.');
|
|
1676
1676
|
} else {
|
|
1677
|
-
if (!process.env.
|
|
1678
|
-
console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "
|
|
1677
|
+
if (!process.env.SUPABASE_SECRET_KEY) {
|
|
1678
|
+
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.');
|
|
1679
1679
|
}
|
|
1680
1680
|
try {
|
|
1681
1681
|
const { createClient } = await import('@supabase/supabase-js');
|
|
@@ -1730,13 +1730,13 @@ async function createStorage() {
|
|
|
1730
1730
|
console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
|
|
1731
1731
|
return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
|
|
1732
1732
|
}
|
|
1733
|
-
if (process.env.SUPABASE_URL && (process.env.
|
|
1733
|
+
if (process.env.SUPABASE_URL && (process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY)) {
|
|
1734
1734
|
try {
|
|
1735
1735
|
const { createClient } = await import('@supabase/supabase-js');
|
|
1736
1736
|
const url = process.env.SUPABASE_URL;
|
|
1737
|
-
const key = process.env.
|
|
1738
|
-
if (!process.env.
|
|
1739
|
-
console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "
|
|
1737
|
+
const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
|
|
1738
|
+
if (!process.env.SUPABASE_SECRET_KEY) {
|
|
1739
|
+
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.');
|
|
1740
1740
|
}
|
|
1741
1741
|
const client = createClient(url, key);
|
|
1742
1742
|
console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
|
|
@@ -2302,7 +2302,6 @@ var MCPClient = class {
|
|
|
2302
2302
|
const response = await fetch(url, { ...init, signal });
|
|
2303
2303
|
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2304
2304
|
if (response.status === 404 && hasSessionHeader) {
|
|
2305
|
-
this.client = null;
|
|
2306
2305
|
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2307
2306
|
}
|
|
2308
2307
|
return response;
|
|
@@ -2683,7 +2682,9 @@ var MCPClient = class {
|
|
|
2683
2682
|
method: "tools/list",
|
|
2684
2683
|
params: {}
|
|
2685
2684
|
};
|
|
2686
|
-
const result = await this.
|
|
2685
|
+
const result = await this.withRetry(
|
|
2686
|
+
() => this.client.request(request, ListToolsResultSchema)
|
|
2687
|
+
);
|
|
2687
2688
|
if (this.serverId) {
|
|
2688
2689
|
this._onConnectionEvent.fire({
|
|
2689
2690
|
type: "tools_discovered",
|
|
@@ -2723,7 +2724,9 @@ var MCPClient = class {
|
|
|
2723
2724
|
}
|
|
2724
2725
|
};
|
|
2725
2726
|
try {
|
|
2726
|
-
const result = await this.
|
|
2727
|
+
const result = await this.withRetry(
|
|
2728
|
+
() => this.client.request(request, CallToolResultSchema)
|
|
2729
|
+
);
|
|
2727
2730
|
this._onObservabilityEvent.fire({
|
|
2728
2731
|
type: "mcp:client:tool_call",
|
|
2729
2732
|
level: "info",
|
|
@@ -2775,7 +2778,9 @@ var MCPClient = class {
|
|
|
2775
2778
|
method: "prompts/list",
|
|
2776
2779
|
params: {}
|
|
2777
2780
|
};
|
|
2778
|
-
const result = await this.
|
|
2781
|
+
const result = await this.withRetry(
|
|
2782
|
+
() => this.client.request(request, ListPromptsResultSchema)
|
|
2783
|
+
);
|
|
2779
2784
|
this.emitStateChange("READY");
|
|
2780
2785
|
this.emitProgress(`Discovered ${result.prompts.length} prompts`);
|
|
2781
2786
|
return result;
|
|
@@ -2804,7 +2809,9 @@ var MCPClient = class {
|
|
|
2804
2809
|
arguments: args
|
|
2805
2810
|
}
|
|
2806
2811
|
};
|
|
2807
|
-
return await this.
|
|
2812
|
+
return await this.withRetry(
|
|
2813
|
+
() => this.client.request(request, GetPromptResultSchema)
|
|
2814
|
+
);
|
|
2808
2815
|
}
|
|
2809
2816
|
/**
|
|
2810
2817
|
* Lists all available resources from the connected MCP server
|
|
@@ -2821,7 +2828,9 @@ var MCPClient = class {
|
|
|
2821
2828
|
method: "resources/list",
|
|
2822
2829
|
params: {}
|
|
2823
2830
|
};
|
|
2824
|
-
const result = await this.
|
|
2831
|
+
const result = await this.withRetry(
|
|
2832
|
+
() => this.client.request(request, ListResourcesResultSchema)
|
|
2833
|
+
);
|
|
2825
2834
|
this.emitStateChange("READY");
|
|
2826
2835
|
this.emitProgress(`Discovered ${result.resources.length} resources`);
|
|
2827
2836
|
return result;
|
|
@@ -2848,7 +2857,36 @@ var MCPClient = class {
|
|
|
2848
2857
|
uri
|
|
2849
2858
|
}
|
|
2850
2859
|
};
|
|
2851
|
-
return await this.
|
|
2860
|
+
return await this.withRetry(
|
|
2861
|
+
() => this.client.request(request, ReadResourceResultSchema)
|
|
2862
|
+
);
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Wraps an MCP request with automatic transport-session recovery.
|
|
2866
|
+
*
|
|
2867
|
+
* When the downstream MCP server rejects the request with a 404 indicating
|
|
2868
|
+
* the transport session has expired, this method tears down the stale SDK
|
|
2869
|
+
* client and transport, calls {@link reconnect} to negotiate a fresh session,
|
|
2870
|
+
* and retries the request once.
|
|
2871
|
+
*
|
|
2872
|
+
* Non-transient errors (network failures, auth errors, etc.) propagate as-is.
|
|
2873
|
+
*/
|
|
2874
|
+
async withRetry(fn) {
|
|
2875
|
+
try {
|
|
2876
|
+
return await fn();
|
|
2877
|
+
} catch (error) {
|
|
2878
|
+
if (!(error instanceof Error && error.message.includes("MCP_SESSION_EXPIRED"))) throw error;
|
|
2879
|
+
if (this.client) {
|
|
2880
|
+
try {
|
|
2881
|
+
await this.client.close();
|
|
2882
|
+
} catch {
|
|
2883
|
+
}
|
|
2884
|
+
this.transport = null;
|
|
2885
|
+
this.client = null;
|
|
2886
|
+
}
|
|
2887
|
+
await this.reconnect();
|
|
2888
|
+
return await fn();
|
|
2889
|
+
}
|
|
2852
2890
|
}
|
|
2853
2891
|
/**
|
|
2854
2892
|
* Reconnects to MCP server using existing OAuth provider from Redis
|
|
@@ -2861,6 +2899,12 @@ var MCPClient = class {
|
|
|
2861
2899
|
if (!this.oauthProvider) {
|
|
2862
2900
|
throw new Error("OAuth provider not initialized");
|
|
2863
2901
|
}
|
|
2902
|
+
if (this.client) {
|
|
2903
|
+
try {
|
|
2904
|
+
await this.client.close();
|
|
2905
|
+
} catch {
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2864
2908
|
this.client = new Client(
|
|
2865
2909
|
{
|
|
2866
2910
|
name: MCP_CLIENT_NAME,
|
|
@@ -2996,17 +3040,14 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
2996
3040
|
var CONNECTION_BATCH_SIZE = 5;
|
|
2997
3041
|
var MultiSessionClient = class {
|
|
2998
3042
|
/**
|
|
2999
|
-
*
|
|
3000
|
-
*
|
|
3001
|
-
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
3002
|
-
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
3003
|
-
* Falls back to sensible defaults if not provided.
|
|
3043
|
+
* @param userId - Unique identifier for the user (e.g. user ID or email).
|
|
3044
|
+
* @param options - Optional tuning and lifecycle hooks.
|
|
3004
3045
|
*/
|
|
3005
3046
|
constructor(userId, options = {}) {
|
|
3006
3047
|
__publicField(this, "clients", []);
|
|
3048
|
+
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
3007
3049
|
__publicField(this, "userId");
|
|
3008
3050
|
__publicField(this, "options");
|
|
3009
|
-
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
3010
3051
|
this.userId = userId;
|
|
3011
3052
|
this.options = {
|
|
3012
3053
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
@@ -3015,28 +3056,97 @@ var MultiSessionClient = class {
|
|
|
3015
3056
|
...options
|
|
3016
3057
|
};
|
|
3017
3058
|
}
|
|
3059
|
+
// -----------------------------------------------------------------------
|
|
3060
|
+
// Public API
|
|
3061
|
+
// -----------------------------------------------------------------------
|
|
3018
3062
|
/**
|
|
3019
|
-
* Fetches
|
|
3020
|
-
* ones that are ready to connect.
|
|
3063
|
+
* Fetches active sessions and establishes connections to all of them.
|
|
3021
3064
|
*
|
|
3022
|
-
*
|
|
3023
|
-
*
|
|
3024
|
-
*
|
|
3025
|
-
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
3065
|
+
* Call this once after creating the client. On long-running servers you
|
|
3066
|
+
* can cache the `MultiSessionClient` instance and call `connect()` on
|
|
3067
|
+
* each request — already-connected sessions are skipped internally.
|
|
3026
3068
|
*/
|
|
3027
|
-
async
|
|
3028
|
-
const
|
|
3029
|
-
const
|
|
3069
|
+
async connect() {
|
|
3070
|
+
const sessions2 = await this.fetchActiveSessions();
|
|
3071
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
3072
|
+
for (const client of this.clients) {
|
|
3073
|
+
if (!activeSessionIds.has(client.getSessionId())) {
|
|
3074
|
+
this.options.onSessionEvicted?.(client.getSessionId());
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
3078
|
+
await this.connectInBatches(sessions2);
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
3082
|
+
*
|
|
3083
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
3084
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
3085
|
+
* "Session not found. Reconnect without session header." errors.
|
|
3086
|
+
*
|
|
3087
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
3088
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
3089
|
+
*/
|
|
3090
|
+
async reconnect() {
|
|
3091
|
+
await this.disconnect();
|
|
3092
|
+
await this.connect();
|
|
3093
|
+
}
|
|
3094
|
+
/**
|
|
3095
|
+
* Returns all currently connected `MCPClient` instances.
|
|
3096
|
+
*
|
|
3097
|
+
* Use this to enumerate available tools across all connected servers,
|
|
3098
|
+
* or to route a tool call to the right client by `serverId`.
|
|
3099
|
+
*/
|
|
3100
|
+
getClients() {
|
|
3101
|
+
return this.clients;
|
|
3102
|
+
}
|
|
3103
|
+
/**
|
|
3104
|
+
* Removes and disconnects a single session by ID.
|
|
3105
|
+
*
|
|
3106
|
+
* @returns `true` if the session was found and removed, `false` if not found.
|
|
3107
|
+
*/
|
|
3108
|
+
async removeSession(sessionId) {
|
|
3109
|
+
const idx = this.clients.findIndex((c) => c.getSessionId() === sessionId);
|
|
3110
|
+
if (idx === -1) return false;
|
|
3111
|
+
const [client] = this.clients.splice(idx, 1);
|
|
3112
|
+
await client.disconnect();
|
|
3113
|
+
return true;
|
|
3114
|
+
}
|
|
3115
|
+
/**
|
|
3116
|
+
* Gracefully disconnects all active MCP clients and clears the internal list.
|
|
3117
|
+
*
|
|
3118
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
3119
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
3120
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
3121
|
+
*
|
|
3122
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
3123
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3124
|
+
*/
|
|
3125
|
+
async disconnect() {
|
|
3126
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3127
|
+
this.clients = [];
|
|
3128
|
+
}
|
|
3129
|
+
// -----------------------------------------------------------------------
|
|
3130
|
+
// Internals
|
|
3131
|
+
// -----------------------------------------------------------------------
|
|
3132
|
+
/**
|
|
3133
|
+
* Resolves the list of sessions to connect.
|
|
3134
|
+
*
|
|
3135
|
+
* Uses the custom `sessionProvider` when provided, otherwise falls back
|
|
3136
|
+
* to querying the storage backend via `sessions.list(userId)`.
|
|
3137
|
+
*/
|
|
3138
|
+
async fetchActiveSessions() {
|
|
3139
|
+
const sessionList = this.options.sessionProvider ? await this.options.sessionProvider() : await sessions.list(this.userId);
|
|
3140
|
+
return sessionList.filter(
|
|
3030
3141
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
|
|
3031
3142
|
);
|
|
3032
|
-
return valid;
|
|
3033
3143
|
}
|
|
3034
3144
|
/**
|
|
3035
|
-
* Connects
|
|
3145
|
+
* Connects a list of sessions in controlled batches.
|
|
3036
3146
|
*
|
|
3037
|
-
* Batching prevents overwhelming the event loop or external servers when
|
|
3038
|
-
* has many active MCP sessions
|
|
3039
|
-
*
|
|
3147
|
+
* Batching prevents overwhelming the event loop or external servers when
|
|
3148
|
+
* a user has many active MCP sessions. Within each batch, sessions are
|
|
3149
|
+
* connected concurrently using `Promise.all`.
|
|
3040
3150
|
*/
|
|
3041
3151
|
async connectInBatches(sessions2) {
|
|
3042
3152
|
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
@@ -3045,17 +3155,16 @@ var MultiSessionClient = class {
|
|
|
3045
3155
|
}
|
|
3046
3156
|
}
|
|
3047
3157
|
/**
|
|
3048
|
-
* Connects a single session, with
|
|
3158
|
+
* Connects a single session, with deduplication to prevent race conditions.
|
|
3049
3159
|
*
|
|
3050
|
-
* - If a client for this session already exists and is connected, returns
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
3053
|
-
*
|
|
3054
|
-
* - If a connection attempt for this session is already in-flight
|
|
3055
|
-
*
|
|
3056
|
-
*
|
|
3057
|
-
*
|
|
3058
|
-
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
3160
|
+
* - If a client for this session already exists and is connected, returns
|
|
3161
|
+
* immediately.
|
|
3162
|
+
* - If the existing client entry is no longer connected (e.g. explicit
|
|
3163
|
+
* disconnect), it is evicted so a fresh transport is created.
|
|
3164
|
+
* - If a connection attempt for this session is already in-flight, the
|
|
3165
|
+
* existing promise is reused as a per-session mutex.
|
|
3166
|
+
* - On completion (success or failure), the promise is cleaned up from
|
|
3167
|
+
* the connectionPromises map.
|
|
3059
3168
|
*/
|
|
3060
3169
|
async connectSession(session) {
|
|
3061
3170
|
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
@@ -3063,6 +3172,7 @@ var MultiSessionClient = class {
|
|
|
3063
3172
|
if (existing.isConnected()) {
|
|
3064
3173
|
return;
|
|
3065
3174
|
}
|
|
3175
|
+
this.options.onSessionEvicted?.(existing.getSessionId());
|
|
3066
3176
|
this.clients = this.clients.filter((c) => c !== existing);
|
|
3067
3177
|
}
|
|
3068
3178
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
@@ -3077,22 +3187,19 @@ var MultiSessionClient = class {
|
|
|
3077
3187
|
}
|
|
3078
3188
|
}
|
|
3079
3189
|
/**
|
|
3080
|
-
*
|
|
3190
|
+
* Core connection loop for a single session with retry logic.
|
|
3081
3191
|
*
|
|
3082
|
-
*
|
|
3083
|
-
*
|
|
3084
|
-
*
|
|
3085
|
-
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
3086
|
-
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
3087
|
-
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
3192
|
+
* 1. Creates a fresh `MCPClient` from the session data.
|
|
3193
|
+
* 2. Races `client.connect()` against a timeout.
|
|
3194
|
+
* 3. On success, replaces any stale entry and fires `onSessionConnected`.
|
|
3088
3195
|
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
3089
3196
|
*
|
|
3090
|
-
* If all attempts are exhausted, logs an error and returns silently
|
|
3091
|
-
*
|
|
3197
|
+
* If all attempts are exhausted, logs an error and returns silently so
|
|
3198
|
+
* a single bad server doesn't block the rest of the batch.
|
|
3092
3199
|
*/
|
|
3093
3200
|
async establishConnectionWithRetries(session) {
|
|
3094
|
-
const maxRetries = this.options.maxRetries
|
|
3095
|
-
const retryDelay = this.options.retryDelay
|
|
3201
|
+
const maxRetries = this.options.maxRetries;
|
|
3202
|
+
const retryDelay = this.options.retryDelay;
|
|
3096
3203
|
let lastError;
|
|
3097
3204
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3098
3205
|
try {
|
|
@@ -3106,10 +3213,13 @@ var MultiSessionClient = class {
|
|
|
3106
3213
|
transportType: session.transportType,
|
|
3107
3214
|
headers: session.headers
|
|
3108
3215
|
});
|
|
3109
|
-
const timeoutMs = this.options.timeout
|
|
3216
|
+
const timeoutMs = this.options.timeout;
|
|
3110
3217
|
let timeoutTimer;
|
|
3111
3218
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3112
|
-
timeoutTimer = setTimeout(
|
|
3219
|
+
timeoutTimer = setTimeout(
|
|
3220
|
+
() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)),
|
|
3221
|
+
timeoutMs
|
|
3222
|
+
);
|
|
3113
3223
|
});
|
|
3114
3224
|
try {
|
|
3115
3225
|
await Promise.race([client.connect(), timeoutPromise]);
|
|
@@ -3118,6 +3228,7 @@ var MultiSessionClient = class {
|
|
|
3118
3228
|
}
|
|
3119
3229
|
this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
|
|
3120
3230
|
this.clients.push(client);
|
|
3231
|
+
this.options.onSessionConnected?.(session.sessionId, client);
|
|
3121
3232
|
return;
|
|
3122
3233
|
} catch (error) {
|
|
3123
3234
|
lastError = error;
|
|
@@ -3126,58 +3237,11 @@ var MultiSessionClient = class {
|
|
|
3126
3237
|
}
|
|
3127
3238
|
}
|
|
3128
3239
|
}
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
*
|
|
3135
|
-
* Call this once after creating the client. On traditional servers, you can
|
|
3136
|
-
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
3137
|
-
* re-fetching and re-connecting on every request.
|
|
3138
|
-
*/
|
|
3139
|
-
async connect() {
|
|
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()));
|
|
3143
|
-
await this.connectInBatches(sessions2);
|
|
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
|
-
}
|
|
3159
|
-
/**
|
|
3160
|
-
* Returns all currently connected `MCPClient` instances.
|
|
3161
|
-
*
|
|
3162
|
-
* Use this to enumerate available tools across all connected servers,
|
|
3163
|
-
* or to route a tool call to the right client by `serverId`.
|
|
3164
|
-
*/
|
|
3165
|
-
getClients() {
|
|
3166
|
-
return this.clients;
|
|
3167
|
-
}
|
|
3168
|
-
/**
|
|
3169
|
-
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
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
|
-
*
|
|
3175
|
-
* Call this during server shutdown or when a user logs out to free up
|
|
3176
|
-
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3177
|
-
*/
|
|
3178
|
-
async disconnect() {
|
|
3179
|
-
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3180
|
-
this.clients = [];
|
|
3240
|
+
this.options.onSessionFailed?.(session.sessionId, lastError);
|
|
3241
|
+
console.error(
|
|
3242
|
+
`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`,
|
|
3243
|
+
lastError
|
|
3244
|
+
);
|
|
3181
3245
|
}
|
|
3182
3246
|
};
|
|
3183
3247
|
|