@mcp-ts/sdk 2.4.4 → 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 (89) 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 +36 -6
  34. package/dist/client/react.js.map +1 -1
  35. package/dist/client/react.mjs +36 -6
  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 +374 -130
  48. package/dist/index.js.map +1 -1
  49. package/dist/index.mjs +370 -130
  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 +368 -130
  56. package/dist/server/index.js.map +1 -1
  57. package/dist/server/index.mjs +364 -130
  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 +2 -6
  62. package/dist/shared/index.js.map +1 -1
  63. package/dist/shared/index.mjs +2 -6
  64. package/dist/shared/index.mjs.map +1 -1
  65. package/dist/{tool-router-DQ-HrD7W.d.ts → tool-router-CS9l0w4a.d.ts} +1 -1
  66. package/dist/{tool-router-CGs3IDOJ.d.mts → tool-router-CVLBaCwH.d.mts} +1 -1
  67. package/dist/{types-Bf-7GOLW.d.mts → types-DK_NGWd4.d.mts} +37 -3
  68. package/dist/{types-Bf-7GOLW.d.ts → types-DK_NGWd4.d.ts} +37 -3
  69. package/migrations/neon/20260513010000_install_mcp_sessions.sql +1 -0
  70. package/migrations/supabase/20260330195700_install_mcp_sessions.sql +1 -0
  71. package/package.json +1 -1
  72. package/src/adapters/agui-adapter.ts +6 -2
  73. package/src/adapters/ai-adapter.ts +3 -1
  74. package/src/adapters/langchain-adapter.ts +5 -3
  75. package/src/adapters/mastra-adapter.ts +5 -3
  76. package/src/client/core/sse-client.ts +17 -0
  77. package/src/client/react/use-mcp.ts +63 -0
  78. package/src/server/handlers/sse-handler.ts +117 -10
  79. package/src/server/mcp/multi-session-client.ts +7 -3
  80. package/src/server/mcp/oauth-client.ts +78 -55
  81. package/src/server/mcp/tool-policy-gateway.ts +90 -0
  82. package/src/server/storage/index.ts +1 -1
  83. package/src/server/storage/neon-backend.ts +65 -60
  84. package/src/server/storage/session-lifecycle.ts +5 -0
  85. package/src/server/storage/supabase-backend.ts +27 -15
  86. package/src/server/storage/tool-policy.ts +89 -0
  87. package/src/server/storage/types.ts +11 -0
  88. package/src/shared/types.ts +48 -1
  89. package/src/shared/utils.ts +16 -14
package/dist/index.js CHANGED
@@ -163,13 +163,9 @@ init_cjs_shims();
163
163
  // src/shared/utils.ts
164
164
  init_cjs_shims();
165
165
  var OAUTH_STATE_SEPARATOR = ".";
166
- var firstChar = nanoid.customAlphabet(
167
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
168
- 1
169
- );
170
- var rest = nanoid.customAlphabet(
171
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
172
- 11
166
+ var serverIdAlphabet = nanoid.customAlphabet(
167
+ "abcdefghijklmnopqrstuvwxyz0123456789",
168
+ 12
173
169
  );
174
170
  function sanitizeServerLabel(name) {
175
171
  let sanitized = name.replace(/[^a-zA-Z0-9-_]/g, "_").replace(/_{2,}/g, "_").toLowerCase();
@@ -179,7 +175,10 @@ function sanitizeServerLabel(name) {
179
175
  return sanitized;
180
176
  }
181
177
  function generateSessionId() {
182
- return firstChar() + rest();
178
+ return "sess_" + nanoid.nanoid(21);
179
+ }
180
+ function generateServerId() {
181
+ return serverIdAlphabet();
183
182
  }
184
183
  function formatOAuthState(nonce, sessionId) {
185
184
  return `${nonce}${OAUTH_STATE_SEPARATOR}${sessionId}`;
@@ -217,6 +216,60 @@ var SOFTWARE_VERSION = "2.3.4";
217
216
  var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
218
217
  var MCP_CLIENT_VERSION = "2.0";
219
218
 
219
+ // src/server/storage/tool-policy.ts
220
+ init_cjs_shims();
221
+ function createToolId(serverId, toolName) {
222
+ return `${serverId}::${toolName}`;
223
+ }
224
+ function normalizeToolIds(input) {
225
+ if (!Array.isArray(input)) return [];
226
+ return Array.from(new Set(
227
+ input.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
228
+ ));
229
+ }
230
+ function normalizeToolPolicy(input, now = Date.now()) {
231
+ if (!input || typeof input !== "object") {
232
+ return void 0;
233
+ }
234
+ const mode = input.mode === "allowlist" || input.mode === "denylist" ? input.mode : "all";
235
+ const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : now;
236
+ if (mode === "all") {
237
+ return { mode: "all", toolIds: [], updatedAt };
238
+ }
239
+ return { mode, toolIds: normalizeToolIds(input.toolIds), updatedAt };
240
+ }
241
+ function normalizeToolPolicyForUpdate(input, now = Date.now()) {
242
+ return normalizeToolPolicy(input, now) ?? { mode: "all", toolIds: [], updatedAt: now };
243
+ }
244
+ function isToolAllowed(policy, toolName, serverId) {
245
+ if (!policy || policy.mode === "all") return true;
246
+ if (!serverId) return false;
247
+ const toolId = createToolId(serverId, toolName);
248
+ if (policy.mode === "allowlist") {
249
+ return policy.toolIds.includes(toolId);
250
+ }
251
+ return !policy.toolIds.includes(toolId);
252
+ }
253
+ function assertToolAllowed(policy, toolName, serverId) {
254
+ if (isToolAllowed(policy, toolName, serverId)) return;
255
+ throw new Error(`Tool "${toolName}" is not allowed for this MCP session`);
256
+ }
257
+ function filterToolsByPolicy(tools, policy, serverId) {
258
+ if (!policy || policy.mode === "all") return tools;
259
+ return tools.filter((tool) => isToolAllowed(policy, tool.name, serverId));
260
+ }
261
+ function validateToolPolicyAgainstTools(policy, tools, serverId) {
262
+ if (policy.mode === "all") return;
263
+ if (!serverId) {
264
+ throw new Error("Cannot validate MCP tool policy without a serverId");
265
+ }
266
+ const availableIds = new Set(tools.map((tool) => createToolId(serverId, tool.name)));
267
+ const unknownIds = policy.toolIds.filter((id) => !availableIds.has(id));
268
+ if (unknownIds.length > 0) {
269
+ throw new Error(`Unknown tool id(s) for this MCP session: ${unknownIds.join(", ")}`);
270
+ }
271
+ }
272
+
220
273
  // src/server/storage/session-lifecycle.ts
221
274
  function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
222
275
  return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
@@ -233,7 +286,8 @@ function normalizeNewSession(session, now = Date.now()) {
233
286
  status,
234
287
  createdAt,
235
288
  updatedAt,
236
- expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
289
+ expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt),
290
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
237
291
  };
238
292
  }
239
293
  function mergeSessionUpdate(current, data, now = Date.now()) {
@@ -247,7 +301,8 @@ function mergeSessionUpdate(current, data, now = Date.now()) {
247
301
  return {
248
302
  ...updated,
249
303
  status,
250
- expiresAt: resolveSessionExpiresAt(status, updatedAt)
304
+ expiresAt: resolveSessionExpiresAt(status, updatedAt),
305
+ toolPolicy: normalizeToolPolicy(updated.toolPolicy, updatedAt)
251
306
  };
252
307
  }
253
308
  function normalizeStoredSession(session) {
@@ -260,7 +315,8 @@ function normalizeStoredSession(session) {
260
315
  status,
261
316
  createdAt,
262
317
  updatedAt,
263
- expiresAt
318
+ expiresAt,
319
+ toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
264
320
  };
265
321
  }
266
322
  function isSessionExpired(session, now = Date.now()) {
@@ -1131,7 +1187,8 @@ var SupabaseStorageBackend = class {
1131
1187
  userId: row.user_id,
1132
1188
  headers: decryptObject(row.headers),
1133
1189
  authUrl: row.auth_url,
1134
- status: row.status ?? "pending"
1190
+ status: row.status ?? "pending",
1191
+ toolPolicy: normalizeToolPolicy(row.tool_policy)
1135
1192
  };
1136
1193
  }
1137
1194
  mapRowToCredentials(row, userId, sessionId) {
@@ -1155,7 +1212,7 @@ var SupabaseStorageBackend = class {
1155
1212
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1156
1213
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1157
1214
  const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
1158
- const { error } = await this.supabase.from("mcp_sessions").insert({
1215
+ const insertData = {
1159
1216
  session_id: sessionId,
1160
1217
  user_id: userId,
1161
1218
  server_id: session.serverId,
@@ -1169,7 +1226,12 @@ var SupabaseStorageBackend = class {
1169
1226
  auth_url: session.authUrl ?? null,
1170
1227
  status,
1171
1228
  expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
1172
- });
1229
+ };
1230
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy);
1231
+ if (toolPolicy) {
1232
+ insertData.tool_policy = toolPolicy;
1233
+ }
1234
+ const { error } = await this.supabase.from("mcp_sessions").insert(insertData);
1173
1235
  if (error) {
1174
1236
  if (error.code === "23505") {
1175
1237
  throw new Error(`Session ${sessionId} already exists`);
@@ -1194,6 +1256,7 @@ var SupabaseStorageBackend = class {
1194
1256
  }
1195
1257
  if ("headers" in data) updateData.headers = encryptObject(data.headers);
1196
1258
  if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
1259
+ if ("toolPolicy" in data) updateData.tool_policy = normalizeToolPolicy(data.toolPolicy);
1197
1260
  const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
1198
1261
  let updatedRows = null;
1199
1262
  if (shouldUpdateSession) {
@@ -1367,7 +1430,8 @@ var NeonStorageBackend = class {
1367
1430
  userId: row.user_id,
1368
1431
  headers: decryptObject(row.headers),
1369
1432
  authUrl: row.auth_url ?? void 0,
1370
- status: row.status ?? "pending"
1433
+ status: row.status ?? "pending",
1434
+ toolPolicy: normalizeToolPolicy(row.tool_policy)
1371
1435
  };
1372
1436
  }
1373
1437
  mapRowToCredentials(row, userId, sessionId) {
@@ -1390,42 +1454,48 @@ var NeonStorageBackend = class {
1390
1454
  const status = session.status ?? "pending";
1391
1455
  const createdAt = new Date(session.createdAt || Date.now()).toISOString();
1392
1456
  const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
1393
- const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
1457
+ const createdAtMs = new Date(createdAt).getTime();
1458
+ const expiresAt = resolveSessionExpiresAt(status, createdAtMs);
1459
+ const toolPolicy = normalizeToolPolicy(session.toolPolicy, createdAtMs);
1460
+ const columns = [
1461
+ "session_id",
1462
+ "user_id",
1463
+ "server_id",
1464
+ "server_name",
1465
+ "server_url",
1466
+ "transport_type",
1467
+ "callback_url",
1468
+ "created_at",
1469
+ "updated_at",
1470
+ "headers",
1471
+ "auth_url",
1472
+ "status",
1473
+ "expires_at"
1474
+ ];
1475
+ const values = [
1476
+ sessionId,
1477
+ userId,
1478
+ session.serverId,
1479
+ session.serverName,
1480
+ session.serverUrl,
1481
+ session.transportType,
1482
+ session.callbackUrl,
1483
+ createdAt,
1484
+ updatedAt,
1485
+ encryptObject(session.headers),
1486
+ session.authUrl ?? null,
1487
+ status,
1488
+ expiresAt === null ? null : new Date(expiresAt).toISOString()
1489
+ ];
1490
+ if (toolPolicy) {
1491
+ columns.push("tool_policy");
1492
+ values.push(toolPolicy);
1493
+ }
1494
+ const placeholders = values.map((_, i) => `$${i + 1}`);
1394
1495
  try {
1395
1496
  await this.sql.query(
1396
- `INSERT INTO ${this.tableName} (
1397
- session_id,
1398
- user_id,
1399
- server_id,
1400
- server_name,
1401
- server_url,
1402
- transport_type,
1403
- callback_url,
1404
- created_at,
1405
- updated_at,
1406
- headers,
1407
- auth_url,
1408
- status,
1409
- expires_at
1410
- ) VALUES (
1411
- $1, $2, $3, $4, $5, $6, $7, $8,
1412
- $9, $10, $11, $12, $13
1413
- )`,
1414
- [
1415
- sessionId,
1416
- userId,
1417
- session.serverId,
1418
- session.serverName,
1419
- session.serverUrl,
1420
- session.transportType,
1421
- session.callbackUrl,
1422
- createdAt,
1423
- updatedAt,
1424
- encryptObject(session.headers),
1425
- session.authUrl ?? null,
1426
- status,
1427
- expiresAt === null ? null : new Date(expiresAt).toISOString()
1428
- ]
1497
+ `INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`,
1498
+ values
1429
1499
  );
1430
1500
  } catch (error) {
1431
1501
  if (error.code === "23505") {
@@ -1442,36 +1512,36 @@ var NeonStorageBackend = class {
1442
1512
  const updatedSession = { ...currentSession, ...data };
1443
1513
  const status = updatedSession.status ?? "pending";
1444
1514
  const expiresAt = resolveSessionExpiresAt(status);
1445
- 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;
1515
+ 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;
1446
1516
  if (shouldUpdateSession) {
1517
+ const setClauses = [];
1518
+ const values = [];
1519
+ let paramIndex = 1;
1520
+ const addSet = (column, value) => {
1521
+ setClauses.push(`${column} = $${paramIndex++}`);
1522
+ values.push(value);
1523
+ };
1524
+ addSet("server_id", updatedSession.serverId);
1525
+ addSet("server_name", updatedSession.serverName);
1526
+ addSet("server_url", updatedSession.serverUrl);
1527
+ addSet("transport_type", updatedSession.transportType);
1528
+ addSet("callback_url", updatedSession.callbackUrl);
1529
+ addSet("status", status);
1530
+ addSet("headers", encryptObject(updatedSession.headers));
1531
+ addSet("auth_url", updatedSession.authUrl ?? null);
1532
+ addSet("expires_at", expiresAt === null ? null : new Date(expiresAt).toISOString());
1533
+ if ("toolPolicy" in data) {
1534
+ const policyUpdatedAt = updatedSession.updatedAt ?? Date.now();
1535
+ const toolPolicy = normalizeToolPolicy(updatedSession.toolPolicy, policyUpdatedAt) ?? { mode: "all", toolIds: [], updatedAt: policyUpdatedAt };
1536
+ addSet("tool_policy", toolPolicy);
1537
+ }
1538
+ setClauses.push("updated_at = now()");
1447
1539
  const updatedRows = await this.sql.query(
1448
1540
  `UPDATE ${this.tableName}
1449
- SET
1450
- server_id = $1,
1451
- server_name = $2,
1452
- server_url = $3,
1453
- transport_type = $4,
1454
- callback_url = $5,
1455
- status = $6,
1456
- headers = $7,
1457
- auth_url = $8,
1458
- expires_at = $9,
1459
- updated_at = now()
1460
- WHERE user_id = $10 AND session_id = $11
1541
+ SET ${setClauses.join(", ")}
1542
+ WHERE user_id = $${paramIndex++} AND session_id = $${paramIndex++}
1461
1543
  RETURNING id`,
1462
- [
1463
- updatedSession.serverId,
1464
- updatedSession.serverName,
1465
- updatedSession.serverUrl,
1466
- updatedSession.transportType,
1467
- updatedSession.callbackUrl,
1468
- status,
1469
- encryptObject(updatedSession.headers),
1470
- updatedSession.authUrl ?? null,
1471
- expiresAt === null ? null : new Date(expiresAt).toISOString(),
1472
- userId,
1473
- sessionId
1474
- ]
1544
+ [...values, userId, sessionId]
1475
1545
  );
1476
1546
  if (updatedRows.length === 0) {
1477
1547
  throw new Error(`Session ${sessionId} not found for userId ${userId}`);
@@ -2459,7 +2529,15 @@ var MCPClient = class {
2459
2529
  if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
2460
2530
  this.createdAt = Date.now();
2461
2531
  const updatedAt = this.createdAt;
2462
- console.log(`[MCPClient] Creating pending session ${this.sessionId} for connection setup`);
2532
+ this._onObservabilityEvent.fire({
2533
+ type: "mcp:client:session_created",
2534
+ level: "info",
2535
+ message: `Creating pending session ${this.sessionId} for connection setup`,
2536
+ sessionId: this.sessionId,
2537
+ serverId: this.serverId,
2538
+ timestamp: Date.now(),
2539
+ id: nanoid.nanoid()
2540
+ });
2463
2541
  await sessions.create({
2464
2542
  sessionId: this.sessionId,
2465
2543
  userId: this.userId,
@@ -2589,7 +2667,15 @@ var MCPClient = class {
2589
2667
  this.transportType = transportType;
2590
2668
  this.emitStateChange("CONNECTED");
2591
2669
  this.emitProgress("Connected successfully");
2592
- console.log(`[MCPClient] Saving active session ${this.sessionId} (connect success)`);
2670
+ this._onObservabilityEvent.fire({
2671
+ type: "mcp:client:session_saved",
2672
+ level: "info",
2673
+ message: `Saving active session ${this.sessionId} (connect success)`,
2674
+ sessionId: this.sessionId,
2675
+ serverId: this.serverId,
2676
+ timestamp: Date.now(),
2677
+ id: nanoid.nanoid()
2678
+ });
2593
2679
  await this.saveSession("active");
2594
2680
  } catch (error) {
2595
2681
  if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
@@ -2606,7 +2692,15 @@ var MCPClient = class {
2606
2692
  throw new Error(message);
2607
2693
  }
2608
2694
  this.emitStateChange("AUTHENTICATING");
2609
- console.log(`[MCPClient] Saving pending OAuth session ${this.sessionId}`);
2695
+ this._onObservabilityEvent.fire({
2696
+ type: "mcp:client:session_saved",
2697
+ level: "info",
2698
+ message: `Saving pending OAuth session ${this.sessionId}`,
2699
+ sessionId: this.sessionId,
2700
+ serverId: this.serverId,
2701
+ timestamp: Date.now(),
2702
+ id: nanoid.nanoid()
2703
+ });
2610
2704
  await this.saveSession("pending");
2611
2705
  if (this.serverId) {
2612
2706
  this._onConnectionEvent.fire({
@@ -2681,27 +2775,25 @@ var MCPClient = class {
2681
2775
  this.emitStateChange("AUTHENTICATED");
2682
2776
  authenticatedStateEmitted = true;
2683
2777
  }
2684
- this.emitProgress("Creating authenticated client...");
2685
- this.client = new index_js.Client(
2686
- {
2687
- name: MCP_CLIENT_NAME,
2688
- version: MCP_CLIENT_VERSION
2689
- },
2690
- {
2691
- capabilities: {
2692
- extensions: {
2693
- "io.modelcontextprotocol/ui": {
2694
- mimeTypes: ["text/html+mcp"]
2695
- }
2696
- }
2697
- }
2698
- }
2699
- );
2700
2778
  this.emitStateChange("CONNECTING");
2779
+ if (this.client.transport) {
2780
+ try {
2781
+ await this.client.close();
2782
+ } catch {
2783
+ }
2784
+ }
2701
2785
  await this.client.connect(this.transport);
2702
2786
  this.transportType = currentType;
2703
2787
  this.emitStateChange("CONNECTED");
2704
- console.log(`[MCPClient] Saving active session ${this.sessionId} (OAuth complete)`);
2788
+ this._onObservabilityEvent.fire({
2789
+ type: "mcp:client:session_saved",
2790
+ level: "info",
2791
+ message: `Saving active session ${this.sessionId} (OAuth complete)`,
2792
+ sessionId: this.sessionId,
2793
+ serverId: this.serverId,
2794
+ timestamp: Date.now(),
2795
+ id: nanoid.nanoid()
2796
+ });
2705
2797
  await this.saveSession("active");
2706
2798
  return;
2707
2799
  } catch (error) {
@@ -2736,24 +2828,28 @@ var MCPClient = class {
2736
2828
  throw lastError;
2737
2829
  }
2738
2830
  }
2831
+ /**
2832
+ * Lists all available tools from the connected MCP server without emitting discovery events.
2833
+ * Gateways use this to apply policy before publishing tools to agents or UI state.
2834
+ */
2835
+ async fetchTools() {
2836
+ const request = {
2837
+ method: "tools/list",
2838
+ params: {}
2839
+ };
2840
+ return await this.withRetry(
2841
+ () => this.client.request(request, types_js.ListToolsResultSchema)
2842
+ );
2843
+ }
2739
2844
  /**
2740
2845
  * Lists all available tools from the connected MCP server
2741
2846
  * @returns List of tools with their schemas and descriptions
2742
2847
  * @throws {Error} When client is not connected
2743
2848
  */
2744
2849
  async listTools() {
2745
- if (!this.client) {
2746
- throw new Error("Not connected to server");
2747
- }
2748
2850
  this.emitStateChange("DISCOVERING");
2749
2851
  try {
2750
- const request = {
2751
- method: "tools/list",
2752
- params: {}
2753
- };
2754
- const result = await this.withRetry(
2755
- () => this.client.request(request, types_js.ListToolsResultSchema)
2756
- );
2852
+ const result = await this.fetchTools();
2757
2853
  if (this.serverId) {
2758
2854
  this._onConnectionEvent.fire({
2759
2855
  type: "tools_discovered",
@@ -2782,9 +2878,6 @@ var MCPClient = class {
2782
2878
  * @throws {Error} When client is not connected
2783
2879
  */
2784
2880
  async callTool(toolName, toolArgs) {
2785
- if (!this.client) {
2786
- throw new Error("Not connected to server");
2787
- }
2788
2881
  const request = {
2789
2882
  method: "tools/call",
2790
2883
  params: {
@@ -2838,9 +2931,6 @@ var MCPClient = class {
2838
2931
  * @throws {Error} When client is not connected
2839
2932
  */
2840
2933
  async listPrompts() {
2841
- if (!this.client) {
2842
- throw new Error("Not connected to server");
2843
- }
2844
2934
  this.emitStateChange("DISCOVERING");
2845
2935
  try {
2846
2936
  const request = {
@@ -2868,9 +2958,6 @@ var MCPClient = class {
2868
2958
  * @throws {Error} When client is not connected
2869
2959
  */
2870
2960
  async getPrompt(name, args) {
2871
- if (!this.client) {
2872
- throw new Error("Not connected to server");
2873
- }
2874
2961
  const request = {
2875
2962
  method: "prompts/get",
2876
2963
  params: {
@@ -2888,9 +2975,6 @@ var MCPClient = class {
2888
2975
  * @throws {Error} When client is not connected
2889
2976
  */
2890
2977
  async listResources() {
2891
- if (!this.client) {
2892
- throw new Error("Not connected to server");
2893
- }
2894
2978
  this.emitStateChange("DISCOVERING");
2895
2979
  try {
2896
2980
  const request = {
@@ -2917,9 +3001,6 @@ var MCPClient = class {
2917
3001
  * @throws {Error} When client is not connected
2918
3002
  */
2919
3003
  async readResource(uri) {
2920
- if (!this.client) {
2921
- throw new Error("Not connected to server");
2922
- }
2923
3004
  const request = {
2924
3005
  method: "resources/read",
2925
3006
  params: {
@@ -2975,7 +3056,16 @@ var MCPClient = class {
2975
3056
  try {
2976
3057
  await this.ensureSession();
2977
3058
  } catch (error) {
2978
- console.warn("[MCPClient] Initialization failed during clearSession:", error);
3059
+ this._onObservabilityEvent.fire({
3060
+ type: "mcp:client:error",
3061
+ level: "warn",
3062
+ message: "Initialization failed during clearSession",
3063
+ sessionId: this.sessionId,
3064
+ serverId: this.serverId,
3065
+ payload: { error: String(error) },
3066
+ timestamp: Date.now(),
3067
+ id: nanoid.nanoid()
3068
+ });
2979
3069
  }
2980
3070
  if (this.oauthProvider) {
2981
3071
  await this.oauthProvider.invalidateCredentials("all");
@@ -3088,6 +3178,69 @@ var MCPClient = class {
3088
3178
 
3089
3179
  // src/server/mcp/multi-session-client.ts
3090
3180
  init_cjs_shims();
3181
+
3182
+ // src/server/mcp/tool-policy-gateway.ts
3183
+ init_cjs_shims();
3184
+ var ToolPolicyGateway = class {
3185
+ constructor(userId, sessionId, client) {
3186
+ this.userId = userId;
3187
+ this.sessionId = sessionId;
3188
+ this.client = client;
3189
+ }
3190
+ isConnected() {
3191
+ return this.client.isConnected();
3192
+ }
3193
+ getServerId() {
3194
+ return this.client.getServerId?.();
3195
+ }
3196
+ getServerName() {
3197
+ return this.client.getServerName?.();
3198
+ }
3199
+ getServerUrl() {
3200
+ return this.client.getServerUrl?.();
3201
+ }
3202
+ getSessionId() {
3203
+ return this.client.getSessionId?.() ?? this.sessionId;
3204
+ }
3205
+ async listTools() {
3206
+ const session = await this.getSession();
3207
+ const result = await this.client.fetchTools();
3208
+ const tools = this.filterTools(session, result.tools);
3209
+ return {
3210
+ ...result,
3211
+ tools
3212
+ };
3213
+ }
3214
+ async listAllTools() {
3215
+ return await this.client.fetchTools();
3216
+ }
3217
+ async callTool(name, args) {
3218
+ const session = await this.getSession();
3219
+ this.assertAllowed(session, name);
3220
+ return await this.client.callTool(name, args);
3221
+ }
3222
+ filterTools(session, tools) {
3223
+ return filterToolsByPolicy(tools, session.toolPolicy, this.getPolicyServerId(session));
3224
+ }
3225
+ assertAllowed(session, toolName) {
3226
+ assertToolAllowed(session.toolPolicy, toolName, this.getPolicyServerId(session));
3227
+ }
3228
+ async getSession() {
3229
+ const session = await sessions.get(this.userId, this.sessionId);
3230
+ if (!session) {
3231
+ throw new Error("Session not found");
3232
+ }
3233
+ return session;
3234
+ }
3235
+ getPolicyServerId(session) {
3236
+ return this.client.getServerId?.() ?? session.serverId;
3237
+ }
3238
+ };
3239
+ function createToolPolicyGateway(userId, sessionId, client) {
3240
+ return new ToolPolicyGateway(userId, sessionId, client);
3241
+ }
3242
+
3243
+ // src/server/mcp/multi-session-client.ts
3091
3244
  var DEFAULT_TIMEOUT_MS = 15e3;
3092
3245
  var DEFAULT_MAX_RETRIES = 2;
3093
3246
  var DEFAULT_RETRY_DELAY_MS = 1e3;
@@ -3152,7 +3305,9 @@ var MultiSessionClient = class {
3152
3305
  * or to route a tool call to the right client by `serverId`.
3153
3306
  */
3154
3307
  getClients() {
3155
- return this.clients;
3308
+ return this.clients.map(
3309
+ (client) => createToolPolicyGateway(this.userId, client.getSessionId(), client)
3310
+ );
3156
3311
  }
3157
3312
  /**
3158
3313
  * Removes and disconnects a single session by ID.
@@ -3391,6 +3546,12 @@ var SSEConnectionManager = class {
3391
3546
  case "listTools":
3392
3547
  result = await this.listTools(request.params);
3393
3548
  break;
3549
+ case "setToolPolicy":
3550
+ result = await this.setToolPolicy(request.params);
3551
+ break;
3552
+ case "getToolPolicy":
3553
+ result = await this.getToolPolicy(request.params);
3554
+ break;
3394
3555
  case "callTool":
3395
3556
  result = await this.callTool(request.params);
3396
3557
  break;
@@ -3447,7 +3608,8 @@ var SSEConnectionManager = class {
3447
3608
  transport: s.transportType,
3448
3609
  createdAt: s.createdAt,
3449
3610
  updatedAt: s.updatedAt ?? s.createdAt,
3450
- status: s.status ?? "pending"
3611
+ status: s.status ?? "pending",
3612
+ toolPolicy: s.toolPolicy
3451
3613
  }))
3452
3614
  };
3453
3615
  }
@@ -3457,7 +3619,7 @@ var SSEConnectionManager = class {
3457
3619
  async connect(params) {
3458
3620
  const { serverName, serverUrl, callbackUrl, transportType } = params;
3459
3621
  const headers = normalizeHeaders(params.headers);
3460
- const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : await sessions.generateSessionId();
3622
+ const serverId = params.serverId && params.serverId.length <= 12 ? params.serverId : generateServerId();
3461
3623
  const existingSessions = await sessions.list(this.userId);
3462
3624
  const duplicate = existingSessions.find(
3463
3625
  (s) => s.serverId === serverId || s.serverUrl === serverUrl
@@ -3495,7 +3657,7 @@ var SSEConnectionManager = class {
3495
3657
  this.sendEvent(event);
3496
3658
  });
3497
3659
  await client.connect();
3498
- await client.listTools();
3660
+ await this.listPolicyFilteredTools(sessionId);
3499
3661
  return {
3500
3662
  sessionId,
3501
3663
  success: true
@@ -3566,22 +3728,98 @@ var SSEConnectionManager = class {
3566
3728
  this.clients.set(sessionId, client);
3567
3729
  return client;
3568
3730
  }
3731
+ async listPolicyFilteredTools(sessionId) {
3732
+ const session = await sessions.get(this.userId, sessionId);
3733
+ if (!session) {
3734
+ throw new Error("Session not found");
3735
+ }
3736
+ const client = await this.getOrCreateClient(sessionId);
3737
+ const gateway = createToolPolicyGateway(this.userId, sessionId, client);
3738
+ const result = await gateway.listTools();
3739
+ this.emitConnectionEvent({
3740
+ type: "tools_discovered",
3741
+ sessionId,
3742
+ serverId: session.serverId ?? "unknown",
3743
+ toolCount: result.tools.length,
3744
+ tools: result.tools,
3745
+ timestamp: Date.now()
3746
+ });
3747
+ return { session, result };
3748
+ }
3569
3749
  /**
3570
3750
  * List tools from a session
3571
3751
  */
3572
3752
  async listTools(params) {
3573
3753
  const { sessionId } = params;
3574
- const client = await this.getOrCreateClient(sessionId);
3575
- const result = await client.listTools();
3754
+ const { result } = await this.listPolicyFilteredTools(sessionId);
3576
3755
  return { tools: result.tools };
3577
3756
  }
3757
+ /**
3758
+ * Get all raw tools with effective access state for management UI.
3759
+ */
3760
+ async getToolPolicy(params) {
3761
+ const { sessionId } = params;
3762
+ const session = await sessions.get(this.userId, sessionId);
3763
+ if (!session) {
3764
+ throw new Error("Session not found");
3765
+ }
3766
+ const client = await this.getOrCreateClient(sessionId);
3767
+ const allTools = await client.fetchTools();
3768
+ const toolPolicy = session.toolPolicy ?? {
3769
+ mode: "all",
3770
+ toolIds: [],
3771
+ updatedAt: session.updatedAt ?? session.createdAt
3772
+ };
3773
+ const serverId = session.serverId ?? "unknown";
3774
+ const tools = allTools.tools.map((tool) => ({
3775
+ ...tool,
3776
+ toolId: createToolId(serverId, tool.name),
3777
+ allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
3778
+ }));
3779
+ return {
3780
+ toolPolicy,
3781
+ tools,
3782
+ toolCount: tools.length,
3783
+ allowedToolCount: tools.filter((tool) => tool.allowed).length
3784
+ };
3785
+ }
3786
+ /**
3787
+ * Update per-session tool access policy.
3788
+ */
3789
+ async setToolPolicy(params) {
3790
+ const { sessionId } = params;
3791
+ const session = await sessions.get(this.userId, sessionId);
3792
+ if (!session) {
3793
+ throw new Error("Session not found");
3794
+ }
3795
+ const client = await this.getOrCreateClient(sessionId);
3796
+ const allTools = await client.fetchTools();
3797
+ const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
3798
+ validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
3799
+ await sessions.update(this.userId, sessionId, { toolPolicy });
3800
+ const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
3801
+ this.emitConnectionEvent({
3802
+ type: "tools_discovered",
3803
+ sessionId,
3804
+ serverId: session.serverId ?? "unknown",
3805
+ toolCount: filteredTools.length,
3806
+ tools: filteredTools,
3807
+ timestamp: Date.now()
3808
+ });
3809
+ return {
3810
+ success: true,
3811
+ toolPolicy,
3812
+ tools: filteredTools,
3813
+ toolCount: filteredTools.length
3814
+ };
3815
+ }
3578
3816
  /**
3579
3817
  * Call a tool on the MCP server
3580
3818
  */
3581
3819
  async callTool(params) {
3582
3820
  const { sessionId, toolName, toolArgs } = params;
3583
3821
  const client = await this.getOrCreateClient(sessionId);
3584
- const result = await client.callTool(toolName, toolArgs);
3822
+ const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
3585
3823
  const meta = result._meta || {};
3586
3824
  return {
3587
3825
  ...result,
@@ -3631,7 +3869,7 @@ var SSEConnectionManager = class {
3631
3869
  client.onObservabilityEvent((event) => this.sendEvent(event));
3632
3870
  await client.connect();
3633
3871
  this.clients.set(sessionId, client);
3634
- const tools = await client.listTools();
3872
+ const { result: tools } = await this.listPolicyFilteredTools(sessionId);
3635
3873
  return { success: true, toolCount: tools.tools.length };
3636
3874
  } catch (error) {
3637
3875
  this.emitConnectionEvent({
@@ -3679,7 +3917,7 @@ var SSEConnectionManager = class {
3679
3917
  client.onConnectionEvent((event) => this.emitConnectionEvent(event));
3680
3918
  await client.finishAuth(code, oauthState);
3681
3919
  this.clients.set(sessionId, client);
3682
- const tools = await client.listTools();
3920
+ const { result: tools } = await this.listPolicyFilteredTools(sessionId);
3683
3921
  return { success: true, toolCount: tools.tools.length };
3684
3922
  } catch (error) {
3685
3923
  this.emitConnectionEvent({
@@ -3978,6 +4216,12 @@ var SSEClient = class {
3978
4216
  async disconnectFromServer(sessionId) {
3979
4217
  return this.sendRequest("disconnect", { sessionId });
3980
4218
  }
4219
+ async setToolPolicy(sessionId, toolPolicy) {
4220
+ return this.sendRequest("setToolPolicy", { sessionId, toolPolicy });
4221
+ }
4222
+ async getToolPolicy(sessionId) {
4223
+ return this.sendRequest("getToolPolicy", { sessionId });
4224
+ }
3981
4225
  async listTools(sessionId) {
3982
4226
  return this.sendRequest("listTools", { sessionId });
3983
4227
  }