@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/cjs/index.cjs +327 -60
  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 +97 -37
  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 +6 -2
  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 +64 -25
  38. package/esm/mcp/serve-http.mjs.map +1 -1
  39. package/esm/mcp/serve-requests.mjs +62 -0
  40. package/esm/mcp/serve-requests.mjs.map +1 -0
  41. package/esm/mcp/serve.d.mts +5 -5
  42. package/esm/mcp/serve.d.mts.map +1 -1
  43. package/esm/mcp/serve.mjs +97 -18
  44. package/esm/mcp/serve.mjs.map +1 -1
  45. package/esm/mcp/transport.d.mts +4 -5
  46. package/esm/mcp/transport.d.mts.map +1 -1
  47. package/esm/mcp/transport.mjs +12 -6
  48. package/esm/mcp/transport.mjs.map +1 -1
  49. package/esm/mcp/transport.type.d.mts +2 -2
  50. package/esm/mcp/transport.type.d.mts.map +1 -1
  51. package/esm/register.d.mts +3 -4
  52. package/esm/register.d.mts.map +1 -1
  53. package/esm/utility/calculator.d.mts +1 -3
  54. package/esm/utility/calculator.d.mts.map +1 -1
  55. package/esm/utility/date-time.d.mts +3 -5
  56. package/esm/utility/date-time.d.mts.map +1 -1
  57. package/esm/utility/date-time.mjs +2 -1
  58. package/esm/utility/date-time.mjs.map +1 -1
  59. package/esm/utility/index.d.mts +2 -1
  60. package/esm/utility/index.mjs +1 -1
  61. package/esm/web/fetch-url.d.mts +2 -3
  62. package/esm/web/fetch-url.d.mts.map +1 -1
  63. package/esm/web/fetch-url.mjs +2 -1
  64. package/esm/web/fetch-url.mjs.map +1 -1
  65. package/esm/web/index.d.mts +2 -1
  66. package/esm/web/index.mjs +1 -1
  67. package/esm/web/web-search.d.mts +2 -3
  68. package/esm/web/web-search.d.mts.map +1 -1
  69. package/esm/web/web-search.mjs +3 -4
  70. package/esm/web/web-search.mjs.map +1 -1
  71. package/llms-full.txt +122 -3
  72. package/llms.txt +1 -1
  73. package/package.json +2 -2
  74. package/skills/connect-mcp-server/SKILL.md +15 -0
  75. package/skills/expose-as-mcp-server/SKILL.md +107 -3
package/llms.txt CHANGED
@@ -7,5 +7,5 @@
7
7
  ## Skills
8
8
 
9
9
  - [connect-mcp-server](@warlock.js/ai-tools/connect-mcp-server/SKILL.md): Connect a @warlock.js/ai agent to an external Model Context Protocol (MCP) server and adapt its tools as native agent tools (Direction A) with `ai.mcp(server)`. Triggers: `signal`, `ai.mcp(`, `ai.mcp.serve` (the other direction), `mcp(`, `McpClient`, `McpClientOptions`, `McpTransport`, `client.tools()`, `client.close()`, `namePrefix`, `filter`, `McpTransportError`, `{ type: "stdio" }`, `{ type: "http" }`, `tools/list`, `tools/call`, `jsonSchemaToStandard`, `@modelcontextprotocol/sdk`, `ajv`; 'consume an MCP server', 'use an external MCP server's tools in my agent', 'connect to the GitHub/Slack/Filesystem MCP server', 'adapt MCP tools as agent tools', 'bridge my agent to the MCP ecosystem', 'run an MCP server over stdio/http'; typical import `import "@warlock.js/ai-tools"; import { ai } from "@warlock.js/ai"`. Skip: exposing YOUR agent/tools AS an MCP server (the reverse direction) — the `expose-as-mcp-server` topic; the built-in web/HTTP/calc/date tools — the `use-web-and-http-tools` topic.
10
- - [expose-as-mcp-server](@warlock.js/ai-tools/expose-as-mcp-server/SKILL.md): Expose a @warlock.js/ai agent / supervisor / orchestrator (or a raw ToolContract[], or named tool groups) AS a Model Context Protocol (MCP) server other clients consume (Direction B) with `ai.mcp.serve(source, options)` — over stdio or authenticated Streamable HTTP. Triggers: `ai.mcp.serve(`, `serve(`, `createServeHandler`, `McpServer`, `McpServeOptions`, `McpServeSource`, `McpServeTransport`, `McpHttpServeTransport`, `McpHttpAuth`, `McpServeError`, `server.start()`, `server.stop()`, `{ url, close }`, `{ type: "http", host, port, path, auth }`, `bearer`, `authorize`, `allowedOrigins`, `maxBodyBytes`, `prefixToolNames`, `groups`, `resources`, `prompts`, `ctx.request.headers`, `schemaTarget`, `tools/list`, `tools/call`, `{ type: "stdio" }`; 'expose my agent as an MCP server', 'serve my tools over HTTP', 'give each agent session its own MCP endpoint', 'protect my MCP server with a token', 'publish my tools to Cursor/Claude Desktop', 'serve MCP resources or prompts', 'tool name collision'; typical import `import "@warlock.js/ai-tools"; import { ai } from "@warlock.js/ai"`. Skip: CONSUMING an external MCP server (the reverse direction) — the `connect-mcp-server` topic; the built-in web/HTTP/calc/date tools — the `use-web-and-http-tools` topic.
10
+ - [expose-as-mcp-server](@warlock.js/ai-tools/expose-as-mcp-server/SKILL.md): Expose a @warlock.js/ai agent / supervisor / orchestrator (or a raw ToolContract[], or named tool groups) AS a Model Context Protocol (MCP) server other clients consume (Direction B) with `ai.mcp.serve(source, options)` — over stdio or authenticated Streamable HTTP. Triggers: `ai.mcp.serve(`, `serve(`, `createServeHandler`, `McpServer`, `McpServeOptions`, `McpServeSource`, `McpServeTransport`, `McpHttpServeTransport`, `McpHttpAuth`, `McpServeError`, `server.start()`, `server.stop()`, `{ url, close }`, `{ type: "http", host, port, path, auth }`, `bearer`, `authorize`, `authenticate`, `beforeCall`, `requestId`, `principal`, `notifications/cancelled`, `allowedOrigins`, `maxBodyBytes`, `prefixToolNames`, `groups`, `resources`, `prompts`, `ctx.request.headers`, `schemaTarget`, `tools/list`, `tools/call`, `{ type: "stdio" }`; 'expose my agent as an MCP server', 'serve my tools over HTTP', 'give each agent session its own MCP endpoint', 'protect my MCP server with a token', 'publish my tools to Cursor/Claude Desktop', 'serve MCP resources or prompts', 'tool name collision'; typical import `import "@warlock.js/ai-tools"; import { ai } from "@warlock.js/ai"`. Skip: CONSUMING an external MCP server (the reverse direction) — the `connect-mcp-server` topic; the built-in web/HTTP/calc/date tools — the `use-web-and-http-tools` topic.
11
11
  - [use-web-and-http-tools](@warlock.js/ai-tools/use-web-and-http-tools/SKILL.md): Wire the @warlock.js/ai-tools belt — web search, fetch/scrape, HTTP/REST, calculator, date-time — into an agent under their guardrails. Triggers: `ai.tools.measure`, `measureTools`, `ai.tools.webSearch`, `ai.tools.fetchUrl`, `ai.tools.http`, `ai.tools.calculator`, `ai.tools.dateTime`, `webSearchTool`, `fetchUrlTool`, `httpRequestTool`, `calculatorTool`, `dateTimeTool`, `web_search`, `fetch_url`, `http_request`, `calculator`, `date_time`, `WebToolError`, `HttpPolicyError`, `CalculatorError`, `DateTimeError`, `allowHosts`, `allowMethods`, `allowPrivateNetwork`, `baseUrl`, `maxBytes`, `guardedFetch`, `SearchProvider`, `TAVILY_API_KEY`; 'give an agent web search', 'let an agent fetch/scrape a page', 'let an agent call a REST API', 'restrict which hosts/methods an agent can hit', 'add a calculator/date tool to an agent', 'SSRF guardrail for an agent tool', 'let a tool reach an internal/dev service'; typical import `import "@warlock.js/ai-tools"; import { ai } from "@warlock.js/ai"`. Skip: connecting to an external MCP server — the `connect-mcp-server` topic; exposing tools AS an MCP server — the `expose-as-mcp-server` topic; filesystem/shell tools — the `use-a-workspace` topic of the `warlock-js-ai-workspace` skill; defining your own custom tool — the `define-ai-tool` topic of the `warlock-js-ai` skill.
package/package.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "peerDependencies": {
23
23
  "@mozilla/readability": "*",
24
24
  "@tavily/core": "*",
25
- "@warlock.js/ai": "5.31.0",
25
+ "@warlock.js/ai": "5.33.0",
26
26
  "ajv": "*",
27
27
  "jsdom": "*"
28
28
  },
@@ -43,7 +43,7 @@
43
43
  "dependencies": {
44
44
  "@standard-schema/spec": "^1.0.0"
45
45
  },
46
- "version": "5.31.0",
46
+ "version": "5.33.0",
47
47
  "main": "./cjs/index.cjs",
48
48
  "module": "./esm/index.mjs",
49
49
  "types": "./esm/index.d.mts",
@@ -87,6 +87,21 @@ For each remote tool:
87
87
  2. `execute` issues `tools/call` honoring `ctx.signal` (cooperative abort) and the configured `timeoutMs`, then unwraps the result content (a single JSON text block is parsed back into an object);
88
88
  3. an MCP `isError` result is thrown so the surrounding `tool()` wraps it as a `ToolExecutionError` — the agent reads it as `{ error }` data and self-corrects.
89
89
 
90
+ ### Selecting one discovered tool
91
+
92
+ When selecting one discovered tool, guard the array result before invoking it.
93
+ A server may advertise no tools, and `noUncheckedIndexedAccess` reflects that:
94
+
95
+ ```ts
96
+ import type { McpClient } from "@warlock.js/ai-tools";
97
+
98
+ export async function invokeFirst(client: McpClient, input: unknown) {
99
+ const [contract] = await client.tools();
100
+ if (!contract) throw new Error("The MCP server advertised no tools");
101
+ return contract.invoke(input);
102
+ }
103
+ ```
104
+
90
105
  ## `type`, never `kind`
91
106
 
92
107
  MCP's wire vocabulary uses `kind` in some content/capability descriptors. The client translates any inbound `kind` to `type` when normalizing content blocks, so a value a tool returns never carries MCP's `kind` — every discriminator the agent sees is `type`.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: expose-as-mcp-server
3
- description: 'Expose a @warlock.js/ai agent / supervisor / orchestrator (or a raw ToolContract[], or named tool groups) AS a Model Context Protocol (MCP) server other clients consume (Direction B) with `ai.mcp.serve(source, options)` — over stdio or authenticated Streamable HTTP. Triggers: `ai.mcp.serve(`, `serve(`, `createServeHandler`, `McpServer`, `McpServeOptions`, `McpServeSource`, `McpServeTransport`, `McpHttpServeTransport`, `McpHttpAuth`, `McpServeError`, `server.start()`, `server.stop()`, `{ url, close }`, `{ type: "http", host, port, path, auth }`, `bearer`, `authorize`, `allowedOrigins`, `maxBodyBytes`, `prefixToolNames`, `groups`, `resources`, `prompts`, `ctx.request.headers`, `schemaTarget`, `tools/list`, `tools/call`, `{ type: "stdio" }`; ''expose my agent as an MCP server'', ''serve my tools over HTTP'', ''give each agent session its own MCP endpoint'', ''protect my MCP server with a token'', ''publish my tools to Cursor/Claude Desktop'', ''serve MCP resources or prompts'', ''tool name collision''; typical import `import "@warlock.js/ai-tools"; import { ai } from "@warlock.js/ai"`. Skip: CONSUMING an external MCP server (the reverse direction) — the `connect-mcp-server` topic; the built-in web/HTTP/calc/date tools — the `use-web-and-http-tools` topic.'
3
+ description: 'Expose a @warlock.js/ai agent / supervisor / orchestrator (or a raw ToolContract[], or named tool groups) AS a Model Context Protocol (MCP) server other clients consume (Direction B) with `ai.mcp.serve(source, options)` — over stdio or authenticated Streamable HTTP. Triggers: `ai.mcp.serve(`, `serve(`, `createServeHandler`, `McpServer`, `McpServeOptions`, `McpServeSource`, `McpServeTransport`, `McpHttpServeTransport`, `McpHttpAuth`, `McpServeError`, `server.start()`, `server.stop()`, `{ url, close }`, `{ type: "http", host, port, path, auth }`, `bearer`, `authorize`, `authenticate`, `beforeCall`, `requestId`, `principal`, `notifications/cancelled`, `allowedOrigins`, `maxBodyBytes`, `prefixToolNames`, `groups`, `resources`, `prompts`, `ctx.request.headers`, `schemaTarget`, `tools/list`, `tools/call`, `{ type: "stdio" }`; ''expose my agent as an MCP server'', ''serve my tools over HTTP'', ''give each agent session its own MCP endpoint'', ''protect my MCP server with a token'', ''publish my tools to Cursor/Claude Desktop'', ''serve MCP resources or prompts'', ''tool name collision''; typical import `import "@warlock.js/ai-tools"; import { ai } from "@warlock.js/ai"`. Skip: CONSUMING an external MCP server (the reverse direction) — the `connect-mcp-server` topic; the built-in web/HTTP/calc/date tools — the `use-web-and-http-tools` topic.'
4
4
  ---
5
5
 
6
6
  # Expose tools AS an MCP server (Direction B)
@@ -64,7 +64,7 @@ transport: {
64
64
  }
65
65
  ```
66
66
 
67
- - With `bearer` and `authorize` both set, a request must pass both. A throwing hook denies. Failures → `401`.
67
+ - Checks run in order: `bearer`, `authorize`, `authenticate`; every configured check must pass. A failed check skips later hooks. `authenticate` alone satisfies the non-loopback auth requirement. Host/Origin checks run first. A throwing authentication hook denies. Failures → `401`; bearer or authenticate failures include a Bearer `WWW-Authenticate` challenge.
68
68
  - A foreign or `null` `Origin` → `403` (DNS-rebinding guard from the MCP spec). Requests without `Origin` pass.
69
69
  - On loopback, a `Host` other than `localhost` / `127.0.0.1` / `[::1]` (or `allowedHosts`) → `403`.
70
70
  - Stateless: no session id; `GET` / `DELETE` → `405`; batches → `400`; unsupported `MCP-Protocol-Version` → `400`.
@@ -91,6 +91,110 @@ const { url, close } = await ai.mcp
91
91
 
92
92
  Header names are lower-cased; every client header is present (including `authorization`). Over stdio `ctx.request` is absent. Nothing on `ctx.request` reaches the model.
93
93
 
94
+ ## Authenticated context, policy and cancellation
95
+
96
+ ```ts
97
+ import { ai } from "@warlock.js/ai";
98
+ import "@warlock.js/ai-tools";
99
+ import type { McpHttpRequestInfo, McpCallRefusal } from "@warlock.js/ai-tools";
100
+
101
+ declare function findMember(
102
+ request: McpHttpRequestInfo,
103
+ ): Promise<{ id: string; canCall: boolean } | undefined>;
104
+ const denied: McpCallRefusal = {
105
+ isError: true,
106
+ content: [{ type: "text", text: "Member cannot call tools" }],
107
+ _meta: { policy: "membership" },
108
+ };
109
+ const identity = ai.tool({
110
+ name: "identity",
111
+ description: "Report the authenticated caller and request id",
112
+ execute: async (_input: unknown, ctx) => {
113
+ ctx?.signal?.throwIfAborted();
114
+ return { requestId: ctx?.requestId, principal: ctx?.principal };
115
+ },
116
+ });
117
+
118
+ const server = ai.mcp.serve([identity], {
119
+ name: "member-tools",
120
+ transport: { type: "http", auth: { authenticate: findMember } },
121
+ beforeCall: async (context, tool, input) => {
122
+ const member = context.principal; // unknown — narrow before use
123
+ if (
124
+ typeof member !== "object" ||
125
+ member === null ||
126
+ !("canCall" in member) ||
127
+ member.canCall !== true
128
+ ) {
129
+ return denied;
130
+ }
131
+ // context.requestId: JSON-RPC id; tool: original ToolContract;
132
+ // input: validated data, treated as read-only.
133
+ },
134
+ });
135
+ ```
136
+
137
+ - `authenticate(request)` may be synchronous or async. `undefined` or a thrown error → `401` with `WWW-Authenticate: Bearer realm="mcp"`; every other value becomes `context.principal` and `ctx.principal`. Secrets in thrown auth errors are not exposed.
138
+ - `beforeCall(context, tool, input)` runs after schema validation. Return nothing to allow; return `McpCallRefusal` (`isError: true`) to send native content/metadata unchanged, independent of result mode. Throw → JSON-RPC `-32602`. Invalid refusals → `-32603`. Invalid input skips policy. The boundary validates before policy and `invoke` keeps its own validation, so schemas must be deterministic and policy input read-only.
139
+ - Stdio identity: `transport: { type: "stdio", principal: { id: "local" } }` or `transport: { type: "stdio", principalProvider: async (context) => ({ id: "local", requestId: context.requestId }) }`. The provider runs per request, takes precedence over static principal, and receives the request id and abort signal. A provider throw → JSON-RPC internal error. No HTTP request metadata exists over stdio.
140
+ - Public types: `McpBeforeCall`, `McpCallRefusal`, `McpServeToolContext`, `McpStdioServeTransport`; `ToolContext` in `@warlock.js/ai` adds optional `requestId` and `principal`.
141
+
142
+ [Cancellation (MCP 2025-11-25)](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation): send `{ jsonrpc: "2.0", method: "notifications/cancelled", params: { requestId: 42, reason: "Caller cancelled" } }`. The matching signal aborts; no JSON-RPC response is sent even if the tool later returns or throws. Unknown/completed/malformed ids are ignored; `initialize` cannot be cancelled. String and numeric ids are distinct; calls sharing an in-flight id still execute, but ambiguous cancellation in one scope is ignored.
143
+
144
+ HTTP cancellation POSTs must repeat caller credentials and application headers (`Authorization`, `Cookie`, tenant/custom auth headers), from the same remote address. Transport headers such as content length, accept, protocol version and connection may differ. Different credentials can reuse ids safely. Clients sharing credential/application headers must use unique in-flight ids for cancellation to identify a call. Notifications get `202`; a cancelled HTTP call ends with `202`, no JSON-RPC body, when its handler settles. Stdio scopes cancellation to the connection. HTTP disconnect, stdio EOF/close/stop, or HTTP shutdown after the drain timeout also abort tools. Pass `ctx.signal` into cancellable work.
145
+
146
+ ## Return native MCP results
147
+
148
+ Default results become one text block, even when an object resembles an MCP result. Return the exported `mcpResult(...)` helper to preserve native content blocks, `isError`, `structuredContent` and `_meta` over stdio or HTTP:
149
+
150
+ ```ts
151
+ import { mcpResult } from "@warlock.js/ai-tools";
152
+ import { ai } from "@warlock.js/ai";
153
+
154
+ const count = ai.tool({
155
+ name: "count",
156
+ description: "Return a structured count",
157
+ meta: {
158
+ outputSchema: {
159
+ type: "object",
160
+ properties: { count: { type: "number" } },
161
+ required: ["count"],
162
+ },
163
+ },
164
+ execute: async (_input: unknown, ctx) =>
165
+ mcpResult({
166
+ content: [{ type: "text", text: '{"count":2}' }],
167
+ structuredContent: { count: 2 },
168
+ _meta: { tenant: ctx?.request?.headers["x-tenant-id"] ?? "unknown" },
169
+ }),
170
+ });
171
+
172
+ ai.mcp.serve([count], { name: "counts" });
173
+ ```
174
+
175
+ The helper returns a branded `McpNativeResult` wrapper. The serving boundary validates and unwraps it without altering its payload. `isError: true` is sent as-is; thrown tool errors retain the existing mapping.
176
+
177
+ For tools that already return native objects, opt in at the server or tool level:
178
+
179
+ ```ts
180
+ const nativeTool = ai.tool({
181
+ name: "native-result",
182
+ description: "Return a native result",
183
+ meta: { mcpResults: "native" }, // this tool opts in even on a default server
184
+ execute: async () => ({ content: [{ type: "text", text: "fixture" }], isError: true }),
185
+ });
186
+
187
+ ai.mcp.serve([nativeTool], { name: "native-tools", results: "native" });
188
+ // On a native-mode server, meta: { mcpResults: "text" } disables detection for a tool.
189
+ // Explicit mcpResult(...) returns always opt in, regardless of the detection setting.
190
+ ```
191
+
192
+ Native mode detects objects containing any of `content`, `isError`, `structuredContent` or `_meta`; other values keep text serialization. `content` must be an array of valid MCP text/image/audio/resource-link/embedded-resource blocks. Optional `isError` must be boolean; `structuredContent` and `_meta` must be JSON objects. Invalid or non-JSON native payloads answer a JSON-RPC internal error (`-32603`), including malformed helper payloads.
193
+
194
+ `meta.outputSchema` is emitted as `outputSchema` in `tools/list`; `structuredContent` passes through unchanged. Tool authors own schema conformity. Follow the [MCP 2025-11-25 tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#structured-content): also include serialized structured output in a text block for compatibility.
195
+
196
+ Request identity is unchanged: native tools receive each HTTP call's headers and remote address on `ctx.request`, plus `ctx.signal`. Over stdio, `ctx.request` is absent. Returning a native result does not automatically expose request metadata; the tool controls its response.
197
+
94
198
  ## Sources and tool-name collisions
95
199
 
96
200
  ```ts
@@ -136,7 +240,7 @@ ai.mcp.serve(tools, {
136
240
 
137
241
  - **`initialize`** → `{ name, version }`, capabilities `tools` (+ `resources` / `prompts` when configured); protocol version negotiated from `SUPPORTED_PROTOCOL_VERSIONS` (`2025-11-25`, `2025-06-18`, `2025-03-26`).
138
242
  - **`tools/list`** → `inputSchema` via `extractJsonSchema(contract.input, { target: schemaTarget })` (default `"draft-2020-12"`).
139
- - **`tools/call`** → `invoke()`; `data` → text block (JSON-stringified unless a string), `error` → `isError: true`. Unknown tool → JSON-RPC error.
243
+ - **`tools/call`** → `invoke()`; default `data` → text block (JSON-stringified unless a string), opted-in native results → validated passthrough, `error` → `isError: true`. Unknown tool → JSON-RPC error.
140
244
  - **`ping`** → `{}`.
141
245
 
142
246
  ## Drive the protocol yourself (tests, custom servers)