@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/index.mjs
CHANGED
|
@@ -162,6 +162,59 @@ var SOFTWARE_VERSION = "2.3.4";
|
|
|
162
162
|
var MCP_CLIENT_NAME = "mcp-ts-oauth-client";
|
|
163
163
|
var MCP_CLIENT_VERSION = "2.0";
|
|
164
164
|
|
|
165
|
+
// src/server/storage/tool-policy.ts
|
|
166
|
+
function createToolId(serverId, toolName) {
|
|
167
|
+
return `${serverId}::${toolName}`;
|
|
168
|
+
}
|
|
169
|
+
function normalizeToolIds(input) {
|
|
170
|
+
if (!Array.isArray(input)) return [];
|
|
171
|
+
return Array.from(new Set(
|
|
172
|
+
input.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
|
|
173
|
+
));
|
|
174
|
+
}
|
|
175
|
+
function normalizeToolPolicy(input, now = Date.now()) {
|
|
176
|
+
if (!input || typeof input !== "object") {
|
|
177
|
+
return void 0;
|
|
178
|
+
}
|
|
179
|
+
const mode = input.mode === "allowlist" || input.mode === "denylist" ? input.mode : "all";
|
|
180
|
+
const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : now;
|
|
181
|
+
if (mode === "all") {
|
|
182
|
+
return { mode: "all", toolIds: [], updatedAt };
|
|
183
|
+
}
|
|
184
|
+
return { mode, toolIds: normalizeToolIds(input.toolIds), updatedAt };
|
|
185
|
+
}
|
|
186
|
+
function normalizeToolPolicyForUpdate(input, now = Date.now()) {
|
|
187
|
+
return normalizeToolPolicy(input, now) ?? { mode: "all", toolIds: [], updatedAt: now };
|
|
188
|
+
}
|
|
189
|
+
function isToolAllowed(policy, toolName, serverId) {
|
|
190
|
+
if (!policy || policy.mode === "all") return true;
|
|
191
|
+
if (!serverId) return false;
|
|
192
|
+
const toolId = createToolId(serverId, toolName);
|
|
193
|
+
if (policy.mode === "allowlist") {
|
|
194
|
+
return policy.toolIds.includes(toolId);
|
|
195
|
+
}
|
|
196
|
+
return !policy.toolIds.includes(toolId);
|
|
197
|
+
}
|
|
198
|
+
function assertToolAllowed(policy, toolName, serverId) {
|
|
199
|
+
if (isToolAllowed(policy, toolName, serverId)) return;
|
|
200
|
+
throw new Error(`Tool "${toolName}" is not allowed for this MCP session`);
|
|
201
|
+
}
|
|
202
|
+
function filterToolsByPolicy(tools, policy, serverId) {
|
|
203
|
+
if (!policy || policy.mode === "all") return tools;
|
|
204
|
+
return tools.filter((tool) => isToolAllowed(policy, tool.name, serverId));
|
|
205
|
+
}
|
|
206
|
+
function validateToolPolicyAgainstTools(policy, tools, serverId) {
|
|
207
|
+
if (policy.mode === "all") return;
|
|
208
|
+
if (!serverId) {
|
|
209
|
+
throw new Error("Cannot validate MCP tool policy without a serverId");
|
|
210
|
+
}
|
|
211
|
+
const availableIds = new Set(tools.map((tool) => createToolId(serverId, tool.name)));
|
|
212
|
+
const unknownIds = policy.toolIds.filter((id) => !availableIds.has(id));
|
|
213
|
+
if (unknownIds.length > 0) {
|
|
214
|
+
throw new Error(`Unknown tool id(s) for this MCP session: ${unknownIds.join(", ")}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
165
218
|
// src/server/storage/session-lifecycle.ts
|
|
166
219
|
function resolveSessionExpiresAt(status = "pending", referenceTime = Date.now()) {
|
|
167
220
|
return status === "active" ? null : referenceTime + STATE_EXPIRATION_MS;
|
|
@@ -178,7 +231,8 @@ function normalizeNewSession(session, now = Date.now()) {
|
|
|
178
231
|
status,
|
|
179
232
|
createdAt,
|
|
180
233
|
updatedAt,
|
|
181
|
-
expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt)
|
|
234
|
+
expiresAt: resolveSessionExpiresAt(status, status === "active" ? updatedAt : createdAt),
|
|
235
|
+
toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
|
|
182
236
|
};
|
|
183
237
|
}
|
|
184
238
|
function mergeSessionUpdate(current, data, now = Date.now()) {
|
|
@@ -192,7 +246,8 @@ function mergeSessionUpdate(current, data, now = Date.now()) {
|
|
|
192
246
|
return {
|
|
193
247
|
...updated,
|
|
194
248
|
status,
|
|
195
|
-
expiresAt: resolveSessionExpiresAt(status, updatedAt)
|
|
249
|
+
expiresAt: resolveSessionExpiresAt(status, updatedAt),
|
|
250
|
+
toolPolicy: normalizeToolPolicy(updated.toolPolicy, updatedAt)
|
|
196
251
|
};
|
|
197
252
|
}
|
|
198
253
|
function normalizeStoredSession(session) {
|
|
@@ -205,7 +260,8 @@ function normalizeStoredSession(session) {
|
|
|
205
260
|
status,
|
|
206
261
|
createdAt,
|
|
207
262
|
updatedAt,
|
|
208
|
-
expiresAt
|
|
263
|
+
expiresAt,
|
|
264
|
+
toolPolicy: normalizeToolPolicy(session.toolPolicy, updatedAt)
|
|
209
265
|
};
|
|
210
266
|
}
|
|
211
267
|
function isSessionExpired(session, now = Date.now()) {
|
|
@@ -1063,7 +1119,8 @@ var SupabaseStorageBackend = class {
|
|
|
1063
1119
|
userId: row.user_id,
|
|
1064
1120
|
headers: decryptObject(row.headers),
|
|
1065
1121
|
authUrl: row.auth_url,
|
|
1066
|
-
status: row.status ?? "pending"
|
|
1122
|
+
status: row.status ?? "pending",
|
|
1123
|
+
toolPolicy: normalizeToolPolicy(row.tool_policy)
|
|
1067
1124
|
};
|
|
1068
1125
|
}
|
|
1069
1126
|
mapRowToCredentials(row, userId, sessionId) {
|
|
@@ -1087,7 +1144,7 @@ var SupabaseStorageBackend = class {
|
|
|
1087
1144
|
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1088
1145
|
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1089
1146
|
const expiresAt = resolveSessionExpiresAt(status, new Date(createdAt).getTime());
|
|
1090
|
-
const
|
|
1147
|
+
const insertData = {
|
|
1091
1148
|
session_id: sessionId,
|
|
1092
1149
|
user_id: userId,
|
|
1093
1150
|
server_id: session.serverId,
|
|
@@ -1101,7 +1158,12 @@ var SupabaseStorageBackend = class {
|
|
|
1101
1158
|
auth_url: session.authUrl ?? null,
|
|
1102
1159
|
status,
|
|
1103
1160
|
expires_at: expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1104
|
-
}
|
|
1161
|
+
};
|
|
1162
|
+
const toolPolicy = normalizeToolPolicy(session.toolPolicy);
|
|
1163
|
+
if (toolPolicy) {
|
|
1164
|
+
insertData.tool_policy = toolPolicy;
|
|
1165
|
+
}
|
|
1166
|
+
const { error } = await this.supabase.from("mcp_sessions").insert(insertData);
|
|
1105
1167
|
if (error) {
|
|
1106
1168
|
if (error.code === "23505") {
|
|
1107
1169
|
throw new Error(`Session ${sessionId} already exists`);
|
|
@@ -1126,6 +1188,7 @@ var SupabaseStorageBackend = class {
|
|
|
1126
1188
|
}
|
|
1127
1189
|
if ("headers" in data) updateData.headers = encryptObject(data.headers);
|
|
1128
1190
|
if ("authUrl" in data) updateData.auth_url = data.authUrl ?? null;
|
|
1191
|
+
if ("toolPolicy" in data) updateData.tool_policy = normalizeToolPolicy(data.toolPolicy);
|
|
1129
1192
|
const shouldUpdateSession = Object.keys(updateData).some((key) => key !== "updated_at");
|
|
1130
1193
|
let updatedRows = null;
|
|
1131
1194
|
if (shouldUpdateSession) {
|
|
@@ -1298,7 +1361,8 @@ var NeonStorageBackend = class {
|
|
|
1298
1361
|
userId: row.user_id,
|
|
1299
1362
|
headers: decryptObject(row.headers),
|
|
1300
1363
|
authUrl: row.auth_url ?? void 0,
|
|
1301
|
-
status: row.status ?? "pending"
|
|
1364
|
+
status: row.status ?? "pending",
|
|
1365
|
+
toolPolicy: normalizeToolPolicy(row.tool_policy)
|
|
1302
1366
|
};
|
|
1303
1367
|
}
|
|
1304
1368
|
mapRowToCredentials(row, userId, sessionId) {
|
|
@@ -1321,42 +1385,48 @@ var NeonStorageBackend = class {
|
|
|
1321
1385
|
const status = session.status ?? "pending";
|
|
1322
1386
|
const createdAt = new Date(session.createdAt || Date.now()).toISOString();
|
|
1323
1387
|
const updatedAt = new Date(session.updatedAt ?? session.createdAt ?? Date.now()).toISOString();
|
|
1324
|
-
const
|
|
1388
|
+
const createdAtMs = new Date(createdAt).getTime();
|
|
1389
|
+
const expiresAt = resolveSessionExpiresAt(status, createdAtMs);
|
|
1390
|
+
const toolPolicy = normalizeToolPolicy(session.toolPolicy, createdAtMs);
|
|
1391
|
+
const columns = [
|
|
1392
|
+
"session_id",
|
|
1393
|
+
"user_id",
|
|
1394
|
+
"server_id",
|
|
1395
|
+
"server_name",
|
|
1396
|
+
"server_url",
|
|
1397
|
+
"transport_type",
|
|
1398
|
+
"callback_url",
|
|
1399
|
+
"created_at",
|
|
1400
|
+
"updated_at",
|
|
1401
|
+
"headers",
|
|
1402
|
+
"auth_url",
|
|
1403
|
+
"status",
|
|
1404
|
+
"expires_at"
|
|
1405
|
+
];
|
|
1406
|
+
const values = [
|
|
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
|
+
];
|
|
1421
|
+
if (toolPolicy) {
|
|
1422
|
+
columns.push("tool_policy");
|
|
1423
|
+
values.push(toolPolicy);
|
|
1424
|
+
}
|
|
1425
|
+
const placeholders = values.map((_, i) => `$${i + 1}`);
|
|
1325
1426
|
try {
|
|
1326
1427
|
await this.sql.query(
|
|
1327
|
-
`INSERT INTO ${this.tableName} (
|
|
1328
|
-
|
|
1329
|
-
user_id,
|
|
1330
|
-
server_id,
|
|
1331
|
-
server_name,
|
|
1332
|
-
server_url,
|
|
1333
|
-
transport_type,
|
|
1334
|
-
callback_url,
|
|
1335
|
-
created_at,
|
|
1336
|
-
updated_at,
|
|
1337
|
-
headers,
|
|
1338
|
-
auth_url,
|
|
1339
|
-
status,
|
|
1340
|
-
expires_at
|
|
1341
|
-
) VALUES (
|
|
1342
|
-
$1, $2, $3, $4, $5, $6, $7, $8,
|
|
1343
|
-
$9, $10, $11, $12, $13
|
|
1344
|
-
)`,
|
|
1345
|
-
[
|
|
1346
|
-
sessionId,
|
|
1347
|
-
userId,
|
|
1348
|
-
session.serverId,
|
|
1349
|
-
session.serverName,
|
|
1350
|
-
session.serverUrl,
|
|
1351
|
-
session.transportType,
|
|
1352
|
-
session.callbackUrl,
|
|
1353
|
-
createdAt,
|
|
1354
|
-
updatedAt,
|
|
1355
|
-
encryptObject(session.headers),
|
|
1356
|
-
session.authUrl ?? null,
|
|
1357
|
-
status,
|
|
1358
|
-
expiresAt === null ? null : new Date(expiresAt).toISOString()
|
|
1359
|
-
]
|
|
1428
|
+
`INSERT INTO ${this.tableName} (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`,
|
|
1429
|
+
values
|
|
1360
1430
|
);
|
|
1361
1431
|
} catch (error) {
|
|
1362
1432
|
if (error.code === "23505") {
|
|
@@ -1373,36 +1443,36 @@ var NeonStorageBackend = class {
|
|
|
1373
1443
|
const updatedSession = { ...currentSession, ...data };
|
|
1374
1444
|
const status = updatedSession.status ?? "pending";
|
|
1375
1445
|
const expiresAt = resolveSessionExpiresAt(status);
|
|
1376
|
-
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;
|
|
1446
|
+
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;
|
|
1377
1447
|
if (shouldUpdateSession) {
|
|
1448
|
+
const setClauses = [];
|
|
1449
|
+
const values = [];
|
|
1450
|
+
let paramIndex = 1;
|
|
1451
|
+
const addSet = (column, value) => {
|
|
1452
|
+
setClauses.push(`${column} = $${paramIndex++}`);
|
|
1453
|
+
values.push(value);
|
|
1454
|
+
};
|
|
1455
|
+
addSet("server_id", updatedSession.serverId);
|
|
1456
|
+
addSet("server_name", updatedSession.serverName);
|
|
1457
|
+
addSet("server_url", updatedSession.serverUrl);
|
|
1458
|
+
addSet("transport_type", updatedSession.transportType);
|
|
1459
|
+
addSet("callback_url", updatedSession.callbackUrl);
|
|
1460
|
+
addSet("status", status);
|
|
1461
|
+
addSet("headers", encryptObject(updatedSession.headers));
|
|
1462
|
+
addSet("auth_url", updatedSession.authUrl ?? null);
|
|
1463
|
+
addSet("expires_at", expiresAt === null ? null : new Date(expiresAt).toISOString());
|
|
1464
|
+
if ("toolPolicy" in data) {
|
|
1465
|
+
const policyUpdatedAt = updatedSession.updatedAt ?? Date.now();
|
|
1466
|
+
const toolPolicy = normalizeToolPolicy(updatedSession.toolPolicy, policyUpdatedAt) ?? { mode: "all", toolIds: [], updatedAt: policyUpdatedAt };
|
|
1467
|
+
addSet("tool_policy", toolPolicy);
|
|
1468
|
+
}
|
|
1469
|
+
setClauses.push("updated_at = now()");
|
|
1378
1470
|
const updatedRows = await this.sql.query(
|
|
1379
1471
|
`UPDATE ${this.tableName}
|
|
1380
|
-
SET
|
|
1381
|
-
|
|
1382
|
-
server_name = $2,
|
|
1383
|
-
server_url = $3,
|
|
1384
|
-
transport_type = $4,
|
|
1385
|
-
callback_url = $5,
|
|
1386
|
-
status = $6,
|
|
1387
|
-
headers = $7,
|
|
1388
|
-
auth_url = $8,
|
|
1389
|
-
expires_at = $9,
|
|
1390
|
-
updated_at = now()
|
|
1391
|
-
WHERE user_id = $10 AND session_id = $11
|
|
1472
|
+
SET ${setClauses.join(", ")}
|
|
1473
|
+
WHERE user_id = $${paramIndex++} AND session_id = $${paramIndex++}
|
|
1392
1474
|
RETURNING id`,
|
|
1393
|
-
[
|
|
1394
|
-
updatedSession.serverId,
|
|
1395
|
-
updatedSession.serverName,
|
|
1396
|
-
updatedSession.serverUrl,
|
|
1397
|
-
updatedSession.transportType,
|
|
1398
|
-
updatedSession.callbackUrl,
|
|
1399
|
-
status,
|
|
1400
|
-
encryptObject(updatedSession.headers),
|
|
1401
|
-
updatedSession.authUrl ?? null,
|
|
1402
|
-
expiresAt === null ? null : new Date(expiresAt).toISOString(),
|
|
1403
|
-
userId,
|
|
1404
|
-
sessionId
|
|
1405
|
-
]
|
|
1475
|
+
[...values, userId, sessionId]
|
|
1406
1476
|
);
|
|
1407
1477
|
if (updatedRows.length === 0) {
|
|
1408
1478
|
throw new Error(`Session ${sessionId} not found for userId ${userId}`);
|
|
@@ -2684,6 +2754,19 @@ var MCPClient = class {
|
|
|
2684
2754
|
throw lastError;
|
|
2685
2755
|
}
|
|
2686
2756
|
}
|
|
2757
|
+
/**
|
|
2758
|
+
* Lists all available tools from the connected MCP server without emitting discovery events.
|
|
2759
|
+
* Gateways use this to apply policy before publishing tools to agents or UI state.
|
|
2760
|
+
*/
|
|
2761
|
+
async fetchTools() {
|
|
2762
|
+
const request = {
|
|
2763
|
+
method: "tools/list",
|
|
2764
|
+
params: {}
|
|
2765
|
+
};
|
|
2766
|
+
return await this.withRetry(
|
|
2767
|
+
() => this.client.request(request, ListToolsResultSchema)
|
|
2768
|
+
);
|
|
2769
|
+
}
|
|
2687
2770
|
/**
|
|
2688
2771
|
* Lists all available tools from the connected MCP server
|
|
2689
2772
|
* @returns List of tools with their schemas and descriptions
|
|
@@ -2692,13 +2775,7 @@ var MCPClient = class {
|
|
|
2692
2775
|
async listTools() {
|
|
2693
2776
|
this.emitStateChange("DISCOVERING");
|
|
2694
2777
|
try {
|
|
2695
|
-
const
|
|
2696
|
-
method: "tools/list",
|
|
2697
|
-
params: {}
|
|
2698
|
-
};
|
|
2699
|
-
const result = await this.withRetry(
|
|
2700
|
-
() => this.client.request(request, ListToolsResultSchema)
|
|
2701
|
-
);
|
|
2778
|
+
const result = await this.fetchTools();
|
|
2702
2779
|
if (this.serverId) {
|
|
2703
2780
|
this._onConnectionEvent.fire({
|
|
2704
2781
|
type: "tools_discovered",
|
|
@@ -3025,6 +3102,66 @@ var MCPClient = class {
|
|
|
3025
3102
|
}
|
|
3026
3103
|
};
|
|
3027
3104
|
|
|
3105
|
+
// src/server/mcp/tool-policy-gateway.ts
|
|
3106
|
+
var ToolPolicyGateway = class {
|
|
3107
|
+
constructor(userId, sessionId, client) {
|
|
3108
|
+
this.userId = userId;
|
|
3109
|
+
this.sessionId = sessionId;
|
|
3110
|
+
this.client = client;
|
|
3111
|
+
}
|
|
3112
|
+
isConnected() {
|
|
3113
|
+
return this.client.isConnected();
|
|
3114
|
+
}
|
|
3115
|
+
getServerId() {
|
|
3116
|
+
return this.client.getServerId?.();
|
|
3117
|
+
}
|
|
3118
|
+
getServerName() {
|
|
3119
|
+
return this.client.getServerName?.();
|
|
3120
|
+
}
|
|
3121
|
+
getServerUrl() {
|
|
3122
|
+
return this.client.getServerUrl?.();
|
|
3123
|
+
}
|
|
3124
|
+
getSessionId() {
|
|
3125
|
+
return this.client.getSessionId?.() ?? this.sessionId;
|
|
3126
|
+
}
|
|
3127
|
+
async listTools() {
|
|
3128
|
+
const session = await this.getSession();
|
|
3129
|
+
const result = await this.client.fetchTools();
|
|
3130
|
+
const tools = this.filterTools(session, result.tools);
|
|
3131
|
+
return {
|
|
3132
|
+
...result,
|
|
3133
|
+
tools
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
async listAllTools() {
|
|
3137
|
+
return await this.client.fetchTools();
|
|
3138
|
+
}
|
|
3139
|
+
async callTool(name, args) {
|
|
3140
|
+
const session = await this.getSession();
|
|
3141
|
+
this.assertAllowed(session, name);
|
|
3142
|
+
return await this.client.callTool(name, args);
|
|
3143
|
+
}
|
|
3144
|
+
filterTools(session, tools) {
|
|
3145
|
+
return filterToolsByPolicy(tools, session.toolPolicy, this.getPolicyServerId(session));
|
|
3146
|
+
}
|
|
3147
|
+
assertAllowed(session, toolName) {
|
|
3148
|
+
assertToolAllowed(session.toolPolicy, toolName, this.getPolicyServerId(session));
|
|
3149
|
+
}
|
|
3150
|
+
async getSession() {
|
|
3151
|
+
const session = await sessions.get(this.userId, this.sessionId);
|
|
3152
|
+
if (!session) {
|
|
3153
|
+
throw new Error("Session not found");
|
|
3154
|
+
}
|
|
3155
|
+
return session;
|
|
3156
|
+
}
|
|
3157
|
+
getPolicyServerId(session) {
|
|
3158
|
+
return this.client.getServerId?.() ?? session.serverId;
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
function createToolPolicyGateway(userId, sessionId, client) {
|
|
3162
|
+
return new ToolPolicyGateway(userId, sessionId, client);
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3028
3165
|
// src/server/mcp/multi-session-client.ts
|
|
3029
3166
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
3030
3167
|
var DEFAULT_MAX_RETRIES = 2;
|
|
@@ -3090,7 +3227,9 @@ var MultiSessionClient = class {
|
|
|
3090
3227
|
* or to route a tool call to the right client by `serverId`.
|
|
3091
3228
|
*/
|
|
3092
3229
|
getClients() {
|
|
3093
|
-
return this.clients
|
|
3230
|
+
return this.clients.map(
|
|
3231
|
+
(client) => createToolPolicyGateway(this.userId, client.getSessionId(), client)
|
|
3232
|
+
);
|
|
3094
3233
|
}
|
|
3095
3234
|
/**
|
|
3096
3235
|
* Removes and disconnects a single session by ID.
|
|
@@ -3325,6 +3464,12 @@ var SSEConnectionManager = class {
|
|
|
3325
3464
|
case "listTools":
|
|
3326
3465
|
result = await this.listTools(request.params);
|
|
3327
3466
|
break;
|
|
3467
|
+
case "setToolPolicy":
|
|
3468
|
+
result = await this.setToolPolicy(request.params);
|
|
3469
|
+
break;
|
|
3470
|
+
case "getToolPolicy":
|
|
3471
|
+
result = await this.getToolPolicy(request.params);
|
|
3472
|
+
break;
|
|
3328
3473
|
case "callTool":
|
|
3329
3474
|
result = await this.callTool(request.params);
|
|
3330
3475
|
break;
|
|
@@ -3381,7 +3526,8 @@ var SSEConnectionManager = class {
|
|
|
3381
3526
|
transport: s.transportType,
|
|
3382
3527
|
createdAt: s.createdAt,
|
|
3383
3528
|
updatedAt: s.updatedAt ?? s.createdAt,
|
|
3384
|
-
status: s.status ?? "pending"
|
|
3529
|
+
status: s.status ?? "pending",
|
|
3530
|
+
toolPolicy: s.toolPolicy
|
|
3385
3531
|
}))
|
|
3386
3532
|
};
|
|
3387
3533
|
}
|
|
@@ -3429,7 +3575,7 @@ var SSEConnectionManager = class {
|
|
|
3429
3575
|
this.sendEvent(event);
|
|
3430
3576
|
});
|
|
3431
3577
|
await client.connect();
|
|
3432
|
-
await
|
|
3578
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
3433
3579
|
return {
|
|
3434
3580
|
sessionId,
|
|
3435
3581
|
success: true
|
|
@@ -3500,22 +3646,98 @@ var SSEConnectionManager = class {
|
|
|
3500
3646
|
this.clients.set(sessionId, client);
|
|
3501
3647
|
return client;
|
|
3502
3648
|
}
|
|
3649
|
+
async listPolicyFilteredTools(sessionId) {
|
|
3650
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3651
|
+
if (!session) {
|
|
3652
|
+
throw new Error("Session not found");
|
|
3653
|
+
}
|
|
3654
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3655
|
+
const gateway = createToolPolicyGateway(this.userId, sessionId, client);
|
|
3656
|
+
const result = await gateway.listTools();
|
|
3657
|
+
this.emitConnectionEvent({
|
|
3658
|
+
type: "tools_discovered",
|
|
3659
|
+
sessionId,
|
|
3660
|
+
serverId: session.serverId ?? "unknown",
|
|
3661
|
+
toolCount: result.tools.length,
|
|
3662
|
+
tools: result.tools,
|
|
3663
|
+
timestamp: Date.now()
|
|
3664
|
+
});
|
|
3665
|
+
return { session, result };
|
|
3666
|
+
}
|
|
3503
3667
|
/**
|
|
3504
3668
|
* List tools from a session
|
|
3505
3669
|
*/
|
|
3506
3670
|
async listTools(params) {
|
|
3507
3671
|
const { sessionId } = params;
|
|
3508
|
-
const
|
|
3509
|
-
const result = await client.listTools();
|
|
3672
|
+
const { result } = await this.listPolicyFilteredTools(sessionId);
|
|
3510
3673
|
return { tools: result.tools };
|
|
3511
3674
|
}
|
|
3675
|
+
/**
|
|
3676
|
+
* Get all raw tools with effective access state for management UI.
|
|
3677
|
+
*/
|
|
3678
|
+
async getToolPolicy(params) {
|
|
3679
|
+
const { sessionId } = params;
|
|
3680
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3681
|
+
if (!session) {
|
|
3682
|
+
throw new Error("Session not found");
|
|
3683
|
+
}
|
|
3684
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3685
|
+
const allTools = await client.fetchTools();
|
|
3686
|
+
const toolPolicy = session.toolPolicy ?? {
|
|
3687
|
+
mode: "all",
|
|
3688
|
+
toolIds: [],
|
|
3689
|
+
updatedAt: session.updatedAt ?? session.createdAt
|
|
3690
|
+
};
|
|
3691
|
+
const serverId = session.serverId ?? "unknown";
|
|
3692
|
+
const tools = allTools.tools.map((tool) => ({
|
|
3693
|
+
...tool,
|
|
3694
|
+
toolId: createToolId(serverId, tool.name),
|
|
3695
|
+
allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
|
|
3696
|
+
}));
|
|
3697
|
+
return {
|
|
3698
|
+
toolPolicy,
|
|
3699
|
+
tools,
|
|
3700
|
+
toolCount: tools.length,
|
|
3701
|
+
allowedToolCount: tools.filter((tool) => tool.allowed).length
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
/**
|
|
3705
|
+
* Update per-session tool access policy.
|
|
3706
|
+
*/
|
|
3707
|
+
async setToolPolicy(params) {
|
|
3708
|
+
const { sessionId } = params;
|
|
3709
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3710
|
+
if (!session) {
|
|
3711
|
+
throw new Error("Session not found");
|
|
3712
|
+
}
|
|
3713
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3714
|
+
const allTools = await client.fetchTools();
|
|
3715
|
+
const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
|
|
3716
|
+
validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
|
|
3717
|
+
await sessions.update(this.userId, sessionId, { toolPolicy });
|
|
3718
|
+
const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
|
|
3719
|
+
this.emitConnectionEvent({
|
|
3720
|
+
type: "tools_discovered",
|
|
3721
|
+
sessionId,
|
|
3722
|
+
serverId: session.serverId ?? "unknown",
|
|
3723
|
+
toolCount: filteredTools.length,
|
|
3724
|
+
tools: filteredTools,
|
|
3725
|
+
timestamp: Date.now()
|
|
3726
|
+
});
|
|
3727
|
+
return {
|
|
3728
|
+
success: true,
|
|
3729
|
+
toolPolicy,
|
|
3730
|
+
tools: filteredTools,
|
|
3731
|
+
toolCount: filteredTools.length
|
|
3732
|
+
};
|
|
3733
|
+
}
|
|
3512
3734
|
/**
|
|
3513
3735
|
* Call a tool on the MCP server
|
|
3514
3736
|
*/
|
|
3515
3737
|
async callTool(params) {
|
|
3516
3738
|
const { sessionId, toolName, toolArgs } = params;
|
|
3517
3739
|
const client = await this.getOrCreateClient(sessionId);
|
|
3518
|
-
const result = await client.callTool(toolName, toolArgs);
|
|
3740
|
+
const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
|
|
3519
3741
|
const meta = result._meta || {};
|
|
3520
3742
|
return {
|
|
3521
3743
|
...result,
|
|
@@ -3565,7 +3787,7 @@ var SSEConnectionManager = class {
|
|
|
3565
3787
|
client.onObservabilityEvent((event) => this.sendEvent(event));
|
|
3566
3788
|
await client.connect();
|
|
3567
3789
|
this.clients.set(sessionId, client);
|
|
3568
|
-
const tools = await
|
|
3790
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3569
3791
|
return { success: true, toolCount: tools.tools.length };
|
|
3570
3792
|
} catch (error) {
|
|
3571
3793
|
this.emitConnectionEvent({
|
|
@@ -3613,7 +3835,7 @@ var SSEConnectionManager = class {
|
|
|
3613
3835
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
3614
3836
|
await client.finishAuth(code, oauthState);
|
|
3615
3837
|
this.clients.set(sessionId, client);
|
|
3616
|
-
const tools = await
|
|
3838
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3617
3839
|
return { success: true, toolCount: tools.tools.length };
|
|
3618
3840
|
} catch (error) {
|
|
3619
3841
|
this.emitConnectionEvent({
|
|
@@ -3905,6 +4127,12 @@ var SSEClient = class {
|
|
|
3905
4127
|
async disconnectFromServer(sessionId) {
|
|
3906
4128
|
return this.sendRequest("disconnect", { sessionId });
|
|
3907
4129
|
}
|
|
4130
|
+
async setToolPolicy(sessionId, toolPolicy) {
|
|
4131
|
+
return this.sendRequest("setToolPolicy", { sessionId, toolPolicy });
|
|
4132
|
+
}
|
|
4133
|
+
async getToolPolicy(sessionId) {
|
|
4134
|
+
return this.sendRequest("getToolPolicy", { sessionId });
|
|
4135
|
+
}
|
|
3908
4136
|
async listTools(sessionId) {
|
|
3909
4137
|
return this.sendRequest("listTools", { sessionId });
|
|
3910
4138
|
}
|