@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.
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +3 -3
- package/dist/adapters/agui-middleware.d.ts +3 -3
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +3 -3
- package/dist/adapters/ai-adapter.d.ts +3 -3
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.js +2 -2
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +2 -2
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/adapters/mastra-adapter.d.mts +2 -2
- package/dist/adapters/mastra-adapter.d.ts +2 -2
- package/dist/adapters/mastra-adapter.js +2 -2
- package/dist/adapters/mastra-adapter.js.map +1 -1
- package/dist/adapters/mastra-adapter.mjs +2 -2
- package/dist/adapters/mastra-adapter.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +9 -0
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +15 -4
- package/dist/client/react.d.ts +15 -4
- package/dist/client/react.js +56 -37
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +56 -37
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +4 -4
- package/dist/client/vue.d.ts +4 -4
- package/dist/client/vue.js +9 -0
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +9 -0
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-CZk2gu2E.d.ts → index-BEdyuz1M.d.ts} +6 -1
- package/dist/{index-CkM3p0eE.d.mts → index-C99wE2Zf.d.mts} +6 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +380 -82
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +376 -82
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-QOOPdEVp.d.ts → multi-session-client-CclWRxTD.d.mts} +14 -2
- package/dist/{multi-session-client-ChQrBZhF.d.mts → multi-session-client-DzvZD6Tt.d.ts} +14 -2
- package/dist/server/index.d.mts +18 -4
- package/dist/server/index.d.ts +18 -4
- package/dist/server/index.js +371 -82
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +367 -82
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +4 -4
- package/dist/shared/index.d.ts +4 -4
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-CGs3IDOJ.d.mts → tool-router-C9ECn3FX.d.mts} +1 -1
- package/dist/{tool-router-DQ-HrD7W.d.ts → tool-router-CUSSk6lT.d.ts} +1 -1
- package/dist/{types-Bf-7GOLW.d.mts → types-CbFbPkfQ.d.mts} +38 -3
- package/dist/{types-Bf-7GOLW.d.ts → types-CbFbPkfQ.d.ts} +38 -3
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +1 -0
- package/migrations/supabase/20260330195700_install_mcp_sessions.sql +1 -0
- package/package.json +2 -2
- package/src/adapters/agui-adapter.ts +6 -2
- package/src/adapters/ai-adapter.ts +3 -1
- package/src/adapters/langchain-adapter.ts +5 -3
- package/src/adapters/mastra-adapter.ts +5 -3
- package/src/client/core/sse-client.ts +21 -0
- package/src/client/react/oauth-popup.tsx +17 -24
- package/src/client/react/use-mcp.ts +69 -15
- package/src/server/handlers/sse-handler.ts +202 -9
- package/src/server/mcp/multi-session-client.ts +7 -3
- package/src/server/mcp/oauth-client.ts +25 -10
- package/src/server/mcp/tool-policy-gateway.ts +90 -0
- package/src/server/storage/neon-backend.ts +65 -60
- package/src/server/storage/session-lifecycle.ts +5 -0
- package/src/server/storage/supabase-backend.ts +27 -15
- package/src/server/storage/tool-policy.ts +89 -0
- package/src/server/storage/types.ts +11 -0
- package/src/shared/types.ts +52 -1
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
@@ -3322,12 +3465,21 @@ var SSEConnectionManager = class {
|
|
|
3322
3465
|
case "connect":
|
|
3323
3466
|
result = await this.connect(request.params);
|
|
3324
3467
|
break;
|
|
3468
|
+
case "reconnect":
|
|
3469
|
+
result = await this.reconnect(request.params);
|
|
3470
|
+
break;
|
|
3325
3471
|
case "disconnect":
|
|
3326
3472
|
result = await this.disconnect(request.params);
|
|
3327
3473
|
break;
|
|
3328
3474
|
case "listTools":
|
|
3329
3475
|
result = await this.listTools(request.params);
|
|
3330
3476
|
break;
|
|
3477
|
+
case "setToolPolicy":
|
|
3478
|
+
result = await this.setToolPolicy(request.params);
|
|
3479
|
+
break;
|
|
3480
|
+
case "getToolPolicy":
|
|
3481
|
+
result = await this.getToolPolicy(request.params);
|
|
3482
|
+
break;
|
|
3331
3483
|
case "callTool":
|
|
3332
3484
|
result = await this.callTool(request.params);
|
|
3333
3485
|
break;
|
|
@@ -3384,7 +3536,8 @@ var SSEConnectionManager = class {
|
|
|
3384
3536
|
transport: s.transportType,
|
|
3385
3537
|
createdAt: s.createdAt,
|
|
3386
3538
|
updatedAt: s.updatedAt ?? s.createdAt,
|
|
3387
|
-
status: s.status ?? "pending"
|
|
3539
|
+
status: s.status ?? "pending",
|
|
3540
|
+
toolPolicy: s.toolPolicy
|
|
3388
3541
|
}))
|
|
3389
3542
|
};
|
|
3390
3543
|
}
|
|
@@ -3432,7 +3585,7 @@ var SSEConnectionManager = class {
|
|
|
3432
3585
|
this.sendEvent(event);
|
|
3433
3586
|
});
|
|
3434
3587
|
await client.connect();
|
|
3435
|
-
await
|
|
3588
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
3436
3589
|
return {
|
|
3437
3590
|
sessionId,
|
|
3438
3591
|
success: true
|
|
@@ -3457,6 +3610,66 @@ var SSEConnectionManager = class {
|
|
|
3457
3610
|
throw error;
|
|
3458
3611
|
}
|
|
3459
3612
|
}
|
|
3613
|
+
/**
|
|
3614
|
+
* Reconnect to an MCP server — tears down the active client transport/connection
|
|
3615
|
+
* and creates a fresh connection while reusing the existing session credentials in a single RPC call.
|
|
3616
|
+
*/
|
|
3617
|
+
async reconnect(params) {
|
|
3618
|
+
const { serverId: rawServerId, serverName, serverUrl, callbackUrl, transportType } = params;
|
|
3619
|
+
const headers = normalizeHeaders(params.headers);
|
|
3620
|
+
const serverId = rawServerId && rawServerId.length <= 12 ? rawServerId : generateServerId();
|
|
3621
|
+
const existingSessions = await sessions.list(this.userId);
|
|
3622
|
+
const duplicate = existingSessions.find(
|
|
3623
|
+
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
3624
|
+
);
|
|
3625
|
+
const sessionId = duplicate ? duplicate.sessionId : await sessions.generateSessionId();
|
|
3626
|
+
if (duplicate) {
|
|
3627
|
+
const existingClient = this.clients.get(duplicate.sessionId);
|
|
3628
|
+
if (existingClient) {
|
|
3629
|
+
await existingClient.disconnect();
|
|
3630
|
+
this.clients.delete(duplicate.sessionId);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
try {
|
|
3634
|
+
const clientMetadata = await this.getResolvedClientMetadata();
|
|
3635
|
+
const client = new MCPClient({
|
|
3636
|
+
userId: this.userId,
|
|
3637
|
+
sessionId,
|
|
3638
|
+
serverId,
|
|
3639
|
+
serverName,
|
|
3640
|
+
serverUrl,
|
|
3641
|
+
callbackUrl,
|
|
3642
|
+
transportType,
|
|
3643
|
+
headers,
|
|
3644
|
+
...clientMetadata
|
|
3645
|
+
});
|
|
3646
|
+
this.clients.set(sessionId, client);
|
|
3647
|
+
client.onConnectionEvent((event) => {
|
|
3648
|
+
this.emitConnectionEvent(event);
|
|
3649
|
+
});
|
|
3650
|
+
client.onObservabilityEvent((event) => {
|
|
3651
|
+
this.sendEvent(event);
|
|
3652
|
+
});
|
|
3653
|
+
await client.connect();
|
|
3654
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
3655
|
+
return { sessionId, success: true };
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
if (error instanceof UnauthorizedError) {
|
|
3658
|
+
this.clients.delete(sessionId);
|
|
3659
|
+
return { sessionId, success: true };
|
|
3660
|
+
}
|
|
3661
|
+
this.emitConnectionEvent({
|
|
3662
|
+
type: "error",
|
|
3663
|
+
sessionId,
|
|
3664
|
+
serverId,
|
|
3665
|
+
error: error instanceof Error ? error.message : "Connection failed",
|
|
3666
|
+
errorType: "connection",
|
|
3667
|
+
timestamp: Date.now()
|
|
3668
|
+
});
|
|
3669
|
+
this.clients.delete(sessionId);
|
|
3670
|
+
throw error;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3460
3673
|
/**
|
|
3461
3674
|
* Disconnect from an MCP server
|
|
3462
3675
|
*/
|
|
@@ -3503,22 +3716,98 @@ var SSEConnectionManager = class {
|
|
|
3503
3716
|
this.clients.set(sessionId, client);
|
|
3504
3717
|
return client;
|
|
3505
3718
|
}
|
|
3719
|
+
async listPolicyFilteredTools(sessionId) {
|
|
3720
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3721
|
+
if (!session) {
|
|
3722
|
+
throw new Error("Session not found");
|
|
3723
|
+
}
|
|
3724
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3725
|
+
const gateway = createToolPolicyGateway(this.userId, sessionId, client);
|
|
3726
|
+
const result = await gateway.listTools();
|
|
3727
|
+
this.emitConnectionEvent({
|
|
3728
|
+
type: "tools_discovered",
|
|
3729
|
+
sessionId,
|
|
3730
|
+
serverId: session.serverId ?? "unknown",
|
|
3731
|
+
toolCount: result.tools.length,
|
|
3732
|
+
tools: result.tools,
|
|
3733
|
+
timestamp: Date.now()
|
|
3734
|
+
});
|
|
3735
|
+
return { session, result };
|
|
3736
|
+
}
|
|
3506
3737
|
/**
|
|
3507
3738
|
* List tools from a session
|
|
3508
3739
|
*/
|
|
3509
3740
|
async listTools(params) {
|
|
3510
3741
|
const { sessionId } = params;
|
|
3511
|
-
const
|
|
3512
|
-
const result = await client.listTools();
|
|
3742
|
+
const { result } = await this.listPolicyFilteredTools(sessionId);
|
|
3513
3743
|
return { tools: result.tools };
|
|
3514
3744
|
}
|
|
3745
|
+
/**
|
|
3746
|
+
* Get all raw tools with effective access state for management UI.
|
|
3747
|
+
*/
|
|
3748
|
+
async getToolPolicy(params) {
|
|
3749
|
+
const { sessionId } = params;
|
|
3750
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3751
|
+
if (!session) {
|
|
3752
|
+
throw new Error("Session not found");
|
|
3753
|
+
}
|
|
3754
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3755
|
+
const allTools = await client.fetchTools();
|
|
3756
|
+
const toolPolicy = session.toolPolicy ?? {
|
|
3757
|
+
mode: "all",
|
|
3758
|
+
toolIds: [],
|
|
3759
|
+
updatedAt: session.updatedAt ?? session.createdAt
|
|
3760
|
+
};
|
|
3761
|
+
const serverId = session.serverId ?? "unknown";
|
|
3762
|
+
const tools = allTools.tools.map((tool) => ({
|
|
3763
|
+
...tool,
|
|
3764
|
+
toolId: createToolId(serverId, tool.name),
|
|
3765
|
+
allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
|
|
3766
|
+
}));
|
|
3767
|
+
return {
|
|
3768
|
+
toolPolicy,
|
|
3769
|
+
tools,
|
|
3770
|
+
toolCount: tools.length,
|
|
3771
|
+
allowedToolCount: tools.filter((tool) => tool.allowed).length
|
|
3772
|
+
};
|
|
3773
|
+
}
|
|
3774
|
+
/**
|
|
3775
|
+
* Update per-session tool access policy.
|
|
3776
|
+
*/
|
|
3777
|
+
async setToolPolicy(params) {
|
|
3778
|
+
const { sessionId } = params;
|
|
3779
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3780
|
+
if (!session) {
|
|
3781
|
+
throw new Error("Session not found");
|
|
3782
|
+
}
|
|
3783
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3784
|
+
const allTools = await client.fetchTools();
|
|
3785
|
+
const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
|
|
3786
|
+
validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
|
|
3787
|
+
await sessions.update(this.userId, sessionId, { toolPolicy });
|
|
3788
|
+
const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
|
|
3789
|
+
this.emitConnectionEvent({
|
|
3790
|
+
type: "tools_discovered",
|
|
3791
|
+
sessionId,
|
|
3792
|
+
serverId: session.serverId ?? "unknown",
|
|
3793
|
+
toolCount: filteredTools.length,
|
|
3794
|
+
tools: filteredTools,
|
|
3795
|
+
timestamp: Date.now()
|
|
3796
|
+
});
|
|
3797
|
+
return {
|
|
3798
|
+
success: true,
|
|
3799
|
+
toolPolicy,
|
|
3800
|
+
tools: filteredTools,
|
|
3801
|
+
toolCount: filteredTools.length
|
|
3802
|
+
};
|
|
3803
|
+
}
|
|
3515
3804
|
/**
|
|
3516
3805
|
* Call a tool on the MCP server
|
|
3517
3806
|
*/
|
|
3518
3807
|
async callTool(params) {
|
|
3519
3808
|
const { sessionId, toolName, toolArgs } = params;
|
|
3520
3809
|
const client = await this.getOrCreateClient(sessionId);
|
|
3521
|
-
const result = await client.callTool(toolName, toolArgs);
|
|
3810
|
+
const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
|
|
3522
3811
|
const meta = result._meta || {};
|
|
3523
3812
|
return {
|
|
3524
3813
|
...result,
|
|
@@ -3568,7 +3857,7 @@ var SSEConnectionManager = class {
|
|
|
3568
3857
|
client.onObservabilityEvent((event) => this.sendEvent(event));
|
|
3569
3858
|
await client.connect();
|
|
3570
3859
|
this.clients.set(sessionId, client);
|
|
3571
|
-
const tools = await
|
|
3860
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3572
3861
|
return { success: true, toolCount: tools.tools.length };
|
|
3573
3862
|
} catch (error) {
|
|
3574
3863
|
this.emitConnectionEvent({
|
|
@@ -3616,7 +3905,7 @@ var SSEConnectionManager = class {
|
|
|
3616
3905
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
3617
3906
|
await client.finishAuth(code, oauthState);
|
|
3618
3907
|
this.clients.set(sessionId, client);
|
|
3619
|
-
const tools = await
|
|
3908
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3620
3909
|
return { success: true, toolCount: tools.tools.length };
|
|
3621
3910
|
} catch (error) {
|
|
3622
3911
|
this.emitConnectionEvent({
|