@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.mjs
CHANGED
|
@@ -1666,12 +1666,12 @@ async function createStorage() {
|
|
|
1666
1666
|
}
|
|
1667
1667
|
if (type === "supabase") {
|
|
1668
1668
|
const url = process.env.SUPABASE_URL;
|
|
1669
|
-
const key = process.env.
|
|
1669
|
+
const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
|
|
1670
1670
|
if (!url || !key) {
|
|
1671
|
-
console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and
|
|
1671
|
+
console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SECRET_KEY.');
|
|
1672
1672
|
} else {
|
|
1673
|
-
if (!process.env.
|
|
1674
|
-
console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "
|
|
1673
|
+
if (!process.env.SUPABASE_SECRET_KEY) {
|
|
1674
|
+
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.');
|
|
1675
1675
|
}
|
|
1676
1676
|
try {
|
|
1677
1677
|
const { createClient } = await import('@supabase/supabase-js');
|
|
@@ -1726,13 +1726,13 @@ async function createStorage() {
|
|
|
1726
1726
|
console.log(`[mcp-ts][Storage] Auto-detection: "sqlite" (${process.env.MCP_TS_STORAGE_SQLITE_PATH})`);
|
|
1727
1727
|
return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
|
|
1728
1728
|
}
|
|
1729
|
-
if (process.env.SUPABASE_URL && (process.env.
|
|
1729
|
+
if (process.env.SUPABASE_URL && (process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY)) {
|
|
1730
1730
|
try {
|
|
1731
1731
|
const { createClient } = await import('@supabase/supabase-js');
|
|
1732
1732
|
const url = process.env.SUPABASE_URL;
|
|
1733
|
-
const key = process.env.
|
|
1734
|
-
if (!process.env.
|
|
1735
|
-
console.warn('[mcp-ts][Storage] \u26A0\uFE0F Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "
|
|
1733
|
+
const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
|
|
1734
|
+
if (!process.env.SUPABASE_SECRET_KEY) {
|
|
1735
|
+
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.');
|
|
1736
1736
|
}
|
|
1737
1737
|
const client = createClient(url, key);
|
|
1738
1738
|
console.log('[mcp-ts][Storage] Auto-detection: "supabase" (via SUPABASE_URL)');
|
|
@@ -2230,7 +2230,6 @@ var MCPClient = class {
|
|
|
2230
2230
|
const response = await fetch(url, { ...init, signal });
|
|
2231
2231
|
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2232
2232
|
if (response.status === 404 && hasSessionHeader) {
|
|
2233
|
-
this.client = null;
|
|
2234
2233
|
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2235
2234
|
}
|
|
2236
2235
|
return response;
|
|
@@ -2611,7 +2610,9 @@ var MCPClient = class {
|
|
|
2611
2610
|
method: "tools/list",
|
|
2612
2611
|
params: {}
|
|
2613
2612
|
};
|
|
2614
|
-
const result = await this.
|
|
2613
|
+
const result = await this.withRetry(
|
|
2614
|
+
() => this.client.request(request, ListToolsResultSchema)
|
|
2615
|
+
);
|
|
2615
2616
|
if (this.serverId) {
|
|
2616
2617
|
this._onConnectionEvent.fire({
|
|
2617
2618
|
type: "tools_discovered",
|
|
@@ -2651,7 +2652,9 @@ var MCPClient = class {
|
|
|
2651
2652
|
}
|
|
2652
2653
|
};
|
|
2653
2654
|
try {
|
|
2654
|
-
const result = await this.
|
|
2655
|
+
const result = await this.withRetry(
|
|
2656
|
+
() => this.client.request(request, CallToolResultSchema)
|
|
2657
|
+
);
|
|
2655
2658
|
this._onObservabilityEvent.fire({
|
|
2656
2659
|
type: "mcp:client:tool_call",
|
|
2657
2660
|
level: "info",
|
|
@@ -2703,7 +2706,9 @@ var MCPClient = class {
|
|
|
2703
2706
|
method: "prompts/list",
|
|
2704
2707
|
params: {}
|
|
2705
2708
|
};
|
|
2706
|
-
const result = await this.
|
|
2709
|
+
const result = await this.withRetry(
|
|
2710
|
+
() => this.client.request(request, ListPromptsResultSchema)
|
|
2711
|
+
);
|
|
2707
2712
|
this.emitStateChange("READY");
|
|
2708
2713
|
this.emitProgress(`Discovered ${result.prompts.length} prompts`);
|
|
2709
2714
|
return result;
|
|
@@ -2732,7 +2737,9 @@ var MCPClient = class {
|
|
|
2732
2737
|
arguments: args
|
|
2733
2738
|
}
|
|
2734
2739
|
};
|
|
2735
|
-
return await this.
|
|
2740
|
+
return await this.withRetry(
|
|
2741
|
+
() => this.client.request(request, GetPromptResultSchema)
|
|
2742
|
+
);
|
|
2736
2743
|
}
|
|
2737
2744
|
/**
|
|
2738
2745
|
* Lists all available resources from the connected MCP server
|
|
@@ -2749,7 +2756,9 @@ var MCPClient = class {
|
|
|
2749
2756
|
method: "resources/list",
|
|
2750
2757
|
params: {}
|
|
2751
2758
|
};
|
|
2752
|
-
const result = await this.
|
|
2759
|
+
const result = await this.withRetry(
|
|
2760
|
+
() => this.client.request(request, ListResourcesResultSchema)
|
|
2761
|
+
);
|
|
2753
2762
|
this.emitStateChange("READY");
|
|
2754
2763
|
this.emitProgress(`Discovered ${result.resources.length} resources`);
|
|
2755
2764
|
return result;
|
|
@@ -2776,7 +2785,36 @@ var MCPClient = class {
|
|
|
2776
2785
|
uri
|
|
2777
2786
|
}
|
|
2778
2787
|
};
|
|
2779
|
-
return await this.
|
|
2788
|
+
return await this.withRetry(
|
|
2789
|
+
() => this.client.request(request, ReadResourceResultSchema)
|
|
2790
|
+
);
|
|
2791
|
+
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Wraps an MCP request with automatic transport-session recovery.
|
|
2794
|
+
*
|
|
2795
|
+
* When the downstream MCP server rejects the request with a 404 indicating
|
|
2796
|
+
* the transport session has expired, this method tears down the stale SDK
|
|
2797
|
+
* client and transport, calls {@link reconnect} to negotiate a fresh session,
|
|
2798
|
+
* and retries the request once.
|
|
2799
|
+
*
|
|
2800
|
+
* Non-transient errors (network failures, auth errors, etc.) propagate as-is.
|
|
2801
|
+
*/
|
|
2802
|
+
async withRetry(fn) {
|
|
2803
|
+
try {
|
|
2804
|
+
return await fn();
|
|
2805
|
+
} catch (error) {
|
|
2806
|
+
if (!(error instanceof Error && error.message.includes("MCP_SESSION_EXPIRED"))) throw error;
|
|
2807
|
+
if (this.client) {
|
|
2808
|
+
try {
|
|
2809
|
+
await this.client.close();
|
|
2810
|
+
} catch {
|
|
2811
|
+
}
|
|
2812
|
+
this.transport = null;
|
|
2813
|
+
this.client = null;
|
|
2814
|
+
}
|
|
2815
|
+
await this.reconnect();
|
|
2816
|
+
return await fn();
|
|
2817
|
+
}
|
|
2780
2818
|
}
|
|
2781
2819
|
/**
|
|
2782
2820
|
* Reconnects to MCP server using existing OAuth provider from Redis
|
|
@@ -2789,6 +2827,12 @@ var MCPClient = class {
|
|
|
2789
2827
|
if (!this.oauthProvider) {
|
|
2790
2828
|
throw new Error("OAuth provider not initialized");
|
|
2791
2829
|
}
|
|
2830
|
+
if (this.client) {
|
|
2831
|
+
try {
|
|
2832
|
+
await this.client.close();
|
|
2833
|
+
} catch {
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2792
2836
|
this.client = new Client(
|
|
2793
2837
|
{
|
|
2794
2838
|
name: MCP_CLIENT_NAME,
|
|
@@ -2924,17 +2968,14 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
2924
2968
|
var CONNECTION_BATCH_SIZE = 5;
|
|
2925
2969
|
var MultiSessionClient = class {
|
|
2926
2970
|
/**
|
|
2927
|
-
*
|
|
2928
|
-
*
|
|
2929
|
-
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
2930
|
-
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
2931
|
-
* Falls back to sensible defaults if not provided.
|
|
2971
|
+
* @param userId - Unique identifier for the user (e.g. user ID or email).
|
|
2972
|
+
* @param options - Optional tuning and lifecycle hooks.
|
|
2932
2973
|
*/
|
|
2933
2974
|
constructor(userId, options = {}) {
|
|
2934
2975
|
__publicField(this, "clients", []);
|
|
2976
|
+
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2935
2977
|
__publicField(this, "userId");
|
|
2936
2978
|
__publicField(this, "options");
|
|
2937
|
-
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2938
2979
|
this.userId = userId;
|
|
2939
2980
|
this.options = {
|
|
2940
2981
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
@@ -2943,28 +2984,97 @@ var MultiSessionClient = class {
|
|
|
2943
2984
|
...options
|
|
2944
2985
|
};
|
|
2945
2986
|
}
|
|
2987
|
+
// -----------------------------------------------------------------------
|
|
2988
|
+
// Public API
|
|
2989
|
+
// -----------------------------------------------------------------------
|
|
2946
2990
|
/**
|
|
2947
|
-
* Fetches
|
|
2948
|
-
* ones that are ready to connect.
|
|
2991
|
+
* Fetches active sessions and establishes connections to all of them.
|
|
2949
2992
|
*
|
|
2950
|
-
*
|
|
2951
|
-
*
|
|
2952
|
-
*
|
|
2953
|
-
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
2993
|
+
* Call this once after creating the client. On long-running servers you
|
|
2994
|
+
* can cache the `MultiSessionClient` instance and call `connect()` on
|
|
2995
|
+
* each request — already-connected sessions are skipped internally.
|
|
2954
2996
|
*/
|
|
2955
|
-
async
|
|
2956
|
-
const
|
|
2957
|
-
const
|
|
2997
|
+
async connect() {
|
|
2998
|
+
const sessions2 = await this.fetchActiveSessions();
|
|
2999
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
3000
|
+
for (const client of this.clients) {
|
|
3001
|
+
if (!activeSessionIds.has(client.getSessionId())) {
|
|
3002
|
+
this.options.onSessionEvicted?.(client.getSessionId());
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
3006
|
+
await this.connectInBatches(sessions2);
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
3010
|
+
*
|
|
3011
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
3012
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
3013
|
+
* "Session not found. Reconnect without session header." errors.
|
|
3014
|
+
*
|
|
3015
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
3016
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
3017
|
+
*/
|
|
3018
|
+
async reconnect() {
|
|
3019
|
+
await this.disconnect();
|
|
3020
|
+
await this.connect();
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* Returns all currently connected `MCPClient` instances.
|
|
3024
|
+
*
|
|
3025
|
+
* Use this to enumerate available tools across all connected servers,
|
|
3026
|
+
* or to route a tool call to the right client by `serverId`.
|
|
3027
|
+
*/
|
|
3028
|
+
getClients() {
|
|
3029
|
+
return this.clients;
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Removes and disconnects a single session by ID.
|
|
3033
|
+
*
|
|
3034
|
+
* @returns `true` if the session was found and removed, `false` if not found.
|
|
3035
|
+
*/
|
|
3036
|
+
async removeSession(sessionId) {
|
|
3037
|
+
const idx = this.clients.findIndex((c) => c.getSessionId() === sessionId);
|
|
3038
|
+
if (idx === -1) return false;
|
|
3039
|
+
const [client] = this.clients.splice(idx, 1);
|
|
3040
|
+
await client.disconnect();
|
|
3041
|
+
return true;
|
|
3042
|
+
}
|
|
3043
|
+
/**
|
|
3044
|
+
* Gracefully disconnects all active MCP clients and clears the internal list.
|
|
3045
|
+
*
|
|
3046
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
3047
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
3048
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
3049
|
+
*
|
|
3050
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
3051
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3052
|
+
*/
|
|
3053
|
+
async disconnect() {
|
|
3054
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3055
|
+
this.clients = [];
|
|
3056
|
+
}
|
|
3057
|
+
// -----------------------------------------------------------------------
|
|
3058
|
+
// Internals
|
|
3059
|
+
// -----------------------------------------------------------------------
|
|
3060
|
+
/**
|
|
3061
|
+
* Resolves the list of sessions to connect.
|
|
3062
|
+
*
|
|
3063
|
+
* Uses the custom `sessionProvider` when provided, otherwise falls back
|
|
3064
|
+
* to querying the storage backend via `sessions.list(userId)`.
|
|
3065
|
+
*/
|
|
3066
|
+
async fetchActiveSessions() {
|
|
3067
|
+
const sessionList = this.options.sessionProvider ? await this.options.sessionProvider() : await sessions.list(this.userId);
|
|
3068
|
+
return sessionList.filter(
|
|
2958
3069
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
|
|
2959
3070
|
);
|
|
2960
|
-
return valid;
|
|
2961
3071
|
}
|
|
2962
3072
|
/**
|
|
2963
|
-
* Connects
|
|
3073
|
+
* Connects a list of sessions in controlled batches.
|
|
2964
3074
|
*
|
|
2965
|
-
* Batching prevents overwhelming the event loop or external servers when
|
|
2966
|
-
* has many active MCP sessions
|
|
2967
|
-
*
|
|
3075
|
+
* Batching prevents overwhelming the event loop or external servers when
|
|
3076
|
+
* a user has many active MCP sessions. Within each batch, sessions are
|
|
3077
|
+
* connected concurrently using `Promise.all`.
|
|
2968
3078
|
*/
|
|
2969
3079
|
async connectInBatches(sessions2) {
|
|
2970
3080
|
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
@@ -2973,17 +3083,16 @@ var MultiSessionClient = class {
|
|
|
2973
3083
|
}
|
|
2974
3084
|
}
|
|
2975
3085
|
/**
|
|
2976
|
-
* Connects a single session, with
|
|
3086
|
+
* Connects a single session, with deduplication to prevent race conditions.
|
|
2977
3087
|
*
|
|
2978
|
-
* - If a client for this session already exists and is connected, returns
|
|
2979
|
-
*
|
|
2980
|
-
*
|
|
2981
|
-
*
|
|
2982
|
-
* - If a connection attempt for this session is already in-flight
|
|
2983
|
-
*
|
|
2984
|
-
*
|
|
2985
|
-
*
|
|
2986
|
-
* - On completion (success or failure), the promise is cleaned up from the map.
|
|
3088
|
+
* - If a client for this session already exists and is connected, returns
|
|
3089
|
+
* immediately.
|
|
3090
|
+
* - If the existing client entry is no longer connected (e.g. explicit
|
|
3091
|
+
* disconnect), it is evicted so a fresh transport is created.
|
|
3092
|
+
* - If a connection attempt for this session is already in-flight, the
|
|
3093
|
+
* existing promise is reused as a per-session mutex.
|
|
3094
|
+
* - On completion (success or failure), the promise is cleaned up from
|
|
3095
|
+
* the connectionPromises map.
|
|
2987
3096
|
*/
|
|
2988
3097
|
async connectSession(session) {
|
|
2989
3098
|
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
@@ -2991,6 +3100,7 @@ var MultiSessionClient = class {
|
|
|
2991
3100
|
if (existing.isConnected()) {
|
|
2992
3101
|
return;
|
|
2993
3102
|
}
|
|
3103
|
+
this.options.onSessionEvicted?.(existing.getSessionId());
|
|
2994
3104
|
this.clients = this.clients.filter((c) => c !== existing);
|
|
2995
3105
|
}
|
|
2996
3106
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
@@ -3005,22 +3115,19 @@ var MultiSessionClient = class {
|
|
|
3005
3115
|
}
|
|
3006
3116
|
}
|
|
3007
3117
|
/**
|
|
3008
|
-
*
|
|
3118
|
+
* Core connection loop for a single session with retry logic.
|
|
3009
3119
|
*
|
|
3010
|
-
*
|
|
3011
|
-
*
|
|
3012
|
-
*
|
|
3013
|
-
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
3014
|
-
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
3015
|
-
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
3120
|
+
* 1. Creates a fresh `MCPClient` from the session data.
|
|
3121
|
+
* 2. Races `client.connect()` against a timeout.
|
|
3122
|
+
* 3. On success, replaces any stale entry and fires `onSessionConnected`.
|
|
3016
3123
|
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
3017
3124
|
*
|
|
3018
|
-
* If all attempts are exhausted, logs an error and returns silently
|
|
3019
|
-
*
|
|
3125
|
+
* If all attempts are exhausted, logs an error and returns silently so
|
|
3126
|
+
* a single bad server doesn't block the rest of the batch.
|
|
3020
3127
|
*/
|
|
3021
3128
|
async establishConnectionWithRetries(session) {
|
|
3022
|
-
const maxRetries = this.options.maxRetries
|
|
3023
|
-
const retryDelay = this.options.retryDelay
|
|
3129
|
+
const maxRetries = this.options.maxRetries;
|
|
3130
|
+
const retryDelay = this.options.retryDelay;
|
|
3024
3131
|
let lastError;
|
|
3025
3132
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3026
3133
|
try {
|
|
@@ -3034,10 +3141,13 @@ var MultiSessionClient = class {
|
|
|
3034
3141
|
transportType: session.transportType,
|
|
3035
3142
|
headers: session.headers
|
|
3036
3143
|
});
|
|
3037
|
-
const timeoutMs = this.options.timeout
|
|
3144
|
+
const timeoutMs = this.options.timeout;
|
|
3038
3145
|
let timeoutTimer;
|
|
3039
3146
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3040
|
-
timeoutTimer = setTimeout(
|
|
3147
|
+
timeoutTimer = setTimeout(
|
|
3148
|
+
() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)),
|
|
3149
|
+
timeoutMs
|
|
3150
|
+
);
|
|
3041
3151
|
});
|
|
3042
3152
|
try {
|
|
3043
3153
|
await Promise.race([client.connect(), timeoutPromise]);
|
|
@@ -3046,6 +3156,7 @@ var MultiSessionClient = class {
|
|
|
3046
3156
|
}
|
|
3047
3157
|
this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
|
|
3048
3158
|
this.clients.push(client);
|
|
3159
|
+
this.options.onSessionConnected?.(session.sessionId, client);
|
|
3049
3160
|
return;
|
|
3050
3161
|
} catch (error) {
|
|
3051
3162
|
lastError = error;
|
|
@@ -3054,58 +3165,11 @@ var MultiSessionClient = class {
|
|
|
3054
3165
|
}
|
|
3055
3166
|
}
|
|
3056
3167
|
}
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
*
|
|
3063
|
-
* Call this once after creating the client. On traditional servers, you can
|
|
3064
|
-
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
3065
|
-
* re-fetching and re-connecting on every request.
|
|
3066
|
-
*/
|
|
3067
|
-
async connect() {
|
|
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()));
|
|
3071
|
-
await this.connectInBatches(sessions2);
|
|
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
|
-
}
|
|
3087
|
-
/**
|
|
3088
|
-
* Returns all currently connected `MCPClient` instances.
|
|
3089
|
-
*
|
|
3090
|
-
* Use this to enumerate available tools across all connected servers,
|
|
3091
|
-
* or to route a tool call to the right client by `serverId`.
|
|
3092
|
-
*/
|
|
3093
|
-
getClients() {
|
|
3094
|
-
return this.clients;
|
|
3095
|
-
}
|
|
3096
|
-
/**
|
|
3097
|
-
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
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
|
-
*
|
|
3103
|
-
* Call this during server shutdown or when a user logs out to free up
|
|
3104
|
-
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3105
|
-
*/
|
|
3106
|
-
async disconnect() {
|
|
3107
|
-
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3108
|
-
this.clients = [];
|
|
3168
|
+
this.options.onSessionFailed?.(session.sessionId, lastError);
|
|
3169
|
+
console.error(
|
|
3170
|
+
`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`,
|
|
3171
|
+
lastError
|
|
3172
|
+
);
|
|
3109
3173
|
}
|
|
3110
3174
|
};
|
|
3111
3175
|
|