@warlock.js/ai-tools 5.29.0 → 5.31.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 (84) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +25 -13
  3. package/cjs/index.cjs +858 -116
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/contracts/http.type.d.mts +1 -1
  6. package/esm/contracts/http.type.d.mts.map +1 -1
  7. package/esm/contracts/index.d.mts +2 -1
  8. package/esm/contracts/mcp-serve.type.d.mts +257 -0
  9. package/esm/contracts/mcp-serve.type.d.mts.map +1 -0
  10. package/esm/contracts/mcp.type.d.mts +17 -60
  11. package/esm/contracts/mcp.type.d.mts.map +1 -1
  12. package/esm/contracts/utility.type.d.mts +1 -1
  13. package/esm/contracts/utility.type.d.mts.map +1 -1
  14. package/esm/contracts/web.type.d.mts +1 -1
  15. package/esm/contracts/web.type.d.mts.map +1 -1
  16. package/esm/errors.d.mts +37 -2
  17. package/esm/errors.d.mts.map +1 -1
  18. package/esm/errors.mjs +21 -2
  19. package/esm/errors.mjs.map +1 -1
  20. package/esm/http/http-request.d.mts +1 -1
  21. package/esm/http/http-request.d.mts.map +1 -1
  22. package/esm/http/http-request.mjs +1 -1
  23. package/esm/http/http-request.mjs.map +1 -1
  24. package/esm/index.d.mts +6 -4
  25. package/esm/index.mjs +3 -2
  26. package/esm/mcp/client.mjs +36 -6
  27. package/esm/mcp/client.mjs.map +1 -1
  28. package/esm/mcp/index.d.mts +13 -6
  29. package/esm/mcp/index.d.mts.map +1 -1
  30. package/esm/mcp/index.mjs +2 -1
  31. package/esm/mcp/index.mjs.map +1 -1
  32. package/esm/mcp/json-schema-to-standard.d.mts +1 -1
  33. package/esm/mcp/json-schema-to-standard.d.mts.map +1 -1
  34. package/esm/mcp/json-schema-to-standard.mjs +1 -1
  35. package/esm/mcp/json-schema-to-standard.mjs.map +1 -1
  36. package/esm/mcp/protocol.d.mts +16 -0
  37. package/esm/mcp/protocol.d.mts.map +1 -0
  38. package/esm/mcp/protocol.mjs +23 -0
  39. package/esm/mcp/protocol.mjs.map +1 -0
  40. package/esm/mcp/serve-http.mjs +443 -0
  41. package/esm/mcp/serve-http.mjs.map +1 -0
  42. package/esm/mcp/serve.d.mts +46 -27
  43. package/esm/mcp/serve.d.mts.map +1 -1
  44. package/esm/mcp/serve.mjs +326 -93
  45. package/esm/mcp/serve.mjs.map +1 -1
  46. package/esm/mcp/transport.d.mts +1 -1
  47. package/esm/mcp/transport.d.mts.map +1 -1
  48. package/esm/mcp/transport.mjs +1 -1
  49. package/esm/mcp/transport.mjs.map +1 -1
  50. package/esm/mcp/transport.type.d.mts +1 -1
  51. package/esm/mcp/transport.type.d.mts.map +1 -1
  52. package/esm/register.d.mts +13 -2
  53. package/esm/register.d.mts.map +1 -1
  54. package/esm/register.mjs +4 -3
  55. package/esm/register.mjs.map +1 -1
  56. package/esm/schema.mjs +1 -1
  57. package/esm/schema.mjs.map +1 -1
  58. package/esm/utility/calculator.d.mts +1 -1
  59. package/esm/utility/calculator.d.mts.map +1 -1
  60. package/esm/utility/calculator.mjs +1 -1
  61. package/esm/utility/calculator.mjs.map +1 -1
  62. package/esm/utility/date-time.d.mts +1 -1
  63. package/esm/utility/date-time.d.mts.map +1 -1
  64. package/esm/utility/date-time.mjs +1 -1
  65. package/esm/utility/date-time.mjs.map +1 -1
  66. package/esm/utility/schema.mjs +1 -1
  67. package/esm/utility/schema.mjs.map +1 -1
  68. package/esm/web/fetch-url.d.mts +1 -1
  69. package/esm/web/fetch-url.d.mts.map +1 -1
  70. package/esm/web/fetch-url.mjs +1 -1
  71. package/esm/web/fetch-url.mjs.map +1 -1
  72. package/esm/web/schema.mjs +1 -1
  73. package/esm/web/schema.mjs.map +1 -1
  74. package/esm/web/web-search.d.mts +1 -1
  75. package/esm/web/web-search.d.mts.map +1 -1
  76. package/esm/web/web-search.mjs +1 -1
  77. package/esm/web/web-search.mjs.map +1 -1
  78. package/llms-full.txt +194 -57
  79. package/llms.txt +3 -3
  80. package/package.json +4 -7
  81. package/skills/connect-mcp-server/SKILL.md +28 -6
  82. package/skills/expose-as-mcp-server/SKILL.md +117 -36
  83. package/skills/index.md +5 -2
  84. package/skills/use-web-and-http-tools/SKILL.md +49 -15
package/CHANGELOG.md CHANGED
@@ -4,6 +4,47 @@ 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.31.0 - 2026-10-18
8
+
9
+ ### Added
10
+
11
+ - `ai.mcp(server, { signal })` binds the client to a run: when the signal aborts the client closes its transport, and later handshakes and tool calls fail with an `McpTransportError` of type `"closed"`. Every `tools/call` honors both the client signal and the per-call `ctx.signal`.
12
+ - `ai.tools.measure(tools, { model, mode })` — the `measureTools` helper from `@warlock.js/ai` — measures the input tokens a tool list costs per call.
13
+ - `ai.mcp.serve(source, { transport: { type: "http" } })` serves the MCP Streamable HTTP transport on `node:http` (no extra dependency). It binds `127.0.0.1` on a free port at `/mcp` by default (`host`, `port`, `path` override them), and `start()` resolves `{ url, close }`. `close()` stops accepting connections, waits up to `drainTimeoutMs` (default 5 s) for in-flight calls, then aborts their `ctx.signal` and frees the port. The endpoint is stateless: JSON responses, `202` for notifications, `405` for `GET` / `DELETE`, `400` for batches or an unsupported `MCP-Protocol-Version`.
14
+ - HTTP auth: `auth: { bearer }` accepts one token or a rotation list; `auth: { authorize(request) }` runs a custom check; with both set, a request must pass both. Failures get `401` before the body is read.
15
+ - Tools served over HTTP receive the inbound request on `ctx.request` (`headers`, `remoteAddress`), for per-user tokens, tenant headers or trace ids.
16
+ - Grouped sources: `serve({ groups: { name: tools } }, { prefixToolNames: true })` exposes `<group>_<tool>`; a function builds custom names.
17
+ - Optional MCP resources (`resources: [{ uri, name, read }]`) and prompts (`prompts: [{ name, arguments, get }]`), with their capabilities advertised only when configured. The server also answers `ping`.
18
+ - `McpServeError` (`type`: `"collision"`, `"config"`, `"listen"`), `SUPPORTED_PROTOCOL_VERSIONS`, and the `McpHttpServeTransport`, `McpHttpAuth`, `McpHttpRequestInfo`, `McpServeHandle`, `McpHttpServeHandle`, `McpServeResource`, `McpServePrompt`, `McpServeCallContext`, `McpToolSource`, `McpGroupedToolSource` types. `createServeHandler(...).handle(request, context?)` takes an optional call context.
19
+
20
+ ### Changed
21
+
22
+ - `initialize` negotiates the protocol version: a client's requested version is echoed when supported (`2025-11-25`, `2025-06-18`, `2025-03-26`), otherwise the newest is offered. It previously always answered `2025-06-18`.
23
+ - `McpServer.start()` resolves a handle (`{ url?, close }`) instead of `void`. The http transport's `port` is optional.
24
+ - `@modelcontextprotocol/sdk` is no longer listed as an optional peer dependency. The MCP client never imported it: it speaks MCP's JSON-RPC itself over a child process (stdio) and `fetch` (http). The SDK is now a development dependency, used only by the interoperability test against the official client. The MCP docs, README and `connect-mcp-server` skill now say so, and name `ajv` as the client's only optional peer.
25
+
26
+ ### Fixed
27
+
28
+ - The stdio transport of `ai.mcp.serve()` no longer crashes the process when a line is JSON but not a request object (`null`, a number, an array): it answers a JSON-RPC "Invalid Request" error (`-32600`, `id: null`). A failure while answering a line is answered as an internal error and can no longer become an unhandled rejection.
29
+
30
+ ### Security
31
+
32
+ - The HTTP transport binds loopback by default and refuses to start on any non-loopback host without `auth` (`McpServeError`, `type: "config"`).
33
+ - Bearer tokens are compared with `crypto.timingSafeEqual` over SHA-256 digests, so neither content nor length leaks through timing; every configured token is checked. Empty tokens are refused, and an `authorize` hook that throws denies the request.
34
+ - `Origin` is validated on every request, per the MCP transport's DNS-rebinding guidance: loopback origins and `allowedOrigins` pass, others (including `null`) get `403`. While bound to loopback, a `Host` header other than `localhost`, `127.0.0.1`, `[::1]` or `allowedHosts` also gets `403`.
35
+ - Request bodies over `maxBodyBytes` (default 1 MiB) get `413` and the connection is closed.
36
+
37
+ ### Upgrading
38
+
39
+ - **BREAKING:** `ai.mcp.serve()` throws `McpServeError` (`type: "collision"`) when two exposed tools share a name, and for duplicate resource URIs or prompt names. Previously both tools were listed and every call reached the last one. Rename one tool, or serve a grouped source with `prefixToolNames: true`.
40
+ - `serve(..., { transport: { type: "http" } }).start()` no longer rejects: it binds a real endpoint. Code that implemented `McpServer` itself must return a handle from `start()`.
41
+
42
+ ## 5.30.0 - 2026-10-16
43
+
44
+ ### Changed
45
+
46
+ - Lockstep release; package APIs are unchanged.
47
+
7
48
  ## 5.29.0 - 2026-10-01
8
49
 
9
50
  ### Changed
@@ -75,6 +116,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
75
116
  ### Changed
76
117
 
77
118
  - Lockstep release maintenance and dependency refresh.
119
+
78
120
  ## 5.20.0 - 2026-09-24
79
121
 
80
122
  ### Changed
@@ -138,7 +180,7 @@ changelogs.
138
180
 
139
181
  ### Security
140
182
 
141
- - **`fetch_url` and `http_request` now deny private-network targets by default and route through `@warlock.js/ai`'s hardened `guardedFetch` instead of a local host check.** Previously `allowHosts` was the *only* SSRF guardrail and it was opt-in — a bare `ai.tools.fetchUrl()` / `ai.tools.http()` would fetch any `http(s)` URL the model supplied, including `http://169.254.169.254/latest/meta-data/...`, `localhost`, and RFC1918 addresses, and neither tool re-validated redirect targets, so even a configured allowlist could be 302'd into an internal endpoint. Both tools now issue every request through the core outbound policy, which by default refuses private / loopback / link-local / CGNAT / cloud-metadata addresses — resolving hostnames through DNS and checking every returned address, failing closed on resolution failure — and re-validates every redirect `Location` (scheme, allowlist, private-IP deny) before following it, with a hop cap and cross-origin credential-header stripping. Blocks surface as the existing typed errors (`WebToolError` `type: "denied-host"`, `HttpPolicyError` `type: "host-not-allowed"`), so agents still read them as `{ error }` data
183
+ - **`fetch_url` and `http_request` now deny private-network targets by default and route through `@warlock.js/ai`'s hardened `guardedFetch` instead of a local host check.** Previously `allowHosts` was the _only_ SSRF guardrail and it was opt-in — a bare `ai.tools.fetchUrl()` / `ai.tools.http()` would fetch any `http(s)` URL the model supplied, including `http://169.254.169.254/latest/meta-data/...`, `localhost`, and RFC1918 addresses, and neither tool re-validated redirect targets, so even a configured allowlist could be 302'd into an internal endpoint. Both tools now issue every request through the core outbound policy, which by default refuses private / loopback / link-local / CGNAT / cloud-metadata addresses — resolving hostnames through DNS and checking every returned address, failing closed on resolution failure — and re-validates every redirect `Location` (scheme, allowlist, private-IP deny) before following it, with a hop cap and cross-origin credential-header stripping. Blocks surface as the existing typed errors (`WebToolError` `type: "denied-host"`, `HttpPolicyError` `type: "host-not-allowed"`), so agents still read them as `{ error }` data
142
184
  - **New `allowPrivateNetwork` option (default `false`) on both tools** for the deliberate case of a tool that must call an internal service (e.g. a local dev server); `allowHosts` still works and now also constrains redirect targets
143
185
 
144
186
  ## 4.12.0
package/README.md CHANGED
@@ -14,13 +14,13 @@ All five tools attach onto the shared `ai` object at import time under the `ai.t
14
14
  (via a `declare module "@warlock.js/ai"` augmentation), so a bare `import "@warlock.js/ai-tools"`
15
15
  makes them available and statically typed.
16
16
 
17
- | Tool | LLM-visible name | What it does |
18
- |---|---|---|
19
- | `ai.tools.webSearch(options)` | `web_search` | Web search via a chosen provider (`tavily` / `brave` / `serpapi`) — ranked title/URL/snippet hits. |
20
- | `ai.tools.fetchUrl(options?)` | `fetch_url` | Fetch a URL → readability `text` (default), raw `html`, or `markdown`; host allowlist + byte cap. |
21
- | `ai.tools.http(options?)` | `http_request` | A guarded HTTP/REST client — method + host allowlists, `baseUrl` join, byte/timeout caps. |
22
- | `ai.tools.calculator(options?)` | `calculator` | A SAFE arithmetic evaluator (shunting-yard; never `eval`/`Function`). |
23
- | `ai.tools.dateTime(options?)` | `date_time` | `now` / `add` / `diff` / `format` over ISO-8601 instants, IANA time zones. |
17
+ | Tool | LLM-visible name | What it does |
18
+ | ------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------- |
19
+ | `ai.tools.webSearch(options)` | `web_search` | Web search via a chosen provider (`tavily` / `brave` / `serpapi`) — ranked title/URL/snippet hits. |
20
+ | `ai.tools.fetchUrl(options?)` | `fetch_url` | Fetch a URL → readability `text` (default), raw `html`, or `markdown`; host allowlist + byte cap. |
21
+ | `ai.tools.http(options?)` | `http_request` | A guarded HTTP/REST client — method + host allowlists, `baseUrl` join, byte/timeout caps. |
22
+ | `ai.tools.calculator(options?)` | `calculator` | A SAFE arithmetic evaluator (shunting-yard; never `eval`/`Function`). |
23
+ | `ai.tools.dateTime(options?)` | `date_time` | `now` / `add` / `diff` / `format` over ISO-8601 instants, IANA time zones. |
24
24
 
25
25
  ## MCP — both directions
26
26
 
@@ -39,7 +39,7 @@ npm install @warlock.js/ai-tools
39
39
  ```
40
40
 
41
41
  `@warlock.js/ai` is the only required runtime peer. Everything heavy — a search provider, the
42
- readability scraper, the MCP SDK, and a JSON-Schema validator — is an **optional peer**, lazily
42
+ readability scraper, and a JSON-Schema validator — is an **optional peer**, lazily
43
43
  `import()`ed only when the relevant path runs; a missing one surfaces a curated `npm install` string
44
44
  rather than crashing at import time. Beyond `@warlock.js/ai` the package uses only Node built-ins and
45
45
  the global `fetch` (Node 18+). Import the package once for its side effect so the surface is
@@ -54,7 +54,7 @@ import "@warlock.js/ai-tools";
54
54
  ### 1. An agent with web search + HTTP
55
55
 
56
56
  Hand an agent the web tools and let it research, then hit an API — both bounded by a host allowlist.
57
- A failing tool call comes back as `{ error }` *data*, so the agent self-corrects instead of crashing.
57
+ A failing tool call comes back as `{ error }` _data_, so the agent self-corrects instead of crashing.
58
58
 
59
59
  ```ts
60
60
  import "@warlock.js/ai-tools"; // registers ai.tools.* + ai.mcp on import
@@ -114,16 +114,28 @@ so Cursor / Claude Desktop / any MCP client can call these tools.
114
114
  ```ts
115
115
  import "@warlock.js/ai-tools";
116
116
 
117
- const server = ai.mcp.serve(
118
- [ai.tools.calculator(), ai.tools.dateTime()],
119
- { name: "warlock-utils", transport: { type: "stdio" } },
120
- );
117
+ const server = ai.mcp.serve([ai.tools.calculator(), ai.tools.dateTime()], {
118
+ name: "warlock-utils",
119
+ transport: { type: "stdio" },
120
+ });
121
121
 
122
122
  await server.start();
123
123
  // ...later:
124
124
  await server.stop();
125
125
  ```
126
126
 
127
+ Over Streamable HTTP, `start()` binds a loopback endpoint and resolves `{ url, close }`. A
128
+ non-loopback `host` requires `auth`; `Origin` is validated and bodies are size-capped:
129
+
130
+ ```ts
131
+ const { url, close } = await ai.mcp
132
+ .serve([ai.tools.calculator()], {
133
+ name: "session-tools",
134
+ transport: { type: "http", auth: { bearer: sessionToken } }, // 127.0.0.1, free port, /mcp
135
+ })
136
+ .start();
137
+ ```
138
+
127
139
  ## Errors flow as data, not throws
128
140
 
129
141
  Every tool follows the framework's errors-as-data contract: a guardrail rejection or a failed call is