@mcp-ts/sdk 2.4.5 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +6 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +6 -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 +34 -0
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +34 -0
- 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 +6 -0
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +6 -0
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-CkM3p0eE.d.mts → index-CTURVnom.d.mts} +5 -1
- package/dist/{index-CZk2gu2E.d.ts → index-sVcqrhf7.d.ts} +5 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +314 -82
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +310 -82
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-ChQrBZhF.d.mts → multi-session-client-CSPSHHla.d.ts} +14 -2
- package/dist/{multi-session-client-QOOPdEVp.d.ts → multi-session-client-DMZGVABI.d.mts} +14 -2
- package/dist/server/index.d.mts +13 -4
- package/dist/server/index.d.ts +13 -4
- package/dist/server/index.js +308 -82
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +304 -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-DQ-HrD7W.d.ts → tool-router-CS9l0w4a.d.ts} +1 -1
- package/dist/{tool-router-CGs3IDOJ.d.mts → tool-router-CVLBaCwH.d.mts} +1 -1
- package/dist/{types-Bf-7GOLW.d.mts → types-DK_NGWd4.d.mts} +37 -3
- package/dist/{types-Bf-7GOLW.d.ts → types-DK_NGWd4.d.ts} +37 -3
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +1 -0
- package/migrations/supabase/20260330195700_install_mcp_sessions.sql +1 -0
- package/package.json +1 -1
- 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 +17 -0
- package/src/client/react/use-mcp.ts +63 -0
- package/src/server/handlers/sse-handler.ts +116 -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 +48 -1
package/dist/server/index.mjs
CHANGED
|
@@ -158,6 +158,59 @@ var SOFTWARE_VERSION = "2.3.4";
|
|
|
158
158
|
var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
|
|
159
159
|
var MCP_CLIENT_VERSION = "2.0";
|
|
160
160
|
|
|
161
|
+
// src/server/storage/tool-policy.ts
|
|
162
|
+
function createToolId(serverId, toolName) {
|
|
163
|
+
return `${serverId}::${toolName}`;
|
|
164
|
+
}
|
|
165
|
+
function normalizeToolIds(input) {
|
|
166
|
+
if (!Array.isArray(input)) return [];
|
|
167
|
+
return Array.from(new Set(
|
|
168
|
+
input.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
|
|
169
|
+
));
|
|
170
|
+
}
|
|
171
|
+
function normalizeToolPolicy(input, now = Date.now()) {
|
|
172
|
+
if (!input || typeof input !== "object") {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
const mode = input.mode === "allowlist" || input.mode === "denylist" ? input.mode : "all";
|
|
176
|
+
const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : now;
|
|
177
|
+
if (mode === "all") {
|
|
178
|
+
return { mode: "all", toolIds: [], updatedAt };
|
|
179
|
+
}
|
|
180
|
+
return { mode, toolIds: normalizeToolIds(input.toolIds), updatedAt };
|
|
181
|
+
}
|
|
182
|
+
function normalizeToolPolicyForUpdate(input, now = Date.now()) {
|
|
183
|
+
return normalizeToolPolicy(input, now) ?? { mode: "all", toolIds: [], updatedAt: now };
|
|
184
|
+
}
|
|
185
|
+
function isToolAllowed(policy, toolName, serverId) {
|
|
186
|
+
if (!policy || policy.mode === "all") return true;
|
|
187
|
+
if (!serverId) return false;
|
|
188
|
+
const toolId = createToolId(serverId, toolName);
|
|
189
|
+
if (policy.mode === "allowlist") {
|
|
190
|
+
return policy.toolIds.includes(toolId);
|
|
191
|
+
}
|
|
192
|
+
return !policy.toolIds.includes(toolId);
|
|
193
|
+
}
|
|
194
|
+
function assertToolAllowed(policy, toolName, serverId) {
|
|
195
|
+
if (isToolAllowed(policy, toolName, serverId)) return;
|
|
196
|
+
throw new Error(`Tool "${toolName}" is not allowed for this MCP session`);
|
|
197
|
+
}
|
|
198
|
+
function filterToolsByPolicy(tools, policy, serverId) {
|
|
199
|
+
if (!policy || policy.mode === "all") return tools;
|
|
200
|
+
return tools.filter((tool) => isToolAllowed(policy, tool.name, serverId));
|
|
201
|
+
}
|
|
202
|
+
function validateToolPolicyAgainstTools(policy, tools, serverId) {
|
|
203
|
+
if (policy.mode === "all") return;
|
|
204
|
+
if (!serverId) {
|
|
205
|
+
throw new Error("Cannot validate MCP tool policy without a serverId");
|
|
206
|
+
}
|
|
207
|
+
const availableIds = new Set(tools.map((tool) => createToolId(serverId, tool.name)));
|
|
208
|
+
const unknownIds = policy.toolIds.filter((id) => !availableIds.has(id));
|
|
209
|
+
if (unknownIds.length > 0) {
|
|
210
|
+
throw new Error(`Unknown tool id(s) for this MCP session: ${unknownIds.join(", ")}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
161
214
|
// src/server/storage/session-lifecycle.ts
|
|
162
215
|
function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
|
|
163
216
|
return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
|
|
@@ -174,7 +227,8 @@ function normalizeNewSession(session, now = Date.now()) {
|
|
|
174
227
|
status,
|
|
175
228
|
createdAt,
|
|
176
229
|
updatedAt,
|
|
177
|
-
expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
|
|
230
|
+
expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt),
|
|
231
|
+
toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
|
|
178
232
|
};
|
|
179
233
|
}
|
|
180
234
|
function mergeSessionUpdate(current, data, now = Date.now()) {
|
|
@@ -188,7 +242,8 @@ function mergeSessionUpdate(current, data, now = Date.now()) {
|
|
|
188
242
|
return {
|
|
189
243
|
...updated,
|
|
190
244
|
status,
|
|
191
|
-
expiresAt: resolveSessionExpiresAt(status, updatedAt)
|
|
245
|
+
expiresAt: resolveSessionExpiresAt(status, updatedAt),
|
|
246
|
+
toolPolicy: normalizeToolPolicy(updated.toolPolicy, updatedAt)
|
|
192
247
|
};
|
|
193
248
|
}
|
|
194
249
|
function normalizeStoredSession(session) {
|
|
@@ -201,7 +256,8 @@ function normalizeStoredSession(session) {
|
|
|
201
256
|
status,
|
|
202
257
|
createdAt,
|
|
203
258
|
updatedAt,
|
|
204
|
-
expiresAt
|
|
259
|
+
expiresAt,
|
|
260
|
+
toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
|
|
205
261
|
};
|
|
206
262
|
}
|
|
207
263
|
function isSessionExpired(session, now = Date.now()) {
|
|
@@ -1059,7 +1115,8 @@ var SupabaseStorageBackend = class {
|
|
|
1059
1115
|
userId: row.user_id,
|
|
1060
1116
|
headers: decryptObject(row.headers),
|
|
1061
1117
|
authUrl: row.auth_url,
|
|
1062
|
-
status: row.status ?? "pending"
|
|
1118
|
+
status: row.status ?? "pending",
|
|
1119
|
+
toolPolicy: normalizeToolPolicy(row.tool_policy)
|
|
1063
1120
|
};
|
|
1064
1121
|
}
|
|
1065
1122
|
mapRowToCredentials(row, userId, sessionId) {
|
|
@@ -1083,7 +1140,7 @@ var SupabaseStorageBackend = class {
|
|
|
1083
1140
|
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1084
1141
|
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1085
1142
|
const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
|
|
1086
|
-
const
|
|
1143
|
+
const insertData = {
|
|
1087
1144
|
session_id: sessionId,
|
|
1088
1145
|
user_id: userId,
|
|
1089
1146
|
server_id: session.serverId,
|
|
@@ -1097,7 +1154,12 @@ var SupabaseStorageBackend = class {
|
|
|
1097
1154
|
auth_url: session.authUrl ?? null,
|
|
1098
1155
|
status,
|
|
1099
1156
|
expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1100
|
-
}
|
|
1157
|
+
};
|
|
1158
|
+
const toolPolicy = normalizeToolPolicy(session.toolPolicy);
|
|
1159
|
+
if (toolPolicy) {
|
|
1160
|
+
insertData.tool_policy = toolPolicy;
|
|
1161
|
+
}
|
|
1162
|
+
const { error } = await this.supabase.from("mcp_sessions").insert(insertData);
|
|
1101
1163
|
if (error) {
|
|
1102
1164
|
if (error.code === "23505") {
|
|
1103
1165
|
throw new Error(`Session ${sessionId} already exists`);
|
|
@@ -1122,6 +1184,7 @@ var SupabaseStorageBackend = class {
|
|
|
1122
1184
|
}
|
|
1123
1185
|
if ("headers" in data) updateData.headers = encryptObject(data.headers);
|
|
1124
1186
|
if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
|
|
1187
|
+
if ("toolPolicy" in data) updateData.tool_policy = normalizeToolPolicy(data.toolPolicy);
|
|
1125
1188
|
const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
|
|
1126
1189
|
let updatedRows = null;
|
|
1127
1190
|
if (shouldUpdateSession) {
|
|
@@ -1294,7 +1357,8 @@ var NeonStorageBackend = class {
|
|
|
1294
1357
|
userId: row.user_id,
|
|
1295
1358
|
headers: decryptObject(row.headers),
|
|
1296
1359
|
authUrl: row.auth_url ?? void 0,
|
|
1297
|
-
status: row.status ?? "pending"
|
|
1360
|
+
status: row.status ?? "pending",
|
|
1361
|
+
toolPolicy: normalizeToolPolicy(row.tool_policy)
|
|
1298
1362
|
};
|
|
1299
1363
|
}
|
|
1300
1364
|
mapRowToCredentials(row, userId, sessionId) {
|
|
@@ -1317,42 +1381,48 @@ var NeonStorageBackend = class {
|
|
|
1317
1381
|
const status = session.status ?? "pending";
|
|
1318
1382
|
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1319
1383
|
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1320
|
-
const
|
|
1384
|
+
const createdAtMs = new Date(createdAt).getTime();
|
|
1385
|
+
const expiresAt = resolveSessionExpiresAt(status, createdAtMs);
|
|
1386
|
+
const toolPolicy = normalizeToolPolicy(session.toolPolicy, createdAtMs);
|
|
1387
|
+
const columns = [
|
|
1388
|
+
"session_id",
|
|
1389
|
+
"user_id",
|
|
1390
|
+
"server_id",
|
|
1391
|
+
"server_name",
|
|
1392
|
+
"server_url",
|
|
1393
|
+
"transport_type",
|
|
1394
|
+
"callback_url",
|
|
1395
|
+
"created_at",
|
|
1396
|
+
"updated_at",
|
|
1397
|
+
"headers",
|
|
1398
|
+
"auth_url",
|
|
1399
|
+
"status",
|
|
1400
|
+
"expires_at"
|
|
1401
|
+
];
|
|
1402
|
+
const values = [
|
|
1403
|
+
sessionId,
|
|
1404
|
+
userId,
|
|
1405
|
+
session.serverId,
|
|
1406
|
+
session.serverName,
|
|
1407
|
+
session.serverUrl,
|
|
1408
|
+
session.transportType,
|
|
1409
|
+
session.callbackUrl,
|
|
1410
|
+
createdAt,
|
|
1411
|
+
updatedAt,
|
|
1412
|
+
encryptObject(session.headers),
|
|
1413
|
+
session.authUrl ?? null,
|
|
1414
|
+
status,
|
|
1415
|
+
expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1416
|
+
];
|
|
1417
|
+
if (toolPolicy) {
|
|
1418
|
+
columns.push("tool_policy");
|
|
1419
|
+
values.push(toolPolicy);
|
|
1420
|
+
}
|
|
1421
|
+
const placeholders = values.map((_, i) => `$${i + 1}`);
|
|
1321
1422
|
try {
|
|
1322
1423
|
await this.sql.query(
|
|
1323
|
-
`INSERT INTO ${this.tableName} (
|
|
1324
|
-
|
|
1325
|
-
user_id,
|
|
1326
|
-
server_id,
|
|
1327
|
-
server_name,
|
|
1328
|
-
server_url,
|
|
1329
|
-
transport_type,
|
|
1330
|
-
callback_url,
|
|
1331
|
-
created_at,
|
|
1332
|
-
updated_at,
|
|
1333
|
-
headers,
|
|
1334
|
-
auth_url,
|
|
1335
|
-
status,
|
|
1336
|
-
expires_at
|
|
1337
|
-
) VALUES (
|
|
1338
|
-
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
1339
|
-
$9, $10, $11, $12, $13
|
|
1340
|
-
)`,
|
|
1341
|
-
[
|
|
1342
|
-
sessionId,
|
|
1343
|
-
userId,
|
|
1344
|
-
session.serverId,
|
|
1345
|
-
session.serverName,
|
|
1346
|
-
session.serverUrl,
|
|
1347
|
-
session.transportType,
|
|
1348
|
-
session.callbackUrl,
|
|
1349
|
-
createdAt,
|
|
1350
|
-
updatedAt,
|
|
1351
|
-
encryptObject(session.headers),
|
|
1352
|
-
session.authUrl ?? null,
|
|
1353
|
-
status,
|
|
1354
|
-
expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1355
|
-
]
|
|
1424
|
+
`INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`,
|
|
1425
|
+
values
|
|
1356
1426
|
);
|
|
1357
1427
|
} catch (error) {
|
|
1358
1428
|
if (error.code === "23505") {
|
|
@@ -1369,36 +1439,36 @@ var NeonStorageBackend = class {
|
|
|
1369
1439
|
const updatedSession = { ...currentSession, ...data };
|
|
1370
1440
|
const status = updatedSession.status ?? "pending";
|
|
1371
1441
|
const expiresAt = resolveSessionExpiresAt(status);
|
|
1372
|
-
const shouldUpdateSession = "serverId" in data || "serverName" in data || "serverUrl" in data || "transportType" in data || "callbackUrl" in data || "status" in data || "headers" in data || "authUrl" in data;
|
|
1442
|
+
const shouldUpdateSession = "serverId" in data || "serverName" in data || "serverUrl" in data || "transportType" in data || "callbackUrl" in data || "status" in data || "headers" in data || "authUrl" in data || "toolPolicy" in data;
|
|
1373
1443
|
if (shouldUpdateSession) {
|
|
1444
|
+
const setClauses = [];
|
|
1445
|
+
const values = [];
|
|
1446
|
+
let paramIndex = 1;
|
|
1447
|
+
const addSet = (column, value) => {
|
|
1448
|
+
setClauses.push(`${column} = $${paramIndex++}`);
|
|
1449
|
+
values.push(value);
|
|
1450
|
+
};
|
|
1451
|
+
addSet("server_id", updatedSession.serverId);
|
|
1452
|
+
addSet("server_name", updatedSession.serverName);
|
|
1453
|
+
addSet("server_url", updatedSession.serverUrl);
|
|
1454
|
+
addSet("transport_type", updatedSession.transportType);
|
|
1455
|
+
addSet("callback_url", updatedSession.callbackUrl);
|
|
1456
|
+
addSet("status", status);
|
|
1457
|
+
addSet("headers", encryptObject(updatedSession.headers));
|
|
1458
|
+
addSet("auth_url", updatedSession.authUrl ?? null);
|
|
1459
|
+
addSet("expires_at", expiresAt === null ? null : new Date(expiresAt).toISOString());
|
|
1460
|
+
if ("toolPolicy" in data) {
|
|
1461
|
+
const policyUpdatedAt = updatedSession.updatedAt ?? Date.now();
|
|
1462
|
+
const toolPolicy = normalizeToolPolicy(updatedSession.toolPolicy, policyUpdatedAt) ?? { mode: "all", toolIds: [], updatedAt: policyUpdatedAt };
|
|
1463
|
+
addSet("tool_policy", toolPolicy);
|
|
1464
|
+
}
|
|
1465
|
+
setClauses.push("updated_at = now()");
|
|
1374
1466
|
const updatedRows = await this.sql.query(
|
|
1375
1467
|
`UPDATE ${this.tableName}
|
|
1376
|
-
SET
|
|
1377
|
-
|
|
1378
|
-
server_name = $2,
|
|
1379
|
-
server_url = $3,
|
|
1380
|
-
transport_type = $4,
|
|
1381
|
-
callback_url = $5,
|
|
1382
|
-
status = $6,
|
|
1383
|
-
headers = $7,
|
|
1384
|
-
auth_url = $8,
|
|
1385
|
-
expires_at = $9,
|
|
1386
|
-
updated_at = now()
|
|
1387
|
-
WHERE user_id = $10 AND session_id = $11
|
|
1468
|
+
SET ${setClauses.join(", ")}
|
|
1469
|
+
WHERE user_id = $${paramIndex++} AND session_id = $${paramIndex++}
|
|
1388
1470
|
RETURNING id`,
|
|
1389
|
-
[
|
|
1390
|
-
updatedSession.serverId,
|
|
1391
|
-
updatedSession.serverName,
|
|
1392
|
-
updatedSession.serverUrl,
|
|
1393
|
-
updatedSession.transportType,
|
|
1394
|
-
updatedSession.callbackUrl,
|
|
1395
|
-
status,
|
|
1396
|
-
encryptObject(updatedSession.headers),
|
|
1397
|
-
updatedSession.authUrl ?? null,
|
|
1398
|
-
expiresAt === null ? null : new Date(expiresAt).toISOString(),
|
|
1399
|
-
userId,
|
|
1400
|
-
sessionId
|
|
1401
|
-
]
|
|
1471
|
+
[...values, userId, sessionId]
|
|
1402
1472
|
);
|
|
1403
1473
|
if (updatedRows.length === 0) {
|
|
1404
1474
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
@@ -2612,6 +2682,19 @@ var MCPClient = class {
|
|
|
2612
2682
|
throw lastError;
|
|
2613
2683
|
}
|
|
2614
2684
|
}
|
|
2685
|
+
/**
|
|
2686
|
+
* Lists all available tools from the connected MCP server without emitting discovery events.
|
|
2687
|
+
* Gateways use this to apply policy before publishing tools to agents or UI state.
|
|
2688
|
+
*/
|
|
2689
|
+
async fetchTools() {
|
|
2690
|
+
const request = {
|
|
2691
|
+
method: "tools/list",
|
|
2692
|
+
params: {}
|
|
2693
|
+
};
|
|
2694
|
+
return await this.withRetry(
|
|
2695
|
+
() => this.client.request(request, ListToolsResultSchema)
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2615
2698
|
/**
|
|
2616
2699
|
* Lists all available tools from the connected MCP server
|
|
2617
2700
|
* @returns List of tools with their schemas and descriptions
|
|
@@ -2620,13 +2703,7 @@ var MCPClient = class {
|
|
|
2620
2703
|
async listTools() {
|
|
2621
2704
|
this.emitStateChange("DISCOVERING");
|
|
2622
2705
|
try {
|
|
2623
|
-
const
|
|
2624
|
-
method: "tools/list",
|
|
2625
|
-
params: {}
|
|
2626
|
-
};
|
|
2627
|
-
const result = await this.withRetry(
|
|
2628
|
-
() => this.client.request(request, ListToolsResultSchema)
|
|
2629
|
-
);
|
|
2706
|
+
const result = await this.fetchTools();
|
|
2630
2707
|
if (this.serverId) {
|
|
2631
2708
|
this._onConnectionEvent.fire({
|
|
2632
2709
|
type: "tools_discovered",
|
|
@@ -2953,6 +3030,66 @@ var MCPClient = class {
|
|
|
2953
3030
|
}
|
|
2954
3031
|
};
|
|
2955
3032
|
|
|
3033
|
+
// src/server/mcp/tool-policy-gateway.ts
|
|
3034
|
+
var ToolPolicyGateway = class {
|
|
3035
|
+
constructor(userId, sessionId, client) {
|
|
3036
|
+
this.userId = userId;
|
|
3037
|
+
this.sessionId = sessionId;
|
|
3038
|
+
this.client = client;
|
|
3039
|
+
}
|
|
3040
|
+
isConnected() {
|
|
3041
|
+
return this.client.isConnected();
|
|
3042
|
+
}
|
|
3043
|
+
getServerId() {
|
|
3044
|
+
return this.client.getServerId?.();
|
|
3045
|
+
}
|
|
3046
|
+
getServerName() {
|
|
3047
|
+
return this.client.getServerName?.();
|
|
3048
|
+
}
|
|
3049
|
+
getServerUrl() {
|
|
3050
|
+
return this.client.getServerUrl?.();
|
|
3051
|
+
}
|
|
3052
|
+
getSessionId() {
|
|
3053
|
+
return this.client.getSessionId?.() ?? this.sessionId;
|
|
3054
|
+
}
|
|
3055
|
+
async listTools() {
|
|
3056
|
+
const session = await this.getSession();
|
|
3057
|
+
const result = await this.client.fetchTools();
|
|
3058
|
+
const tools = this.filterTools(session, result.tools);
|
|
3059
|
+
return {
|
|
3060
|
+
...result,
|
|
3061
|
+
tools
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
async listAllTools() {
|
|
3065
|
+
return await this.client.fetchTools();
|
|
3066
|
+
}
|
|
3067
|
+
async callTool(name, args) {
|
|
3068
|
+
const session = await this.getSession();
|
|
3069
|
+
this.assertAllowed(session, name);
|
|
3070
|
+
return await this.client.callTool(name, args);
|
|
3071
|
+
}
|
|
3072
|
+
filterTools(session, tools) {
|
|
3073
|
+
return filterToolsByPolicy(tools, session.toolPolicy, this.getPolicyServerId(session));
|
|
3074
|
+
}
|
|
3075
|
+
assertAllowed(session, toolName) {
|
|
3076
|
+
assertToolAllowed(session.toolPolicy, toolName, this.getPolicyServerId(session));
|
|
3077
|
+
}
|
|
3078
|
+
async getSession() {
|
|
3079
|
+
const session = await sessions.get(this.userId, this.sessionId);
|
|
3080
|
+
if (!session) {
|
|
3081
|
+
throw new Error("Session not found");
|
|
3082
|
+
}
|
|
3083
|
+
return session;
|
|
3084
|
+
}
|
|
3085
|
+
getPolicyServerId(session) {
|
|
3086
|
+
return this.client.getServerId?.() ?? session.serverId;
|
|
3087
|
+
}
|
|
3088
|
+
};
|
|
3089
|
+
function createToolPolicyGateway(userId, sessionId, client) {
|
|
3090
|
+
return new ToolPolicyGateway(userId, sessionId, client);
|
|
3091
|
+
}
|
|
3092
|
+
|
|
2956
3093
|
// src/server/mcp/multi-session-client.ts
|
|
2957
3094
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
2958
3095
|
var DEFAULT_MAX_RETRIES = 2;
|
|
@@ -3018,7 +3155,9 @@ var MultiSessionClient = class {
|
|
|
3018
3155
|
* or to route a tool call to the right client by `serverId`.
|
|
3019
3156
|
*/
|
|
3020
3157
|
getClients() {
|
|
3021
|
-
return this.clients
|
|
3158
|
+
return this.clients.map(
|
|
3159
|
+
(client) => createToolPolicyGateway(this.userId, client.getSessionId(), client)
|
|
3160
|
+
);
|
|
3022
3161
|
}
|
|
3023
3162
|
/**
|
|
3024
3163
|
* Removes and disconnects a single session by ID.
|
|
@@ -3253,6 +3392,12 @@ var SSEConnectionManager = class {
|
|
|
3253
3392
|
case "listTools":
|
|
3254
3393
|
result = await this.listTools(request.params);
|
|
3255
3394
|
break;
|
|
3395
|
+
case "setToolPolicy":
|
|
3396
|
+
result = await this.setToolPolicy(request.params);
|
|
3397
|
+
break;
|
|
3398
|
+
case "getToolPolicy":
|
|
3399
|
+
result = await this.getToolPolicy(request.params);
|
|
3400
|
+
break;
|
|
3256
3401
|
case "callTool":
|
|
3257
3402
|
result = await this.callTool(request.params);
|
|
3258
3403
|
break;
|
|
@@ -3309,7 +3454,8 @@ var SSEConnectionManager = class {
|
|
|
3309
3454
|
transport: s.transportType,
|
|
3310
3455
|
createdAt: s.createdAt,
|
|
3311
3456
|
updatedAt: s.updatedAt ?? s.createdAt,
|
|
3312
|
-
status: s.status ?? "pending"
|
|
3457
|
+
status: s.status ?? "pending",
|
|
3458
|
+
toolPolicy: s.toolPolicy
|
|
3313
3459
|
}))
|
|
3314
3460
|
};
|
|
3315
3461
|
}
|
|
@@ -3357,7 +3503,7 @@ var SSEConnectionManager = class {
|
|
|
3357
3503
|
this.sendEvent(event);
|
|
3358
3504
|
});
|
|
3359
3505
|
await client.connect();
|
|
3360
|
-
await
|
|
3506
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
3361
3507
|
return {
|
|
3362
3508
|
sessionId,
|
|
3363
3509
|
success: true
|
|
@@ -3428,22 +3574,98 @@ var SSEConnectionManager = class {
|
|
|
3428
3574
|
this.clients.set(sessionId, client);
|
|
3429
3575
|
return client;
|
|
3430
3576
|
}
|
|
3577
|
+
async listPolicyFilteredTools(sessionId) {
|
|
3578
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3579
|
+
if (!session) {
|
|
3580
|
+
throw new Error("Session not found");
|
|
3581
|
+
}
|
|
3582
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3583
|
+
const gateway = createToolPolicyGateway(this.userId, sessionId, client);
|
|
3584
|
+
const result = await gateway.listTools();
|
|
3585
|
+
this.emitConnectionEvent({
|
|
3586
|
+
type: "tools_discovered",
|
|
3587
|
+
sessionId,
|
|
3588
|
+
serverId: session.serverId ?? "unknown",
|
|
3589
|
+
toolCount: result.tools.length,
|
|
3590
|
+
tools: result.tools,
|
|
3591
|
+
timestamp: Date.now()
|
|
3592
|
+
});
|
|
3593
|
+
return { session, result };
|
|
3594
|
+
}
|
|
3431
3595
|
/**
|
|
3432
3596
|
* List tools from a session
|
|
3433
3597
|
*/
|
|
3434
3598
|
async listTools(params) {
|
|
3435
3599
|
const { sessionId } = params;
|
|
3436
|
-
const
|
|
3437
|
-
const result = await client.listTools();
|
|
3600
|
+
const { result } = await this.listPolicyFilteredTools(sessionId);
|
|
3438
3601
|
return { tools: result.tools };
|
|
3439
3602
|
}
|
|
3603
|
+
/**
|
|
3604
|
+
* Get all raw tools with effective access state for management UI.
|
|
3605
|
+
*/
|
|
3606
|
+
async getToolPolicy(params) {
|
|
3607
|
+
const { sessionId } = params;
|
|
3608
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3609
|
+
if (!session) {
|
|
3610
|
+
throw new Error("Session not found");
|
|
3611
|
+
}
|
|
3612
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3613
|
+
const allTools = await client.fetchTools();
|
|
3614
|
+
const toolPolicy = session.toolPolicy ?? {
|
|
3615
|
+
mode: "all",
|
|
3616
|
+
toolIds: [],
|
|
3617
|
+
updatedAt: session.updatedAt ?? session.createdAt
|
|
3618
|
+
};
|
|
3619
|
+
const serverId = session.serverId ?? "unknown";
|
|
3620
|
+
const tools = allTools.tools.map((tool) => ({
|
|
3621
|
+
...tool,
|
|
3622
|
+
toolId: createToolId(serverId, tool.name),
|
|
3623
|
+
allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
|
|
3624
|
+
}));
|
|
3625
|
+
return {
|
|
3626
|
+
toolPolicy,
|
|
3627
|
+
tools,
|
|
3628
|
+
toolCount: tools.length,
|
|
3629
|
+
allowedToolCount: tools.filter((tool) => tool.allowed).length
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
/**
|
|
3633
|
+
* Update per-session tool access policy.
|
|
3634
|
+
*/
|
|
3635
|
+
async setToolPolicy(params) {
|
|
3636
|
+
const { sessionId } = params;
|
|
3637
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3638
|
+
if (!session) {
|
|
3639
|
+
throw new Error("Session not found");
|
|
3640
|
+
}
|
|
3641
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3642
|
+
const allTools = await client.fetchTools();
|
|
3643
|
+
const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
|
|
3644
|
+
validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
|
|
3645
|
+
await sessions.update(this.userId, sessionId, { toolPolicy });
|
|
3646
|
+
const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
|
|
3647
|
+
this.emitConnectionEvent({
|
|
3648
|
+
type: "tools_discovered",
|
|
3649
|
+
sessionId,
|
|
3650
|
+
serverId: session.serverId ?? "unknown",
|
|
3651
|
+
toolCount: filteredTools.length,
|
|
3652
|
+
tools: filteredTools,
|
|
3653
|
+
timestamp: Date.now()
|
|
3654
|
+
});
|
|
3655
|
+
return {
|
|
3656
|
+
success: true,
|
|
3657
|
+
toolPolicy,
|
|
3658
|
+
tools: filteredTools,
|
|
3659
|
+
toolCount: filteredTools.length
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3440
3662
|
/**
|
|
3441
3663
|
* Call a tool on the MCP server
|
|
3442
3664
|
*/
|
|
3443
3665
|
async callTool(params) {
|
|
3444
3666
|
const { sessionId, toolName, toolArgs } = params;
|
|
3445
3667
|
const client = await this.getOrCreateClient(sessionId);
|
|
3446
|
-
const result = await client.callTool(toolName, toolArgs);
|
|
3668
|
+
const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
|
|
3447
3669
|
const meta = result._meta || {};
|
|
3448
3670
|
return {
|
|
3449
3671
|
...result,
|
|
@@ -3493,7 +3715,7 @@ var SSEConnectionManager = class {
|
|
|
3493
3715
|
client.onObservabilityEvent((event) => this.sendEvent(event));
|
|
3494
3716
|
await client.connect();
|
|
3495
3717
|
this.clients.set(sessionId, client);
|
|
3496
|
-
const tools = await
|
|
3718
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3497
3719
|
return { success: true, toolCount: tools.tools.length };
|
|
3498
3720
|
} catch (error) {
|
|
3499
3721
|
this.emitConnectionEvent({
|
|
@@ -3541,7 +3763,7 @@ var SSEConnectionManager = class {
|
|
|
3541
3763
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
3542
3764
|
await client.finishAuth(code, oauthState);
|
|
3543
3765
|
this.clients.set(sessionId, client);
|
|
3544
|
-
const tools = await
|
|
3766
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3545
3767
|
return { success: true, toolCount: tools.tools.length };
|
|
3546
3768
|
} catch (error) {
|
|
3547
3769
|
this.emitConnectionEvent({
|