@warlock.js/ai-tools 5.31.0 → 5.33.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 +26 -0
- package/cjs/index.cjs +327 -60
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/http.type.d.mts +4 -5
- package/esm/contracts/http.type.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +2 -1
- package/esm/contracts/mcp-serve.type.d.mts +97 -37
- package/esm/contracts/mcp-serve.type.d.mts.map +1 -1
- package/esm/contracts/mcp.type.d.mts +29 -19
- package/esm/contracts/mcp.type.d.mts.map +1 -1
- package/esm/contracts/utility.type.d.mts +7 -8
- package/esm/contracts/utility.type.d.mts.map +1 -1
- package/esm/contracts/web.type.d.mts +9 -10
- package/esm/contracts/web.type.d.mts.map +1 -1
- package/esm/errors.d.mts +31 -26
- package/esm/errors.d.mts.map +1 -1
- package/esm/http/http-request.d.mts +2 -3
- package/esm/http/http-request.d.mts.map +1 -1
- package/esm/http/http-request.mjs +7 -5
- package/esm/http/http-request.mjs.map +1 -1
- package/esm/index.d.mts +6 -2
- package/esm/index.mjs +2 -1
- package/esm/mcp/client.mjs +2 -1
- package/esm/mcp/client.mjs.map +1 -1
- package/esm/mcp/index.d.mts +5 -4
- 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/json-schema-to-standard.d.mts +1 -3
- package/esm/mcp/json-schema-to-standard.d.mts.map +1 -1
- package/esm/mcp/protocol.d.mts +1 -2
- package/esm/mcp/protocol.d.mts.map +1 -1
- package/esm/mcp/result.d.mts +17 -0
- package/esm/mcp/result.d.mts.map +1 -0
- package/esm/mcp/result.mjs +85 -0
- package/esm/mcp/result.mjs.map +1 -0
- package/esm/mcp/serve-http.mjs +64 -25
- 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 +5 -5
- package/esm/mcp/serve.d.mts.map +1 -1
- package/esm/mcp/serve.mjs +97 -18
- package/esm/mcp/serve.mjs.map +1 -1
- package/esm/mcp/transport.d.mts +4 -5
- package/esm/mcp/transport.d.mts.map +1 -1
- package/esm/mcp/transport.mjs +12 -6
- package/esm/mcp/transport.mjs.map +1 -1
- package/esm/mcp/transport.type.d.mts +2 -2
- package/esm/mcp/transport.type.d.mts.map +1 -1
- package/esm/register.d.mts +3 -4
- package/esm/register.d.mts.map +1 -1
- package/esm/utility/calculator.d.mts +1 -3
- package/esm/utility/calculator.d.mts.map +1 -1
- package/esm/utility/date-time.d.mts +3 -5
- package/esm/utility/date-time.d.mts.map +1 -1
- package/esm/utility/date-time.mjs +2 -1
- package/esm/utility/date-time.mjs.map +1 -1
- package/esm/utility/index.d.mts +2 -1
- package/esm/utility/index.mjs +1 -1
- package/esm/web/fetch-url.d.mts +2 -3
- package/esm/web/fetch-url.d.mts.map +1 -1
- package/esm/web/fetch-url.mjs +2 -1
- package/esm/web/fetch-url.mjs.map +1 -1
- package/esm/web/index.d.mts +2 -1
- package/esm/web/index.mjs +1 -1
- package/esm/web/web-search.d.mts +2 -3
- package/esm/web/web-search.d.mts.map +1 -1
- package/esm/web/web-search.mjs +3 -4
- package/esm/web/web-search.mjs.map +1 -1
- package/llms-full.txt +122 -3
- 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 +107 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes to `@warlock.js/ai-tools` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.33.0 - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Keep HTTP and MCP fixtures valid under strict indexed access, accept string JSON-RPC request IDs in serving tests, and replace stdin through its descriptor without pretending a stream is a terminal.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- MCP serving now exposes per-call `requestId`, authenticated `principal` and cancellation-aware signals over HTTP and stdio. HTTP `auth.authenticate` resolves principals alongside bearer/authorize checks; stdio accepts static or provider principals. `beforeCall` validates policy after input validation and can return a native error refusal or throw a JSON-RPC error. `notifications/cancelled` aborts only the matching caller/request and suppresses its response. New hook/context/refusal and stdio transport types are exported.
|
|
16
|
+
|
|
17
|
+
## 5.32.0 - 2026-10-18
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Pin the package development compiler to TypeScript 7.0.2 and use explicit Node types and source roots with supported module resolution.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Opted-in native MCP tool results preserve content blocks, `isError`, `structuredContent` and `_meta` over stdio and HTTP, including tools with an advertised output schema. Per-request headers and abort signals remain available to tools; default result serialization and thrown-tool error handling are unchanged.
|
|
26
|
+
- Malformed native results produce a JSON-RPC internal error instead of sending invalid MCP payloads.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `mcpResult(...)` and its `McpNativeResult` type explicitly mark native tool results. `serve(..., { results: "native" })` enables detection for existing native-returning tools; `meta.mcpResults: "native" | "text"` overrides detection per tool.
|
|
31
|
+
- `meta.outputSchema` advertises structured tool output in `tools/list`; `McpToolCallResult` now includes `structuredContent` and `_meta`.
|
|
32
|
+
|
|
7
33
|
## 5.31.0 - 2026-10-18
|
|
8
34
|
|
|
9
35
|
### Added
|
package/cjs/index.cjs
CHANGED
|
@@ -298,7 +298,7 @@ const ALL_METHODS = [
|
|
|
298
298
|
"DELETE"
|
|
299
299
|
];
|
|
300
300
|
/** Methods that conventionally carry no request body — `body` is dropped for these. */
|
|
301
|
-
const BODYLESS_METHODS = new Set(["GET"]);
|
|
301
|
+
const BODYLESS_METHODS = /* @__PURE__ */ new Set(["GET"]);
|
|
302
302
|
/**
|
|
303
303
|
* Standard Schema for {@link HttpRequestInput}. `url` is required;
|
|
304
304
|
* `method` is constrained to the canonical HTTP verb set (further
|
|
@@ -462,10 +462,12 @@ function httpRequestTool(options = {}) {
|
|
|
462
462
|
...input.headers
|
|
463
463
|
};
|
|
464
464
|
let body;
|
|
465
|
-
if (!BODYLESS_METHODS.has(method) && input.body !== void 0)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
465
|
+
if (!BODYLESS_METHODS.has(method) && input.body !== void 0) {
|
|
466
|
+
if (typeof input.body === "string") body = input.body;
|
|
467
|
+
else {
|
|
468
|
+
body = JSON.stringify(input.body);
|
|
469
|
+
if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
|
|
470
|
+
}
|
|
469
471
|
}
|
|
470
472
|
let response;
|
|
471
473
|
try {
|
|
@@ -717,8 +719,10 @@ function armCall(reject, method, options, onSettle) {
|
|
|
717
719
|
}));
|
|
718
720
|
};
|
|
719
721
|
const signal = options?.signal;
|
|
720
|
-
if (signal)
|
|
721
|
-
|
|
722
|
+
if (signal) {
|
|
723
|
+
if (signal.aborted) queueMicrotask(onAbort);
|
|
724
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
725
|
+
}
|
|
722
726
|
function cleanup() {
|
|
723
727
|
clearTimeout(timer);
|
|
724
728
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
@@ -871,8 +875,10 @@ var HttpTransport = class {
|
|
|
871
875
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
872
876
|
const onAbort = () => controller.abort();
|
|
873
877
|
const signal = options?.signal;
|
|
874
|
-
if (signal)
|
|
875
|
-
|
|
878
|
+
if (signal) {
|
|
879
|
+
if (signal.aborted) controller.abort();
|
|
880
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
881
|
+
}
|
|
876
882
|
let response;
|
|
877
883
|
try {
|
|
878
884
|
response = await fetch(this.url, {
|
|
@@ -937,7 +943,8 @@ var HttpTransport = class {
|
|
|
937
943
|
body: JSON.stringify(notification)
|
|
938
944
|
});
|
|
939
945
|
} catch (cause) {
|
|
940
|
-
|
|
946
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
947
|
+
throw new McpTransportError(`MCP http transport notification "${method}" failed: ${message}`, {
|
|
941
948
|
type: "connect",
|
|
942
949
|
method,
|
|
943
950
|
cause
|
|
@@ -1040,7 +1047,8 @@ var JsonRpcClient = class {
|
|
|
1040
1047
|
* @returns A JSON-RPC client handle.
|
|
1041
1048
|
*/
|
|
1042
1049
|
function createJsonRpcClient(source) {
|
|
1043
|
-
|
|
1050
|
+
const transport = isTransportConfig(source) ? createTransport(source) : withIdAllocator(source);
|
|
1051
|
+
return new JsonRpcClient(transport);
|
|
1044
1052
|
}
|
|
1045
1053
|
/**
|
|
1046
1054
|
* Distinguish an {@link McpTransport} config (a plain object with a `type`
|
|
@@ -1121,7 +1129,8 @@ var McpClientImpl = class {
|
|
|
1121
1129
|
await this.rpc.notify("notifications/initialized");
|
|
1122
1130
|
} catch (cause) {
|
|
1123
1131
|
if (cause instanceof McpTransportError) throw cause;
|
|
1124
|
-
|
|
1132
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
1133
|
+
throw new McpTransportError(`MCP initialize handshake failed: ${message}`, {
|
|
1125
1134
|
type: "connect",
|
|
1126
1135
|
method: "initialize",
|
|
1127
1136
|
cause
|
|
@@ -1306,6 +1315,147 @@ const SUPPORTED_PROTOCOL_VERSIONS = [
|
|
|
1306
1315
|
/** The newest MCP protocol version the server speaks. */
|
|
1307
1316
|
const LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
1308
1317
|
|
|
1318
|
+
//#endregion
|
|
1319
|
+
//#region ../../packages/ai-tools/src/mcp/result.ts
|
|
1320
|
+
const NATIVE_RESULT = Symbol("mcpResult");
|
|
1321
|
+
/**
|
|
1322
|
+
* Opt in to native MCP content, error status, structured output and metadata.
|
|
1323
|
+
* Validation happens after invoke(), so malformed payloads become JSON-RPC
|
|
1324
|
+
* errors rather than being caught as tool execution errors.
|
|
1325
|
+
*/
|
|
1326
|
+
function mcpResult(result) {
|
|
1327
|
+
return { [NATIVE_RESULT]: result };
|
|
1328
|
+
}
|
|
1329
|
+
/** Read a branded value without accepting lookalike application objects. */
|
|
1330
|
+
function isMcpResult(value) {
|
|
1331
|
+
return isRecord(value) && NATIVE_RESULT in value;
|
|
1332
|
+
}
|
|
1333
|
+
/** Get the original payload; no cloning, normalization or field stripping. */
|
|
1334
|
+
function unwrapMcpResult(value) {
|
|
1335
|
+
return value[NATIVE_RESULT];
|
|
1336
|
+
}
|
|
1337
|
+
/** Objects with result fields are candidates only when native mode is enabled. */
|
|
1338
|
+
function looksLikeMcpResult(value) {
|
|
1339
|
+
return isRecord(value) && [
|
|
1340
|
+
"content",
|
|
1341
|
+
"isError",
|
|
1342
|
+
"structuredContent",
|
|
1343
|
+
"_meta"
|
|
1344
|
+
].some((key) => key in value);
|
|
1345
|
+
}
|
|
1346
|
+
/** Non-array object used by the MCP JSON object fields. */
|
|
1347
|
+
function isRecord(value) {
|
|
1348
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1349
|
+
}
|
|
1350
|
+
function optionalString(value) {
|
|
1351
|
+
return value === void 0 || typeof value === "string";
|
|
1352
|
+
}
|
|
1353
|
+
function validAnnotations(value) {
|
|
1354
|
+
if (value === void 0) return true;
|
|
1355
|
+
if (!isRecord(value)) return false;
|
|
1356
|
+
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);
|
|
1357
|
+
}
|
|
1358
|
+
function validMetadata(value) {
|
|
1359
|
+
return (value["_meta"] === void 0 || isRecord(value["_meta"])) && validAnnotations(value.annotations);
|
|
1360
|
+
}
|
|
1361
|
+
function validIcon(value) {
|
|
1362
|
+
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");
|
|
1363
|
+
}
|
|
1364
|
+
function validContent(value) {
|
|
1365
|
+
if (!isRecord(value) || !validMetadata(value)) return false;
|
|
1366
|
+
switch (value.type) {
|
|
1367
|
+
case "text": return typeof value.text === "string";
|
|
1368
|
+
case "image":
|
|
1369
|
+
case "audio": return typeof value.data === "string" && typeof value.mimeType === "string";
|
|
1370
|
+
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));
|
|
1371
|
+
case "resource": {
|
|
1372
|
+
const resource = value.resource;
|
|
1373
|
+
return isRecord(resource) && typeof resource.uri === "string" && optionalString(resource.mimeType) && validMetadata(resource) && (typeof resource.text === "string" || typeof resource.blob === "string");
|
|
1374
|
+
}
|
|
1375
|
+
default: return false;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
/** Reject values JSON would drop, coerce or fail to encode (including cycles). */
|
|
1379
|
+
function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
1380
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
1381
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1382
|
+
if (typeof value !== "object" || ancestors.has(value)) return false;
|
|
1383
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1384
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false;
|
|
1385
|
+
ancestors.add(value);
|
|
1386
|
+
const valid = Array.isArray(value) ? Array.from(value).every((entry) => isJsonValue(entry, ancestors)) : Object.values(value).every((entry) => entry === void 0 || isJsonValue(entry, ancestors));
|
|
1387
|
+
ancestors.delete(value);
|
|
1388
|
+
return valid;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* MCP 2025-11-25 CallToolResult and ContentBlock fields:
|
|
1392
|
+
* https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-result
|
|
1393
|
+
* https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts
|
|
1394
|
+
* structuredContent is preserved even when tools/list declares outputSchema.
|
|
1395
|
+
* Tool authors remain responsible for conformity to that application schema.
|
|
1396
|
+
*/
|
|
1397
|
+
function assertMcpResult(value) {
|
|
1398
|
+
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.");
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
//#endregion
|
|
1402
|
+
//#region ../../packages/ai-tools/src/mcp/serve-requests.ts
|
|
1403
|
+
/**
|
|
1404
|
+
* In-flight requests for one transport. HTTP partitions these by credentials;
|
|
1405
|
+
* stdio has a single connection. Cancellation never crosses those boundaries.
|
|
1406
|
+
* https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation
|
|
1407
|
+
* (checked 2026-10-19): ignore unknown/completed ids and never answer cancelled work.
|
|
1408
|
+
*/
|
|
1409
|
+
var McpServeRequests = class {
|
|
1410
|
+
constructor() {
|
|
1411
|
+
this.scopes = /* @__PURE__ */ new Map();
|
|
1412
|
+
}
|
|
1413
|
+
cancel(message, scope = "") {
|
|
1414
|
+
if (!isRecord(message) || message.jsonrpc !== "2.0" || message.method !== "notifications/cancelled" || message.id !== void 0 || !isRecord(message.params)) return;
|
|
1415
|
+
const { requestId, reason } = message.params;
|
|
1416
|
+
if (typeof requestId !== "string" && typeof requestId !== "number" || reason !== void 0 && typeof reason !== "string") return;
|
|
1417
|
+
const pending = this.scopes.get(scope)?.get(requestId);
|
|
1418
|
+
if (pending?.size === 1) {
|
|
1419
|
+
for (const entry of pending) if (entry.cancellable) entry.controller.abort(new DOMException(typeof reason === "string" ? reason : "Request cancelled.", "AbortError"));
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
async run(request, context, dispatch, scope = "") {
|
|
1423
|
+
let pending = this.scopes.get(scope);
|
|
1424
|
+
if (!pending) {
|
|
1425
|
+
pending = /* @__PURE__ */ new Map();
|
|
1426
|
+
this.scopes.set(scope, pending);
|
|
1427
|
+
}
|
|
1428
|
+
const controller = new AbortController();
|
|
1429
|
+
const entry = {
|
|
1430
|
+
controller,
|
|
1431
|
+
cancellable: request.method !== "initialize"
|
|
1432
|
+
};
|
|
1433
|
+
const calls = pending.get(request.id) ?? /* @__PURE__ */ new Set();
|
|
1434
|
+
calls.add(entry);
|
|
1435
|
+
pending.set(request.id, calls);
|
|
1436
|
+
const onAbort = () => controller.abort(context.signal?.reason);
|
|
1437
|
+
context.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1438
|
+
if (context.signal?.aborted) onAbort();
|
|
1439
|
+
try {
|
|
1440
|
+
if (controller.signal.aborted) return;
|
|
1441
|
+
const response = await dispatch({
|
|
1442
|
+
...context,
|
|
1443
|
+
requestId: request.id,
|
|
1444
|
+
signal: controller.signal
|
|
1445
|
+
});
|
|
1446
|
+
return controller.signal.aborted ? void 0 : response;
|
|
1447
|
+
} catch (cause) {
|
|
1448
|
+
if (controller.signal.aborted) return;
|
|
1449
|
+
throw cause;
|
|
1450
|
+
} finally {
|
|
1451
|
+
context.signal?.removeEventListener("abort", onAbort);
|
|
1452
|
+
calls.delete(entry);
|
|
1453
|
+
if (calls.size === 0) pending.delete(request.id);
|
|
1454
|
+
if (pending.size === 0) this.scopes.delete(scope);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1309
1459
|
//#endregion
|
|
1310
1460
|
//#region ../../packages/ai-tools/src/mcp/serve-http.ts
|
|
1311
1461
|
/**
|
|
@@ -1342,7 +1492,7 @@ const JSON_RPC_INVALID_REQUEST$1 = -32600;
|
|
|
1342
1492
|
/** Generic server-defined JSON-RPC code for HTTP-level refusals (auth, origin, size). */
|
|
1343
1493
|
const JSON_RPC_SERVER_ERROR = -32e3;
|
|
1344
1494
|
/** Host names that always mean "this machine". */
|
|
1345
|
-
const LOOPBACK_NAMES = new Set([
|
|
1495
|
+
const LOOPBACK_NAMES = /* @__PURE__ */ new Set([
|
|
1346
1496
|
"localhost",
|
|
1347
1497
|
"127.0.0.1",
|
|
1348
1498
|
"::1",
|
|
@@ -1404,9 +1554,9 @@ function resolveSettings(transport) {
|
|
|
1404
1554
|
const tokens = auth?.bearer === void 0 ? [] : typeof auth.bearer === "string" ? [auth.bearer] : [...auth.bearer];
|
|
1405
1555
|
if (tokens.some((token) => token.length === 0)) throw new McpServeError("MCP http auth.bearer tokens must be non-empty strings.", { type: "config" });
|
|
1406
1556
|
if (auth && auth.bearer !== void 0 && tokens.length === 0) throw new McpServeError("MCP http auth.bearer needs at least one token.", { type: "config" });
|
|
1407
|
-
if (auth && tokens.length === 0 && !auth.authorize) throw new McpServeError("MCP http auth needs `bearer` and/or `
|
|
1408
|
-
const authRequired = tokens.length > 0 || auth?.authorize !== void 0;
|
|
1409
|
-
if (!loopback && !authRequired) throw new McpServeError(`Refusing to serve MCP over http on non-loopback host "${host}" without auth. Set transport.auth ({ bearer } or {
|
|
1557
|
+
if (auth && tokens.length === 0 && !auth.authorize && !auth.authenticate) throw new McpServeError("MCP http auth needs `bearer`, `authorize` and/or `authenticate`.", { type: "config" });
|
|
1558
|
+
const authRequired = tokens.length > 0 || auth?.authorize !== void 0 || auth?.authenticate !== void 0;
|
|
1559
|
+
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.`, {
|
|
1410
1560
|
type: "config",
|
|
1411
1561
|
context: { host }
|
|
1412
1562
|
});
|
|
@@ -1419,6 +1569,7 @@ function resolveSettings(transport) {
|
|
|
1419
1569
|
drainTimeoutMs,
|
|
1420
1570
|
tokenDigests: tokens.map(digest),
|
|
1421
1571
|
authorize: auth?.authorize,
|
|
1572
|
+
authenticate: auth?.authenticate,
|
|
1422
1573
|
authRequired,
|
|
1423
1574
|
allowedOrigins: new Set((transport.allowedOrigins ?? []).map(normalizeOrigin)),
|
|
1424
1575
|
allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name)))
|
|
@@ -1477,6 +1628,7 @@ function sendEmpty(response, status, headers = {}) {
|
|
|
1477
1628
|
async function startHttpServer(transport, dispatch) {
|
|
1478
1629
|
const settings = resolveSettings(transport);
|
|
1479
1630
|
const shutdown = new AbortController();
|
|
1631
|
+
const requests = new McpServeRequests();
|
|
1480
1632
|
const inFlight = /* @__PURE__ */ new Set();
|
|
1481
1633
|
let closing;
|
|
1482
1634
|
const server = (0, node_http.createServer)((request, response) => {
|
|
@@ -1484,7 +1636,7 @@ async function startHttpServer(transport, dispatch) {
|
|
|
1484
1636
|
sendRefusal(response, 503, JSON_RPC_SERVER_ERROR, "Server is shutting down.", { connection: "close" });
|
|
1485
1637
|
return;
|
|
1486
1638
|
}
|
|
1487
|
-
const work = handleRequest(request, response, settings, dispatch, shutdown.signal).catch(() => {
|
|
1639
|
+
const work = handleRequest(request, response, settings, dispatch, shutdown.signal, requests).catch(() => {
|
|
1488
1640
|
sendRefusal(response, 500, JSON_RPC_SERVER_ERROR, "Internal server error.");
|
|
1489
1641
|
});
|
|
1490
1642
|
inFlight.add(work);
|
|
@@ -1546,7 +1698,7 @@ async function startHttpServer(transport, dispatch) {
|
|
|
1546
1698
|
* Validate and answer one HTTP request: path → Host / Origin → auth →
|
|
1547
1699
|
* method → headers → body → JSON-RPC dispatch.
|
|
1548
1700
|
*/
|
|
1549
|
-
async function handleRequest(request, response, settings, dispatch, signal) {
|
|
1701
|
+
async function handleRequest(request, response, settings, dispatch, signal, requests) {
|
|
1550
1702
|
const headers = flattenHeaders(request.headers);
|
|
1551
1703
|
const method = (request.method ?? "GET").toUpperCase();
|
|
1552
1704
|
const path = new URL(request.url ?? "/", "http://localhost").pathname;
|
|
@@ -1568,8 +1720,10 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
1568
1720
|
headers
|
|
1569
1721
|
};
|
|
1570
1722
|
if (request.socket.remoteAddress !== void 0) info.remoteAddress = request.socket.remoteAddress;
|
|
1571
|
-
|
|
1572
|
-
|
|
1723
|
+
const authentication = await authorized(info, settings);
|
|
1724
|
+
if (!authentication.allowed) {
|
|
1725
|
+
const challenge = settings.tokenDigests.length > 0 || settings.authenticate ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {};
|
|
1726
|
+
sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", challenge);
|
|
1573
1727
|
return;
|
|
1574
1728
|
}
|
|
1575
1729
|
if (method !== "POST") {
|
|
@@ -1613,6 +1767,7 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
1613
1767
|
const id = record.id;
|
|
1614
1768
|
if (typeof record.method === "string") {
|
|
1615
1769
|
if (typeof id !== "string" && typeof id !== "number") {
|
|
1770
|
+
requests.cancel(record, cancellationScope(info));
|
|
1616
1771
|
sendEmpty(response, 202);
|
|
1617
1772
|
return;
|
|
1618
1773
|
}
|
|
@@ -1622,10 +1777,20 @@ async function handleRequest(request, response, settings, dispatch, signal) {
|
|
|
1622
1777
|
method: record.method
|
|
1623
1778
|
};
|
|
1624
1779
|
if (record.params !== void 0) rpcRequest.params = record.params;
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1780
|
+
const disconnected = new AbortController();
|
|
1781
|
+
const onClose = () => disconnected.abort();
|
|
1782
|
+
response.once("close", onClose);
|
|
1783
|
+
try {
|
|
1784
|
+
const rpcResponse = await requests.run(rpcRequest, {
|
|
1785
|
+
request: info,
|
|
1786
|
+
principal: authentication.principal,
|
|
1787
|
+
signal: AbortSignal.any([signal, disconnected.signal])
|
|
1788
|
+
}, (context) => dispatch(rpcRequest, context), cancellationScope(info));
|
|
1789
|
+
if (rpcResponse === void 0) sendEmpty(response, 202);
|
|
1790
|
+
else sendJson(response, 200, rpcResponse);
|
|
1791
|
+
} finally {
|
|
1792
|
+
response.off("close", onClose);
|
|
1793
|
+
}
|
|
1629
1794
|
return;
|
|
1630
1795
|
}
|
|
1631
1796
|
if ((typeof id === "string" || typeof id === "number") && ("result" in record || "error" in record)) {
|
|
@@ -1664,27 +1829,50 @@ function originAllowed(origin, settings) {
|
|
|
1664
1829
|
if (settings.allowedOrigins.has(parsed.origin)) return true;
|
|
1665
1830
|
return (parsed.protocol === "http:" || parsed.protocol === "https:") && isLoopbackHost(parsed.hostname);
|
|
1666
1831
|
}
|
|
1667
|
-
/**
|
|
1668
|
-
* Run the configured auth. Bearer tokens are compared in constant time
|
|
1669
|
-
* over SHA-256 digests (equal-length buffers for `timingSafeEqual`), and
|
|
1670
|
-
* every configured token is compared so the match position does not leak.
|
|
1671
|
-
* An `authorize` hook that throws denies the request.
|
|
1672
|
-
*/
|
|
1673
1832
|
async function authorized(info, settings) {
|
|
1674
|
-
if (!settings.authRequired) return true;
|
|
1833
|
+
if (!settings.authRequired) return { allowed: true };
|
|
1675
1834
|
if (settings.tokenDigests.length > 0) {
|
|
1676
1835
|
const match = /^Bearer[ \t]+(.+)$/i.exec(info.headers.authorization ?? "");
|
|
1677
1836
|
const presented = digest(match?.[1]?.trim() ?? "");
|
|
1678
1837
|
let matched = false;
|
|
1679
1838
|
for (const expected of settings.tokenDigests) matched = (0, node_crypto.timingSafeEqual)(presented, expected) || matched;
|
|
1680
|
-
if (!match || !matched) return false;
|
|
1839
|
+
if (!match || !matched) return { allowed: false };
|
|
1681
1840
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1841
|
+
try {
|
|
1842
|
+
if (settings.authorize && await settings.authorize(info) !== true) return { allowed: false };
|
|
1843
|
+
if (settings.authenticate) {
|
|
1844
|
+
const principal = await settings.authenticate(info);
|
|
1845
|
+
return principal === void 0 ? { allowed: false } : {
|
|
1846
|
+
allowed: true,
|
|
1847
|
+
principal
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
return { allowed: true };
|
|
1684
1851
|
} catch {
|
|
1685
|
-
return false;
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1852
|
+
return { allowed: false };
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Stateless HTTP has no connection/session id. Repeat the caller's credential
|
|
1857
|
+
* and application headers on notifications; per-message transport headers do
|
|
1858
|
+
* not identify a caller. Hash the material rather than retaining secrets as keys.
|
|
1859
|
+
*/
|
|
1860
|
+
function cancellationScope(info) {
|
|
1861
|
+
const transportHeaders = /* @__PURE__ */ new Set([
|
|
1862
|
+
"host",
|
|
1863
|
+
"origin",
|
|
1864
|
+
"accept",
|
|
1865
|
+
"accept-encoding",
|
|
1866
|
+
"accept-language",
|
|
1867
|
+
"connection",
|
|
1868
|
+
"content-type",
|
|
1869
|
+
"content-length",
|
|
1870
|
+
"transfer-encoding",
|
|
1871
|
+
"mcp-protocol-version",
|
|
1872
|
+
"user-agent"
|
|
1873
|
+
]);
|
|
1874
|
+
const credentials = Object.entries(info.headers).filter(([name]) => !transportHeaders.has(name)).sort(([left], [right]) => left.localeCompare(right));
|
|
1875
|
+
return digest(JSON.stringify([info.remoteAddress, credentials])).toString("hex");
|
|
1688
1876
|
}
|
|
1689
1877
|
/** True for `application/json` (any parameters, e.g. `; charset=utf-8`). */
|
|
1690
1878
|
function isJsonContentType(contentType) {
|
|
@@ -1859,7 +2047,10 @@ var McpServeHandler = class {
|
|
|
1859
2047
|
*/
|
|
1860
2048
|
async handle(request, context = {}) {
|
|
1861
2049
|
try {
|
|
1862
|
-
return this.ok(request.id, await this.dispatch(request,
|
|
2050
|
+
return this.ok(request.id, await this.dispatch(request, {
|
|
2051
|
+
...context,
|
|
2052
|
+
requestId: request.id
|
|
2053
|
+
}));
|
|
1863
2054
|
} catch (cause) {
|
|
1864
2055
|
if (cause instanceof RpcFailure) return this.error(request.id, cause.code, cause.message, cause.data);
|
|
1865
2056
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -1872,19 +2063,19 @@ var McpServeHandler = class {
|
|
|
1872
2063
|
case "initialize": return this.initializeResult(request.params);
|
|
1873
2064
|
case "ping": return {};
|
|
1874
2065
|
case "tools/list": return { tools: this.listTools() };
|
|
1875
|
-
case "tools/call": return this.callTool(request.params,
|
|
1876
|
-
|
|
2066
|
+
case "tools/call": return this.callTool(request.params, {
|
|
2067
|
+
...context,
|
|
2068
|
+
requestId: request.id
|
|
2069
|
+
});
|
|
1877
2070
|
}
|
|
1878
2071
|
if (this.resources) switch (request.method) {
|
|
1879
2072
|
case "resources/list": return { resources: this.listResources(this.resources) };
|
|
1880
2073
|
case "resources/templates/list": return { resourceTemplates: [] };
|
|
1881
2074
|
case "resources/read": return this.readResource(this.resources, request.params, context);
|
|
1882
|
-
default: break;
|
|
1883
2075
|
}
|
|
1884
2076
|
if (this.prompts) switch (request.method) {
|
|
1885
2077
|
case "prompts/list": return { prompts: this.listPrompts(this.prompts) };
|
|
1886
2078
|
case "prompts/get": return this.getPrompt(this.prompts, request.params, context);
|
|
1887
|
-
default: break;
|
|
1888
2079
|
}
|
|
1889
2080
|
throw new RpcFailure(JSON_RPC_METHOD_NOT_FOUND, `Method "${request.method}" is not supported by this MCP server.`);
|
|
1890
2081
|
}
|
|
@@ -1924,15 +2115,17 @@ var McpServeHandler = class {
|
|
|
1924
2115
|
return {
|
|
1925
2116
|
name,
|
|
1926
2117
|
description: contract.description,
|
|
1927
|
-
inputSchema
|
|
2118
|
+
inputSchema,
|
|
2119
|
+
...isRecord(contract.meta?.outputSchema) ? { outputSchema: contract.meta.outputSchema } : {}
|
|
1928
2120
|
};
|
|
1929
2121
|
});
|
|
1930
2122
|
}
|
|
1931
2123
|
/**
|
|
1932
2124
|
* Route a `tools/call` to the named contract's `invoke()` and map the
|
|
1933
2125
|
* never-throwing {@link import("@warlock.js/ai").ToolInvokeResult}: `data`
|
|
1934
|
-
* → a text content block, `error` → an
|
|
1935
|
-
* tool name throws (mapped to a
|
|
2126
|
+
* → a text content block (or an opted-in native result), `error` → an
|
|
2127
|
+
* `isError: true` result. An unknown tool name throws (mapped to a
|
|
2128
|
+
* JSON-RPC error by {@link handle}).
|
|
1936
2129
|
*
|
|
1937
2130
|
* The contract receives a `ToolContext` carrying the abort signal and,
|
|
1938
2131
|
* over HTTP, the inbound request's headers on `ctx.request`.
|
|
@@ -1944,7 +2137,11 @@ var McpServeHandler = class {
|
|
|
1944
2137
|
type: "protocol",
|
|
1945
2138
|
method: "tools/call"
|
|
1946
2139
|
});
|
|
1947
|
-
const toolContext = {
|
|
2140
|
+
const toolContext = {
|
|
2141
|
+
artifacts: {},
|
|
2142
|
+
requestId: context.requestId,
|
|
2143
|
+
principal: context.principal
|
|
2144
|
+
};
|
|
1948
2145
|
if (context.signal) toolContext.signal = context.signal;
|
|
1949
2146
|
if (context.request) {
|
|
1950
2147
|
const { headers, remoteAddress } = context.request;
|
|
@@ -1953,6 +2150,43 @@ var McpServeHandler = class {
|
|
|
1953
2150
|
remoteAddress
|
|
1954
2151
|
};
|
|
1955
2152
|
}
|
|
2153
|
+
if (this.options.beforeCall) {
|
|
2154
|
+
let input = args;
|
|
2155
|
+
if (contract.input) {
|
|
2156
|
+
let validation;
|
|
2157
|
+
try {
|
|
2158
|
+
validation = await contract.input["~standard"].validate(args);
|
|
2159
|
+
} catch (cause) {
|
|
2160
|
+
return {
|
|
2161
|
+
content: [{
|
|
2162
|
+
type: "text",
|
|
2163
|
+
text: `Schema validation threw for tool "${name}": ${cause instanceof Error ? cause.message : String(cause)}`
|
|
2164
|
+
}],
|
|
2165
|
+
isError: true
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
2168
|
+
if (validation.issues) return {
|
|
2169
|
+
content: [{
|
|
2170
|
+
type: "text",
|
|
2171
|
+
text: `Validation failed: ${validation.issues.map((issue) => issue.message).join("; ")}`
|
|
2172
|
+
}],
|
|
2173
|
+
isError: true
|
|
2174
|
+
};
|
|
2175
|
+
input = validation.value;
|
|
2176
|
+
}
|
|
2177
|
+
context.signal?.throwIfAborted();
|
|
2178
|
+
const refusal = await this.options.beforeCall(context, contract, input);
|
|
2179
|
+
if (refusal !== void 0) {
|
|
2180
|
+
try {
|
|
2181
|
+
assertMcpResult(refusal);
|
|
2182
|
+
if (refusal.isError !== true) throw new Error("A refusal must be an error result.");
|
|
2183
|
+
} catch {
|
|
2184
|
+
throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, "Invalid MCP beforeCall refusal.");
|
|
2185
|
+
}
|
|
2186
|
+
return refusal;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
context.signal?.throwIfAborted();
|
|
1956
2190
|
const result = await contract.invoke(args, toolContext);
|
|
1957
2191
|
if (result.error) return {
|
|
1958
2192
|
content: [{
|
|
@@ -1961,6 +2195,16 @@ var McpServeHandler = class {
|
|
|
1961
2195
|
}],
|
|
1962
2196
|
isError: true
|
|
1963
2197
|
};
|
|
2198
|
+
const mode = contract.meta?.mcpResults ?? this.options.results ?? "text";
|
|
2199
|
+
if (isMcpResult(result.data) || mode === "native" && looksLikeMcpResult(result.data)) {
|
|
2200
|
+
const native = isMcpResult(result.data) ? unwrapMcpResult(result.data) : result.data;
|
|
2201
|
+
try {
|
|
2202
|
+
assertMcpResult(native);
|
|
2203
|
+
} catch {
|
|
2204
|
+
throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, "Invalid native MCP CallToolResult.");
|
|
2205
|
+
}
|
|
2206
|
+
return native;
|
|
2207
|
+
}
|
|
1964
2208
|
return {
|
|
1965
2209
|
content: [{
|
|
1966
2210
|
type: "text",
|
|
@@ -2073,7 +2317,7 @@ const JSON_RPC_INTERNAL_ERROR = -32603;
|
|
|
2073
2317
|
* answered with a JSON-RPC "Invalid Request" error whose `id` is `null`.
|
|
2074
2318
|
* Never rejects: a dispatch failure is answered as an internal error.
|
|
2075
2319
|
*/
|
|
2076
|
-
async function answerStdioLine(line, dispatch) {
|
|
2320
|
+
async function answerStdioLine(line, dispatch, notify) {
|
|
2077
2321
|
const trimmed = line.trim();
|
|
2078
2322
|
if (!trimmed) return;
|
|
2079
2323
|
let message;
|
|
@@ -2092,10 +2336,21 @@ async function answerStdioLine(line, dispatch) {
|
|
|
2092
2336
|
})}\n`;
|
|
2093
2337
|
const request = message;
|
|
2094
2338
|
const id = request.id;
|
|
2095
|
-
if (id === void 0 || id === null)
|
|
2339
|
+
if (id === void 0 || id === null) {
|
|
2340
|
+
notify?.(message);
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
if (typeof id !== "string" && typeof id !== "number") return `${JSON.stringify({
|
|
2344
|
+
jsonrpc: JSONRPC_VERSION,
|
|
2345
|
+
id: null,
|
|
2346
|
+
error: {
|
|
2347
|
+
code: JSON_RPC_INVALID_REQUEST,
|
|
2348
|
+
message: "Invalid request: id must be a string or number."
|
|
2349
|
+
}
|
|
2350
|
+
})}\n`;
|
|
2096
2351
|
try {
|
|
2097
2352
|
const response = await dispatch(request);
|
|
2098
|
-
return `${JSON.stringify(response)}\n`;
|
|
2353
|
+
return response === void 0 ? void 0 : `${JSON.stringify(response)}\n`;
|
|
2099
2354
|
} catch {
|
|
2100
2355
|
return `${JSON.stringify({
|
|
2101
2356
|
jsonrpc: JSONRPC_VERSION,
|
|
@@ -2179,7 +2434,7 @@ var McpServerImpl = class {
|
|
|
2179
2434
|
start() {
|
|
2180
2435
|
if (!this.running) {
|
|
2181
2436
|
const transport = this.options.transport ?? { type: "stdio" };
|
|
2182
|
-
const starting = transport.type === "http" ? this.startHttp(transport) : Promise.resolve(this.startStdio());
|
|
2437
|
+
const starting = transport.type === "http" ? this.startHttp(transport) : Promise.resolve(this.startStdio(transport));
|
|
2183
2438
|
this.running = starting.catch((error) => {
|
|
2184
2439
|
this.running = void 0;
|
|
2185
2440
|
throw error;
|
|
@@ -2199,19 +2454,29 @@ var McpServerImpl = class {
|
|
|
2199
2454
|
};
|
|
2200
2455
|
}
|
|
2201
2456
|
/** Wire the stdin line reader and answer each request on stdout. */
|
|
2202
|
-
startStdio() {
|
|
2457
|
+
startStdio(transport) {
|
|
2458
|
+
const shutdown = new AbortController();
|
|
2459
|
+
const requests = new McpServeRequests();
|
|
2203
2460
|
const reader = (0, node_readline.createInterface)({ input: process.stdin });
|
|
2461
|
+
reader.once("close", () => shutdown.abort());
|
|
2204
2462
|
reader.on("line", (line) => {
|
|
2205
|
-
this.onLine(line).catch(() => {});
|
|
2463
|
+
this.onLine(line, transport, requests, shutdown.signal).catch(() => {});
|
|
2206
2464
|
});
|
|
2207
2465
|
return { close: async () => {
|
|
2466
|
+
shutdown.abort();
|
|
2208
2467
|
reader.close();
|
|
2209
2468
|
this.running = void 0;
|
|
2210
2469
|
} };
|
|
2211
2470
|
}
|
|
2212
2471
|
/** Answer one stdin line on stdout (see {@link answerStdioLine}). */
|
|
2213
|
-
async onLine(line) {
|
|
2214
|
-
const reply = await answerStdioLine(line, (request) =>
|
|
2472
|
+
async onLine(line, transport, requests, signal) {
|
|
2473
|
+
const reply = await answerStdioLine(line, (request) => requests.run(request, { signal }, async (context) => {
|
|
2474
|
+
const principal = transport.principalProvider ? await transport.principalProvider(context) : transport.principal;
|
|
2475
|
+
return this.handler.handle(request, {
|
|
2476
|
+
...context,
|
|
2477
|
+
principal
|
|
2478
|
+
});
|
|
2479
|
+
}), (message) => requests.cancel(message));
|
|
2215
2480
|
if (reply !== void 0) process.stdout.write(reply);
|
|
2216
2481
|
}
|
|
2217
2482
|
async stop() {
|
|
@@ -2674,7 +2939,8 @@ const UNIT_ALIASES = {
|
|
|
2674
2939
|
};
|
|
2675
2940
|
/** Resolve a (possibly aliased / singular) unit token to its ms length. */
|
|
2676
2941
|
function unitToMs(unit) {
|
|
2677
|
-
const
|
|
2942
|
+
const canonical = UNIT_ALIASES[unit] ?? unit;
|
|
2943
|
+
const ms = UNIT_MS[canonical];
|
|
2678
2944
|
if (ms === void 0) throw new DateTimeError(`Unknown unit "${unit}". Supported units: ${Object.keys(UNIT_MS).join(", ")}.`, { type: "invalid-unit" });
|
|
2679
2945
|
return ms;
|
|
2680
2946
|
}
|
|
@@ -3114,7 +3380,8 @@ function fetchUrlTool(options) {
|
|
|
3114
3380
|
type: "denied-host",
|
|
3115
3381
|
cause
|
|
3116
3382
|
});
|
|
3117
|
-
|
|
3383
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
3384
|
+
throw new WebToolError(`fetch_url request failed: ${message}`, {
|
|
3118
3385
|
type: "request-failed",
|
|
3119
3386
|
cause
|
|
3120
3387
|
});
|
|
@@ -3178,7 +3445,8 @@ async function fetchJson(url, init, provider) {
|
|
|
3178
3445
|
try {
|
|
3179
3446
|
response = await fetch(url, init);
|
|
3180
3447
|
} catch (cause) {
|
|
3181
|
-
|
|
3448
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
3449
|
+
throw new WebToolError(`web_search request to the "${provider}" provider failed: ${message}`, {
|
|
3182
3450
|
type: "request-failed",
|
|
3183
3451
|
cause
|
|
3184
3452
|
});
|
|
@@ -3283,9 +3551,7 @@ function webSearchTool(options) {
|
|
|
3283
3551
|
case "brave":
|
|
3284
3552
|
results = await searchBrave(input.query, limit, apiKey);
|
|
3285
3553
|
break;
|
|
3286
|
-
case "serpapi":
|
|
3287
|
-
results = await searchSerpApi(input.query, limit, apiKey);
|
|
3288
|
-
break;
|
|
3554
|
+
case "serpapi": results = await searchSerpApi(input.query, limit, apiKey);
|
|
3289
3555
|
}
|
|
3290
3556
|
return { results };
|
|
3291
3557
|
}
|
|
@@ -3321,6 +3587,7 @@ exports.fetchUrlTool = fetchUrlTool;
|
|
|
3321
3587
|
exports.httpRequestTool = httpRequestTool;
|
|
3322
3588
|
exports.jsonSchemaToStandard = jsonSchemaToStandard;
|
|
3323
3589
|
exports.mcp = mcp;
|
|
3590
|
+
exports.mcpResult = mcpResult;
|
|
3324
3591
|
exports.serve = serve;
|
|
3325
3592
|
exports.webSearchTool = webSearchTool;
|
|
3326
3593
|
//# sourceMappingURL=index.cjs.map
|