@mcp-ts/sdk 2.4.5 → 2.5.1

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.
Files changed (86) hide show
  1. package/dist/adapters/agui-adapter.d.mts +3 -3
  2. package/dist/adapters/agui-adapter.d.ts +3 -3
  3. package/dist/adapters/agui-adapter.js.map +1 -1
  4. package/dist/adapters/agui-adapter.mjs.map +1 -1
  5. package/dist/adapters/agui-middleware.d.mts +3 -3
  6. package/dist/adapters/agui-middleware.d.ts +3 -3
  7. package/dist/adapters/agui-middleware.js.map +1 -1
  8. package/dist/adapters/agui-middleware.mjs.map +1 -1
  9. package/dist/adapters/ai-adapter.d.mts +3 -3
  10. package/dist/adapters/ai-adapter.d.ts +3 -3
  11. package/dist/adapters/ai-adapter.js.map +1 -1
  12. package/dist/adapters/ai-adapter.mjs.map +1 -1
  13. package/dist/adapters/langchain-adapter.d.mts +3 -3
  14. package/dist/adapters/langchain-adapter.d.ts +3 -3
  15. package/dist/adapters/langchain-adapter.js +2 -2
  16. package/dist/adapters/langchain-adapter.js.map +1 -1
  17. package/dist/adapters/langchain-adapter.mjs +2 -2
  18. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  19. package/dist/adapters/mastra-adapter.d.mts +2 -2
  20. package/dist/adapters/mastra-adapter.d.ts +2 -2
  21. package/dist/adapters/mastra-adapter.js +2 -2
  22. package/dist/adapters/mastra-adapter.js.map +1 -1
  23. package/dist/adapters/mastra-adapter.mjs +2 -2
  24. package/dist/adapters/mastra-adapter.mjs.map +1 -1
  25. package/dist/client/index.d.mts +2 -2
  26. package/dist/client/index.d.ts +2 -2
  27. package/dist/client/index.js +9 -0
  28. package/dist/client/index.js.map +1 -1
  29. package/dist/client/index.mjs +9 -0
  30. package/dist/client/index.mjs.map +1 -1
  31. package/dist/client/react.d.mts +15 -4
  32. package/dist/client/react.d.ts +15 -4
  33. package/dist/client/react.js +56 -37
  34. package/dist/client/react.js.map +1 -1
  35. package/dist/client/react.mjs +56 -37
  36. package/dist/client/react.mjs.map +1 -1
  37. package/dist/client/vue.d.mts +4 -4
  38. package/dist/client/vue.d.ts +4 -4
  39. package/dist/client/vue.js +9 -0
  40. package/dist/client/vue.js.map +1 -1
  41. package/dist/client/vue.mjs +9 -0
  42. package/dist/client/vue.mjs.map +1 -1
  43. package/dist/{index-CZk2gu2E.d.ts → index-BEdyuz1M.d.ts} +6 -1
  44. package/dist/{index-CkM3p0eE.d.mts → index-C99wE2Zf.d.mts} +6 -1
  45. package/dist/index.d.mts +4 -4
  46. package/dist/index.d.ts +4 -4
  47. package/dist/index.js +380 -82
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +376 -82
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/{multi-session-client-QOOPdEVp.d.ts → multi-session-client-CclWRxTD.d.mts} +14 -2
  52. package/dist/{multi-session-client-ChQrBZhF.d.mts → multi-session-client-DzvZD6Tt.d.ts} +14 -2
  53. package/dist/server/index.d.mts +18 -4
  54. package/dist/server/index.d.ts +18 -4
  55. package/dist/server/index.js +371 -82
  56. package/dist/server/index.js.map +1 -1
  57. package/dist/server/index.mjs +367 -82
  58. package/dist/server/index.mjs.map +1 -1
  59. package/dist/shared/index.d.mts +4 -4
  60. package/dist/shared/index.d.ts +4 -4
  61. package/dist/shared/index.js.map +1 -1
  62. package/dist/shared/index.mjs.map +1 -1
  63. package/dist/{tool-router-CGs3IDOJ.d.mts → tool-router-C9ECn3FX.d.mts} +1 -1
  64. package/dist/{tool-router-DQ-HrD7W.d.ts → tool-router-CUSSk6lT.d.ts} +1 -1
  65. package/dist/{types-Bf-7GOLW.d.mts → types-CbFbPkfQ.d.mts} +38 -3
  66. package/dist/{types-Bf-7GOLW.d.ts → types-CbFbPkfQ.d.ts} +38 -3
  67. package/migrations/neon/20260513010000_install_mcp_sessions.sql +1 -0
  68. package/migrations/supabase/20260330195700_install_mcp_sessions.sql +1 -0
  69. package/package.json +2 -2
  70. package/src/adapters/agui-adapter.ts +6 -2
  71. package/src/adapters/ai-adapter.ts +3 -1
  72. package/src/adapters/langchain-adapter.ts +5 -3
  73. package/src/adapters/mastra-adapter.ts +5 -3
  74. package/src/client/core/sse-client.ts +21 -0
  75. package/src/client/react/oauth-popup.tsx +17 -24
  76. package/src/client/react/use-mcp.ts +69 -15
  77. package/src/server/handlers/sse-handler.ts +202 -9
  78. package/src/server/mcp/multi-session-client.ts +7 -3
  79. package/src/server/mcp/oauth-client.ts +25 -10
  80. package/src/server/mcp/tool-policy-gateway.ts +90 -0
  81. package/src/server/storage/neon-backend.ts +65 -60
  82. package/src/server/storage/session-lifecycle.ts +5 -0
  83. package/src/server/storage/supabase-backend.ts +27 -15
  84. package/src/server/storage/tool-policy.ts +89 -0
  85. package/src/server/storage/types.ts +11 -0
  86. package/src/shared/types.ts +52 -1
package/dist/index.mjs CHANGED
@@ -162,6 +162,59 @@ var SOFTWARE_VERSION = "2.3.4";
162
162
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
163
163
  var MCP_CLIENT_VERSION = "2.0";
164
164
 
165
+ // src/server/storage/tool-policy.ts
166
+ function createToolId(serverId, toolName) {
167
+ return `${serverId}::${toolName}`;
168
+ }
169
+ function normalizeToolIds(input) {
170
+ if (!Array.isArray(input)) return [];
171
+ return Array.from(new Set(
172
+ input.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
173
+ ));
174
+ }
175
+ function normalizeToolPolicy(input, now = Date.now()) {
176
+ if (!input || typeof input !== "object") {
177
+ return void 0;
178
+ }
179
+ const mode = input.mode === "allowlist" || input.mode === "denylist" ? input.mode : "all";
180
+ const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : now;
181
+ if (mode === "all") {
182
+ return { mode: "all", toolIds: [], updatedAt };
183
+ }
184
+ return { mode, toolIds: normalizeToolIds(input.toolIds), updatedAt };
185
+ }
186
+ function normalizeToolPolicyForUpdate(input, now = Date.now()) {
187
+ return normalizeToolPolicy(input, now) ?? { mode: "all", toolIds: [], updatedAt: now };
188
+ }
189
+ function isToolAllowed(policy, toolName, serverId) {
190
+ if (!policy || policy.mode === "all") return true;
191
+ if (!serverId) return false;
192
+ const toolId = createToolId(serverId, toolName);
193
+ if (policy.mode === "allowlist") {
194
+ return policy.toolIds.includes(toolId);
195
+ }
196
+ return !policy.toolIds.includes(toolId);
197
+ }
198
+ function assertToolAllowed(policy, toolName, serverId) {
199
+ if (isToolAllowed(policy, toolName, serverId)) return;
200
+ throw new Error(`Tool "${toolName}" is not allowed for this MCP session`);
201
+ }
202
+ function filterToolsByPolicy(tools, policy, serverId) {
203
+ if (!policy || policy.mode === "all") return tools;
204
+ return tools.filter((tool) => isToolAllowed(policy, tool.name, serverId));
205
+ }
206
+ function validateToolPolicyAgainstTools(policy, tools, serverId) {
207
+ if (policy.mode === "all") return;
208
+ if (!serverId) {
209
+ throw new Error("Cannot validate MCP tool policy without a serverId");
210
+ }
211
+ const availableIds = new Set(tools.map((tool) => createToolId(serverId, tool.name)));
212
+ const unknownIds = policy.toolIds.filter((id) => !availableIds.has(id));
213
+ if (unknownIds.length > 0) {
214
+ throw new Error(`Unknown tool id(s) for this MCP session: ${unknownIds.join(", ")}`);
215
+ }
216
+ }
217
+
165
218
  // src/server/storage/session-lifecycle.ts
166
219
  function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
167
220
  return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
@@ -178,7 +231,8 @@ function normalizeNewSession(session, now = Date.now()) {
178
231
  status,
179
232
  createdAt,
180
233
  updatedAt,
181
- expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
234
+ expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt),
235
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
182
236
  };
183
237
  }
184
238
  function mergeSessionUpdate(current, data, now = Date.now()) {
@@ -192,7 +246,8 @@ function mergeSessionUpdate(current, data, now = Date.now()) {
192
246
  return {
193
247
  ...updated,
194
248
  status,
195
- expiresAt: resolveSessionExpiresAt(status, updatedAt)
249
+ expiresAt: resolveSessionExpiresAt(status, updatedAt),
250
+ toolPolicy: normalizeToolPolicy(updated.toolPolicy, updatedAt)
196
251
  };
197
252
  }
198
253
  function normalizeStoredSession(session) {
@@ -205,7 +260,8 @@ function normalizeStoredSession(session) {
205
260
  status,
206
261
  createdAt,
207
262
  updatedAt,
208
- expiresAt
263
+ expiresAt,
264
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
209
265
  };
210
266
  }
211
267
  function isSessionExpired(session, now = Date.now()) {
@@ -1063,7 +1119,8 @@ var SupabaseStorageBackend = class {
1063
1119
  userId: row.user_id,
1064
1120
  headers: decryptObject(row.headers),
1065
1121
  authUrl: row.auth_url,
1066
- status: row.status ?? "pending"
1122
+ status: row.status ?? "pending",
1123
+ toolPolicy: normalizeToolPolicy(row.tool_policy)
1067
1124
  };
1068
1125
  }
1069
1126
  mapRowToCredentials(row, userId, sessionId) {
@@ -1087,7 +1144,7 @@ var SupabaseStorageBackend = class {
1087
1144
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1088
1145
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1089
1146
  const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
1090
- const { error } = await this.supabase.from("mcp_sessions").insert({
1147
+ const insertData = {
1091
1148
  session_id: sessionId,
1092
1149
  user_id: userId,
1093
1150
  server_id: session.serverId,
@@ -1101,7 +1158,12 @@ var SupabaseStorageBackend = class {
1101
1158
  auth_url: session.authUrl ?? null,
1102
1159
  status,
1103
1160
  expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
1104
- });
1161
+ };
1162
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy);
1163
+ if (toolPolicy) {
1164
+ insertData.tool_policy = toolPolicy;
1165
+ }
1166
+ const { error } = await this.supabase.from("mcp_sessions").insert(insertData);
1105
1167
  if (error) {
1106
1168
  if (error.code === "23505") {
1107
1169
  throw new Error(`Session ${sessionId} already exists`);
@@ -1126,6 +1188,7 @@ var SupabaseStorageBackend = class {
1126
1188
  }
1127
1189
  if ("headers" in data) updateData.headers = encryptObject(data.headers);
1128
1190
  if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
1191
+ if ("toolPolicy" in data) updateData.tool_policy = normalizeToolPolicy(data.toolPolicy);
1129
1192
  const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
1130
1193
  let updatedRows = null;
1131
1194
  if (shouldUpdateSession) {
@@ -1298,7 +1361,8 @@ var NeonStorageBackend = class {
1298
1361
  userId: row.user_id,
1299
1362
  headers: decryptObject(row.headers),
1300
1363
  authUrl: row.auth_url ?? void 0,
1301
- status: row.status ?? "pending"
1364
+ status: row.status ?? "pending",
1365
+ toolPolicy: normalizeToolPolicy(row.tool_policy)
1302
1366
  };
1303
1367
  }
1304
1368
  mapRowToCredentials(row, userId, sessionId) {
@@ -1321,42 +1385,48 @@ var NeonStorageBackend = class {
1321
1385
  const status = session.status ?? "pending";
1322
1386
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1323
1387
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1324
- const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
1388
+ const createdAtMs = new Date(createdAt).getTime();
1389
+ const expiresAt = resolveSessionExpiresAt(status, createdAtMs);
1390
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy, createdAtMs);
1391
+ const columns = [
1392
+ "session_id",
1393
+ "user_id",
1394
+ "server_id",
1395
+ "server_name",
1396
+ "server_url",
1397
+ "transport_type",
1398
+ "callback_url",
1399
+ "created_at",
1400
+ "updated_at",
1401
+ "headers",
1402
+ "auth_url",
1403
+ "status",
1404
+ "expires_at"
1405
+ ];
1406
+ const values = [
1407
+ sessionId,
1408
+ userId,
1409
+ session.serverId,
1410
+ session.serverName,
1411
+ session.serverUrl,
1412
+ session.transportType,
1413
+ session.callbackUrl,
1414
+ createdAt,
1415
+ updatedAt,
1416
+ encryptObject(session.headers),
1417
+ session.authUrl ?? null,
1418
+ status,
1419
+ expiresAt === null ? null : new Date(expiresAt).toISOString()
1420
+ ];
1421
+ if (toolPolicy) {
1422
+ columns.push("tool_policy");
1423
+ values.push(toolPolicy);
1424
+ }
1425
+ const placeholders = values.map((_, i) => `$${i + 1}`);
1325
1426
  try {
1326
1427
  await this.sql.query(
1327
- `INSERT INTO ${this.tableName} (
1328
- session_id,
1329
- user_id,
1330
- server_id,
1331
- server_name,
1332
- server_url,
1333
- transport_type,
1334
- callback_url,
1335
- created_at,
1336
- updated_at,
1337
- headers,
1338
- auth_url,
1339
- status,
1340
- expires_at
1341
- ) VALUES (
1342
- $1, $2, $3, $4, $5, $6, $7, $8,
1343
- $9, $10, $11, $12, $13
1344
- )`,
1345
- [
1346
- sessionId,
1347
- userId,
1348
- session.serverId,
1349
- session.serverName,
1350
- session.serverUrl,
1351
- session.transportType,
1352
- session.callbackUrl,
1353
- createdAt,
1354
- updatedAt,
1355
- encryptObject(session.headers),
1356
- session.authUrl ?? null,
1357
- status,
1358
- expiresAt === null ? null : new Date(expiresAt).toISOString()
1359
- ]
1428
+ `INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`,
1429
+ values
1360
1430
  );
1361
1431
  } catch (error) {
1362
1432
  if (error.code === "23505") {
@@ -1373,36 +1443,36 @@ var NeonStorageBackend = class {
1373
1443
  const updatedSession = { ...currentSession, ...data };
1374
1444
  const status = updatedSession.status ?? "pending";
1375
1445
  const expiresAt = resolveSessionExpiresAt(status);
1376
- const shouldUpdateSession = "serverId" in data || "serverName" in data || "serverUrl" in data || "transportType" in data || "callbackUrl" in data || "status" in data || "headers" in data || "authUrl" in data;
1446
+ const shouldUpdateSession = "serverId" in data || "serverName" in data || "serverUrl" in data || "transportType" in data || "callbackUrl" in data || "status" in data || "headers" in data || "authUrl" in data || "toolPolicy" in data;
1377
1447
  if (shouldUpdateSession) {
1448
+ const setClauses = [];
1449
+ const values = [];
1450
+ let paramIndex = 1;
1451
+ const addSet = (column, value) => {
1452
+ setClauses.push(`${column} = $${paramIndex++}`);
1453
+ values.push(value);
1454
+ };
1455
+ addSet("server_id", updatedSession.serverId);
1456
+ addSet("server_name", updatedSession.serverName);
1457
+ addSet("server_url", updatedSession.serverUrl);
1458
+ addSet("transport_type", updatedSession.transportType);
1459
+ addSet("callback_url", updatedSession.callbackUrl);
1460
+ addSet("status", status);
1461
+ addSet("headers", encryptObject(updatedSession.headers));
1462
+ addSet("auth_url", updatedSession.authUrl ?? null);
1463
+ addSet("expires_at", expiresAt === null ? null : new Date(expiresAt).toISOString());
1464
+ if ("toolPolicy" in data) {
1465
+ const policyUpdatedAt = updatedSession.updatedAt ?? Date.now();
1466
+ const toolPolicy = normalizeToolPolicy(updatedSession.toolPolicy, policyUpdatedAt) ?? { mode: "all", toolIds: [], updatedAt: policyUpdatedAt };
1467
+ addSet("tool_policy", toolPolicy);
1468
+ }
1469
+ setClauses.push("updated_at = now()");
1378
1470
  const updatedRows = await this.sql.query(
1379
1471
  `UPDATE ${this.tableName}
1380
- SET
1381
- server_id = $1,
1382
- server_name = $2,
1383
- server_url = $3,
1384
- transport_type = $4,
1385
- callback_url = $5,
1386
- status = $6,
1387
- headers = $7,
1388
- auth_url = $8,
1389
- expires_at = $9,
1390
- updated_at = now()
1391
- WHERE user_id = $10 AND session_id = $11
1472
+ SET ${setClauses.join(", ")}
1473
+ WHERE user_id = $${paramIndex++} AND session_id = $${paramIndex++}
1392
1474
  RETURNING id`,
1393
- [
1394
- updatedSession.serverId,
1395
- updatedSession.serverName,
1396
- updatedSession.serverUrl,
1397
- updatedSession.transportType,
1398
- updatedSession.callbackUrl,
1399
- status,
1400
- encryptObject(updatedSession.headers),
1401
- updatedSession.authUrl ?? null,
1402
- expiresAt === null ? null : new Date(expiresAt).toISOString(),
1403
- userId,
1404
- sessionId
1405
- ]
1475
+ [...values, userId, sessionId]
1406
1476
  );
1407
1477
  if (updatedRows.length === 0) {
1408
1478
  throw new Error(`Session ${sessionId} not found for userId ${userId}`);
@@ -2684,6 +2754,19 @@ var MCPClient = class {
2684
2754
  throw lastError;
2685
2755
  }
2686
2756
  }
2757
+ /**
2758
+ * Lists all available tools from the connected MCP server without emitting discovery events.
2759
+ * Gateways use this to apply policy before publishing tools to agents or UI state.
2760
+ */
2761
+ async fetchTools() {
2762
+ const request = {
2763
+ method: "tools/list",
2764
+ params: {}
2765
+ };
2766
+ return await this.withRetry(
2767
+ () => this.client.request(request, ListToolsResultSchema)
2768
+ );
2769
+ }
2687
2770
  /**
2688
2771
  * Lists all available tools from the connected MCP server
2689
2772
  * @returns List of tools with their schemas and descriptions
@@ -2692,13 +2775,7 @@ var MCPClient = class {
2692
2775
  async listTools() {
2693
2776
  this.emitStateChange("DISCOVERING");
2694
2777
  try {
2695
- const request = {
2696
- method: "tools/list",
2697
- params: {}
2698
- };
2699
- const result = await this.withRetry(
2700
- () => this.client.request(request, ListToolsResultSchema)
2701
- );
2778
+ const result = await this.fetchTools();
2702
2779
  if (this.serverId) {
2703
2780
  this._onConnectionEvent.fire({
2704
2781
  type: "tools_discovered",
@@ -3025,6 +3102,66 @@ var MCPClient = class {
3025
3102
  }
3026
3103
  };
3027
3104
 
3105
+ // src/server/mcp/tool-policy-gateway.ts
3106
+ var ToolPolicyGateway = class {
3107
+ constructor(userId, sessionId, client) {
3108
+ this.userId = userId;
3109
+ this.sessionId = sessionId;
3110
+ this.client = client;
3111
+ }
3112
+ isConnected() {
3113
+ return this.client.isConnected();
3114
+ }
3115
+ getServerId() {
3116
+ return this.client.getServerId?.();
3117
+ }
3118
+ getServerName() {
3119
+ return this.client.getServerName?.();
3120
+ }
3121
+ getServerUrl() {
3122
+ return this.client.getServerUrl?.();
3123
+ }
3124
+ getSessionId() {
3125
+ return this.client.getSessionId?.() ?? this.sessionId;
3126
+ }
3127
+ async listTools() {
3128
+ const session = await this.getSession();
3129
+ const result = await this.client.fetchTools();
3130
+ const tools = this.filterTools(session, result.tools);
3131
+ return {
3132
+ ...result,
3133
+ tools
3134
+ };
3135
+ }
3136
+ async listAllTools() {
3137
+ return await this.client.fetchTools();
3138
+ }
3139
+ async callTool(name, args) {
3140
+ const session = await this.getSession();
3141
+ this.assertAllowed(session, name);
3142
+ return await this.client.callTool(name, args);
3143
+ }
3144
+ filterTools(session, tools) {
3145
+ return filterToolsByPolicy(tools, session.toolPolicy, this.getPolicyServerId(session));
3146
+ }
3147
+ assertAllowed(session, toolName) {
3148
+ assertToolAllowed(session.toolPolicy, toolName, this.getPolicyServerId(session));
3149
+ }
3150
+ async getSession() {
3151
+ const session = await sessions.get(this.userId, this.sessionId);
3152
+ if (!session) {
3153
+ throw new Error("Session not found");
3154
+ }
3155
+ return session;
3156
+ }
3157
+ getPolicyServerId(session) {
3158
+ return this.client.getServerId?.() ?? session.serverId;
3159
+ }
3160
+ };
3161
+ function createToolPolicyGateway(userId, sessionId, client) {
3162
+ return new ToolPolicyGateway(userId, sessionId, client);
3163
+ }
3164
+
3028
3165
  // src/server/mcp/multi-session-client.ts
3029
3166
  var DEFAULT_TIMEOUT_MS = 15e3;
3030
3167
  var DEFAULT_MAX_RETRIES = 2;
@@ -3090,7 +3227,9 @@ var MultiSessionClient = class {
3090
3227
  * or to route a tool call to the right client by `serverId`.
3091
3228
  */
3092
3229
  getClients() {
3093
- return this.clients;
3230
+ return this.clients.map(
3231
+ (client) => createToolPolicyGateway(this.userId, client.getSessionId(), client)
3232
+ );
3094
3233
  }
3095
3234
  /**
3096
3235
  * Removes and disconnects a single session by ID.
@@ -3319,12 +3458,21 @@ var SSEConnectionManager = class {
3319
3458
  case "connect":
3320
3459
  result = await this.connect(request.params);
3321
3460
  break;
3461
+ case "reconnect":
3462
+ result = await this.reconnect(request.params);
3463
+ break;
3322
3464
  case "disconnect":
3323
3465
  result = await this.disconnect(request.params);
3324
3466
  break;
3325
3467
  case "listTools":
3326
3468
  result = await this.listTools(request.params);
3327
3469
  break;
3470
+ case "setToolPolicy":
3471
+ result = await this.setToolPolicy(request.params);
3472
+ break;
3473
+ case "getToolPolicy":
3474
+ result = await this.getToolPolicy(request.params);
3475
+ break;
3328
3476
  case "callTool":
3329
3477
  result = await this.callTool(request.params);
3330
3478
  break;
@@ -3381,7 +3529,8 @@ var SSEConnectionManager = class {
3381
3529
  transport: s.transportType,
3382
3530
  createdAt: s.createdAt,
3383
3531
  updatedAt: s.updatedAt ?? s.createdAt,
3384
- status: s.status ?? "pending"
3532
+ status: s.status ?? "pending",
3533
+ toolPolicy: s.toolPolicy
3385
3534
  }))
3386
3535
  };
3387
3536
  }
@@ -3429,7 +3578,7 @@ var SSEConnectionManager = class {
3429
3578
  this.sendEvent(event);
3430
3579
  });
3431
3580
  await client.connect();
3432
- await client.listTools();
3581
+ await this.listPolicyFilteredTools(sessionId);
3433
3582
  return {
3434
3583
  sessionId,
3435
3584
  success: true
@@ -3454,6 +3603,66 @@ var SSEConnectionManager = class {
3454
3603
  throw error;
3455
3604
  }
3456
3605
  }
3606
+ /**
3607
+ * Reconnect to an MCP server — tears down the active client transport/connection
3608
+ * and creates a fresh connection while reusing the existing session credentials in a single RPC call.
3609
+ */
3610
+ async reconnect(params) {
3611
+ const { serverId: rawServerId, serverName, serverUrl, callbackUrl, transportType } = params;
3612
+ const headers = normalizeHeaders(params.headers);
3613
+ const serverId = rawServerId && rawServerId.length <= 12 ? rawServerId : generateServerId();
3614
+ const existingSessions = await sessions.list(this.userId);
3615
+ const duplicate = existingSessions.find(
3616
+ (s) => s.serverId === serverId || s.serverUrl === serverUrl
3617
+ );
3618
+ const sessionId = duplicate ? duplicate.sessionId : await sessions.generateSessionId();
3619
+ if (duplicate) {
3620
+ const existingClient = this.clients.get(duplicate.sessionId);
3621
+ if (existingClient) {
3622
+ await existingClient.disconnect();
3623
+ this.clients.delete(duplicate.sessionId);
3624
+ }
3625
+ }
3626
+ try {
3627
+ const clientMetadata = await this.getResolvedClientMetadata();
3628
+ const client = new MCPClient({
3629
+ userId: this.userId,
3630
+ sessionId,
3631
+ serverId,
3632
+ serverName,
3633
+ serverUrl,
3634
+ callbackUrl,
3635
+ transportType,
3636
+ headers,
3637
+ ...clientMetadata
3638
+ });
3639
+ this.clients.set(sessionId, client);
3640
+ client.onConnectionEvent((event) => {
3641
+ this.emitConnectionEvent(event);
3642
+ });
3643
+ client.onObservabilityEvent((event) => {
3644
+ this.sendEvent(event);
3645
+ });
3646
+ await client.connect();
3647
+ await this.listPolicyFilteredTools(sessionId);
3648
+ return { sessionId, success: true };
3649
+ } catch (error) {
3650
+ if (error instanceof UnauthorizedError) {
3651
+ this.clients.delete(sessionId);
3652
+ return { sessionId, success: true };
3653
+ }
3654
+ this.emitConnectionEvent({
3655
+ type: "error",
3656
+ sessionId,
3657
+ serverId,
3658
+ error: error instanceof Error ? error.message : "Connection failed",
3659
+ errorType: "connection",
3660
+ timestamp: Date.now()
3661
+ });
3662
+ this.clients.delete(sessionId);
3663
+ throw error;
3664
+ }
3665
+ }
3457
3666
  /**
3458
3667
  * Disconnect from an MCP server
3459
3668
  */
@@ -3500,22 +3709,98 @@ var SSEConnectionManager = class {
3500
3709
  this.clients.set(sessionId, client);
3501
3710
  return client;
3502
3711
  }
3712
+ async listPolicyFilteredTools(sessionId) {
3713
+ const session = await sessions.get(this.userId, sessionId);
3714
+ if (!session) {
3715
+ throw new Error("Session not found");
3716
+ }
3717
+ const client = await this.getOrCreateClient(sessionId);
3718
+ const gateway = createToolPolicyGateway(this.userId, sessionId, client);
3719
+ const result = await gateway.listTools();
3720
+ this.emitConnectionEvent({
3721
+ type: "tools_discovered",
3722
+ sessionId,
3723
+ serverId: session.serverId ?? "unknown",
3724
+ toolCount: result.tools.length,
3725
+ tools: result.tools,
3726
+ timestamp: Date.now()
3727
+ });
3728
+ return { session, result };
3729
+ }
3503
3730
  /**
3504
3731
  * List tools from a session
3505
3732
  */
3506
3733
  async listTools(params) {
3507
3734
  const { sessionId } = params;
3508
- const client = await this.getOrCreateClient(sessionId);
3509
- const result = await client.listTools();
3735
+ const { result } = await this.listPolicyFilteredTools(sessionId);
3510
3736
  return { tools: result.tools };
3511
3737
  }
3738
+ /**
3739
+ * Get all raw tools with effective access state for management UI.
3740
+ */
3741
+ async getToolPolicy(params) {
3742
+ const { sessionId } = params;
3743
+ const session = await sessions.get(this.userId, sessionId);
3744
+ if (!session) {
3745
+ throw new Error("Session not found");
3746
+ }
3747
+ const client = await this.getOrCreateClient(sessionId);
3748
+ const allTools = await client.fetchTools();
3749
+ const toolPolicy = session.toolPolicy ?? {
3750
+ mode: "all",
3751
+ toolIds: [],
3752
+ updatedAt: session.updatedAt ?? session.createdAt
3753
+ };
3754
+ const serverId = session.serverId ?? "unknown";
3755
+ const tools = allTools.tools.map((tool) => ({
3756
+ ...tool,
3757
+ toolId: createToolId(serverId, tool.name),
3758
+ allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
3759
+ }));
3760
+ return {
3761
+ toolPolicy,
3762
+ tools,
3763
+ toolCount: tools.length,
3764
+ allowedToolCount: tools.filter((tool) => tool.allowed).length
3765
+ };
3766
+ }
3767
+ /**
3768
+ * Update per-session tool access policy.
3769
+ */
3770
+ async setToolPolicy(params) {
3771
+ const { sessionId } = params;
3772
+ const session = await sessions.get(this.userId, sessionId);
3773
+ if (!session) {
3774
+ throw new Error("Session not found");
3775
+ }
3776
+ const client = await this.getOrCreateClient(sessionId);
3777
+ const allTools = await client.fetchTools();
3778
+ const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
3779
+ validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
3780
+ await sessions.update(this.userId, sessionId, { toolPolicy });
3781
+ const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
3782
+ this.emitConnectionEvent({
3783
+ type: "tools_discovered",
3784
+ sessionId,
3785
+ serverId: session.serverId ?? "unknown",
3786
+ toolCount: filteredTools.length,
3787
+ tools: filteredTools,
3788
+ timestamp: Date.now()
3789
+ });
3790
+ return {
3791
+ success: true,
3792
+ toolPolicy,
3793
+ tools: filteredTools,
3794
+ toolCount: filteredTools.length
3795
+ };
3796
+ }
3512
3797
  /**
3513
3798
  * Call a tool on the MCP server
3514
3799
  */
3515
3800
  async callTool(params) {
3516
3801
  const { sessionId, toolName, toolArgs } = params;
3517
3802
  const client = await this.getOrCreateClient(sessionId);
3518
- const result = await client.callTool(toolName, toolArgs);
3803
+ const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
3519
3804
  const meta = result._meta || {};
3520
3805
  return {
3521
3806
  ...result,
@@ -3565,7 +3850,7 @@ var SSEConnectionManager = class {
3565
3850
  client.onObservabilityEvent((event) => this.sendEvent(event));
3566
3851
  await client.connect();
3567
3852
  this.clients.set(sessionId, client);
3568
- const tools = await client.listTools();
3853
+ const { result: tools } = await this.listPolicyFilteredTools(sessionId);
3569
3854
  return { success: true, toolCount: tools.tools.length };
3570
3855
  } catch (error) {
3571
3856
  this.emitConnectionEvent({
@@ -3613,7 +3898,7 @@ var SSEConnectionManager = class {
3613
3898
  client.onConnectionEvent((event) => this.emitConnectionEvent(event));
3614
3899
  await client.finishAuth(code, oauthState);
3615
3900
  this.clients.set(sessionId, client);
3616
- const tools = await client.listTools();
3901
+ const { result: tools } = await this.listPolicyFilteredTools(sessionId);
3617
3902
  return { success: true, toolCount: tools.tools.length };
3618
3903
  } catch (error) {
3619
3904
  this.emitConnectionEvent({
@@ -3905,6 +4190,15 @@ var SSEClient = class {
3905
4190
  async disconnectFromServer(sessionId) {
3906
4191
  return this.sendRequest("disconnect", { sessionId });
3907
4192
  }
4193
+ async reconnectToServer(params) {
4194
+ return this.sendRequest("reconnect", params);
4195
+ }
4196
+ async setToolPolicy(sessionId, toolPolicy) {
4197
+ return this.sendRequest("setToolPolicy", { sessionId, toolPolicy });
4198
+ }
4199
+ async getToolPolicy(sessionId) {
4200
+ return this.sendRequest("getToolPolicy", { sessionId });
4201
+ }
3908
4202
  async listTools(sessionId) {
3909
4203
  return this.sendRequest("listTools", { sessionId });
3910
4204
  }