@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
@@ -158,6 +158,59 @@ var SOFTWARE_VERSION = "2.3.4";
158
158
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
159
159
  var MCP_CLIENT_VERSION = "2.0";
160
160
 
161
+ // src/server/storage/tool-policy.ts
162
+ function createToolId(serverId, toolName) {
163
+ return `${serverId}::${toolName}`;
164
+ }
165
+ function normalizeToolIds(input) {
166
+ if (!Array.isArray(input)) return [];
167
+ return Array.from(new Set(
168
+ input.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
169
+ ));
170
+ }
171
+ function normalizeToolPolicy(input, now = Date.now()) {
172
+ if (!input || typeof input !== "object") {
173
+ return void 0;
174
+ }
175
+ const mode = input.mode === "allowlist" || input.mode === "denylist" ? input.mode : "all";
176
+ const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : now;
177
+ if (mode === "all") {
178
+ return { mode: "all", toolIds: [], updatedAt };
179
+ }
180
+ return { mode, toolIds: normalizeToolIds(input.toolIds), updatedAt };
181
+ }
182
+ function normalizeToolPolicyForUpdate(input, now = Date.now()) {
183
+ return normalizeToolPolicy(input, now) ?? { mode: "all", toolIds: [], updatedAt: now };
184
+ }
185
+ function isToolAllowed(policy, toolName, serverId) {
186
+ if (!policy || policy.mode === "all") return true;
187
+ if (!serverId) return false;
188
+ const toolId = createToolId(serverId, toolName);
189
+ if (policy.mode === "allowlist") {
190
+ return policy.toolIds.includes(toolId);
191
+ }
192
+ return !policy.toolIds.includes(toolId);
193
+ }
194
+ function assertToolAllowed(policy, toolName, serverId) {
195
+ if (isToolAllowed(policy, toolName, serverId)) return;
196
+ throw new Error(`Tool "${toolName}" is not allowed for this MCP session`);
197
+ }
198
+ function filterToolsByPolicy(tools, policy, serverId) {
199
+ if (!policy || policy.mode === "all") return tools;
200
+ return tools.filter((tool) => isToolAllowed(policy, tool.name, serverId));
201
+ }
202
+ function validateToolPolicyAgainstTools(policy, tools, serverId) {
203
+ if (policy.mode === "all") return;
204
+ if (!serverId) {
205
+ throw new Error("Cannot validate MCP tool policy without a serverId");
206
+ }
207
+ const availableIds = new Set(tools.map((tool) => createToolId(serverId, tool.name)));
208
+ const unknownIds = policy.toolIds.filter((id) => !availableIds.has(id));
209
+ if (unknownIds.length > 0) {
210
+ throw new Error(`Unknown tool id(s) for this MCP session: ${unknownIds.join(", ")}`);
211
+ }
212
+ }
213
+
161
214
  // src/server/storage/session-lifecycle.ts
162
215
  function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
163
216
  return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
@@ -174,7 +227,8 @@ function normalizeNewSession(session, now = Date.now()) {
174
227
  status,
175
228
  createdAt,
176
229
  updatedAt,
177
- expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
230
+ expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt),
231
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
178
232
  };
179
233
  }
180
234
  function mergeSessionUpdate(current, data, now = Date.now()) {
@@ -188,7 +242,8 @@ function mergeSessionUpdate(current, data, now = Date.now()) {
188
242
  return {
189
243
  ...updated,
190
244
  status,
191
- expiresAt: resolveSessionExpiresAt(status, updatedAt)
245
+ expiresAt: resolveSessionExpiresAt(status, updatedAt),
246
+ toolPolicy: normalizeToolPolicy(updated.toolPolicy, updatedAt)
192
247
  };
193
248
  }
194
249
  function normalizeStoredSession(session) {
@@ -201,7 +256,8 @@ function normalizeStoredSession(session) {
201
256
  status,
202
257
  createdAt,
203
258
  updatedAt,
204
- expiresAt
259
+ expiresAt,
260
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
205
261
  };
206
262
  }
207
263
  function isSessionExpired(session, now = Date.now()) {
@@ -1059,7 +1115,8 @@ var SupabaseStorageBackend = class {
1059
1115
  userId: row.user_id,
1060
1116
  headers: decryptObject(row.headers),
1061
1117
  authUrl: row.auth_url,
1062
- status: row.status ?? "pending"
1118
+ status: row.status ?? "pending",
1119
+ toolPolicy: normalizeToolPolicy(row.tool_policy)
1063
1120
  };
1064
1121
  }
1065
1122
  mapRowToCredentials(row, userId, sessionId) {
@@ -1083,7 +1140,7 @@ var SupabaseStorageBackend = class {
1083
1140
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1084
1141
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1085
1142
  const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
1086
- const { error } = await this.supabase.from("mcp_sessions").insert({
1143
+ const insertData = {
1087
1144
  session_id: sessionId,
1088
1145
  user_id: userId,
1089
1146
  server_id: session.serverId,
@@ -1097,7 +1154,12 @@ var SupabaseStorageBackend = class {
1097
1154
  auth_url: session.authUrl ?? null,
1098
1155
  status,
1099
1156
  expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
1100
- });
1157
+ };
1158
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy);
1159
+ if (toolPolicy) {
1160
+ insertData.tool_policy = toolPolicy;
1161
+ }
1162
+ const { error } = await this.supabase.from("mcp_sessions").insert(insertData);
1101
1163
  if (error) {
1102
1164
  if (error.code === "23505") {
1103
1165
  throw new Error(`Session ${sessionId} already exists`);
@@ -1122,6 +1184,7 @@ var SupabaseStorageBackend = class {
1122
1184
  }
1123
1185
  if ("headers" in data) updateData.headers = encryptObject(data.headers);
1124
1186
  if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
1187
+ if ("toolPolicy" in data) updateData.tool_policy = normalizeToolPolicy(data.toolPolicy);
1125
1188
  const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
1126
1189
  let updatedRows = null;
1127
1190
  if (shouldUpdateSession) {
@@ -1294,7 +1357,8 @@ var NeonStorageBackend = class {
1294
1357
  userId: row.user_id,
1295
1358
  headers: decryptObject(row.headers),
1296
1359
  authUrl: row.auth_url ?? void 0,
1297
- status: row.status ?? "pending"
1360
+ status: row.status ?? "pending",
1361
+ toolPolicy: normalizeToolPolicy(row.tool_policy)
1298
1362
  };
1299
1363
  }
1300
1364
  mapRowToCredentials(row, userId, sessionId) {
@@ -1317,42 +1381,48 @@ var NeonStorageBackend = class {
1317
1381
  const status = session.status ?? "pending";
1318
1382
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1319
1383
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1320
- const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
1384
+ const createdAtMs = new Date(createdAt).getTime();
1385
+ const expiresAt = resolveSessionExpiresAt(status, createdAtMs);
1386
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy, createdAtMs);
1387
+ const columns = [
1388
+ "session_id",
1389
+ "user_id",
1390
+ "server_id",
1391
+ "server_name",
1392
+ "server_url",
1393
+ "transport_type",
1394
+ "callback_url",
1395
+ "created_at",
1396
+ "updated_at",
1397
+ "headers",
1398
+ "auth_url",
1399
+ "status",
1400
+ "expires_at"
1401
+ ];
1402
+ const values = [
1403
+ sessionId,
1404
+ userId,
1405
+ session.serverId,
1406
+ session.serverName,
1407
+ session.serverUrl,
1408
+ session.transportType,
1409
+ session.callbackUrl,
1410
+ createdAt,
1411
+ updatedAt,
1412
+ encryptObject(session.headers),
1413
+ session.authUrl ?? null,
1414
+ status,
1415
+ expiresAt === null ? null : new Date(expiresAt).toISOString()
1416
+ ];
1417
+ if (toolPolicy) {
1418
+ columns.push("tool_policy");
1419
+ values.push(toolPolicy);
1420
+ }
1421
+ const placeholders = values.map((_, i) => `$${i + 1}`);
1321
1422
  try {
1322
1423
  await this.sql.query(
1323
- `INSERT INTO ${this.tableName} (
1324
- session_id,
1325
- user_id,
1326
- server_id,
1327
- server_name,
1328
- server_url,
1329
- transport_type,
1330
- callback_url,
1331
- created_at,
1332
- updated_at,
1333
- headers,
1334
- auth_url,
1335
- status,
1336
- expires_at
1337
- ) VALUES (
1338
- $1, $2, $3, $4, $5, $6, $7, $8,
1339
- $9, $10, $11, $12, $13
1340
- )`,
1341
- [
1342
- sessionId,
1343
- userId,
1344
- session.serverId,
1345
- session.serverName,
1346
- session.serverUrl,
1347
- session.transportType,
1348
- session.callbackUrl,
1349
- createdAt,
1350
- updatedAt,
1351
- encryptObject(session.headers),
1352
- session.authUrl ?? null,
1353
- status,
1354
- expiresAt === null ? null : new Date(expiresAt).toISOString()
1355
- ]
1424
+ `INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`,
1425
+ values
1356
1426
  );
1357
1427
  } catch (error) {
1358
1428
  if (error.code === "23505") {
@@ -1369,36 +1439,36 @@ var NeonStorageBackend = class {
1369
1439
  const updatedSession = { ...currentSession, ...data };
1370
1440
  const status = updatedSession.status ?? "pending";
1371
1441
  const expiresAt = resolveSessionExpiresAt(status);
1372
- 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;
1442
+ 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;
1373
1443
  if (shouldUpdateSession) {
1444
+ const setClauses = [];
1445
+ const values = [];
1446
+ let paramIndex = 1;
1447
+ const addSet = (column, value) => {
1448
+ setClauses.push(`${column} = $${paramIndex++}`);
1449
+ values.push(value);
1450
+ };
1451
+ addSet("server_id", updatedSession.serverId);
1452
+ addSet("server_name", updatedSession.serverName);
1453
+ addSet("server_url", updatedSession.serverUrl);
1454
+ addSet("transport_type", updatedSession.transportType);
1455
+ addSet("callback_url", updatedSession.callbackUrl);
1456
+ addSet("status", status);
1457
+ addSet("headers", encryptObject(updatedSession.headers));
1458
+ addSet("auth_url", updatedSession.authUrl ?? null);
1459
+ addSet("expires_at", expiresAt === null ? null : new Date(expiresAt).toISOString());
1460
+ if ("toolPolicy" in data) {
1461
+ const policyUpdatedAt = updatedSession.updatedAt ?? Date.now();
1462
+ const toolPolicy = normalizeToolPolicy(updatedSession.toolPolicy, policyUpdatedAt) ?? { mode: "all", toolIds: [], updatedAt: policyUpdatedAt };
1463
+ addSet("tool_policy", toolPolicy);
1464
+ }
1465
+ setClauses.push("updated_at = now()");
1374
1466
  const updatedRows = await this.sql.query(
1375
1467
  `UPDATE ${this.tableName}
1376
- SET
1377
- server_id = $1,
1378
- server_name = $2,
1379
- server_url = $3,
1380
- transport_type = $4,
1381
- callback_url = $5,
1382
- status = $6,
1383
- headers = $7,
1384
- auth_url = $8,
1385
- expires_at = $9,
1386
- updated_at = now()
1387
- WHERE user_id = $10 AND session_id = $11
1468
+ SET ${setClauses.join(", ")}
1469
+ WHERE user_id = $${paramIndex++} AND session_id = $${paramIndex++}
1388
1470
  RETURNING id`,
1389
- [
1390
- updatedSession.serverId,
1391
- updatedSession.serverName,
1392
- updatedSession.serverUrl,
1393
- updatedSession.transportType,
1394
- updatedSession.callbackUrl,
1395
- status,
1396
- encryptObject(updatedSession.headers),
1397
- updatedSession.authUrl ?? null,
1398
- expiresAt === null ? null : new Date(expiresAt).toISOString(),
1399
- userId,
1400
- sessionId
1401
- ]
1471
+ [...values, userId, sessionId]
1402
1472
  );
1403
1473
  if (updatedRows.length === 0) {
1404
1474
  throw new Error(`Session ${sessionId} not found for userId ${userId}`);
@@ -2612,6 +2682,19 @@ var MCPClient = class {
2612
2682
  throw lastError;
2613
2683
  }
2614
2684
  }
2685
+ /**
2686
+ * Lists all available tools from the connected MCP server without emitting discovery events.
2687
+ * Gateways use this to apply policy before publishing tools to agents or UI state.
2688
+ */
2689
+ async fetchTools() {
2690
+ const request = {
2691
+ method: "tools/list",
2692
+ params: {}
2693
+ };
2694
+ return await this.withRetry(
2695
+ () => this.client.request(request, ListToolsResultSchema)
2696
+ );
2697
+ }
2615
2698
  /**
2616
2699
  * Lists all available tools from the connected MCP server
2617
2700
  * @returns List of tools with their schemas and descriptions
@@ -2620,13 +2703,7 @@ var MCPClient = class {
2620
2703
  async listTools() {
2621
2704
  this.emitStateChange("DISCOVERING");
2622
2705
  try {
2623
- const request = {
2624
- method: "tools/list",
2625
- params: {}
2626
- };
2627
- const result = await this.withRetry(
2628
- () => this.client.request(request, ListToolsResultSchema)
2629
- );
2706
+ const result = await this.fetchTools();
2630
2707
  if (this.serverId) {
2631
2708
  this._onConnectionEvent.fire({
2632
2709
  type: "tools_discovered",
@@ -2953,6 +3030,66 @@ var MCPClient = class {
2953
3030
  }
2954
3031
  };
2955
3032
 
3033
+ // src/server/mcp/tool-policy-gateway.ts
3034
+ var ToolPolicyGateway = class {
3035
+ constructor(userId, sessionId, client) {
3036
+ this.userId = userId;
3037
+ this.sessionId = sessionId;
3038
+ this.client = client;
3039
+ }
3040
+ isConnected() {
3041
+ return this.client.isConnected();
3042
+ }
3043
+ getServerId() {
3044
+ return this.client.getServerId?.();
3045
+ }
3046
+ getServerName() {
3047
+ return this.client.getServerName?.();
3048
+ }
3049
+ getServerUrl() {
3050
+ return this.client.getServerUrl?.();
3051
+ }
3052
+ getSessionId() {
3053
+ return this.client.getSessionId?.() ?? this.sessionId;
3054
+ }
3055
+ async listTools() {
3056
+ const session = await this.getSession();
3057
+ const result = await this.client.fetchTools();
3058
+ const tools = this.filterTools(session, result.tools);
3059
+ return {
3060
+ ...result,
3061
+ tools
3062
+ };
3063
+ }
3064
+ async listAllTools() {
3065
+ return await this.client.fetchTools();
3066
+ }
3067
+ async callTool(name, args) {
3068
+ const session = await this.getSession();
3069
+ this.assertAllowed(session, name);
3070
+ return await this.client.callTool(name, args);
3071
+ }
3072
+ filterTools(session, tools) {
3073
+ return filterToolsByPolicy(tools, session.toolPolicy, this.getPolicyServerId(session));
3074
+ }
3075
+ assertAllowed(session, toolName) {
3076
+ assertToolAllowed(session.toolPolicy, toolName, this.getPolicyServerId(session));
3077
+ }
3078
+ async getSession() {
3079
+ const session = await sessions.get(this.userId, this.sessionId);
3080
+ if (!session) {
3081
+ throw new Error("Session not found");
3082
+ }
3083
+ return session;
3084
+ }
3085
+ getPolicyServerId(session) {
3086
+ return this.client.getServerId?.() ?? session.serverId;
3087
+ }
3088
+ };
3089
+ function createToolPolicyGateway(userId, sessionId, client) {
3090
+ return new ToolPolicyGateway(userId, sessionId, client);
3091
+ }
3092
+
2956
3093
  // src/server/mcp/multi-session-client.ts
2957
3094
  var DEFAULT_TIMEOUT_MS = 15e3;
2958
3095
  var DEFAULT_MAX_RETRIES = 2;
@@ -3018,7 +3155,9 @@ var MultiSessionClient = class {
3018
3155
  * or to route a tool call to the right client by `serverId`.
3019
3156
  */
3020
3157
  getClients() {
3021
- return this.clients;
3158
+ return this.clients.map(
3159
+ (client) => createToolPolicyGateway(this.userId, client.getSessionId(), client)
3160
+ );
3022
3161
  }
3023
3162
  /**
3024
3163
  * Removes and disconnects a single session by ID.
@@ -3247,12 +3386,21 @@ var SSEConnectionManager = class {
3247
3386
  case "connect":
3248
3387
  result = await this.connect(request.params);
3249
3388
  break;
3389
+ case "reconnect":
3390
+ result = await this.reconnect(request.params);
3391
+ break;
3250
3392
  case "disconnect":
3251
3393
  result = await this.disconnect(request.params);
3252
3394
  break;
3253
3395
  case "listTools":
3254
3396
  result = await this.listTools(request.params);
3255
3397
  break;
3398
+ case "setToolPolicy":
3399
+ result = await this.setToolPolicy(request.params);
3400
+ break;
3401
+ case "getToolPolicy":
3402
+ result = await this.getToolPolicy(request.params);
3403
+ break;
3256
3404
  case "callTool":
3257
3405
  result = await this.callTool(request.params);
3258
3406
  break;
@@ -3309,7 +3457,8 @@ var SSEConnectionManager = class {
3309
3457
  transport: s.transportType,
3310
3458
  createdAt: s.createdAt,
3311
3459
  updatedAt: s.updatedAt ?? s.createdAt,
3312
- status: s.status ?? "pending"
3460
+ status: s.status ?? "pending",
3461
+ toolPolicy: s.toolPolicy
3313
3462
  }))
3314
3463
  };
3315
3464
  }
@@ -3357,7 +3506,7 @@ var SSEConnectionManager = class {
3357
3506
  this.sendEvent(event);
3358
3507
  });
3359
3508
  await client.connect();
3360
- await client.listTools();
3509
+ await this.listPolicyFilteredTools(sessionId);
3361
3510
  return {
3362
3511
  sessionId,
3363
3512
  success: true
@@ -3382,6 +3531,66 @@ var SSEConnectionManager = class {
3382
3531
  throw error;
3383
3532
  }
3384
3533
  }
3534
+ /**
3535
+ * Reconnect to an MCP server — tears down the active client transport/connection
3536
+ * and creates a fresh connection while reusing the existing session credentials in a single RPC call.
3537
+ */
3538
+ async reconnect(params) {
3539
+ const { serverId: rawServerId, serverName, serverUrl, callbackUrl, transportType } = params;
3540
+ const headers = normalizeHeaders(params.headers);
3541
+ const serverId = rawServerId && rawServerId.length <= 12 ? rawServerId : generateServerId();
3542
+ const existingSessions = await sessions.list(this.userId);
3543
+ const duplicate = existingSessions.find(
3544
+ (s) => s.serverId === serverId || s.serverUrl === serverUrl
3545
+ );
3546
+ const sessionId = duplicate ? duplicate.sessionId : await sessions.generateSessionId();
3547
+ if (duplicate) {
3548
+ const existingClient = this.clients.get(duplicate.sessionId);
3549
+ if (existingClient) {
3550
+ await existingClient.disconnect();
3551
+ this.clients.delete(duplicate.sessionId);
3552
+ }
3553
+ }
3554
+ try {
3555
+ const clientMetadata = await this.getResolvedClientMetadata();
3556
+ const client = new MCPClient({
3557
+ userId: this.userId,
3558
+ sessionId,
3559
+ serverId,
3560
+ serverName,
3561
+ serverUrl,
3562
+ callbackUrl,
3563
+ transportType,
3564
+ headers,
3565
+ ...clientMetadata
3566
+ });
3567
+ this.clients.set(sessionId, client);
3568
+ client.onConnectionEvent((event) => {
3569
+ this.emitConnectionEvent(event);
3570
+ });
3571
+ client.onObservabilityEvent((event) => {
3572
+ this.sendEvent(event);
3573
+ });
3574
+ await client.connect();
3575
+ await this.listPolicyFilteredTools(sessionId);
3576
+ return { sessionId, success: true };
3577
+ } catch (error) {
3578
+ if (error instanceof UnauthorizedError) {
3579
+ this.clients.delete(sessionId);
3580
+ return { sessionId, success: true };
3581
+ }
3582
+ this.emitConnectionEvent({
3583
+ type: "error",
3584
+ sessionId,
3585
+ serverId,
3586
+ error: error instanceof Error ? error.message : "Connection failed",
3587
+ errorType: "connection",
3588
+ timestamp: Date.now()
3589
+ });
3590
+ this.clients.delete(sessionId);
3591
+ throw error;
3592
+ }
3593
+ }
3385
3594
  /**
3386
3595
  * Disconnect from an MCP server
3387
3596
  */
@@ -3428,22 +3637,98 @@ var SSEConnectionManager = class {
3428
3637
  this.clients.set(sessionId, client);
3429
3638
  return client;
3430
3639
  }
3640
+ async listPolicyFilteredTools(sessionId) {
3641
+ const session = await sessions.get(this.userId, sessionId);
3642
+ if (!session) {
3643
+ throw new Error("Session not found");
3644
+ }
3645
+ const client = await this.getOrCreateClient(sessionId);
3646
+ const gateway = createToolPolicyGateway(this.userId, sessionId, client);
3647
+ const result = await gateway.listTools();
3648
+ this.emitConnectionEvent({
3649
+ type: "tools_discovered",
3650
+ sessionId,
3651
+ serverId: session.serverId ?? "unknown",
3652
+ toolCount: result.tools.length,
3653
+ tools: result.tools,
3654
+ timestamp: Date.now()
3655
+ });
3656
+ return { session, result };
3657
+ }
3431
3658
  /**
3432
3659
  * List tools from a session
3433
3660
  */
3434
3661
  async listTools(params) {
3435
3662
  const { sessionId } = params;
3436
- const client = await this.getOrCreateClient(sessionId);
3437
- const result = await client.listTools();
3663
+ const { result } = await this.listPolicyFilteredTools(sessionId);
3438
3664
  return { tools: result.tools };
3439
3665
  }
3666
+ /**
3667
+ * Get all raw tools with effective access state for management UI.
3668
+ */
3669
+ async getToolPolicy(params) {
3670
+ const { sessionId } = params;
3671
+ const session = await sessions.get(this.userId, sessionId);
3672
+ if (!session) {
3673
+ throw new Error("Session not found");
3674
+ }
3675
+ const client = await this.getOrCreateClient(sessionId);
3676
+ const allTools = await client.fetchTools();
3677
+ const toolPolicy = session.toolPolicy ?? {
3678
+ mode: "all",
3679
+ toolIds: [],
3680
+ updatedAt: session.updatedAt ?? session.createdAt
3681
+ };
3682
+ const serverId = session.serverId ?? "unknown";
3683
+ const tools = allTools.tools.map((tool) => ({
3684
+ ...tool,
3685
+ toolId: createToolId(serverId, tool.name),
3686
+ allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
3687
+ }));
3688
+ return {
3689
+ toolPolicy,
3690
+ tools,
3691
+ toolCount: tools.length,
3692
+ allowedToolCount: tools.filter((tool) => tool.allowed).length
3693
+ };
3694
+ }
3695
+ /**
3696
+ * Update per-session tool access policy.
3697
+ */
3698
+ async setToolPolicy(params) {
3699
+ const { sessionId } = params;
3700
+ const session = await sessions.get(this.userId, sessionId);
3701
+ if (!session) {
3702
+ throw new Error("Session not found");
3703
+ }
3704
+ const client = await this.getOrCreateClient(sessionId);
3705
+ const allTools = await client.fetchTools();
3706
+ const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
3707
+ validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
3708
+ await sessions.update(this.userId, sessionId, { toolPolicy });
3709
+ const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
3710
+ this.emitConnectionEvent({
3711
+ type: "tools_discovered",
3712
+ sessionId,
3713
+ serverId: session.serverId ?? "unknown",
3714
+ toolCount: filteredTools.length,
3715
+ tools: filteredTools,
3716
+ timestamp: Date.now()
3717
+ });
3718
+ return {
3719
+ success: true,
3720
+ toolPolicy,
3721
+ tools: filteredTools,
3722
+ toolCount: filteredTools.length
3723
+ };
3724
+ }
3440
3725
  /**
3441
3726
  * Call a tool on the MCP server
3442
3727
  */
3443
3728
  async callTool(params) {
3444
3729
  const { sessionId, toolName, toolArgs } = params;
3445
3730
  const client = await this.getOrCreateClient(sessionId);
3446
- const result = await client.callTool(toolName, toolArgs);
3731
+ const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
3447
3732
  const meta = result._meta || {};
3448
3733
  return {
3449
3734
  ...result,
@@ -3493,7 +3778,7 @@ var SSEConnectionManager = class {
3493
3778
  client.onObservabilityEvent((event) => this.sendEvent(event));
3494
3779
  await client.connect();
3495
3780
  this.clients.set(sessionId, client);
3496
- const tools = await client.listTools();
3781
+ const { result: tools } = await this.listPolicyFilteredTools(sessionId);
3497
3782
  return { success: true, toolCount: tools.tools.length };
3498
3783
  } catch (error) {
3499
3784
  this.emitConnectionEvent({
@@ -3541,7 +3826,7 @@ var SSEConnectionManager = class {
3541
3826
  client.onConnectionEvent((event) => this.emitConnectionEvent(event));
3542
3827
  await client.finishAuth(code, oauthState);
3543
3828
  this.clients.set(sessionId, client);
3544
- const tools = await client.listTools();
3829
+ const { result: tools } = await this.listPolicyFilteredTools(sessionId);
3545
3830
  return { success: true, toolCount: tools.tools.length };
3546
3831
  } catch (error) {
3547
3832
  this.emitConnectionEvent({