@mcp-ts/sdk 2.4.2 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-ts/sdk",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/bin/mcp-ts.ts CHANGED
@@ -82,13 +82,13 @@ async function initSupabase() {
82
82
  console.log(' npx supabase db push');
83
83
  console.log('\n3. Add your Supabase credentials to .env:');
84
84
  console.log(' SUPABASE_URL=https://<your-project-id>.supabase.co');
85
- console.log(' SUPABASE_SERVICE_ROLE_KEY=<your-service-role-key>');
86
- console.log('\n⚠️ Important: Use the service_role key (not the anon key) for server-side storage.');
87
- console.log(' The service_role key bypasses RLS policies and is required for mcp-ts to work correctly.');
88
- console.log(' Find it in: Supabase Dashboard -> Project Settings -> API -> service_role');
85
+ console.log(' SUPABASE_SECRET_KEY=<your-secret-key>');
86
+ console.log('\n⚠️ Important: Use the secret key (not the anon key) for server-side storage.');
87
+ console.log(' The secret key bypasses RLS policies and is required for mcp-ts to work correctly.');
88
+ console.log(' Find it in: Supabase Dashboard -> Project Settings -> API -> Secret key');
89
89
  } else if (files.length > 0) {
90
90
  console.log('\n👍 All migration files are already present in your project.');
91
- console.log(' Ensure SUPABASE_SERVICE_ROLE_KEY (not SUPABASE_ANON_KEY) is set in your .env');
91
+ console.log(' Ensure SUPABASE_SECRET_KEY (not SUPABASE_ANON_KEY) is set in your .env');
92
92
  } else {
93
93
  console.log('⚠️ No migration files found to copy.');
94
94
  }
@@ -152,6 +152,19 @@ export class MultiSessionClient implements ToolClientProvider {
152
152
  return this.clients;
153
153
  }
154
154
 
155
+ /**
156
+ * Removes and disconnects a single session by ID.
157
+ *
158
+ * @returns `true` if the session was found and removed, `false` if not found.
159
+ */
160
+ async removeSession(sessionId: string): Promise<boolean> {
161
+ const idx = this.clients.findIndex(c => c.getSessionId() === sessionId);
162
+ if (idx === -1) return false;
163
+ const [client] = this.clients.splice(idx, 1);
164
+ await client.disconnect();
165
+ return true;
166
+ }
167
+
155
168
  /**
156
169
  * Gracefully disconnects all active MCP clients and clears the internal list.
157
170
  *
@@ -257,7 +257,6 @@ export class MCPClient {
257
257
  const hasSessionHeader = init?.headers && new Headers(init.headers as HeadersInit).has('mcp-session-id');
258
258
 
259
259
  if (response.status === 404 && hasSessionHeader) {
260
- this.client = null;
261
260
  throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
262
261
  }
263
262
 
@@ -791,7 +790,9 @@ export class MCPClient {
791
790
  params: {},
792
791
  };
793
792
 
794
- const result = await this.client.request(request, ListToolsResultSchema);
793
+ const result = await this.withRetry(() =>
794
+ this.client!.request(request, ListToolsResultSchema)
795
+ );
795
796
 
796
797
  if (this.serverId) {
797
798
  this._onConnectionEvent.fire({
@@ -837,7 +838,9 @@ export class MCPClient {
837
838
  };
838
839
 
839
840
  try {
840
- const result = await this.client.request(request, CallToolResultSchema);
841
+ const result = await this.withRetry(() =>
842
+ this.client!.request(request, CallToolResultSchema)
843
+ );
841
844
 
842
845
  this._onObservabilityEvent.fire({
843
846
  type: 'mcp:client:tool_call',
@@ -897,7 +900,9 @@ export class MCPClient {
897
900
  params: {},
898
901
  };
899
902
 
900
- const result = await this.client.request(request, ListPromptsResultSchema);
903
+ const result = await this.withRetry(() =>
904
+ this.client!.request(request, ListPromptsResultSchema)
905
+ );
901
906
 
902
907
  this.emitStateChange('READY');
903
908
  this.emitProgress(`Discovered ${result.prompts.length} prompts`);
@@ -931,7 +936,9 @@ export class MCPClient {
931
936
  },
932
937
  };
933
938
 
934
- return await this.client.request(request, GetPromptResultSchema);
939
+ return await this.withRetry(() =>
940
+ this.client!.request(request, GetPromptResultSchema)
941
+ );
935
942
  }
936
943
 
937
944
  /**
@@ -952,7 +959,9 @@ export class MCPClient {
952
959
  params: {},
953
960
  };
954
961
 
955
- const result = await this.client.request(request, ListResourcesResultSchema);
962
+ const result = await this.withRetry(() =>
963
+ this.client!.request(request, ListResourcesResultSchema)
964
+ );
956
965
 
957
966
  this.emitStateChange('READY');
958
967
  this.emitProgress(`Discovered ${result.resources.length} resources`);
@@ -984,7 +993,34 @@ export class MCPClient {
984
993
  },
985
994
  };
986
995
 
987
- return await this.client.request(request, ReadResourceResultSchema);
996
+ return await this.withRetry(() =>
997
+ this.client!.request(request, ReadResourceResultSchema)
998
+ );
999
+ }
1000
+
1001
+ /**
1002
+ * Wraps an MCP request with automatic transport-session recovery.
1003
+ *
1004
+ * When the downstream MCP server rejects the request with a 404 indicating
1005
+ * the transport session has expired, this method tears down the stale SDK
1006
+ * client and transport, calls {@link reconnect} to negotiate a fresh session,
1007
+ * and retries the request once.
1008
+ *
1009
+ * Non-transient errors (network failures, auth errors, etc.) propagate as-is.
1010
+ */
1011
+ private async withRetry<T>(fn: () => Promise<T>): Promise<T> {
1012
+ try {
1013
+ return await fn();
1014
+ } catch (error) {
1015
+ if (!(error instanceof Error && error.message.includes('MCP_SESSION_EXPIRED'))) throw error;
1016
+ if (this.client) {
1017
+ try { await this.client.close(); } catch {}
1018
+ this.transport = null;
1019
+ this.client = null;
1020
+ }
1021
+ await this.reconnect();
1022
+ return await fn();
1023
+ }
988
1024
  }
989
1025
 
990
1026
  /**
@@ -1000,6 +1036,12 @@ export class MCPClient {
1000
1036
  throw new Error('OAuth provider not initialized');
1001
1037
  }
1002
1038
 
1039
+ // Close the client initialize() may have created — we need a fresh
1040
+ // client that will negotiate a new transport session.
1041
+ if (this.client) {
1042
+ try { await this.client.close(); } catch {}
1043
+ }
1044
+
1003
1045
  this.client = new Client(
1004
1046
  {
1005
1047
  name: MCP_CLIENT_NAME,
@@ -141,13 +141,13 @@ async function createStorage(): Promise<SessionStore> {
141
141
 
142
142
  if (type === 'supabase') {
143
143
  const url = process.env.SUPABASE_URL;
144
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY;
144
+ const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY;
145
145
 
146
146
  if (!url || !key) {
147
- console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.');
147
+ console.warn('[mcp-ts][Storage] Explicit selection "supabase" requires SUPABASE_URL and SUPABASE_SECRET_KEY.');
148
148
  } else {
149
- if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
150
- console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
149
+ if (!process.env.SUPABASE_SECRET_KEY) {
150
+ console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SECRET_KEY" is recommended.');
151
151
  }
152
152
  try {
153
153
  const { createClient } = await import('@supabase/supabase-js');
@@ -210,14 +210,14 @@ async function createStorage(): Promise<SessionStore> {
210
210
  return await initializeStorage(new SqliteStorage({ path: process.env.MCP_TS_STORAGE_SQLITE_PATH }));
211
211
  }
212
212
 
213
- if (process.env.SUPABASE_URL && (process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY)) {
213
+ if (process.env.SUPABASE_URL && (process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY)) {
214
214
  try {
215
215
  const { createClient } = await import('@supabase/supabase-js');
216
216
  const url = process.env.SUPABASE_URL;
217
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_ANON_KEY!;
217
+ const key = process.env.SUPABASE_SECRET_KEY || process.env.SUPABASE_ANON_KEY!;
218
218
 
219
- if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
220
- console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SERVICE_ROLE_KEY" is recommended.');
219
+ if (!process.env.SUPABASE_SECRET_KEY) {
220
+ console.warn('[mcp-ts][Storage] ⚠️ Warning: Using "SUPABASE_ANON_KEY" for server-side storage. You may encounter RLS policy violations. "SUPABASE_SECRET_KEY" is recommended.');
221
221
  }
222
222
 
223
223
  const client = createClient(url, key);