@warlock.js/ai-tools 5.32.0 → 5.34.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/CHANGELOG.md +25 -103
- package/cjs/index.cjs +403 -131
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/index.d.mts +2 -2
- package/esm/contracts/mcp-serve.type.d.mts +85 -8
- package/esm/contracts/mcp-serve.type.d.mts.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/mcp/error.d.mts +9 -0
- package/esm/mcp/error.d.mts.map +1 -0
- package/esm/mcp/error.mjs +24 -0
- package/esm/mcp/error.mjs.map +1 -0
- package/esm/mcp/index.d.mts +2 -1
- package/esm/mcp/index.d.mts.map +1 -1
- package/esm/mcp/index.mjs +2 -1
- package/esm/mcp/index.mjs.map +1 -1
- package/esm/mcp/serve-http.mjs +134 -29
- package/esm/mcp/serve-http.mjs.map +1 -1
- package/esm/mcp/serve-requests.mjs +62 -0
- package/esm/mcp/serve-requests.mjs.map +1 -0
- package/esm/mcp/serve.d.mts.map +1 -1
- package/esm/mcp/serve.mjs +105 -15
- package/esm/mcp/serve.mjs.map +1 -1
- package/esm/register.d.mts +13 -52
- package/esm/register.d.mts.map +1 -1
- package/esm/register.mjs +4 -5
- package/esm/register.mjs.map +1 -1
- package/llms-full.txt +190 -2
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/skills/connect-mcp-server/SKILL.md +15 -0
- package/skills/expose-as-mcp-server/SKILL.md +154 -2
- package/skills/use-web-and-http-tools/SKILL.md +21 -0
package/cjs/index.cjs
CHANGED
|
@@ -1294,6 +1294,28 @@ function mcp$1(server, options) {
|
|
|
1294
1294
|
return new McpClientImpl(server, options);
|
|
1295
1295
|
}
|
|
1296
1296
|
|
|
1297
|
+
//#endregion
|
|
1298
|
+
//#region ../../packages/ai-tools/src/mcp/error.ts
|
|
1299
|
+
/** An application-defined JSON-RPC error thrown by MCP serving policy hooks. */
|
|
1300
|
+
var McpError = class extends Error {
|
|
1301
|
+
constructor(code, message, data) {
|
|
1302
|
+
super(message);
|
|
1303
|
+
this.name = "McpError";
|
|
1304
|
+
this.code = code;
|
|
1305
|
+
this.data = data;
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
/**
|
|
1309
|
+
* Recognize our error and the SDK's McpError without a runtime SDK dependency.
|
|
1310
|
+
* SDK source (checked 2026-10-19):
|
|
1311
|
+
* https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.x/src/types.ts
|
|
1312
|
+
* Only explicitly named errors with an integer code opt in; ordinary throws
|
|
1313
|
+
* retain the existing mapping. Preserve the error's message verbatim.
|
|
1314
|
+
*/
|
|
1315
|
+
function isMcpError(cause) {
|
|
1316
|
+
return cause instanceof Error && cause.name === "McpError" && "code" in cause && typeof cause.code === "number" && Number.isInteger(cause.code);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1297
1319
|
//#endregion
|
|
1298
1320
|
//#region ../../packages/ai-tools/src/mcp/protocol.ts
|
|
1299
1321
|
/**
|
|
@@ -1315,6 +1337,147 @@ const SUPPORTED_PROTOCOL_VERSIONS = [
|
|
|
1315
1337
|
/** The newest MCP protocol version the server speaks. */
|
|
1316
1338
|
const LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
1317
1339
|
|
|
1340
|
+
//#endregion
|
|
1341
|
+
//#region ../../packages/ai-tools/src/mcp/result.ts
|
|
1342
|
+
const NATIVE_RESULT = Symbol("mcpResult");
|
|
1343
|
+
/**
|
|
1344
|
+
* Opt in to native MCP content, error status, structured output and metadata.
|
|
1345
|
+
* Validation happens after invoke(), so malformed payloads become JSON-RPC
|
|
1346
|
+
* errors rather than being caught as tool execution errors.
|
|
1347
|
+
*/
|
|
1348
|
+
function mcpResult(result) {
|
|
1349
|
+
return { [NATIVE_RESULT]: result };
|
|
1350
|
+
}
|
|
1351
|
+
/** Read a branded value without accepting lookalike application objects. */
|
|
1352
|
+
function isMcpResult(value) {
|
|
1353
|
+
return isRecord(value) && NATIVE_RESULT in value;
|
|
1354
|
+
}
|
|
1355
|
+
/** Get the original payload; no cloning, normalization or field stripping. */
|
|
1356
|
+
function unwrapMcpResult(value) {
|
|
1357
|
+
return value[NATIVE_RESULT];
|
|
1358
|
+
}
|
|
1359
|
+
/** Objects with result fields are candidates only when native mode is enabled. */
|
|
1360
|
+
function looksLikeMcpResult(value) {
|
|
1361
|
+
return isRecord(value) && [
|
|
1362
|
+
"content",
|
|
1363
|
+
"isError",
|
|
1364
|
+
"structuredContent",
|
|
1365
|
+
"_meta"
|
|
1366
|
+
].some((key) => key in value);
|
|
1367
|
+
}
|
|
1368
|
+
/** Non-array object used by the MCP JSON object fields. */
|
|
1369
|
+
function isRecord(value) {
|
|
1370
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1371
|
+
}
|
|
1372
|
+
function optionalString(value) {
|
|
1373
|
+
return value === void 0 || typeof value === "string";
|
|
1374
|
+
}
|
|
1375
|
+
function validAnnotations(value) {
|
|
1376
|
+
if (value === void 0) return true;
|
|
1377
|
+
if (!isRecord(value)) return false;
|
|
1378
|
+
return (value.audience === void 0 || Array.isArray(value.audience) && value.audience.every((role) => role === "user" || role === "assistant")) && (value.priority === void 0 || typeof value.priority === "number" && value.priority >= 0 && value.priority <= 1) && optionalString(value.lastModified);
|
|
1379
|
+
}
|
|
1380
|
+
function validMetadata(value) {
|
|
1381
|
+
return (value["_meta"] === void 0 || isRecord(value["_meta"])) && validAnnotations(value.annotations);
|
|
1382
|
+
}
|
|
1383
|
+
function validIcon(value) {
|
|
1384
|
+
return isRecord(value) && typeof value.src === "string" && optionalString(value.mimeType) && (value.sizes === void 0 || Array.isArray(value.sizes) && value.sizes.every((size) => typeof size === "string")) && (value.theme === void 0 || value.theme === "light" || value.theme === "dark");
|
|
1385
|
+
}
|
|
1386
|
+
function validContent(value) {
|
|
1387
|
+
if (!isRecord(value) || !validMetadata(value)) return false;
|
|
1388
|
+
switch (value.type) {
|
|
1389
|
+
case "text": return typeof value.text === "string";
|
|
1390
|
+
case "image":
|
|
1391
|
+
case "audio": return typeof value.data === "string" && typeof value.mimeType === "string";
|
|
1392
|
+
case "resource_link": return typeof value.uri === "string" && typeof value.name === "string" && optionalString(value.title) && optionalString(value.description) && optionalString(value.mimeType) && (value.size === void 0 || typeof value.size === "number") && (value.icons === void 0 || Array.isArray(value.icons) && value.icons.every(validIcon));
|
|
1393
|
+
case "resource": {
|
|
1394
|
+
const resource = value.resource;
|
|
1395
|
+
return isRecord(resource) && typeof resource.uri === "string" && optionalString(resource.mimeType) && validMetadata(resource) && (typeof resource.text === "string" || typeof resource.blob === "string");
|
|
1396
|
+
}
|
|
1397
|
+
default: return false;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
/** Reject values JSON would drop, coerce or fail to encode (including cycles). */
|
|
1401
|
+
function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
1402
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1403
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1404
|
+
if (typeof value !== "object" || ancestors.has(value)) return false;
|
|
1405
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1406
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false;
|
|
1407
|
+
ancestors.add(value);
|
|
1408
|
+
const valid = Array.isArray(value) ? Array.from(value).every((entry) => isJsonValue(entry, ancestors)) : Object.values(value).every((entry) => entry === void 0 || isJsonValue(entry, ancestors));
|
|
1409
|
+
ancestors.delete(value);
|
|
1410
|
+
return valid;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* MCP 2025-11-25 CallToolResult and ContentBlock fields:
|
|
1414
|
+
* https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-result
|
|
1415
|
+
* https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts
|
|
1416
|
+
* structuredContent is preserved even when tools/list declares outputSchema.
|
|
1417
|
+
* Tool authors remain responsible for conformity to that application schema.
|
|
1418
|
+
*/
|
|
1419
|
+
function assertMcpResult(value) {
|
|
1420
|
+
if (!isRecord(value) || !Array.isArray(value.content) || !Array.from(value.content).every(validContent) || value.isError !== void 0 && typeof value.isError !== "boolean" || value.structuredContent !== void 0 && !isRecord(value.structuredContent) || value["_meta"] !== void 0 && !isRecord(value["_meta"]) || !isJsonValue(value)) throw new TypeError("Invalid native MCP CallToolResult.");
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
//#endregion
|
|
1424
|
+
//#region ../../packages/ai-tools/src/mcp/serve-requests.ts
|
|
1425
|
+
/**
|
|
1426
|
+
* In-flight requests for one transport. HTTP partitions these by credentials;
|
|
1427
|
+
* stdio has a single connection. Cancellation never crosses those boundaries.
|
|
1428
|
+
* https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation
|
|
1429
|
+
* (checked 2026-10-19): ignore unknown/completed ids and never answer cancelled work.
|
|
1430
|
+
*/
|
|
1431
|
+
var McpServeRequests = class {
|
|
1432
|
+
constructor() {
|
|
1433
|
+
this.scopes = /* @__PURE__ */ new Map();
|
|
1434
|
+
}
|
|
1435
|
+
cancel(message, scope = "") {
|
|
1436
|
+
if (!isRecord(message) || message.jsonrpc !== "2.0" || message.method !== "notifications/cancelled" || message.id !== void 0 || !isRecord(message.params)) return;
|
|
1437
|
+
const { requestId, reason } = message.params;
|
|
1438
|
+
if (typeof requestId !== "string" && typeof requestId !== "number" || reason !== void 0 && typeof reason !== "string") return;
|
|
1439
|
+
const pending = this.scopes.get(scope)?.get(requestId);
|
|
1440
|
+
if (pending?.size === 1) {
|
|
1441
|
+
for (const entry of pending) if (entry.cancellable) entry.controller.abort(new DOMException(typeof reason === "string" ? reason : "Request cancelled.", "AbortError"));
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
async run(request, context, dispatch, scope = "") {
|
|
1445
|
+
let pending = this.scopes.get(scope);
|
|
1446
|
+
if (!pending) {
|
|
1447
|
+
pending = /* @__PURE__ */ new Map();
|
|
1448
|
+
this.scopes.set(scope, pending);
|
|
1449
|
+
}
|
|
1450
|
+
const controller = new AbortController();
|
|
1451
|
+
const entry = {
|
|
1452
|
+
controller,
|
|
1453
|
+
cancellable: request.method !== "initialize"
|
|
1454
|
+
};
|
|
1455
|
+
const calls = pending.get(request.id) ?? /* @__PURE__ */ new Set();
|
|
1456
|
+
calls.add(entry);
|
|
1457
|
+
pending.set(request.id, calls);
|
|
1458
|
+
const onAbort = () => controller.abort(context.signal?.reason);
|
|
1459
|
+
context.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1460
|
+
if (context.signal?.aborted) onAbort();
|
|
1461
|
+
try {
|
|
1462
|
+
if (controller.signal.aborted) return;
|
|
1463
|
+
const response = await dispatch({
|
|
1464
|
+
...context,
|
|
1465
|
+
requestId: request.id,
|
|
1466
|
+
signal: controller.signal
|
|
1467
|
+
});
|
|
1468
|
+
return controller.signal.aborted ? void 0 : response;
|
|
1469
|
+
} catch (cause) {
|
|
1470
|
+
if (controller.signal.aborted) return;
|
|
1471
|
+
throw cause;
|
|
1472
|
+
} finally {
|
|
1473
|
+
context.signal?.removeEventListener("abort", onAbort);
|
|
1474
|
+
calls.delete(entry);
|
|
1475
|
+
if (calls.size === 0) pending.delete(request.id);
|
|
1476
|
+
if (pending.size === 0) this.scopes.delete(scope);
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
};
|
|
1480
|
+
|
|
1318
1481
|
//#endregion
|
|
1319
1482
|
//#region ../../packages/ai-tools/src/mcp/serve-http.ts
|
|
1320
1483
|
/**
|
|
@@ -1324,7 +1487,7 @@ const LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
|
1324
1487
|
* specification (https://modelcontextprotocol.io/specification/2025-11-25/basic/transports,
|
|
1325
1488
|
* checked 2026-10-17) on `node:http`, with no SDK dependency:
|
|
1326
1489
|
*
|
|
1327
|
-
* - one endpoint path; each POST carries exactly one JSON-RPC message
|
|
1490
|
+
* - one MCP endpoint path (plus optional authenticated app routes); each MCP POST carries exactly one JSON-RPC message
|
|
1328
1491
|
* (batches were removed from the protocol in 2025-06-18 and get 400);
|
|
1329
1492
|
* - a request is answered with `Content-Type: application/json` (the spec
|
|
1330
1493
|
* lets a server pick JSON or SSE per request); a notification or a
|
|
@@ -1382,6 +1545,35 @@ function hostnameOf(hostHeader) {
|
|
|
1382
1545
|
function digest(value) {
|
|
1383
1546
|
return (0, node_crypto.createHash)("sha256").update(value, "utf8").digest();
|
|
1384
1547
|
}
|
|
1548
|
+
/** Resolve exact descendant routes; no route may replace the MCP endpoint. */
|
|
1549
|
+
function resolveRoutes(path, routes) {
|
|
1550
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1551
|
+
const methods = /* @__PURE__ */ new Set([
|
|
1552
|
+
"GET",
|
|
1553
|
+
"POST",
|
|
1554
|
+
"PUT",
|
|
1555
|
+
"PATCH",
|
|
1556
|
+
"DELETE",
|
|
1557
|
+
"HEAD",
|
|
1558
|
+
"OPTIONS"
|
|
1559
|
+
]);
|
|
1560
|
+
if (routes.length > 0) {
|
|
1561
|
+
let canonical = false;
|
|
1562
|
+
try {
|
|
1563
|
+
canonical = !path.startsWith("//") && new URL(path, "http://localhost").pathname === path;
|
|
1564
|
+
} catch {}
|
|
1565
|
+
if (!canonical) throw new McpServeError("MCP http routes require a canonical endpoint pathname.", { type: "config" });
|
|
1566
|
+
}
|
|
1567
|
+
for (const route of routes) {
|
|
1568
|
+
if (!methods.has(route.method) || !/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/.test(route.path) || route.path.split("/").some((segment) => segment === "." || segment === "..") || typeof route.handler !== "function") throw new McpServeError("Invalid MCP http route method, path or handler.", { type: "config" });
|
|
1569
|
+
const fullPath = `${path.replace(/\/+$/, "")}${route.path}`;
|
|
1570
|
+
const byMethod = resolved.get(fullPath) ?? /* @__PURE__ */ new Map();
|
|
1571
|
+
if (fullPath === path || byMethod.has(route.method)) throw new McpServeError(`MCP http route "${route.method} ${fullPath}" conflicts.`, { type: "collision" });
|
|
1572
|
+
byMethod.set(route.method, { ...route });
|
|
1573
|
+
resolved.set(fullPath, byMethod);
|
|
1574
|
+
}
|
|
1575
|
+
return resolved;
|
|
1576
|
+
}
|
|
1385
1577
|
/** Normalise a configured origin to `scheme://host[:port]`, or throw a config error. */
|
|
1386
1578
|
function normalizeOrigin(origin) {
|
|
1387
1579
|
try {
|
|
@@ -1413,9 +1605,9 @@ function resolveSettings(transport) {
|
|
|
1413
1605
|
const tokens = auth?.bearer === void 0 ? [] : typeof auth.bearer === "string" ? [auth.bearer] : [...auth.bearer];
|
|
1414
1606
|
if (tokens.some((token) => token.length === 0)) throw new McpServeError("MCP http auth.bearer tokens must be non-empty strings.", { type: "config" });
|
|
1415
1607
|
if (auth && auth.bearer !== void 0 && tokens.length === 0) throw new McpServeError("MCP http auth.bearer needs at least one token.", { type: "config" });
|
|
1416
|
-
if (auth && tokens.length === 0 && !auth.authorize) throw new McpServeError("MCP http auth needs `bearer` and/or `
|
|
1417
|
-
const authRequired = tokens.length > 0 || auth?.authorize !== void 0;
|
|
1418
|
-
if (!loopback && !authRequired) throw new McpServeError(`Refusing to serve MCP over http on non-loopback host "${host}" without auth. Set transport.auth ({ bearer } or {
|
|
1608
|
+
if (auth && tokens.length === 0 && !auth.authorize && !auth.authenticate) throw new McpServeError("MCP http auth needs `bearer`, `authorize` and/or `authenticate`.", { type: "config" });
|
|
1609
|
+
const authRequired = tokens.length > 0 || auth?.authorize !== void 0 || auth?.authenticate !== void 0;
|
|
1610
|
+
if (!loopback && !authRequired) throw new McpServeError(`Refusing to serve MCP over http on non-loopback host "${host}" without auth. Set transport.auth ({ bearer }, { authorize } or { authenticate }), or bind 127.0.0.1.`, {
|
|
1419
1611
|
type: "config",
|
|
1420
1612
|
context: { host }
|
|
1421
1613
|
});
|
|
@@ -1428,9 +1620,11 @@ function resolveSettings(transport) {
|
|
|
1428
1620
|
drainTimeoutMs,
|
|
1429
1621
|
tokenDigests: tokens.map(digest),
|
|
1430
1622
|
authorize: auth?.authorize,
|
|
1623
|
+
authenticate: auth?.authenticate,
|
|
1431
1624
|
authRequired,
|
|
1432
1625
|
allowedOrigins: new Set((transport.allowedOrigins ?? []).map(normalizeOrigin)),
|
|
1433
|
-
allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name)))
|
|
1626
|
+
allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name))),
|
|
1627
|
+
routes: resolveRoutes(path, transport.routes ?? [])
|
|
1434
1628
|
};
|
|
1435
1629
|
}
|
|
1436
1630
|
/** Flatten Node's header bag into lower-cased `name → value` strings. */
|
|
@@ -1486,6 +1680,7 @@ function sendEmpty(response, status, headers = {}) {
|
|
|
1486
1680
|
async function startHttpServer(transport, dispatch) {
|
|
1487
1681
|
const settings = resolveSettings(transport);
|
|
1488
1682
|
const shutdown = new AbortController();
|
|
1683
|
+
const requests = new McpServeRequests();
|
|
1489
1684
|
const inFlight = /* @__PURE__ */ new Set();
|
|
1490
1685
|
let closing;
|
|
1491
1686
|
const server = (0, node_http.createServer)((request, response) => {
|
|
@@ -1493,7 +1688,7 @@ async function startHttpServer(transport, dispatch) {
|
|
|
1493
1688
|
sendRefusal(response, 503, JSON_RPC_SERVER_ERROR, "Server is shutting down.", { connection: "close" });
|
|
1494
1689
|
return;
|
|
1495
1690
|
}
|
|
1496
|
-
const work = handleRequest(request, response, settings, dispatch, shutdown.signal).catch(() => {
|
|
1691
|
+
const work = handleRequest(request, response, settings, dispatch, shutdown.signal, requests).catch(() => {
|
|
1497
1692
|
sendRefusal(response, 500, JSON_RPC_SERVER_ERROR, "Internal server error.");
|
|
1498
1693
|
});
|
|
1499
1694
|
inFlight.add(work);
|
|
@@ -1552,13 +1747,15 @@ async function startHttpServer(transport, dispatch) {
|
|
|
1552
1747
|
};
|
|
1553
1748
|
}
|
|
1554
1749
|
/**
|
|
1555
|
-
* Validate and answer one HTTP request:
|
|
1556
|
-
* method →
|
|
1750
|
+
* Validate and answer one HTTP request: Host / Origin → path → auth →
|
|
1751
|
+
* method → route handler or MCP headers/body/JSON-RPC dispatch.
|
|
1557
1752
|
*/
|
|
1558
|
-
async function handleRequest(request, response, settings, dispatch, signal) {
|
|
1753
|
+
async function handleRequest(request, response, settings, dispatch, signal, requests) {
|
|
1559
1754
|
const headers = flattenHeaders(request.headers);
|
|
1560
1755
|
const method = (request.method ?? "GET").toUpperCase();
|
|
1561
|
-
const
|
|
1756
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
1757
|
+
const path = url.pathname;
|
|
1758
|
+
const routes = settings.routes.get(path);
|
|
1562
1759
|
if (!hostAllowed(headers.host, settings)) {
|
|
1563
1760
|
sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, "Forbidden: Host header not allowed.");
|
|
1564
1761
|
return;
|
|
@@ -1567,7 +1764,7 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
1567
1764
|
sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, "Forbidden: Origin not allowed.");
|
|
1568
1765
|
return;
|
|
1569
1766
|
}
|
|
1570
|
-
if (path !== settings.path) {
|
|
1767
|
+
if (path !== settings.path && !routes) {
|
|
1571
1768
|
sendRefusal(response, 404, JSON_RPC_SERVER_ERROR, "Not found.");
|
|
1572
1769
|
return;
|
|
1573
1770
|
}
|
|
@@ -1577,11 +1774,21 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
1577
1774
|
headers
|
|
1578
1775
|
};
|
|
1579
1776
|
if (request.socket.remoteAddress !== void 0) info.remoteAddress = request.socket.remoteAddress;
|
|
1580
|
-
|
|
1581
|
-
|
|
1777
|
+
const authentication = await authorized(info, settings);
|
|
1778
|
+
if (!authentication.allowed) {
|
|
1779
|
+
const challenge = settings.tokenDigests.length > 0 || settings.authenticate ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {};
|
|
1582
1780
|
sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", challenge);
|
|
1583
1781
|
return;
|
|
1584
1782
|
}
|
|
1783
|
+
if (routes) {
|
|
1784
|
+
const route = routes.get(method);
|
|
1785
|
+
if (!route) {
|
|
1786
|
+
sendEmpty(response, 405, { allow: [...routes.keys()].sort().join(", ") });
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
await handleRoute(request, response, route, info, url, authentication.principal, signal, settings);
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1585
1792
|
if (method !== "POST") {
|
|
1586
1793
|
sendEmpty(response, 405, { allow: "POST" });
|
|
1587
1794
|
return;
|
|
@@ -1623,6 +1830,7 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
1623
1830
|
const id = record.id;
|
|
1624
1831
|
if (typeof record.method === "string") {
|
|
1625
1832
|
if (typeof id !== "string" && typeof id !== "number") {
|
|
1833
|
+
requests.cancel(record, cancellationScope(info));
|
|
1626
1834
|
sendEmpty(response, 202);
|
|
1627
1835
|
return;
|
|
1628
1836
|
}
|
|
@@ -1632,10 +1840,20 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
1632
1840
|
method: record.method
|
|
1633
1841
|
};
|
|
1634
1842
|
if (record.params !== void 0) rpcRequest.params = record.params;
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1843
|
+
const disconnected = new AbortController();
|
|
1844
|
+
const onClose = () => disconnected.abort();
|
|
1845
|
+
response.once("close", onClose);
|
|
1846
|
+
try {
|
|
1847
|
+
const rpcResponse = await requests.run(rpcRequest, {
|
|
1848
|
+
request: info,
|
|
1849
|
+
principal: authentication.principal,
|
|
1850
|
+
signal: AbortSignal.any([signal, disconnected.signal])
|
|
1851
|
+
}, (context) => dispatch(rpcRequest, context), cancellationScope(info));
|
|
1852
|
+
if (rpcResponse === void 0) sendEmpty(response, 202);
|
|
1853
|
+
else sendJson(response, 200, rpcResponse);
|
|
1854
|
+
} finally {
|
|
1855
|
+
response.off("close", onClose);
|
|
1856
|
+
}
|
|
1639
1857
|
return;
|
|
1640
1858
|
}
|
|
1641
1859
|
if ((typeof id === "string" || typeof id === "number") && ("result" in record || "error" in record)) {
|
|
@@ -1644,6 +1862,32 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
1644
1862
|
}
|
|
1645
1863
|
sendRefusal(response, 400, JSON_RPC_INVALID_REQUEST$1, "Invalid request: not a JSON-RPC message.");
|
|
1646
1864
|
}
|
|
1865
|
+
/** Run a side route after the shared guards, tracking disconnect and shutdown. */
|
|
1866
|
+
async function handleRoute(request, response, route, info, url, principal, shutdown, settings) {
|
|
1867
|
+
const disconnected = new AbortController();
|
|
1868
|
+
const onClose = () => disconnected.abort();
|
|
1869
|
+
response.once("close", onClose);
|
|
1870
|
+
try {
|
|
1871
|
+
const body = await readBody$1(request, settings.maxBodyBytes);
|
|
1872
|
+
if (body.type === "too-large") {
|
|
1873
|
+
sendRefusal(response, 413, JSON_RPC_SERVER_ERROR, `Request body exceeds ${settings.maxBodyBytes} bytes.`, { connection: "close" });
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
const signal = AbortSignal.any([shutdown, disconnected.signal]);
|
|
1877
|
+
signal.throwIfAborted();
|
|
1878
|
+
const result = await route.handler({
|
|
1879
|
+
request: info,
|
|
1880
|
+
url,
|
|
1881
|
+
principal,
|
|
1882
|
+
body: body.text,
|
|
1883
|
+
signal
|
|
1884
|
+
});
|
|
1885
|
+
if (result.body === void 0 || info.method === "HEAD") sendEmpty(response, result.status ?? 200, result.headers);
|
|
1886
|
+
else sendJson(response, result.status ?? 200, result.body, result.headers);
|
|
1887
|
+
} finally {
|
|
1888
|
+
response.off("close", onClose);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1647
1891
|
/**
|
|
1648
1892
|
* Host-header check. While bound to loopback only loopback names (plus
|
|
1649
1893
|
* `allowedHosts`) are accepted, so a DNS-rebound name pointing at
|
|
@@ -1674,27 +1918,50 @@ function originAllowed(origin, settings) {
|
|
|
1674
1918
|
if (settings.allowedOrigins.has(parsed.origin)) return true;
|
|
1675
1919
|
return (parsed.protocol === "http:" || parsed.protocol === "https:") && isLoopbackHost(parsed.hostname);
|
|
1676
1920
|
}
|
|
1677
|
-
/**
|
|
1678
|
-
* Run the configured auth. Bearer tokens are compared in constant time
|
|
1679
|
-
* over SHA-256 digests (equal-length buffers for `timingSafeEqual`), and
|
|
1680
|
-
* every configured token is compared so the match position does not leak.
|
|
1681
|
-
* An `authorize` hook that throws denies the request.
|
|
1682
|
-
*/
|
|
1683
1921
|
async function authorized(info, settings) {
|
|
1684
|
-
if (!settings.authRequired) return true;
|
|
1922
|
+
if (!settings.authRequired) return { allowed: true };
|
|
1685
1923
|
if (settings.tokenDigests.length > 0) {
|
|
1686
1924
|
const match = /^Bearer[ \t]+(.+)$/i.exec(info.headers.authorization ?? "");
|
|
1687
1925
|
const presented = digest(match?.[1]?.trim() ?? "");
|
|
1688
1926
|
let matched = false;
|
|
1689
1927
|
for (const expected of settings.tokenDigests) matched = (0, node_crypto.timingSafeEqual)(presented, expected) || matched;
|
|
1690
|
-
if (!match || !matched) return false;
|
|
1928
|
+
if (!match || !matched) return { allowed: false };
|
|
1691
1929
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1930
|
+
try {
|
|
1931
|
+
if (settings.authorize && await settings.authorize(info) !== true) return { allowed: false };
|
|
1932
|
+
if (settings.authenticate) {
|
|
1933
|
+
const principal = await settings.authenticate(info);
|
|
1934
|
+
return principal === void 0 ? { allowed: false } : {
|
|
1935
|
+
allowed: true,
|
|
1936
|
+
principal
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
return { allowed: true };
|
|
1694
1940
|
} catch {
|
|
1695
|
-
return false;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1941
|
+
return { allowed: false };
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Stateless HTTP has no connection/session id. Repeat the caller's credential
|
|
1946
|
+
* and application headers on notifications; per-message transport headers do
|
|
1947
|
+
* not identify a caller. Hash the material rather than retaining secrets as keys.
|
|
1948
|
+
*/
|
|
1949
|
+
function cancellationScope(info) {
|
|
1950
|
+
const transportHeaders = /* @__PURE__ */ new Set([
|
|
1951
|
+
"host",
|
|
1952
|
+
"origin",
|
|
1953
|
+
"accept",
|
|
1954
|
+
"accept-encoding",
|
|
1955
|
+
"accept-language",
|
|
1956
|
+
"connection",
|
|
1957
|
+
"content-type",
|
|
1958
|
+
"content-length",
|
|
1959
|
+
"transfer-encoding",
|
|
1960
|
+
"mcp-protocol-version",
|
|
1961
|
+
"user-agent"
|
|
1962
|
+
]);
|
|
1963
|
+
const credentials = Object.entries(info.headers).filter(([name]) => !transportHeaders.has(name)).sort(([left], [right]) => left.localeCompare(right));
|
|
1964
|
+
return digest(JSON.stringify([info.remoteAddress, credentials])).toString("hex");
|
|
1698
1965
|
}
|
|
1699
1966
|
/** True for `application/json` (any parameters, e.g. `; charset=utf-8`). */
|
|
1700
1967
|
function isJsonContentType(contentType) {
|
|
@@ -1751,89 +2018,6 @@ function readBody$1(request, limit) {
|
|
|
1751
2018
|
});
|
|
1752
2019
|
}
|
|
1753
2020
|
|
|
1754
|
-
//#endregion
|
|
1755
|
-
//#region ../../packages/ai-tools/src/mcp/result.ts
|
|
1756
|
-
const NATIVE_RESULT = Symbol("mcpResult");
|
|
1757
|
-
/**
|
|
1758
|
-
* Opt in to native MCP content, error status, structured output and metadata.
|
|
1759
|
-
* Validation happens after invoke(), so malformed payloads become JSON-RPC
|
|
1760
|
-
* errors rather than being caught as tool execution errors.
|
|
1761
|
-
*/
|
|
1762
|
-
function mcpResult(result) {
|
|
1763
|
-
return { [NATIVE_RESULT]: result };
|
|
1764
|
-
}
|
|
1765
|
-
/** Read a branded value without accepting lookalike application objects. */
|
|
1766
|
-
function isMcpResult(value) {
|
|
1767
|
-
return isRecord(value) && NATIVE_RESULT in value;
|
|
1768
|
-
}
|
|
1769
|
-
/** Get the original payload; no cloning, normalization or field stripping. */
|
|
1770
|
-
function unwrapMcpResult(value) {
|
|
1771
|
-
return value[NATIVE_RESULT];
|
|
1772
|
-
}
|
|
1773
|
-
/** Objects with result fields are candidates only when native mode is enabled. */
|
|
1774
|
-
function looksLikeMcpResult(value) {
|
|
1775
|
-
return isRecord(value) && [
|
|
1776
|
-
"content",
|
|
1777
|
-
"isError",
|
|
1778
|
-
"structuredContent",
|
|
1779
|
-
"_meta"
|
|
1780
|
-
].some((key) => key in value);
|
|
1781
|
-
}
|
|
1782
|
-
/** Non-array object used by the MCP JSON object fields. */
|
|
1783
|
-
function isRecord(value) {
|
|
1784
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1785
|
-
}
|
|
1786
|
-
function optionalString(value) {
|
|
1787
|
-
return value === void 0 || typeof value === "string";
|
|
1788
|
-
}
|
|
1789
|
-
function validAnnotations(value) {
|
|
1790
|
-
if (value === void 0) return true;
|
|
1791
|
-
if (!isRecord(value)) return false;
|
|
1792
|
-
return (value.audience === void 0 || Array.isArray(value.audience) && value.audience.every((role) => role === "user" || role === "assistant")) && (value.priority === void 0 || typeof value.priority === "number" && value.priority >= 0 && value.priority <= 1) && optionalString(value.lastModified);
|
|
1793
|
-
}
|
|
1794
|
-
function validMetadata(value) {
|
|
1795
|
-
return (value["_meta"] === void 0 || isRecord(value["_meta"])) && validAnnotations(value.annotations);
|
|
1796
|
-
}
|
|
1797
|
-
function validIcon(value) {
|
|
1798
|
-
return isRecord(value) && typeof value.src === "string" && optionalString(value.mimeType) && (value.sizes === void 0 || Array.isArray(value.sizes) && value.sizes.every((size) => typeof size === "string")) && (value.theme === void 0 || value.theme === "light" || value.theme === "dark");
|
|
1799
|
-
}
|
|
1800
|
-
function validContent(value) {
|
|
1801
|
-
if (!isRecord(value) || !validMetadata(value)) return false;
|
|
1802
|
-
switch (value.type) {
|
|
1803
|
-
case "text": return typeof value.text === "string";
|
|
1804
|
-
case "image":
|
|
1805
|
-
case "audio": return typeof value.data === "string" && typeof value.mimeType === "string";
|
|
1806
|
-
case "resource_link": return typeof value.uri === "string" && typeof value.name === "string" && optionalString(value.title) && optionalString(value.description) && optionalString(value.mimeType) && (value.size === void 0 || typeof value.size === "number") && (value.icons === void 0 || Array.isArray(value.icons) && value.icons.every(validIcon));
|
|
1807
|
-
case "resource": {
|
|
1808
|
-
const resource = value.resource;
|
|
1809
|
-
return isRecord(resource) && typeof resource.uri === "string" && optionalString(resource.mimeType) && validMetadata(resource) && (typeof resource.text === "string" || typeof resource.blob === "string");
|
|
1810
|
-
}
|
|
1811
|
-
default: return false;
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
/** Reject values JSON would drop, coerce or fail to encode (including cycles). */
|
|
1815
|
-
function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
1816
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1817
|
-
if (typeof value === "number") return Number.isFinite(value);
|
|
1818
|
-
if (typeof value !== "object" || ancestors.has(value)) return false;
|
|
1819
|
-
const prototype = Object.getPrototypeOf(value);
|
|
1820
|
-
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false;
|
|
1821
|
-
ancestors.add(value);
|
|
1822
|
-
const valid = Array.isArray(value) ? Array.from(value).every((entry) => isJsonValue(entry, ancestors)) : Object.values(value).every((entry) => entry === void 0 || isJsonValue(entry, ancestors));
|
|
1823
|
-
ancestors.delete(value);
|
|
1824
|
-
return valid;
|
|
1825
|
-
}
|
|
1826
|
-
/**
|
|
1827
|
-
* MCP 2025-11-25 CallToolResult and ContentBlock fields:
|
|
1828
|
-
* https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-result
|
|
1829
|
-
* https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts
|
|
1830
|
-
* structuredContent is preserved even when tools/list declares outputSchema.
|
|
1831
|
-
* Tool authors remain responsible for conformity to that application schema.
|
|
1832
|
-
*/
|
|
1833
|
-
function assertMcpResult(value) {
|
|
1834
|
-
if (!isRecord(value) || !Array.isArray(value.content) || !Array.from(value.content).every(validContent) || value.isError !== void 0 && typeof value.isError !== "boolean" || value.structuredContent !== void 0 && !isRecord(value.structuredContent) || value["_meta"] !== void 0 && !isRecord(value["_meta"]) || !isJsonValue(value)) throw new TypeError("Invalid native MCP CallToolResult.");
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
2021
|
//#endregion
|
|
1838
2022
|
//#region ../../packages/ai-tools/src/mcp/serve.ts
|
|
1839
2023
|
/** JSON-RPC version literal every outbound message carries. */
|
|
@@ -1952,7 +2136,10 @@ var McpServeHandler = class {
|
|
|
1952
2136
|
*/
|
|
1953
2137
|
async handle(request, context = {}) {
|
|
1954
2138
|
try {
|
|
1955
|
-
return this.ok(request.id, await this.dispatch(request,
|
|
2139
|
+
return this.ok(request.id, await this.dispatch(request, {
|
|
2140
|
+
...context,
|
|
2141
|
+
requestId: request.id
|
|
2142
|
+
}));
|
|
1956
2143
|
} catch (cause) {
|
|
1957
2144
|
if (cause instanceof RpcFailure) return this.error(request.id, cause.code, cause.message, cause.data);
|
|
1958
2145
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -1964,8 +2151,14 @@ var McpServeHandler = class {
|
|
|
1964
2151
|
switch (request.method) {
|
|
1965
2152
|
case "initialize": return this.initializeResult(request.params);
|
|
1966
2153
|
case "ping": return {};
|
|
1967
|
-
case "tools/list": return { tools: this.listTools(
|
|
1968
|
-
|
|
2154
|
+
case "tools/list": return { tools: await this.listTools({
|
|
2155
|
+
...context,
|
|
2156
|
+
requestId: request.id
|
|
2157
|
+
}) };
|
|
2158
|
+
case "tools/call": return this.callTool(request.params, {
|
|
2159
|
+
...context,
|
|
2160
|
+
requestId: request.id
|
|
2161
|
+
});
|
|
1969
2162
|
}
|
|
1970
2163
|
if (this.resources) switch (request.method) {
|
|
1971
2164
|
case "resources/list": return { resources: this.listResources(this.resources) };
|
|
@@ -2004,9 +2197,9 @@ var McpServeHandler = class {
|
|
|
2004
2197
|
* the configured dialect (default `draft-2020-12` — overriding
|
|
2005
2198
|
* `extractJsonSchema`'s own `openai-strict` default to a neutral MCP draft).
|
|
2006
2199
|
*/
|
|
2007
|
-
listTools() {
|
|
2200
|
+
async listTools(context) {
|
|
2008
2201
|
const target = this.options.schemaTarget ?? DEFAULT_SCHEMA_TARGET;
|
|
2009
|
-
|
|
2202
|
+
const tools = this.tools.map(({ name, contract }) => {
|
|
2010
2203
|
const inputSchema = (0, _warlock_js_ai.extractJsonSchema)(contract.input, { target }) ?? {
|
|
2011
2204
|
type: "object",
|
|
2012
2205
|
properties: {}
|
|
@@ -2018,6 +2211,8 @@ var McpServeHandler = class {
|
|
|
2018
2211
|
...isRecord(contract.meta?.outputSchema) ? { outputSchema: contract.meta.outputSchema } : {}
|
|
2019
2212
|
};
|
|
2020
2213
|
});
|
|
2214
|
+
context.signal?.throwIfAborted();
|
|
2215
|
+
return this.options.listFilter ? this.options.listFilter(context, tools) : tools;
|
|
2021
2216
|
}
|
|
2022
2217
|
/**
|
|
2023
2218
|
* Route a `tools/call` to the named contract's `invoke()` and map the
|
|
@@ -2031,12 +2226,25 @@ var McpServeHandler = class {
|
|
|
2031
2226
|
*/
|
|
2032
2227
|
async callTool(params, context) {
|
|
2033
2228
|
const { name, args } = readCallParams(params);
|
|
2229
|
+
if (this.options.validateCallParams) {
|
|
2230
|
+
context.signal?.throwIfAborted();
|
|
2231
|
+
try {
|
|
2232
|
+
await this.options.validateCallParams(context, readRecord(params));
|
|
2233
|
+
} catch (cause) {
|
|
2234
|
+
if (isMcpError(cause)) throw new RpcFailure(cause.code, cause.message, cause.data);
|
|
2235
|
+
throw cause;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2034
2238
|
const contract = this.byName.get(name);
|
|
2035
2239
|
if (!contract) throw new McpTransportError(`Unknown tool "${name}".`, {
|
|
2036
2240
|
type: "protocol",
|
|
2037
2241
|
method: "tools/call"
|
|
2038
2242
|
});
|
|
2039
|
-
const toolContext = {
|
|
2243
|
+
const toolContext = {
|
|
2244
|
+
artifacts: {},
|
|
2245
|
+
requestId: context.requestId,
|
|
2246
|
+
principal: context.principal
|
|
2247
|
+
};
|
|
2040
2248
|
if (context.signal) toolContext.signal = context.signal;
|
|
2041
2249
|
if (context.request) {
|
|
2042
2250
|
const { headers, remoteAddress } = context.request;
|
|
@@ -2045,6 +2253,49 @@ var McpServeHandler = class {
|
|
|
2045
2253
|
remoteAddress
|
|
2046
2254
|
};
|
|
2047
2255
|
}
|
|
2256
|
+
if (this.options.beforeCall) {
|
|
2257
|
+
let input = args;
|
|
2258
|
+
if (contract.input) {
|
|
2259
|
+
let validation;
|
|
2260
|
+
try {
|
|
2261
|
+
validation = await contract.input["~standard"].validate(args);
|
|
2262
|
+
} catch (cause) {
|
|
2263
|
+
return {
|
|
2264
|
+
content: [{
|
|
2265
|
+
type: "text",
|
|
2266
|
+
text: `Schema validation threw for tool "${name}": ${cause instanceof Error ? cause.message : String(cause)}`
|
|
2267
|
+
}],
|
|
2268
|
+
isError: true
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
if (validation.issues) return {
|
|
2272
|
+
content: [{
|
|
2273
|
+
type: "text",
|
|
2274
|
+
text: `Validation failed: ${validation.issues.map((issue) => issue.message).join("; ")}`
|
|
2275
|
+
}],
|
|
2276
|
+
isError: true
|
|
2277
|
+
};
|
|
2278
|
+
input = validation.value;
|
|
2279
|
+
}
|
|
2280
|
+
context.signal?.throwIfAborted();
|
|
2281
|
+
let refusal;
|
|
2282
|
+
try {
|
|
2283
|
+
refusal = await this.options.beforeCall(context, contract, input);
|
|
2284
|
+
} catch (cause) {
|
|
2285
|
+
if (isMcpError(cause)) throw new RpcFailure(cause.code, cause.message, cause.data);
|
|
2286
|
+
throw cause;
|
|
2287
|
+
}
|
|
2288
|
+
if (refusal !== void 0) {
|
|
2289
|
+
try {
|
|
2290
|
+
assertMcpResult(refusal);
|
|
2291
|
+
if (refusal.isError !== true) throw new Error("A refusal must be an error result.");
|
|
2292
|
+
} catch {
|
|
2293
|
+
throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, "Invalid MCP beforeCall refusal.");
|
|
2294
|
+
}
|
|
2295
|
+
return refusal;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
context.signal?.throwIfAborted();
|
|
2048
2299
|
const result = await contract.invoke(args, toolContext);
|
|
2049
2300
|
if (result.error) return {
|
|
2050
2301
|
content: [{
|
|
@@ -2175,7 +2426,7 @@ const JSON_RPC_INTERNAL_ERROR = -32603;
|
|
|
2175
2426
|
* answered with a JSON-RPC "Invalid Request" error whose `id` is `null`.
|
|
2176
2427
|
* Never rejects: a dispatch failure is answered as an internal error.
|
|
2177
2428
|
*/
|
|
2178
|
-
async function answerStdioLine(line, dispatch) {
|
|
2429
|
+
async function answerStdioLine(line, dispatch, notify) {
|
|
2179
2430
|
const trimmed = line.trim();
|
|
2180
2431
|
if (!trimmed) return;
|
|
2181
2432
|
let message;
|
|
@@ -2194,10 +2445,21 @@ async function answerStdioLine(line, dispatch) {
|
|
|
2194
2445
|
})}\n`;
|
|
2195
2446
|
const request = message;
|
|
2196
2447
|
const id = request.id;
|
|
2197
|
-
if (id === void 0 || id === null)
|
|
2448
|
+
if (id === void 0 || id === null) {
|
|
2449
|
+
notify?.(message);
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (typeof id !== "string" && typeof id !== "number") return `${JSON.stringify({
|
|
2453
|
+
jsonrpc: JSONRPC_VERSION,
|
|
2454
|
+
id: null,
|
|
2455
|
+
error: {
|
|
2456
|
+
code: JSON_RPC_INVALID_REQUEST,
|
|
2457
|
+
message: "Invalid request: id must be a string or number."
|
|
2458
|
+
}
|
|
2459
|
+
})}\n`;
|
|
2198
2460
|
try {
|
|
2199
2461
|
const response = await dispatch(request);
|
|
2200
|
-
return `${JSON.stringify(response)}\n`;
|
|
2462
|
+
return response === void 0 ? void 0 : `${JSON.stringify(response)}\n`;
|
|
2201
2463
|
} catch {
|
|
2202
2464
|
return `${JSON.stringify({
|
|
2203
2465
|
jsonrpc: JSONRPC_VERSION,
|
|
@@ -2281,7 +2543,7 @@ var McpServerImpl = class {
|
|
|
2281
2543
|
start() {
|
|
2282
2544
|
if (!this.running) {
|
|
2283
2545
|
const transport = this.options.transport ?? { type: "stdio" };
|
|
2284
|
-
const starting = transport.type === "http" ? this.startHttp(transport) : Promise.resolve(this.startStdio());
|
|
2546
|
+
const starting = transport.type === "http" ? this.startHttp(transport) : Promise.resolve(this.startStdio(transport));
|
|
2285
2547
|
this.running = starting.catch((error) => {
|
|
2286
2548
|
this.running = void 0;
|
|
2287
2549
|
throw error;
|
|
@@ -2301,19 +2563,29 @@ var McpServerImpl = class {
|
|
|
2301
2563
|
};
|
|
2302
2564
|
}
|
|
2303
2565
|
/** Wire the stdin line reader and answer each request on stdout. */
|
|
2304
|
-
startStdio() {
|
|
2566
|
+
startStdio(transport) {
|
|
2567
|
+
const shutdown = new AbortController();
|
|
2568
|
+
const requests = new McpServeRequests();
|
|
2305
2569
|
const reader = (0, node_readline.createInterface)({ input: process.stdin });
|
|
2570
|
+
reader.once("close", () => shutdown.abort());
|
|
2306
2571
|
reader.on("line", (line) => {
|
|
2307
|
-
this.onLine(line).catch(() => {});
|
|
2572
|
+
this.onLine(line, transport, requests, shutdown.signal).catch(() => {});
|
|
2308
2573
|
});
|
|
2309
2574
|
return { close: async () => {
|
|
2575
|
+
shutdown.abort();
|
|
2310
2576
|
reader.close();
|
|
2311
2577
|
this.running = void 0;
|
|
2312
2578
|
} };
|
|
2313
2579
|
}
|
|
2314
2580
|
/** Answer one stdin line on stdout (see {@link answerStdioLine}). */
|
|
2315
|
-
async onLine(line) {
|
|
2316
|
-
const reply = await answerStdioLine(line, (request) =>
|
|
2581
|
+
async onLine(line, transport, requests, signal) {
|
|
2582
|
+
const reply = await answerStdioLine(line, (request) => requests.run(request, { signal }, async (context) => {
|
|
2583
|
+
const principal = transport.principalProvider ? await transport.principalProvider(context) : transport.principal;
|
|
2584
|
+
return this.handler.handle(request, {
|
|
2585
|
+
...context,
|
|
2586
|
+
principal
|
|
2587
|
+
});
|
|
2588
|
+
}), (message) => requests.cancel(message));
|
|
2317
2589
|
if (reply !== void 0) process.stdout.write(reply);
|
|
2318
2590
|
}
|
|
2319
2591
|
async stop() {
|
|
@@ -3397,20 +3669,20 @@ function webSearchTool(options) {
|
|
|
3397
3669
|
|
|
3398
3670
|
//#endregion
|
|
3399
3671
|
//#region ../../packages/ai-tools/src/register.ts
|
|
3400
|
-
_warlock_js_ai.ai.tools
|
|
3672
|
+
Object.assign(_warlock_js_ai.ai.tools, {
|
|
3401
3673
|
webSearch: webSearchTool,
|
|
3402
3674
|
fetchUrl: fetchUrlTool,
|
|
3403
3675
|
http: httpRequestTool,
|
|
3404
3676
|
calculator: calculatorTool,
|
|
3405
|
-
dateTime: (options) => dateTimeTool(options)
|
|
3406
|
-
|
|
3407
|
-
};
|
|
3677
|
+
dateTime: (options) => dateTimeTool(options)
|
|
3678
|
+
});
|
|
3408
3679
|
_warlock_js_ai.ai.mcp = mcp;
|
|
3409
3680
|
|
|
3410
3681
|
//#endregion
|
|
3411
3682
|
exports.CalculatorError = CalculatorError;
|
|
3412
3683
|
exports.DateTimeError = DateTimeError;
|
|
3413
3684
|
exports.HttpPolicyError = HttpPolicyError;
|
|
3685
|
+
exports.McpError = McpError;
|
|
3414
3686
|
exports.McpServeError = McpServeError;
|
|
3415
3687
|
exports.McpTransportError = McpTransportError;
|
|
3416
3688
|
exports.SUPPORTED_PROTOCOL_VERSIONS = SUPPORTED_PROTOCOL_VERSIONS;
|