@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/index.js
CHANGED
|
@@ -216,6 +216,60 @@ var SOFTWARE_VERSION = "2.3.4";
|
|
|
216
216
|
var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
|
|
217
217
|
var MCP_CLIENT_VERSION = "2.0";
|
|
218
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
|
+
|
|
219
273
|
// src/server/storage/session-lifecycle.ts
|
|
220
274
|
function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
|
|
221
275
|
return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
|
|
@@ -232,7 +286,8 @@ function normalizeNewSession(session, now = Date.now()) {
|
|
|
232
286
|
status,
|
|
233
287
|
createdAt,
|
|
234
288
|
updatedAt,
|
|
235
|
-
expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
|
|
289
|
+
expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt),
|
|
290
|
+
toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
|
|
236
291
|
};
|
|
237
292
|
}
|
|
238
293
|
function mergeSessionUpdate(current, data, now = Date.now()) {
|
|
@@ -246,7 +301,8 @@ function mergeSessionUpdate(current, data, now = Date.now()) {
|
|
|
246
301
|
return {
|
|
247
302
|
...updated,
|
|
248
303
|
status,
|
|
249
|
-
expiresAt: resolveSessionExpiresAt(status, updatedAt)
|
|
304
|
+
expiresAt: resolveSessionExpiresAt(status, updatedAt),
|
|
305
|
+
toolPolicy: normalizeToolPolicy(updated.toolPolicy, updatedAt)
|
|
250
306
|
};
|
|
251
307
|
}
|
|
252
308
|
function normalizeStoredSession(session) {
|
|
@@ -259,7 +315,8 @@ function normalizeStoredSession(session) {
|
|
|
259
315
|
status,
|
|
260
316
|
createdAt,
|
|
261
317
|
updatedAt,
|
|
262
|
-
expiresAt
|
|
318
|
+
expiresAt,
|
|
319
|
+
toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
|
|
263
320
|
};
|
|
264
321
|
}
|
|
265
322
|
function isSessionExpired(session, now = Date.now()) {
|
|
@@ -1130,7 +1187,8 @@ var SupabaseStorageBackend = class {
|
|
|
1130
1187
|
userId: row.user_id,
|
|
1131
1188
|
headers: decryptObject(row.headers),
|
|
1132
1189
|
authUrl: row.auth_url,
|
|
1133
|
-
status: row.status ?? "pending"
|
|
1190
|
+
status: row.status ?? "pending",
|
|
1191
|
+
toolPolicy: normalizeToolPolicy(row.tool_policy)
|
|
1134
1192
|
};
|
|
1135
1193
|
}
|
|
1136
1194
|
mapRowToCredentials(row, userId, sessionId) {
|
|
@@ -1154,7 +1212,7 @@ var SupabaseStorageBackend = class {
|
|
|
1154
1212
|
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1155
1213
|
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1156
1214
|
const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
|
|
1157
|
-
const
|
|
1215
|
+
const insertData = {
|
|
1158
1216
|
session_id: sessionId,
|
|
1159
1217
|
user_id: userId,
|
|
1160
1218
|
server_id: session.serverId,
|
|
@@ -1168,7 +1226,12 @@ var SupabaseStorageBackend = class {
|
|
|
1168
1226
|
auth_url: session.authUrl ?? null,
|
|
1169
1227
|
status,
|
|
1170
1228
|
expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1171
|
-
}
|
|
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);
|
|
1172
1235
|
if (error) {
|
|
1173
1236
|
if (error.code === "23505") {
|
|
1174
1237
|
throw new Error(`Session ${sessionId} already exists`);
|
|
@@ -1193,6 +1256,7 @@ var SupabaseStorageBackend = class {
|
|
|
1193
1256
|
}
|
|
1194
1257
|
if ("headers" in data) updateData.headers = encryptObject(data.headers);
|
|
1195
1258
|
if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
|
|
1259
|
+
if ("toolPolicy" in data) updateData.tool_policy = normalizeToolPolicy(data.toolPolicy);
|
|
1196
1260
|
const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
|
|
1197
1261
|
let updatedRows = null;
|
|
1198
1262
|
if (shouldUpdateSession) {
|
|
@@ -1366,7 +1430,8 @@ var NeonStorageBackend = class {
|
|
|
1366
1430
|
userId: row.user_id,
|
|
1367
1431
|
headers: decryptObject(row.headers),
|
|
1368
1432
|
authUrl: row.auth_url ?? void 0,
|
|
1369
|
-
status: row.status ?? "pending"
|
|
1433
|
+
status: row.status ?? "pending",
|
|
1434
|
+
toolPolicy: normalizeToolPolicy(row.tool_policy)
|
|
1370
1435
|
};
|
|
1371
1436
|
}
|
|
1372
1437
|
mapRowToCredentials(row, userId, sessionId) {
|
|
@@ -1389,42 +1454,48 @@ var NeonStorageBackend = class {
|
|
|
1389
1454
|
const status = session.status ?? "pending";
|
|
1390
1455
|
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1391
1456
|
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1392
|
-
const
|
|
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}`);
|
|
1393
1495
|
try {
|
|
1394
1496
|
await this.sql.query(
|
|
1395
|
-
`INSERT INTO ${this.tableName} (
|
|
1396
|
-
|
|
1397
|
-
user_id,
|
|
1398
|
-
server_id,
|
|
1399
|
-
server_name,
|
|
1400
|
-
server_url,
|
|
1401
|
-
transport_type,
|
|
1402
|
-
callback_url,
|
|
1403
|
-
created_at,
|
|
1404
|
-
updated_at,
|
|
1405
|
-
headers,
|
|
1406
|
-
auth_url,
|
|
1407
|
-
status,
|
|
1408
|
-
expires_at
|
|
1409
|
-
) VALUES (
|
|
1410
|
-
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
1411
|
-
$9, $10, $11, $12, $13
|
|
1412
|
-
)`,
|
|
1413
|
-
[
|
|
1414
|
-
sessionId,
|
|
1415
|
-
userId,
|
|
1416
|
-
session.serverId,
|
|
1417
|
-
session.serverName,
|
|
1418
|
-
session.serverUrl,
|
|
1419
|
-
session.transportType,
|
|
1420
|
-
session.callbackUrl,
|
|
1421
|
-
createdAt,
|
|
1422
|
-
updatedAt,
|
|
1423
|
-
encryptObject(session.headers),
|
|
1424
|
-
session.authUrl ?? null,
|
|
1425
|
-
status,
|
|
1426
|
-
expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1427
|
-
]
|
|
1497
|
+
`INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`,
|
|
1498
|
+
values
|
|
1428
1499
|
);
|
|
1429
1500
|
} catch (error) {
|
|
1430
1501
|
if (error.code === "23505") {
|
|
@@ -1441,36 +1512,36 @@ var NeonStorageBackend = class {
|
|
|
1441
1512
|
const updatedSession = { ...currentSession, ...data };
|
|
1442
1513
|
const status = updatedSession.status ?? "pending";
|
|
1443
1514
|
const expiresAt = resolveSessionExpiresAt(status);
|
|
1444
|
-
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;
|
|
1445
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()");
|
|
1446
1539
|
const updatedRows = await this.sql.query(
|
|
1447
1540
|
`UPDATE ${this.tableName}
|
|
1448
|
-
SET
|
|
1449
|
-
|
|
1450
|
-
server_name = $2,
|
|
1451
|
-
server_url = $3,
|
|
1452
|
-
transport_type = $4,
|
|
1453
|
-
callback_url = $5,
|
|
1454
|
-
status = $6,
|
|
1455
|
-
headers = $7,
|
|
1456
|
-
auth_url = $8,
|
|
1457
|
-
expires_at = $9,
|
|
1458
|
-
updated_at = now()
|
|
1459
|
-
WHERE user_id = $10 AND session_id = $11
|
|
1541
|
+
SET ${setClauses.join(", ")}
|
|
1542
|
+
WHERE user_id = $${paramIndex++} AND session_id = $${paramIndex++}
|
|
1460
1543
|
RETURNING id`,
|
|
1461
|
-
[
|
|
1462
|
-
updatedSession.serverId,
|
|
1463
|
-
updatedSession.serverName,
|
|
1464
|
-
updatedSession.serverUrl,
|
|
1465
|
-
updatedSession.transportType,
|
|
1466
|
-
updatedSession.callbackUrl,
|
|
1467
|
-
status,
|
|
1468
|
-
encryptObject(updatedSession.headers),
|
|
1469
|
-
updatedSession.authUrl ?? null,
|
|
1470
|
-
expiresAt === null ? null : new Date(expiresAt).toISOString(),
|
|
1471
|
-
userId,
|
|
1472
|
-
sessionId
|
|
1473
|
-
]
|
|
1544
|
+
[...values, userId, sessionId]
|
|
1474
1545
|
);
|
|
1475
1546
|
if (updatedRows.length === 0) {
|
|
1476
1547
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
@@ -2757,6 +2828,19 @@ var MCPClient = class {
|
|
|
2757
2828
|
throw lastError;
|
|
2758
2829
|
}
|
|
2759
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
|
+
}
|
|
2760
2844
|
/**
|
|
2761
2845
|
* Lists all available tools from the connected MCP server
|
|
2762
2846
|
* @returns List of tools with their schemas and descriptions
|
|
@@ -2765,13 +2849,7 @@ var MCPClient = class {
|
|
|
2765
2849
|
async listTools() {
|
|
2766
2850
|
this.emitStateChange("DISCOVERING");
|
|
2767
2851
|
try {
|
|
2768
|
-
const
|
|
2769
|
-
method: "tools/list",
|
|
2770
|
-
params: {}
|
|
2771
|
-
};
|
|
2772
|
-
const result = await this.withRetry(
|
|
2773
|
-
() => this.client.request(request, types_js.ListToolsResultSchema)
|
|
2774
|
-
);
|
|
2852
|
+
const result = await this.fetchTools();
|
|
2775
2853
|
if (this.serverId) {
|
|
2776
2854
|
this._onConnectionEvent.fire({
|
|
2777
2855
|
type: "tools_discovered",
|
|
@@ -3100,6 +3178,69 @@ var MCPClient = class {
|
|
|
3100
3178
|
|
|
3101
3179
|
// src/server/mcp/multi-session-client.ts
|
|
3102
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
|
|
3103
3244
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
3104
3245
|
var DEFAULT_MAX_RETRIES = 2;
|
|
3105
3246
|
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
@@ -3164,7 +3305,9 @@ var MultiSessionClient = class {
|
|
|
3164
3305
|
* or to route a tool call to the right client by `serverId`.
|
|
3165
3306
|
*/
|
|
3166
3307
|
getClients() {
|
|
3167
|
-
return this.clients
|
|
3308
|
+
return this.clients.map(
|
|
3309
|
+
(client) => createToolPolicyGateway(this.userId, client.getSessionId(), client)
|
|
3310
|
+
);
|
|
3168
3311
|
}
|
|
3169
3312
|
/**
|
|
3170
3313
|
* Removes and disconnects a single session by ID.
|
|
@@ -3397,12 +3540,21 @@ var SSEConnectionManager = class {
|
|
|
3397
3540
|
case "connect":
|
|
3398
3541
|
result = await this.connect(request.params);
|
|
3399
3542
|
break;
|
|
3543
|
+
case "reconnect":
|
|
3544
|
+
result = await this.reconnect(request.params);
|
|
3545
|
+
break;
|
|
3400
3546
|
case "disconnect":
|
|
3401
3547
|
result = await this.disconnect(request.params);
|
|
3402
3548
|
break;
|
|
3403
3549
|
case "listTools":
|
|
3404
3550
|
result = await this.listTools(request.params);
|
|
3405
3551
|
break;
|
|
3552
|
+
case "setToolPolicy":
|
|
3553
|
+
result = await this.setToolPolicy(request.params);
|
|
3554
|
+
break;
|
|
3555
|
+
case "getToolPolicy":
|
|
3556
|
+
result = await this.getToolPolicy(request.params);
|
|
3557
|
+
break;
|
|
3406
3558
|
case "callTool":
|
|
3407
3559
|
result = await this.callTool(request.params);
|
|
3408
3560
|
break;
|
|
@@ -3459,7 +3611,8 @@ var SSEConnectionManager = class {
|
|
|
3459
3611
|
transport: s.transportType,
|
|
3460
3612
|
createdAt: s.createdAt,
|
|
3461
3613
|
updatedAt: s.updatedAt ?? s.createdAt,
|
|
3462
|
-
status: s.status ?? "pending"
|
|
3614
|
+
status: s.status ?? "pending",
|
|
3615
|
+
toolPolicy: s.toolPolicy
|
|
3463
3616
|
}))
|
|
3464
3617
|
};
|
|
3465
3618
|
}
|
|
@@ -3507,7 +3660,7 @@ var SSEConnectionManager = class {
|
|
|
3507
3660
|
this.sendEvent(event);
|
|
3508
3661
|
});
|
|
3509
3662
|
await client.connect();
|
|
3510
|
-
await
|
|
3663
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
3511
3664
|
return {
|
|
3512
3665
|
sessionId,
|
|
3513
3666
|
success: true
|
|
@@ -3532,6 +3685,66 @@ var SSEConnectionManager = class {
|
|
|
3532
3685
|
throw error;
|
|
3533
3686
|
}
|
|
3534
3687
|
}
|
|
3688
|
+
/**
|
|
3689
|
+
* Reconnect to an MCP server — tears down the active client transport/connection
|
|
3690
|
+
* and creates a fresh connection while reusing the existing session credentials in a single RPC call.
|
|
3691
|
+
*/
|
|
3692
|
+
async reconnect(params) {
|
|
3693
|
+
const { serverId: rawServerId, serverName, serverUrl, callbackUrl, transportType } = params;
|
|
3694
|
+
const headers = normalizeHeaders(params.headers);
|
|
3695
|
+
const serverId = rawServerId && rawServerId.length <= 12 ? rawServerId : generateServerId();
|
|
3696
|
+
const existingSessions = await sessions.list(this.userId);
|
|
3697
|
+
const duplicate = existingSessions.find(
|
|
3698
|
+
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
3699
|
+
);
|
|
3700
|
+
const sessionId = duplicate ? duplicate.sessionId : await sessions.generateSessionId();
|
|
3701
|
+
if (duplicate) {
|
|
3702
|
+
const existingClient = this.clients.get(duplicate.sessionId);
|
|
3703
|
+
if (existingClient) {
|
|
3704
|
+
await existingClient.disconnect();
|
|
3705
|
+
this.clients.delete(duplicate.sessionId);
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
try {
|
|
3709
|
+
const clientMetadata = await this.getResolvedClientMetadata();
|
|
3710
|
+
const client = new MCPClient({
|
|
3711
|
+
userId: this.userId,
|
|
3712
|
+
sessionId,
|
|
3713
|
+
serverId,
|
|
3714
|
+
serverName,
|
|
3715
|
+
serverUrl,
|
|
3716
|
+
callbackUrl,
|
|
3717
|
+
transportType,
|
|
3718
|
+
headers,
|
|
3719
|
+
...clientMetadata
|
|
3720
|
+
});
|
|
3721
|
+
this.clients.set(sessionId, client);
|
|
3722
|
+
client.onConnectionEvent((event) => {
|
|
3723
|
+
this.emitConnectionEvent(event);
|
|
3724
|
+
});
|
|
3725
|
+
client.onObservabilityEvent((event) => {
|
|
3726
|
+
this.sendEvent(event);
|
|
3727
|
+
});
|
|
3728
|
+
await client.connect();
|
|
3729
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
3730
|
+
return { sessionId, success: true };
|
|
3731
|
+
} catch (error) {
|
|
3732
|
+
if (error instanceof UnauthorizedError) {
|
|
3733
|
+
this.clients.delete(sessionId);
|
|
3734
|
+
return { sessionId, success: true };
|
|
3735
|
+
}
|
|
3736
|
+
this.emitConnectionEvent({
|
|
3737
|
+
type: "error",
|
|
3738
|
+
sessionId,
|
|
3739
|
+
serverId,
|
|
3740
|
+
error: error instanceof Error ? error.message : "Connection failed",
|
|
3741
|
+
errorType: "connection",
|
|
3742
|
+
timestamp: Date.now()
|
|
3743
|
+
});
|
|
3744
|
+
this.clients.delete(sessionId);
|
|
3745
|
+
throw error;
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3535
3748
|
/**
|
|
3536
3749
|
* Disconnect from an MCP server
|
|
3537
3750
|
*/
|
|
@@ -3578,22 +3791,98 @@ var SSEConnectionManager = class {
|
|
|
3578
3791
|
this.clients.set(sessionId, client);
|
|
3579
3792
|
return client;
|
|
3580
3793
|
}
|
|
3794
|
+
async listPolicyFilteredTools(sessionId) {
|
|
3795
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3796
|
+
if (!session) {
|
|
3797
|
+
throw new Error("Session not found");
|
|
3798
|
+
}
|
|
3799
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3800
|
+
const gateway = createToolPolicyGateway(this.userId, sessionId, client);
|
|
3801
|
+
const result = await gateway.listTools();
|
|
3802
|
+
this.emitConnectionEvent({
|
|
3803
|
+
type: "tools_discovered",
|
|
3804
|
+
sessionId,
|
|
3805
|
+
serverId: session.serverId ?? "unknown",
|
|
3806
|
+
toolCount: result.tools.length,
|
|
3807
|
+
tools: result.tools,
|
|
3808
|
+
timestamp: Date.now()
|
|
3809
|
+
});
|
|
3810
|
+
return { session, result };
|
|
3811
|
+
}
|
|
3581
3812
|
/**
|
|
3582
3813
|
* List tools from a session
|
|
3583
3814
|
*/
|
|
3584
3815
|
async listTools(params) {
|
|
3585
3816
|
const { sessionId } = params;
|
|
3586
|
-
const
|
|
3587
|
-
const result = await client.listTools();
|
|
3817
|
+
const { result } = await this.listPolicyFilteredTools(sessionId);
|
|
3588
3818
|
return { tools: result.tools };
|
|
3589
3819
|
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Get all raw tools with effective access state for management UI.
|
|
3822
|
+
*/
|
|
3823
|
+
async getToolPolicy(params) {
|
|
3824
|
+
const { sessionId } = params;
|
|
3825
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3826
|
+
if (!session) {
|
|
3827
|
+
throw new Error("Session not found");
|
|
3828
|
+
}
|
|
3829
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3830
|
+
const allTools = await client.fetchTools();
|
|
3831
|
+
const toolPolicy = session.toolPolicy ?? {
|
|
3832
|
+
mode: "all",
|
|
3833
|
+
toolIds: [],
|
|
3834
|
+
updatedAt: session.updatedAt ?? session.createdAt
|
|
3835
|
+
};
|
|
3836
|
+
const serverId = session.serverId ?? "unknown";
|
|
3837
|
+
const tools = allTools.tools.map((tool) => ({
|
|
3838
|
+
...tool,
|
|
3839
|
+
toolId: createToolId(serverId, tool.name),
|
|
3840
|
+
allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
|
|
3841
|
+
}));
|
|
3842
|
+
return {
|
|
3843
|
+
toolPolicy,
|
|
3844
|
+
tools,
|
|
3845
|
+
toolCount: tools.length,
|
|
3846
|
+
allowedToolCount: tools.filter((tool) => tool.allowed).length
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
/**
|
|
3850
|
+
* Update per-session tool access policy.
|
|
3851
|
+
*/
|
|
3852
|
+
async setToolPolicy(params) {
|
|
3853
|
+
const { sessionId } = params;
|
|
3854
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3855
|
+
if (!session) {
|
|
3856
|
+
throw new Error("Session not found");
|
|
3857
|
+
}
|
|
3858
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3859
|
+
const allTools = await client.fetchTools();
|
|
3860
|
+
const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
|
|
3861
|
+
validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
|
|
3862
|
+
await sessions.update(this.userId, sessionId, { toolPolicy });
|
|
3863
|
+
const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
|
|
3864
|
+
this.emitConnectionEvent({
|
|
3865
|
+
type: "tools_discovered",
|
|
3866
|
+
sessionId,
|
|
3867
|
+
serverId: session.serverId ?? "unknown",
|
|
3868
|
+
toolCount: filteredTools.length,
|
|
3869
|
+
tools: filteredTools,
|
|
3870
|
+
timestamp: Date.now()
|
|
3871
|
+
});
|
|
3872
|
+
return {
|
|
3873
|
+
success: true,
|
|
3874
|
+
toolPolicy,
|
|
3875
|
+
tools: filteredTools,
|
|
3876
|
+
toolCount: filteredTools.length
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3590
3879
|
/**
|
|
3591
3880
|
* Call a tool on the MCP server
|
|
3592
3881
|
*/
|
|
3593
3882
|
async callTool(params) {
|
|
3594
3883
|
const { sessionId, toolName, toolArgs } = params;
|
|
3595
3884
|
const client = await this.getOrCreateClient(sessionId);
|
|
3596
|
-
const result = await client.callTool(toolName, toolArgs);
|
|
3885
|
+
const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
|
|
3597
3886
|
const meta = result._meta || {};
|
|
3598
3887
|
return {
|
|
3599
3888
|
...result,
|
|
@@ -3643,7 +3932,7 @@ var SSEConnectionManager = class {
|
|
|
3643
3932
|
client.onObservabilityEvent((event) => this.sendEvent(event));
|
|
3644
3933
|
await client.connect();
|
|
3645
3934
|
this.clients.set(sessionId, client);
|
|
3646
|
-
const tools = await
|
|
3935
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3647
3936
|
return { success: true, toolCount: tools.tools.length };
|
|
3648
3937
|
} catch (error) {
|
|
3649
3938
|
this.emitConnectionEvent({
|
|
@@ -3691,7 +3980,7 @@ var SSEConnectionManager = class {
|
|
|
3691
3980
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
3692
3981
|
await client.finishAuth(code, oauthState);
|
|
3693
3982
|
this.clients.set(sessionId, client);
|
|
3694
|
-
const tools = await
|
|
3983
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3695
3984
|
return { success: true, toolCount: tools.tools.length };
|
|
3696
3985
|
} catch (error) {
|
|
3697
3986
|
this.emitConnectionEvent({
|
|
@@ -3990,6 +4279,15 @@ var SSEClient = class {
|
|
|
3990
4279
|
async disconnectFromServer(sessionId) {
|
|
3991
4280
|
return this.sendRequest("disconnect", { sessionId });
|
|
3992
4281
|
}
|
|
4282
|
+
async reconnectToServer(params) {
|
|
4283
|
+
return this.sendRequest("reconnect", params);
|
|
4284
|
+
}
|
|
4285
|
+
async setToolPolicy(sessionId, toolPolicy) {
|
|
4286
|
+
return this.sendRequest("setToolPolicy", { sessionId, toolPolicy });
|
|
4287
|
+
}
|
|
4288
|
+
async getToolPolicy(sessionId) {
|
|
4289
|
+
return this.sendRequest("getToolPolicy", { sessionId });
|
|
4290
|
+
}
|
|
3993
4291
|
async listTools(sessionId) {
|
|
3994
4292
|
return this.sendRequest("listTools", { sessionId });
|
|
3995
4293
|
}
|