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