@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.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.
|
|
@@ -3319,12 +3458,21 @@ var SSEConnectionManager = class {
|
|
|
3319
3458
|
case "connect":
|
|
3320
3459
|
result = await this.connect(request.params);
|
|
3321
3460
|
break;
|
|
3461
|
+
case "reconnect":
|
|
3462
|
+
result = await this.reconnect(request.params);
|
|
3463
|
+
break;
|
|
3322
3464
|
case "disconnect":
|
|
3323
3465
|
result = await this.disconnect(request.params);
|
|
3324
3466
|
break;
|
|
3325
3467
|
case "listTools":
|
|
3326
3468
|
result = await this.listTools(request.params);
|
|
3327
3469
|
break;
|
|
3470
|
+
case "setToolPolicy":
|
|
3471
|
+
result = await this.setToolPolicy(request.params);
|
|
3472
|
+
break;
|
|
3473
|
+
case "getToolPolicy":
|
|
3474
|
+
result = await this.getToolPolicy(request.params);
|
|
3475
|
+
break;
|
|
3328
3476
|
case "callTool":
|
|
3329
3477
|
result = await this.callTool(request.params);
|
|
3330
3478
|
break;
|
|
@@ -3381,7 +3529,8 @@ var SSEConnectionManager = class {
|
|
|
3381
3529
|
transport: s.transportType,
|
|
3382
3530
|
createdAt: s.createdAt,
|
|
3383
3531
|
updatedAt: s.updatedAt ?? s.createdAt,
|
|
3384
|
-
status: s.status ?? "pending"
|
|
3532
|
+
status: s.status ?? "pending",
|
|
3533
|
+
toolPolicy: s.toolPolicy
|
|
3385
3534
|
}))
|
|
3386
3535
|
};
|
|
3387
3536
|
}
|
|
@@ -3429,7 +3578,7 @@ var SSEConnectionManager = class {
|
|
|
3429
3578
|
this.sendEvent(event);
|
|
3430
3579
|
});
|
|
3431
3580
|
await client.connect();
|
|
3432
|
-
await
|
|
3581
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
3433
3582
|
return {
|
|
3434
3583
|
sessionId,
|
|
3435
3584
|
success: true
|
|
@@ -3454,6 +3603,66 @@ var SSEConnectionManager = class {
|
|
|
3454
3603
|
throw error;
|
|
3455
3604
|
}
|
|
3456
3605
|
}
|
|
3606
|
+
/**
|
|
3607
|
+
* Reconnect to an MCP server — tears down the active client transport/connection
|
|
3608
|
+
* and creates a fresh connection while reusing the existing session credentials in a single RPC call.
|
|
3609
|
+
*/
|
|
3610
|
+
async reconnect(params) {
|
|
3611
|
+
const { serverId: rawServerId, serverName, serverUrl, callbackUrl, transportType } = params;
|
|
3612
|
+
const headers = normalizeHeaders(params.headers);
|
|
3613
|
+
const serverId = rawServerId && rawServerId.length <= 12 ? rawServerId : generateServerId();
|
|
3614
|
+
const existingSessions = await sessions.list(this.userId);
|
|
3615
|
+
const duplicate = existingSessions.find(
|
|
3616
|
+
(s) => s.serverId === serverId || s.serverUrl === serverUrl
|
|
3617
|
+
);
|
|
3618
|
+
const sessionId = duplicate ? duplicate.sessionId : await sessions.generateSessionId();
|
|
3619
|
+
if (duplicate) {
|
|
3620
|
+
const existingClient = this.clients.get(duplicate.sessionId);
|
|
3621
|
+
if (existingClient) {
|
|
3622
|
+
await existingClient.disconnect();
|
|
3623
|
+
this.clients.delete(duplicate.sessionId);
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
try {
|
|
3627
|
+
const clientMetadata = await this.getResolvedClientMetadata();
|
|
3628
|
+
const client = new MCPClient({
|
|
3629
|
+
userId: this.userId,
|
|
3630
|
+
sessionId,
|
|
3631
|
+
serverId,
|
|
3632
|
+
serverName,
|
|
3633
|
+
serverUrl,
|
|
3634
|
+
callbackUrl,
|
|
3635
|
+
transportType,
|
|
3636
|
+
headers,
|
|
3637
|
+
...clientMetadata
|
|
3638
|
+
});
|
|
3639
|
+
this.clients.set(sessionId, client);
|
|
3640
|
+
client.onConnectionEvent((event) => {
|
|
3641
|
+
this.emitConnectionEvent(event);
|
|
3642
|
+
});
|
|
3643
|
+
client.onObservabilityEvent((event) => {
|
|
3644
|
+
this.sendEvent(event);
|
|
3645
|
+
});
|
|
3646
|
+
await client.connect();
|
|
3647
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
3648
|
+
return { sessionId, success: true };
|
|
3649
|
+
} catch (error) {
|
|
3650
|
+
if (error instanceof UnauthorizedError) {
|
|
3651
|
+
this.clients.delete(sessionId);
|
|
3652
|
+
return { sessionId, success: true };
|
|
3653
|
+
}
|
|
3654
|
+
this.emitConnectionEvent({
|
|
3655
|
+
type: "error",
|
|
3656
|
+
sessionId,
|
|
3657
|
+
serverId,
|
|
3658
|
+
error: error instanceof Error ? error.message : "Connection failed",
|
|
3659
|
+
errorType: "connection",
|
|
3660
|
+
timestamp: Date.now()
|
|
3661
|
+
});
|
|
3662
|
+
this.clients.delete(sessionId);
|
|
3663
|
+
throw error;
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3457
3666
|
/**
|
|
3458
3667
|
* Disconnect from an MCP server
|
|
3459
3668
|
*/
|
|
@@ -3500,22 +3709,98 @@ var SSEConnectionManager = class {
|
|
|
3500
3709
|
this.clients.set(sessionId, client);
|
|
3501
3710
|
return client;
|
|
3502
3711
|
}
|
|
3712
|
+
async listPolicyFilteredTools(sessionId) {
|
|
3713
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3714
|
+
if (!session) {
|
|
3715
|
+
throw new Error("Session not found");
|
|
3716
|
+
}
|
|
3717
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3718
|
+
const gateway = createToolPolicyGateway(this.userId, sessionId, client);
|
|
3719
|
+
const result = await gateway.listTools();
|
|
3720
|
+
this.emitConnectionEvent({
|
|
3721
|
+
type: "tools_discovered",
|
|
3722
|
+
sessionId,
|
|
3723
|
+
serverId: session.serverId ?? "unknown",
|
|
3724
|
+
toolCount: result.tools.length,
|
|
3725
|
+
tools: result.tools,
|
|
3726
|
+
timestamp: Date.now()
|
|
3727
|
+
});
|
|
3728
|
+
return { session, result };
|
|
3729
|
+
}
|
|
3503
3730
|
/**
|
|
3504
3731
|
* List tools from a session
|
|
3505
3732
|
*/
|
|
3506
3733
|
async listTools(params) {
|
|
3507
3734
|
const { sessionId } = params;
|
|
3508
|
-
const
|
|
3509
|
-
const result = await client.listTools();
|
|
3735
|
+
const { result } = await this.listPolicyFilteredTools(sessionId);
|
|
3510
3736
|
return { tools: result.tools };
|
|
3511
3737
|
}
|
|
3738
|
+
/**
|
|
3739
|
+
* Get all raw tools with effective access state for management UI.
|
|
3740
|
+
*/
|
|
3741
|
+
async getToolPolicy(params) {
|
|
3742
|
+
const { sessionId } = params;
|
|
3743
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3744
|
+
if (!session) {
|
|
3745
|
+
throw new Error("Session not found");
|
|
3746
|
+
}
|
|
3747
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3748
|
+
const allTools = await client.fetchTools();
|
|
3749
|
+
const toolPolicy = session.toolPolicy ?? {
|
|
3750
|
+
mode: "all",
|
|
3751
|
+
toolIds: [],
|
|
3752
|
+
updatedAt: session.updatedAt ?? session.createdAt
|
|
3753
|
+
};
|
|
3754
|
+
const serverId = session.serverId ?? "unknown";
|
|
3755
|
+
const tools = allTools.tools.map((tool) => ({
|
|
3756
|
+
...tool,
|
|
3757
|
+
toolId: createToolId(serverId, tool.name),
|
|
3758
|
+
allowed: isToolAllowed(toolPolicy, tool.name, session.serverId)
|
|
3759
|
+
}));
|
|
3760
|
+
return {
|
|
3761
|
+
toolPolicy,
|
|
3762
|
+
tools,
|
|
3763
|
+
toolCount: tools.length,
|
|
3764
|
+
allowedToolCount: tools.filter((tool) => tool.allowed).length
|
|
3765
|
+
};
|
|
3766
|
+
}
|
|
3767
|
+
/**
|
|
3768
|
+
* Update per-session tool access policy.
|
|
3769
|
+
*/
|
|
3770
|
+
async setToolPolicy(params) {
|
|
3771
|
+
const { sessionId } = params;
|
|
3772
|
+
const session = await sessions.get(this.userId, sessionId);
|
|
3773
|
+
if (!session) {
|
|
3774
|
+
throw new Error("Session not found");
|
|
3775
|
+
}
|
|
3776
|
+
const client = await this.getOrCreateClient(sessionId);
|
|
3777
|
+
const allTools = await client.fetchTools();
|
|
3778
|
+
const toolPolicy = normalizeToolPolicyForUpdate(params.toolPolicy);
|
|
3779
|
+
validateToolPolicyAgainstTools(toolPolicy, allTools.tools, session.serverId);
|
|
3780
|
+
await sessions.update(this.userId, sessionId, { toolPolicy });
|
|
3781
|
+
const filteredTools = createToolPolicyGateway(this.userId, sessionId, client).filterTools({ ...session, toolPolicy }, allTools.tools);
|
|
3782
|
+
this.emitConnectionEvent({
|
|
3783
|
+
type: "tools_discovered",
|
|
3784
|
+
sessionId,
|
|
3785
|
+
serverId: session.serverId ?? "unknown",
|
|
3786
|
+
toolCount: filteredTools.length,
|
|
3787
|
+
tools: filteredTools,
|
|
3788
|
+
timestamp: Date.now()
|
|
3789
|
+
});
|
|
3790
|
+
return {
|
|
3791
|
+
success: true,
|
|
3792
|
+
toolPolicy,
|
|
3793
|
+
tools: filteredTools,
|
|
3794
|
+
toolCount: filteredTools.length
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3512
3797
|
/**
|
|
3513
3798
|
* Call a tool on the MCP server
|
|
3514
3799
|
*/
|
|
3515
3800
|
async callTool(params) {
|
|
3516
3801
|
const { sessionId, toolName, toolArgs } = params;
|
|
3517
3802
|
const client = await this.getOrCreateClient(sessionId);
|
|
3518
|
-
const result = await client.callTool(toolName, toolArgs);
|
|
3803
|
+
const result = await createToolPolicyGateway(this.userId, sessionId, client).callTool(toolName, toolArgs);
|
|
3519
3804
|
const meta = result._meta || {};
|
|
3520
3805
|
return {
|
|
3521
3806
|
...result,
|
|
@@ -3565,7 +3850,7 @@ var SSEConnectionManager = class {
|
|
|
3565
3850
|
client.onObservabilityEvent((event) => this.sendEvent(event));
|
|
3566
3851
|
await client.connect();
|
|
3567
3852
|
this.clients.set(sessionId, client);
|
|
3568
|
-
const tools = await
|
|
3853
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3569
3854
|
return { success: true, toolCount: tools.tools.length };
|
|
3570
3855
|
} catch (error) {
|
|
3571
3856
|
this.emitConnectionEvent({
|
|
@@ -3613,7 +3898,7 @@ var SSEConnectionManager = class {
|
|
|
3613
3898
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
|
3614
3899
|
await client.finishAuth(code, oauthState);
|
|
3615
3900
|
this.clients.set(sessionId, client);
|
|
3616
|
-
const tools = await
|
|
3901
|
+
const { result: tools } = await this.listPolicyFilteredTools(sessionId);
|
|
3617
3902
|
return { success: true, toolCount: tools.tools.length };
|
|
3618
3903
|
} catch (error) {
|
|
3619
3904
|
this.emitConnectionEvent({
|
|
@@ -3905,6 +4190,15 @@ var SSEClient = class {
|
|
|
3905
4190
|
async disconnectFromServer(sessionId) {
|
|
3906
4191
|
return this.sendRequest("disconnect", { sessionId });
|
|
3907
4192
|
}
|
|
4193
|
+
async reconnectToServer(params) {
|
|
4194
|
+
return this.sendRequest("reconnect", params);
|
|
4195
|
+
}
|
|
4196
|
+
async setToolPolicy(sessionId, toolPolicy) {
|
|
4197
|
+
return this.sendRequest("setToolPolicy", { sessionId, toolPolicy });
|
|
4198
|
+
}
|
|
4199
|
+
async getToolPolicy(sessionId) {
|
|
4200
|
+
return this.sendRequest("getToolPolicy", { sessionId });
|
|
4201
|
+
}
|
|
3908
4202
|
async listTools(sessionId) {
|
|
3909
4203
|
return this.sendRequest("listTools", { sessionId });
|
|
3910
4204
|
}
|