@mcp-ts/sdk 2.4.5 → 2.5.0

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 (85) 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 +6 -0
  28. package/dist/client/index.js.map +1 -1
  29. package/dist/client/index.mjs +6 -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 +34 -0
  34. package/dist/client/react.js.map +1 -1
  35. package/dist/client/react.mjs +34 -0
  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 +6 -0
  40. package/dist/client/vue.js.map +1 -1
  41. package/dist/client/vue.mjs +6 -0
  42. package/dist/client/vue.mjs.map +1 -1
  43. package/dist/{index-CkM3p0eE.d.mts → index-CTURVnom.d.mts} +5 -1
  44. package/dist/{index-CZk2gu2E.d.ts → index-sVcqrhf7.d.ts} +5 -1
  45. package/dist/index.d.mts +4 -4
  46. package/dist/index.d.ts +4 -4
  47. package/dist/index.js +314 -82
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +310 -82
  50. package/dist/index.mjs.map +1 -1
  51. package/dist/{multi-session-client-ChQrBZhF.d.mts → multi-session-client-CSPSHHla.d.ts} +14 -2
  52. package/dist/{multi-session-client-QOOPdEVp.d.ts → multi-session-client-DMZGVABI.d.mts} +14 -2
  53. package/dist/server/index.d.mts +13 -4
  54. package/dist/server/index.d.ts +13 -4
  55. package/dist/server/index.js +308 -82
  56. package/dist/server/index.js.map +1 -1
  57. package/dist/server/index.mjs +304 -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-DQ-HrD7W.d.ts → tool-router-CS9l0w4a.d.ts} +1 -1
  64. package/dist/{tool-router-CGs3IDOJ.d.mts → tool-router-CVLBaCwH.d.mts} +1 -1
  65. package/dist/{types-Bf-7GOLW.d.mts → types-DK_NGWd4.d.mts} +37 -3
  66. package/dist/{types-Bf-7GOLW.d.ts → types-DK_NGWd4.d.ts} +37 -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 +1 -1
  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 +17 -0
  75. package/src/client/react/use-mcp.ts +63 -0
  76. package/src/server/handlers/sse-handler.ts +116 -9
  77. package/src/server/mcp/multi-session-client.ts +7 -3
  78. package/src/server/mcp/oauth-client.ts +25 -10
  79. package/src/server/mcp/tool-policy-gateway.ts +90 -0
  80. package/src/server/storage/neon-backend.ts +65 -60
  81. package/src/server/storage/session-lifecycle.ts +5 -0
  82. package/src/server/storage/supabase-backend.ts +27 -15
  83. package/src/server/storage/tool-policy.ts +89 -0
  84. package/src/server/storage/types.ts +11 -0
  85. package/src/shared/types.ts +48 -1
@@ -209,6 +209,60 @@ var SOFTWARE_VERSION = "2.3.4";
209
209
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
210
210
  var MCP_CLIENT_VERSION = "2.0";
211
211
 
212
+ // src/server/storage/tool-policy.ts
213
+ init_cjs_shims();
214
+ function createToolId(serverId, toolName) {
215
+ return `${serverId}::${toolName}`;
216
+ }
217
+ function normalizeToolIds(input) {
218
+ if (!Array.isArray(input)) return [];
219
+ return Array.from(new Set(
220
+ input.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
221
+ ));
222
+ }
223
+ function normalizeToolPolicy(input, now = Date.now()) {
224
+ if (!input || typeof input !== "object") {
225
+ return void 0;
226
+ }
227
+ const mode = input.mode === "allowlist" || input.mode === "denylist" ? input.mode : "all";
228
+ const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : now;
229
+ if (mode === "all") {
230
+ return { mode: "all", toolIds: [], updatedAt };
231
+ }
232
+ return { mode, toolIds: normalizeToolIds(input.toolIds), updatedAt };
233
+ }
234
+ function normalizeToolPolicyForUpdate(input, now = Date.now()) {
235
+ return normalizeToolPolicy(input, now) ?? { mode: "all", toolIds: [], updatedAt: now };
236
+ }
237
+ function isToolAllowed(policy, toolName, serverId) {
238
+ if (!policy || policy.mode === "all") return true;
239
+ if (!serverId) return false;
240
+ const toolId = createToolId(serverId, toolName);
241
+ if (policy.mode === "allowlist") {
242
+ return policy.toolIds.includes(toolId);
243
+ }
244
+ return !policy.toolIds.includes(toolId);
245
+ }
246
+ function assertToolAllowed(policy, toolName, serverId) {
247
+ if (isToolAllowed(policy, toolName, serverId)) return;
248
+ throw new Error(`Tool "${toolName}" is not allowed for this MCP session`);
249
+ }
250
+ function filterToolsByPolicy(tools, policy, serverId) {
251
+ if (!policy || policy.mode === "all") return tools;
252
+ return tools.filter((tool) => isToolAllowed(policy, tool.name, serverId));
253
+ }
254
+ function validateToolPolicyAgainstTools(policy, tools, serverId) {
255
+ if (policy.mode === "all") return;
256
+ if (!serverId) {
257
+ throw new Error("Cannot validate MCP tool policy without a serverId");
258
+ }
259
+ const availableIds = new Set(tools.map((tool) => createToolId(serverId, tool.name)));
260
+ const unknownIds = policy.toolIds.filter((id) => !availableIds.has(id));
261
+ if (unknownIds.length > 0) {
262
+ throw new Error(`Unknown tool id(s) for this MCP session: ${unknownIds.join(", ")}`);
263
+ }
264
+ }
265
+
212
266
  // src/server/storage/session-lifecycle.ts
213
267
  function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
214
268
  return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
@@ -225,7 +279,8 @@ function normalizeNewSession(session, now = Date.now()) {
225
279
  status,
226
280
  createdAt,
227
281
  updatedAt,
228
- expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
282
+ expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt),
283
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
229
284
  };
230
285
  }
231
286
  function mergeSessionUpdate(current, data, now = Date.now()) {
@@ -239,7 +294,8 @@ function mergeSessionUpdate(current, data, now = Date.now()) {
239
294
  return {
240
295
  ...updated,
241
296
  status,
242
- expiresAt: resolveSessionExpiresAt(status, updatedAt)
297
+ expiresAt: resolveSessionExpiresAt(status, updatedAt),
298
+ toolPolicy: normalizeToolPolicy(updated.toolPolicy, updatedAt)
243
299
  };
244
300
  }
245
301
  function normalizeStoredSession(session) {
@@ -252,7 +308,8 @@ function normalizeStoredSession(session) {
252
308
  status,
253
309
  createdAt,
254
310
  updatedAt,
255
- expiresAt
311
+ expiresAt,
312
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
256
313
  };
257
314
  }
258
315
  function isSessionExpired(session, now = Date.now()) {
@@ -1123,7 +1180,8 @@ var SupabaseStorageBackend = class {
1123
1180
  userId: row.user_id,
1124
1181
  headers: decryptObject(row.headers),
1125
1182
  authUrl: row.auth_url,
1126
- status: row.status ?? "pending"
1183
+ status: row.status ?? "pending",
1184
+ toolPolicy: normalizeToolPolicy(row.tool_policy)
1127
1185
  };
1128
1186
  }
1129
1187
  mapRowToCredentials(row, userId, sessionId) {
@@ -1147,7 +1205,7 @@ var SupabaseStorageBackend = class {
1147
1205
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1148
1206
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1149
1207
  const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
1150
- const { error } = await this.supabase.from("mcp_sessions").insert({
1208
+ const insertData = {
1151
1209
  session_id: sessionId,
1152
1210
  user_id: userId,
1153
1211
  server_id: session.serverId,
@@ -1161,7 +1219,12 @@ var SupabaseStorageBackend = class {
1161
1219
  auth_url: session.authUrl ?? null,
1162
1220
  status,
1163
1221
  expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
1164
- });
1222
+ };
1223
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy);
1224
+ if (toolPolicy) {
1225
+ insertData.tool_policy = toolPolicy;
1226
+ }
1227
+ const { error } = await this.supabase.from("mcp_sessions").insert(insertData);
1165
1228
  if (error) {
1166
1229
  if (error.code === "23505") {
1167
1230
  throw new Error(`Session ${sessionId} already exists`);
@@ -1186,6 +1249,7 @@ var SupabaseStorageBackend = class {
1186
1249
  }
1187
1250
  if ("headers" in data) updateData.headers = encryptObject(data.headers);
1188
1251
  if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
1252
+ if ("toolPolicy" in data) updateData.tool_policy = normalizeToolPolicy(data.toolPolicy);
1189
1253
  const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
1190
1254
  let updatedRows = null;
1191
1255
  if (shouldUpdateSession) {
@@ -1359,7 +1423,8 @@ var NeonStorageBackend = class {
1359
1423
  userId: row.user_id,
1360
1424
  headers: decryptObject(row.headers),
1361
1425
  authUrl: row.auth_url ?? void 0,
1362
- status: row.status ?? "pending"
1426
+ status: row.status ?? "pending",
1427
+ toolPolicy: normalizeToolPolicy(row.tool_policy)
1363
1428
  };
1364
1429
  }
1365
1430
  mapRowToCredentials(row, userId, sessionId) {
@@ -1382,42 +1447,48 @@ var NeonStorageBackend = class {
1382
1447
  const status = session.status ?? "pending";
1383
1448
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1384
1449
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1385
- const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
1450
+ const createdAtMs = new Date(createdAt).getTime();
1451
+ const expiresAt = resolveSessionExpiresAt(status, createdAtMs);
1452
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy, createdAtMs);
1453
+ const columns = [
1454
+ "session_id",
1455
+ "user_id",
1456
+ "server_id",
1457
+ "server_name",
1458
+ "server_url",
1459
+ "transport_type",
1460
+ "callback_url",
1461
+ "created_at",
1462
+ "updated_at",
1463
+ "headers",
1464
+ "auth_url",
1465
+ "status",
1466
+ "expires_at"
1467
+ ];
1468
+ const values = [
1469
+ sessionId,
1470
+ userId,
1471
+ session.serverId,
1472
+ session.serverName,
1473
+ session.serverUrl,
1474
+ session.transportType,
1475
+ session.callbackUrl,
1476
+ createdAt,
1477
+ updatedAt,
1478
+ encryptObject(session.headers),
1479
+ session.authUrl ?? null,
1480
+ status,
1481
+ expiresAt === null ? null : new Date(expiresAt).toISOString()
1482
+ ];
1483
+ if (toolPolicy) {
1484
+ columns.push("tool_policy");
1485
+ values.push(toolPolicy);
1486
+ }
1487
+ const placeholders = values.map((_, i) => `$${i + 1}`);
1386
1488
  try {
1387
1489
  await this.sql.query(
1388
- `INSERT INTO ${this.tableName} (
1389
- session_id,
1390
- user_id,
1391
- server_id,
1392
- server_name,
1393
- server_url,
1394
- transport_type,
1395
- callback_url,
1396
- created_at,
1397
- updated_at,
1398
- headers,
1399
- auth_url,
1400
- status,
1401
- expires_at
1402
- ) VALUES (
1403
- $1, $2, $3, $4, $5, $6, $7, $8,
1404
- $9, $10, $11, $12, $13
1405
- )`,
1406
- [
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
- ]
1490
+ `INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`,
1491
+ values
1421
1492
  );
1422
1493
  } catch (error) {
1423
1494
  if (error.code === "23505") {
@@ -1434,36 +1505,36 @@ var NeonStorageBackend = class {
1434
1505
  const updatedSession = { ...currentSession, ...data };
1435
1506
  const status = updatedSession.status ?? "pending";
1436
1507
  const expiresAt = resolveSessionExpiresAt(status);
1437
- 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;
1508
+ 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;
1438
1509
  if (shouldUpdateSession) {
1510
+ const setClauses = [];
1511
+ const values = [];
1512
+ let paramIndex = 1;
1513
+ const addSet = (column, value) => {
1514
+ setClauses.push(`${column} = $${paramIndex++}`);
1515
+ values.push(value);
1516
+ };
1517
+ addSet("server_id", updatedSession.serverId);
1518
+ addSet("server_name", updatedSession.serverName);
1519
+ addSet("server_url", updatedSession.serverUrl);
1520
+ addSet("transport_type", updatedSession.transportType);
1521
+ addSet("callback_url", updatedSession.callbackUrl);
1522
+ addSet("status", status);
1523
+ addSet("headers", encryptObject(updatedSession.headers));
1524
+ addSet("auth_url", updatedSession.authUrl ?? null);
1525
+ addSet("expires_at", expiresAt === null ? null : new Date(expiresAt).toISOString());
1526
+ if ("toolPolicy" in data) {
1527
+ const policyUpdatedAt = updatedSession.updatedAt ?? Date.now();
1528
+ const toolPolicy = normalizeToolPolicy(updatedSession.toolPolicy, policyUpdatedAt) ?? { mode: "all", toolIds: [], updatedAt: policyUpdatedAt };
1529
+ addSet("tool_policy", toolPolicy);
1530
+ }
1531
+ setClauses.push("updated_at = now()");
1439
1532
  const updatedRows = await this.sql.query(
1440
1533
  `UPDATE ${this.tableName}
1441
- SET
1442
- server_id = $1,
1443
- server_name = $2,
1444
- server_url = $3,
1445
- transport_type = $4,
1446
- callback_url = $5,
1447
- status = $6,
1448
- headers = $7,
1449
- auth_url = $8,
1450
- expires_at = $9,
1451
- updated_at = now()
1452
- WHERE user_id = $10 AND session_id = $11
1534
+ SET ${setClauses.join(", ")}
1535
+ WHERE user_id = $${paramIndex++} AND session_id = $${paramIndex++}
1453
1536
  RETURNING id`,
1454
- [
1455
- updatedSession.serverId,
1456
- updatedSession.serverName,
1457
- updatedSession.serverUrl,
1458
- updatedSession.transportType,
1459
- updatedSession.callbackUrl,
1460
- status,
1461
- encryptObject(updatedSession.headers),
1462
- updatedSession.authUrl ?? null,
1463
- expiresAt === null ? null : new Date(expiresAt).toISOString(),
1464
- userId,
1465
- sessionId
1466
- ]
1537
+ [...values, userId, sessionId]
1467
1538
  );
1468
1539
  if (updatedRows.length === 0) {
1469
1540
  throw new Error(`Session ${sessionId} not found for userId ${userId}`);
@@ -2682,6 +2753,19 @@ var MCPClient = class {
2682
2753
  throw lastError;
2683
2754
  }
2684
2755
  }
2756
+ /**
2757
+ * Lists all available tools from the connected MCP server without emitting discovery events.
2758
+ * Gateways use this to apply policy before publishing tools to agents or UI state.
2759
+ */
2760
+ async fetchTools() {
2761
+ const request = {
2762
+ method: "tools/list",
2763
+ params: {}
2764
+ };
2765
+ return await this.withRetry(
2766
+ () => this.client.request(request, types_js.ListToolsResultSchema)
2767
+ );
2768
+ }
2685
2769
  /**
2686
2770
  * Lists all available tools from the connected MCP server
2687
2771
  * @returns List of tools with their schemas and descriptions
@@ -2690,13 +2774,7 @@ var MCPClient = class {
2690
2774
  async listTools() {
2691
2775
  this.emitStateChange("DISCOVERING");
2692
2776
  try {
2693
- const request = {
2694
- method: "tools/list",
2695
- params: {}
2696
- };
2697
- const result = await this.withRetry(
2698
- () => this.client.request(request, types_js.ListToolsResultSchema)
2699
- );
2777
+ const result = await this.fetchTools();
2700
2778
  if (this.serverId) {
2701
2779
  this._onConnectionEvent.fire({
2702
2780
  type: "tools_discovered",
@@ -3025,6 +3103,69 @@ var MCPClient = class {
3025
3103
 
3026
3104
  // src/server/mcp/multi-session-client.ts
3027
3105
  init_cjs_shims();
3106
+
3107
+ // src/server/mcp/tool-policy-gateway.ts
3108
+ init_cjs_shims();
3109
+ var ToolPolicyGateway = class {
3110
+ constructor(userId, sessionId, client) {
3111
+ this.userId = userId;
3112
+ this.sessionId = sessionId;
3113
+ this.client = client;
3114
+ }
3115
+ isConnected() {
3116
+ return this.client.isConnected();
3117
+ }
3118
+ getServerId() {
3119
+ return this.client.getServerId?.();
3120
+ }
3121
+ getServerName() {
3122
+ return this.client.getServerName?.();
3123
+ }
3124
+ getServerUrl() {
3125
+ return this.client.getServerUrl?.();
3126
+ }
3127
+ getSessionId() {
3128
+ return this.client.getSessionId?.() ?? this.sessionId;
3129
+ }
3130
+ async listTools() {
3131
+ const session = await this.getSession();
3132
+ const result = await this.client.fetchTools();
3133
+ const tools = this.filterTools(session, result.tools);
3134
+ return {
3135
+ ...result,
3136
+ tools
3137
+ };
3138
+ }
3139
+ async listAllTools() {
3140
+ return await this.client.fetchTools();
3141
+ }
3142
+ async callTool(name, args) {
3143
+ const session = await this.getSession();
3144
+ this.assertAllowed(session, name);
3145
+ return await this.client.callTool(name, args);
3146
+ }
3147
+ filterTools(session, tools) {
3148
+ return filterToolsByPolicy(tools, session.toolPolicy, this.getPolicyServerId(session));
3149
+ }
3150
+ assertAllowed(session, toolName) {
3151
+ assertToolAllowed(session.toolPolicy, toolName, this.getPolicyServerId(session));
3152
+ }
3153
+ async getSession() {
3154
+ const session = await sessions.get(this.userId, this.sessionId);
3155
+ if (!session) {
3156
+ throw new Error("Session not found");
3157
+ }
3158
+ return session;
3159
+ }
3160
+ getPolicyServerId(session) {
3161
+ return this.client.getServerId?.() ?? session.serverId;
3162
+ }
3163
+ };
3164
+ function createToolPolicyGateway(userId, sessionId, client) {
3165
+ return new ToolPolicyGateway(userId, sessionId, client);
3166
+ }
3167
+
3168
+ // src/server/mcp/multi-session-client.ts
3028
3169
  var DEFAULT_TIMEOUT_MS = 15e3;
3029
3170
  var DEFAULT_MAX_RETRIES = 2;
3030
3171
  var DEFAULT_RETRY_DELAY_MS = 1e3;
@@ -3089,7 +3230,9 @@ var MultiSessionClient = class {
3089
3230
  * or to route a tool call to the right client by `serverId`.
3090
3231
  */
3091
3232
  getClients() {
3092
- return this.clients;
3233
+ return this.clients.map(
3234
+ (client) => createToolPolicyGateway(this.userId, client.getSessionId(), client)
3235
+ );
3093
3236
  }
3094
3237
  /**
3095
3238
  * Removes and disconnects a single session by ID.
@@ -3328,6 +3471,12 @@ var SSEConnectionManager = class {
3328
3471
  case "listTools":
3329
3472
  result = await this.listTools(request.params);
3330
3473
  break;
3474
+ case "setToolPolicy":
3475
+ result = await this.setToolPolicy(request.params);
3476
+ break;
3477
+ case "getToolPolicy":
3478
+ result = await this.getToolPolicy(request.params);
3479
+ break;
3331
3480
  case "callTool":
3332
3481
  result = await this.callTool(request.params);
3333
3482
  break;
@@ -3384,7 +3533,8 @@ var SSEConnectionManager = class {
3384
3533
  transport: s.transportType,
3385
3534
  createdAt: s.createdAt,
3386
3535
  updatedAt: s.updatedAt ?? s.createdAt,
3387
- status: s.status ?? "pending"
3536
+ status: s.status ?? "pending",
3537
+ toolPolicy: s.toolPolicy
3388
3538
  }))
3389
3539
  };
3390
3540
  }
@@ -3432,7 +3582,7 @@ var SSEConnectionManager = class {
3432
3582
  this.sendEvent(event);
3433
3583
  });
3434
3584
  await client.connect();
3435
- await client.listTools();
3585
+ await this.listPolicyFilteredTools(sessionId);
3436
3586
  return {
3437
3587
  sessionId,
3438
3588
  success: true
@@ -3503,22 +3653,98 @@ var SSEConnectionManager = class {
3503
3653
  this.clients.set(sessionId, client);
3504
3654
  return client;
3505
3655
  }
3656
+ async listPolicyFilteredTools(sessionId) {
3657
+ const session = await sessions.get(this.userId, sessionId);
3658
+ if (!session) {
3659
+ throw new Error("Session not found");
3660
+ }
3661
+ const client = await this.getOrCreateClient(sessionId);
3662
+ const gateway = createToolPolicyGateway(this.userId, sessionId, client);
3663
+ const result = await gateway.listTools();
3664
+ this.emitConnectionEvent({
3665
+ type: "tools_discovered",
3666
+ sessionId,
3667
+ serverId: session.serverId ?? "unknown",
3668
+ toolCount: result.tools.length,
3669
+ tools: result.tools,
3670
+ timestamp: Date.now()
3671
+ });
3672
+ return { session, result };
3673
+ }
3506
3674
  /**
3507
3675
  * List tools from a session
3508
3676
  */
3509
3677
  async listTools(params) {
3510
3678
  const { sessionId } = params;
3511
- const client = await this.getOrCreateClient(sessionId);
3512
- const result = await client.listTools();
3679
+ const { result } = await this.listPolicyFilteredTools(sessionId);
3513
3680
  return { tools: result.tools };
3514
3681
  }
3682
+ /**
3683
+ * Get all raw tools with effective access state for management UI.
3684
+ */
3685
+ async getToolPolicy(params) {
3686
+ const { sessionId } = params;
3687
+ const session = await sessions.get(this.userId, sessionId);
3688
+ if (!session) {
3689
+ throw new Error("Session not found");
3690
+ }
3691
+ const client = await this.getOrCreateClient(sessionId);
3692
+ const allTools = await client.fetchTools();
3693
+ const toolPolicy = session.toolPolicy ?? {
3694
+ mode: "all",
3695
+ toolIds: [],
3696
+ updatedAt: session.updatedAt ?? session.createdAt
3697
+ };
3698
+ const serverId = session.serverId ?? "unknown";
3699
+ const tools = allTools.tools.map((tool) => ({
3700
+ ...tool,
3701
+ toolId: createToolId(serverId, tool.name),
3702
+ allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
3703
+ }));
3704
+ return {
3705
+ toolPolicy,
3706
+ tools,
3707
+ toolCount: tools.length,
3708
+ allowedToolCount: tools.filter((tool) => tool.allowed).length
3709
+ };
3710
+ }
3711
+ /**
3712
+ * Update per-session tool access policy.
3713
+ */
3714
+ async setToolPolicy(params) {
3715
+ const { sessionId } = params;
3716
+ const session = await sessions.get(this.userId, sessionId);
3717
+ if (!session) {
3718
+ throw new Error("Session not found");
3719
+ }
3720
+ const client = await this.getOrCreateClient(sessionId);
3721
+ const allTools = await client.fetchTools();
3722
+ const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
3723
+ validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
3724
+ await sessions.update(this.userId, sessionId, { toolPolicy });
3725
+ const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
3726
+ this.emitConnectionEvent({
3727
+ type: "tools_discovered",
3728
+ sessionId,
3729
+ serverId: session.serverId ?? "unknown",
3730
+ toolCount: filteredTools.length,
3731
+ tools: filteredTools,
3732
+ timestamp: Date.now()
3733
+ });
3734
+ return {
3735
+ success: true,
3736
+ toolPolicy,
3737
+ tools: filteredTools,
3738
+ toolCount: filteredTools.length
3739
+ };
3740
+ }
3515
3741
  /**
3516
3742
  * Call a tool on the MCP server
3517
3743
  */
3518
3744
  async callTool(params) {
3519
3745
  const { sessionId, toolName, toolArgs } = params;
3520
3746
  const client = await this.getOrCreateClient(sessionId);
3521
- const result = await client.callTool(toolName, toolArgs);
3747
+ const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
3522
3748
  const meta = result._meta || {};
3523
3749
  return {
3524
3750
  ...result,
@@ -3568,7 +3794,7 @@ var SSEConnectionManager = class {
3568
3794
  client.onObservabilityEvent((event) => this.sendEvent(event));
3569
3795
  await client.connect();
3570
3796
  this.clients.set(sessionId, client);
3571
- const tools = await client.listTools();
3797
+ const { result: tools } = await this.listPolicyFilteredTools(sessionId);
3572
3798
  return { success: true, toolCount: tools.tools.length };
3573
3799
  } catch (error) {
3574
3800
  this.emitConnectionEvent({
@@ -3616,7 +3842,7 @@ var SSEConnectionManager = class {
3616
3842
  client.onConnectionEvent((event) => this.emitConnectionEvent(event));
3617
3843
  await client.finishAuth(code, oauthState);
3618
3844
  this.clients.set(sessionId, client);
3619
- const tools = await client.listTools();
3845
+ const { result: tools } = await this.listPolicyFilteredTools(sessionId);
3620
3846
  return { success: true, toolCount: tools.tools.length };
3621
3847
  } catch (error) {
3622
3848
  this.emitConnectionEvent({