@warlock.js/ai-tools 5.31.0 → 5.32.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/cjs/index.cjs +130 -26
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/contracts/http.type.d.mts +4 -5
  5. package/esm/contracts/http.type.d.mts.map +1 -1
  6. package/esm/contracts/index.d.mts +2 -1
  7. package/esm/contracts/mcp-serve.type.d.mts +61 -33
  8. package/esm/contracts/mcp-serve.type.d.mts.map +1 -1
  9. package/esm/contracts/mcp.type.d.mts +29 -19
  10. package/esm/contracts/mcp.type.d.mts.map +1 -1
  11. package/esm/contracts/utility.type.d.mts +7 -8
  12. package/esm/contracts/utility.type.d.mts.map +1 -1
  13. package/esm/contracts/web.type.d.mts +9 -10
  14. package/esm/contracts/web.type.d.mts.map +1 -1
  15. package/esm/errors.d.mts +31 -26
  16. package/esm/errors.d.mts.map +1 -1
  17. package/esm/http/http-request.d.mts +2 -3
  18. package/esm/http/http-request.d.mts.map +1 -1
  19. package/esm/http/http-request.mjs +7 -5
  20. package/esm/http/http-request.mjs.map +1 -1
  21. package/esm/index.d.mts +5 -1
  22. package/esm/index.mjs +2 -1
  23. package/esm/mcp/client.mjs +2 -1
  24. package/esm/mcp/client.mjs.map +1 -1
  25. package/esm/mcp/index.d.mts +5 -4
  26. package/esm/mcp/index.d.mts.map +1 -1
  27. package/esm/mcp/index.mjs +2 -1
  28. package/esm/mcp/index.mjs.map +1 -1
  29. package/esm/mcp/json-schema-to-standard.d.mts +1 -3
  30. package/esm/mcp/json-schema-to-standard.d.mts.map +1 -1
  31. package/esm/mcp/protocol.d.mts +1 -2
  32. package/esm/mcp/protocol.d.mts.map +1 -1
  33. package/esm/mcp/result.d.mts +17 -0
  34. package/esm/mcp/result.d.mts.map +1 -0
  35. package/esm/mcp/result.mjs +85 -0
  36. package/esm/mcp/result.mjs.map +1 -0
  37. package/esm/mcp/serve-http.mjs +4 -3
  38. package/esm/mcp/serve-http.mjs.map +1 -1
  39. package/esm/mcp/serve.d.mts +5 -5
  40. package/esm/mcp/serve.d.mts.map +1 -1
  41. package/esm/mcp/serve.mjs +17 -7
  42. package/esm/mcp/serve.mjs.map +1 -1
  43. package/esm/mcp/transport.d.mts +4 -5
  44. package/esm/mcp/transport.d.mts.map +1 -1
  45. package/esm/mcp/transport.mjs +12 -6
  46. package/esm/mcp/transport.mjs.map +1 -1
  47. package/esm/mcp/transport.type.d.mts +2 -2
  48. package/esm/mcp/transport.type.d.mts.map +1 -1
  49. package/esm/register.d.mts +3 -4
  50. package/esm/register.d.mts.map +1 -1
  51. package/esm/utility/calculator.d.mts +1 -3
  52. package/esm/utility/calculator.d.mts.map +1 -1
  53. package/esm/utility/date-time.d.mts +3 -5
  54. package/esm/utility/date-time.d.mts.map +1 -1
  55. package/esm/utility/date-time.mjs +2 -1
  56. package/esm/utility/date-time.mjs.map +1 -1
  57. package/esm/utility/index.d.mts +2 -1
  58. package/esm/utility/index.mjs +1 -1
  59. package/esm/web/fetch-url.d.mts +2 -3
  60. package/esm/web/fetch-url.d.mts.map +1 -1
  61. package/esm/web/fetch-url.mjs +2 -1
  62. package/esm/web/fetch-url.mjs.map +1 -1
  63. package/esm/web/index.d.mts +2 -1
  64. package/esm/web/index.mjs +1 -1
  65. package/esm/web/web-search.d.mts +2 -3
  66. package/esm/web/web-search.d.mts.map +1 -1
  67. package/esm/web/web-search.mjs +3 -4
  68. package/esm/web/web-search.mjs.map +1 -1
  69. package/llms-full.txt +53 -1
  70. package/package.json +2 -2
  71. package/skills/expose-as-mcp-server/SKILL.md +53 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ 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.32.0 - 2026-10-18
8
+
9
+ ### Changed
10
+
11
+ - Pin the package development compiler to TypeScript 7.0.2 and use explicit Node types and source roots with supported module resolution.
12
+
13
+ ### Fixed
14
+
15
+ - 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.
16
+ - Malformed native results produce a JSON-RPC internal error instead of sending invalid MCP payloads.
17
+
18
+ ### Added
19
+
20
+ - `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.
21
+ - `meta.outputSchema` advertises structured tool output in `tools/list`; `McpToolCallResult` now includes `structuredContent` and `_meta`.
22
+
7
23
  ## 5.31.0 - 2026-10-18
8
24
 
9
25
  ### 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) if (typeof input.body === "string") body = input.body;
466
- else {
467
- body = JSON.stringify(input.body);
468
- if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) headers["content-type"] = "application/json";
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) if (signal.aborted) queueMicrotask(onAbort);
721
- else signal.addEventListener("abort", onAbort, { once: true });
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) if (signal.aborted) controller.abort();
875
- else signal.addEventListener("abort", onAbort, { once: true });
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
- throw new McpTransportError(`MCP http transport notification "${method}" failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
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
- return new JsonRpcClient(isTransportConfig(source) ? createTransport(source) : withIdAllocator(source));
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
- throw new McpTransportError(`MCP initialize handshake failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
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
@@ -1342,7 +1351,7 @@ const JSON_RPC_INVALID_REQUEST$1 = -32600;
1342
1351
  /** Generic server-defined JSON-RPC code for HTTP-level refusals (auth, origin, size). */
1343
1352
  const JSON_RPC_SERVER_ERROR = -32e3;
1344
1353
  /** Host names that always mean "this machine". */
1345
- const LOOPBACK_NAMES = new Set([
1354
+ const LOOPBACK_NAMES = /* @__PURE__ */ new Set([
1346
1355
  "localhost",
1347
1356
  "127.0.0.1",
1348
1357
  "::1",
@@ -1569,7 +1578,8 @@ async function handleRequest(request, response, settings, dispatch, signal) {
1569
1578
  };
1570
1579
  if (request.socket.remoteAddress !== void 0) info.remoteAddress = request.socket.remoteAddress;
1571
1580
  if (!await authorized(info, settings)) {
1572
- sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", settings.tokenDigests.length > 0 ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {});
1581
+ const challenge = settings.tokenDigests.length > 0 ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {};
1582
+ sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", challenge);
1573
1583
  return;
1574
1584
  }
1575
1585
  if (method !== "POST") {
@@ -1741,6 +1751,89 @@ function readBody$1(request, limit) {
1741
1751
  });
1742
1752
  }
1743
1753
 
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
+
1744
1837
  //#endregion
1745
1838
  //#region ../../packages/ai-tools/src/mcp/serve.ts
1746
1839
  /** JSON-RPC version literal every outbound message carries. */
@@ -1873,18 +1966,15 @@ var McpServeHandler = class {
1873
1966
  case "ping": return {};
1874
1967
  case "tools/list": return { tools: this.listTools() };
1875
1968
  case "tools/call": return this.callTool(request.params, context);
1876
- default: break;
1877
1969
  }
1878
1970
  if (this.resources) switch (request.method) {
1879
1971
  case "resources/list": return { resources: this.listResources(this.resources) };
1880
1972
  case "resources/templates/list": return { resourceTemplates: [] };
1881
1973
  case "resources/read": return this.readResource(this.resources, request.params, context);
1882
- default: break;
1883
1974
  }
1884
1975
  if (this.prompts) switch (request.method) {
1885
1976
  case "prompts/list": return { prompts: this.listPrompts(this.prompts) };
1886
1977
  case "prompts/get": return this.getPrompt(this.prompts, request.params, context);
1887
- default: break;
1888
1978
  }
1889
1979
  throw new RpcFailure(JSON_RPC_METHOD_NOT_FOUND, `Method "${request.method}" is not supported by this MCP server.`);
1890
1980
  }
@@ -1924,15 +2014,17 @@ var McpServeHandler = class {
1924
2014
  return {
1925
2015
  name,
1926
2016
  description: contract.description,
1927
- inputSchema
2017
+ inputSchema,
2018
+ ...isRecord(contract.meta?.outputSchema) ? { outputSchema: contract.meta.outputSchema } : {}
1928
2019
  };
1929
2020
  });
1930
2021
  }
1931
2022
  /**
1932
2023
  * Route a `tools/call` to the named contract's `invoke()` and map the
1933
2024
  * never-throwing {@link import("@warlock.js/ai").ToolInvokeResult}: `data`
1934
- * → a text content block, `error` → an `isError: true` result. An unknown
1935
- * tool name throws (mapped to a JSON-RPC error by {@link handle}).
2025
+ * → a text content block (or an opted-in native result), `error` → an
2026
+ * `isError: true` result. An unknown tool name throws (mapped to a
2027
+ * JSON-RPC error by {@link handle}).
1936
2028
  *
1937
2029
  * The contract receives a `ToolContext` carrying the abort signal and,
1938
2030
  * over HTTP, the inbound request's headers on `ctx.request`.
@@ -1961,6 +2053,16 @@ var McpServeHandler = class {
1961
2053
  }],
1962
2054
  isError: true
1963
2055
  };
2056
+ const mode = contract.meta?.mcpResults ?? this.options.results ?? "text";
2057
+ if (isMcpResult(result.data) || mode === "native" && looksLikeMcpResult(result.data)) {
2058
+ const native = isMcpResult(result.data) ? unwrapMcpResult(result.data) : result.data;
2059
+ try {
2060
+ assertMcpResult(native);
2061
+ } catch {
2062
+ throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, "Invalid native MCP CallToolResult.");
2063
+ }
2064
+ return native;
2065
+ }
1964
2066
  return {
1965
2067
  content: [{
1966
2068
  type: "text",
@@ -2674,7 +2776,8 @@ const UNIT_ALIASES = {
2674
2776
  };
2675
2777
  /** Resolve a (possibly aliased / singular) unit token to its ms length. */
2676
2778
  function unitToMs(unit) {
2677
- const ms = UNIT_MS[UNIT_ALIASES[unit] ?? unit];
2779
+ const canonical = UNIT_ALIASES[unit] ?? unit;
2780
+ const ms = UNIT_MS[canonical];
2678
2781
  if (ms === void 0) throw new DateTimeError(`Unknown unit "${unit}". Supported units: ${Object.keys(UNIT_MS).join(", ")}.`, { type: "invalid-unit" });
2679
2782
  return ms;
2680
2783
  }
@@ -3114,7 +3217,8 @@ function fetchUrlTool(options) {
3114
3217
  type: "denied-host",
3115
3218
  cause
3116
3219
  });
3117
- throw new WebToolError(`fetch_url request failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
3220
+ const message = cause instanceof Error ? cause.message : String(cause);
3221
+ throw new WebToolError(`fetch_url request failed: ${message}`, {
3118
3222
  type: "request-failed",
3119
3223
  cause
3120
3224
  });
@@ -3178,7 +3282,8 @@ async function fetchJson(url, init, provider) {
3178
3282
  try {
3179
3283
  response = await fetch(url, init);
3180
3284
  } catch (cause) {
3181
- throw new WebToolError(`web_search request to the "${provider}" provider failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
3285
+ const message = cause instanceof Error ? cause.message : String(cause);
3286
+ throw new WebToolError(`web_search request to the "${provider}" provider failed: ${message}`, {
3182
3287
  type: "request-failed",
3183
3288
  cause
3184
3289
  });
@@ -3283,9 +3388,7 @@ function webSearchTool(options) {
3283
3388
  case "brave":
3284
3389
  results = await searchBrave(input.query, limit, apiKey);
3285
3390
  break;
3286
- case "serpapi":
3287
- results = await searchSerpApi(input.query, limit, apiKey);
3288
- break;
3391
+ case "serpapi": results = await searchSerpApi(input.query, limit, apiKey);
3289
3392
  }
3290
3393
  return { results };
3291
3394
  }
@@ -3321,6 +3424,7 @@ exports.fetchUrlTool = fetchUrlTool;
3321
3424
  exports.httpRequestTool = httpRequestTool;
3322
3425
  exports.jsonSchemaToStandard = jsonSchemaToStandard;
3323
3426
  exports.mcp = mcp;
3427
+ exports.mcpResult = mcpResult;
3324
3428
  exports.serve = serve;
3325
3429
  exports.webSearchTool = webSearchTool;
3326
3430
  //# sourceMappingURL=index.cjs.map