@warlock.js/ai-tools 4.5.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 (83) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +164 -0
  4. package/cjs/index.cjs +2519 -0
  5. package/cjs/index.cjs.map +1 -0
  6. package/esm/contracts/http.type.d.mts +96 -0
  7. package/esm/contracts/http.type.d.mts.map +1 -0
  8. package/esm/contracts/index.d.mts +4 -0
  9. package/esm/contracts/mcp.type.d.mts +216 -0
  10. package/esm/contracts/mcp.type.d.mts.map +1 -0
  11. package/esm/contracts/utility.type.d.mts +96 -0
  12. package/esm/contracts/utility.type.d.mts.map +1 -0
  13. package/esm/contracts/web.type.d.mts +136 -0
  14. package/esm/contracts/web.type.d.mts.map +1 -0
  15. package/esm/errors.d.mts +225 -0
  16. package/esm/errors.d.mts.map +1 -0
  17. package/esm/errors.mjs +136 -0
  18. package/esm/errors.mjs.map +1 -0
  19. package/esm/http/http-request.d.mts +57 -0
  20. package/esm/http/http-request.d.mts.map +1 -0
  21. package/esm/http/http-request.mjs +221 -0
  22. package/esm/http/http-request.mjs.map +1 -0
  23. package/esm/index.d.mts +19 -0
  24. package/esm/index.mjs +15 -0
  25. package/esm/mcp/client.mjs +199 -0
  26. package/esm/mcp/client.mjs.map +1 -0
  27. package/esm/mcp/index.d.mts +43 -0
  28. package/esm/mcp/index.d.mts.map +1 -0
  29. package/esm/mcp/index.mjs +19 -0
  30. package/esm/mcp/index.mjs.map +1 -0
  31. package/esm/mcp/json-schema-to-standard.d.mts +34 -0
  32. package/esm/mcp/json-schema-to-standard.d.mts.map +1 -0
  33. package/esm/mcp/json-schema-to-standard.mjs +147 -0
  34. package/esm/mcp/json-schema-to-standard.mjs.map +1 -0
  35. package/esm/mcp/serve.d.mts +46 -0
  36. package/esm/mcp/serve.d.mts.map +1 -0
  37. package/esm/mcp/serve.mjs +264 -0
  38. package/esm/mcp/serve.mjs.map +1 -0
  39. package/esm/mcp/transport.d.mts +48 -0
  40. package/esm/mcp/transport.d.mts.map +1 -0
  41. package/esm/mcp/transport.mjs +381 -0
  42. package/esm/mcp/transport.mjs.map +1 -0
  43. package/esm/mcp/transport.type.d.mts +51 -0
  44. package/esm/mcp/transport.type.d.mts.map +1 -0
  45. package/esm/node_modules/@standard-schema/spec/dist/index.d.mts +80 -0
  46. package/esm/node_modules/@standard-schema/spec/dist/index.d.mts.map +1 -0
  47. package/esm/register.d.mts +55 -0
  48. package/esm/register.d.mts.map +1 -0
  49. package/esm/register.mjs +21 -0
  50. package/esm/register.mjs.map +1 -0
  51. package/esm/schema.mjs +127 -0
  52. package/esm/schema.mjs.map +1 -0
  53. package/esm/utility/calculator.d.mts +35 -0
  54. package/esm/utility/calculator.d.mts.map +1 -0
  55. package/esm/utility/calculator.mjs +272 -0
  56. package/esm/utility/calculator.mjs.map +1 -0
  57. package/esm/utility/date-time.d.mts +57 -0
  58. package/esm/utility/date-time.d.mts.map +1 -0
  59. package/esm/utility/date-time.mjs +193 -0
  60. package/esm/utility/date-time.mjs.map +1 -0
  61. package/esm/utility/index.d.mts +2 -0
  62. package/esm/utility/index.mjs +4 -0
  63. package/esm/utility/schema.mjs +114 -0
  64. package/esm/utility/schema.mjs.map +1 -0
  65. package/esm/web/fetch-url.d.mts +39 -0
  66. package/esm/web/fetch-url.d.mts.map +1 -0
  67. package/esm/web/fetch-url.mjs +228 -0
  68. package/esm/web/fetch-url.mjs.map +1 -0
  69. package/esm/web/index.d.mts +2 -0
  70. package/esm/web/index.mjs +4 -0
  71. package/esm/web/schema.mjs +86 -0
  72. package/esm/web/schema.mjs.map +1 -0
  73. package/esm/web/web-search.d.mts +38 -0
  74. package/esm/web/web-search.d.mts.map +1 -0
  75. package/esm/web/web-search.mjs +167 -0
  76. package/esm/web/web-search.mjs.map +1 -0
  77. package/llms-full.txt +326 -0
  78. package/llms.txt +11 -0
  79. package/package.json +45 -0
  80. package/skills/README.md +17 -0
  81. package/skills/connect-mcp-server/SKILL.md +98 -0
  82. package/skills/expose-as-mcp-server/SKILL.md +85 -0
  83. package/skills/use-web-and-http-tools/SKILL.md +125 -0
@@ -0,0 +1,199 @@
1
+ import { McpTransportError } from "../errors.mjs";
2
+ import { jsonSchemaToStandard } from "./json-schema-to-standard.mjs";
3
+ import { createJsonRpcClient } from "./transport.mjs";
4
+ import { tool } from "@warlock.js/ai";
5
+
6
+ //#region ../@warlock.js/ai-tools/src/mcp/client.ts
7
+ /** Default per-call timeout for `tools/call`. */
8
+ const DEFAULT_CALL_TIMEOUT_MS = 3e4;
9
+ /** The MCP protocol version this client advertises in `initialize`. */
10
+ const PROTOCOL_VERSION = "2025-06-18";
11
+ /**
12
+ * The internal {@link McpClient} implementation. Owns one JSON-RPC client
13
+ * over a transport, runs the `initialize` handshake on first use, lists
14
+ * the server's tools, and adapts each into a {@link ToolContract} whose
15
+ * `execute` issues `tools/call`. The adapted contracts are cached after
16
+ * the first `tools()` so repeat calls don't re-handshake.
17
+ *
18
+ * Constructed via {@link mcp}; the class itself is internal.
19
+ */
20
+ var McpClientImpl = class {
21
+ constructor(source, options = {}) {
22
+ this.initialized = false;
23
+ this.rpc = createJsonRpcClient(source);
24
+ this.options = options;
25
+ }
26
+ /**
27
+ * Run the MCP `initialize` handshake exactly once, then send the
28
+ * `notifications/initialized` notification the protocol requires before
29
+ * any other request. Wraps a handshake failure as a typed
30
+ * {@link McpTransportError} of type `"connect"`.
31
+ */
32
+ async handshake() {
33
+ if (this.initialized) return;
34
+ try {
35
+ await this.rpc.call("initialize", {
36
+ protocolVersion: PROTOCOL_VERSION,
37
+ capabilities: {},
38
+ clientInfo: {
39
+ name: "@warlock.js/ai-tools",
40
+ version: "4.4.0"
41
+ }
42
+ });
43
+ await this.rpc.notify("notifications/initialized");
44
+ } catch (cause) {
45
+ if (cause instanceof McpTransportError) throw cause;
46
+ throw new McpTransportError(`MCP initialize handshake failed: ${cause instanceof Error ? cause.message : String(cause)}`, {
47
+ type: "connect",
48
+ method: "initialize",
49
+ cause
50
+ });
51
+ }
52
+ this.initialized = true;
53
+ }
54
+ tools() {
55
+ if (this.cached) return Promise.resolve(this.cached);
56
+ if (this.pending) return this.pending;
57
+ this.pending = this.listAndAdapt().then((tools) => {
58
+ this.cached = tools;
59
+ return tools;
60
+ }).finally(() => {
61
+ this.pending = void 0;
62
+ });
63
+ return this.pending;
64
+ }
65
+ /**
66
+ * Handshake, `tools/list`, and adapt each descriptor into a
67
+ * {@link ToolContract}, applying the `filter` and `namePrefix` options.
68
+ */
69
+ async listAndAdapt() {
70
+ await this.handshake();
71
+ const descriptors = (await this.rpc.call("tools/list")).tools ?? [];
72
+ const filter = this.options.filter;
73
+ return (filter ? descriptors.filter((d) => filter(d.name)) : descriptors).map((descriptor) => this.adapt(descriptor));
74
+ }
75
+ /**
76
+ * Adapt one remote tool descriptor into a {@link ToolContract}: build
77
+ * the input schema from its JSON Schema via {@link jsonSchemaToStandard},
78
+ * prefix the name, and route `execute` through a `tools/call` that
79
+ * honors `ctx.signal`, unwraps the content blocks, and throws on an
80
+ * `isError` result so `tool()` wraps it.
81
+ */
82
+ adapt(descriptor) {
83
+ const prefixedName = `${this.options.namePrefix ?? ""}${descriptor.name}`;
84
+ const remoteName = descriptor.name;
85
+ const timeoutMs = this.options.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
86
+ const input = jsonSchemaToStandard(descriptor.inputSchema);
87
+ return tool({
88
+ name: prefixedName,
89
+ description: descriptor.description ?? `Invoke the remote MCP tool "${remoteName}".`,
90
+ input,
91
+ execute: async (args, ctx) => {
92
+ const result = await this.rpc.call("tools/call", {
93
+ name: remoteName,
94
+ arguments: args ?? {}
95
+ }, {
96
+ signal: ctx?.signal,
97
+ timeoutMs
98
+ });
99
+ if (result.isError) throw new McpTransportError(`MCP tool "${remoteName}" returned an error: ${unwrapContent(result.content)}`, {
100
+ type: "protocol",
101
+ method: "tools/call"
102
+ });
103
+ return unwrapResult(result.content);
104
+ }
105
+ });
106
+ }
107
+ close() {
108
+ return this.rpc.close();
109
+ }
110
+ };
111
+ /**
112
+ * Flatten an MCP `tools/call` result's content blocks into the value a
113
+ * tool returns. Text blocks are concatenated; a single block whose text is
114
+ * valid JSON is parsed so structured tool output flows back as an object
115
+ * rather than a string. Non-text blocks are preserved as `type`-tagged
116
+ * objects (MCP's wire `type` is kept; any inbound `kind` is normalized to
117
+ * `type`).
118
+ */
119
+ function unwrapResult(content) {
120
+ const blocks = content ?? [];
121
+ if (blocks.length === 1 && blocks[0].type === "text") return tryParseJson(blocks[0].text ?? "");
122
+ return blocks.map((block) => normalizeBlock(block));
123
+ }
124
+ /**
125
+ * Render content blocks to a short human string for error messages — the
126
+ * concatenated text of every text block.
127
+ */
128
+ function unwrapContent(content) {
129
+ return (content ?? []).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join(" ").trim();
130
+ }
131
+ /**
132
+ * Normalize one content block onto our `type`-only shape: translate an
133
+ * inbound `kind` discriminator to `type` (and strip `kind`) so the value a
134
+ * tool returns never carries MCP's `kind` vocabulary.
135
+ */
136
+ function normalizeBlock(block) {
137
+ const { kind, ...rest } = block;
138
+ const type = block.type ?? kind ?? "unknown";
139
+ return {
140
+ ...rest,
141
+ type
142
+ };
143
+ }
144
+ /**
145
+ * Parse a string as JSON, returning the parsed value on success or the
146
+ * original string when it is not JSON — so a plain-text tool result stays
147
+ * a string while a JSON tool result becomes an object.
148
+ */
149
+ function tryParseJson(text) {
150
+ const trimmed = text.trim();
151
+ if (!trimmed) return text;
152
+ const first = trimmed[0];
153
+ if (first !== "{" && first !== "[") return text;
154
+ try {
155
+ return JSON.parse(trimmed);
156
+ } catch {
157
+ return text;
158
+ }
159
+ }
160
+ /**
161
+ * Connect to an external MCP server and adapt its tools as agent tools
162
+ * (Direction A: server → local agent tools).
163
+ *
164
+ * Opens the transport lazily and exposes {@link McpClient.tools}, which on
165
+ * first call runs the `initialize` handshake, lists the server's tools via
166
+ * `tools/list`, and maps each into a {@link ToolContract}:
167
+ * - **input schema** — the remote tool's JSON Schema is wrapped as a
168
+ * Standard Schema via {@link jsonSchemaToStandard} (Ajv-backed, an
169
+ * optional peer);
170
+ * - **execute** — issues `tools/call` honoring `ctx.signal` and the
171
+ * configured `timeoutMs`, unwraps the result content, and throws on an
172
+ * `isError` result so the `tool()` wrapper surfaces it as `{ error }`;
173
+ * - **name** — prefixed with `options.namePrefix` to avoid local
174
+ * collisions; only tools passing `options.filter` are adapted.
175
+ *
176
+ * The adapted contracts are cached after the first `tools()` call, so the
177
+ * handshake + list happen exactly once. The returned contracts drop
178
+ * straight into `ai.agent({ tools: [...] })`.
179
+ *
180
+ * @param server - The transport config (`{ type: "stdio" }` /
181
+ * `{ type: "http" }`). A pre-built transport client may be injected for
182
+ * testing.
183
+ * @param options - Prefix / filter / per-call timeout.
184
+ * @returns An {@link McpClient} handle.
185
+ *
186
+ * @example
187
+ * const github = mcp(
188
+ * { type: "stdio", command: "npx", args: ["-y", "@modelcontextprotocol/server-github"] },
189
+ * { namePrefix: "github." },
190
+ * );
191
+ * const dev = ai.agent({ model, tools: [...(await github.tools())] });
192
+ */
193
+ function mcp(server, options) {
194
+ return new McpClientImpl(server, options);
195
+ }
196
+
197
+ //#endregion
198
+ export { mcp };
199
+ //# sourceMappingURL=client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/mcp/client.ts"],"sourcesContent":["import { tool, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n McpClient,\n McpClientOptions,\n McpContentBlock,\n McpToolCallResult,\n McpToolDescriptor,\n McpTransport,\n} from \"../contracts\";\nimport { McpTransportError } from \"../errors\";\nimport { jsonSchemaToStandard } from \"./json-schema-to-standard\";\nimport { createJsonRpcClient, type JsonRpcClientHandle } from \"./transport\";\nimport type { McpTransportClient } from \"./transport.type\";\n\n/** Default per-call timeout for `tools/call`. */\nconst DEFAULT_CALL_TIMEOUT_MS = 30_000;\n\n/** The MCP protocol version this client advertises in `initialize`. */\nconst PROTOCOL_VERSION = \"2025-06-18\";\n\n/** The `tools/list` response slice we read. */\ninterface ToolsListResult {\n tools?: McpToolDescriptor[];\n}\n\n/**\n * The internal {@link McpClient} implementation. Owns one JSON-RPC client\n * over a transport, runs the `initialize` handshake on first use, lists\n * the server's tools, and adapts each into a {@link ToolContract} whose\n * `execute` issues `tools/call`. The adapted contracts are cached after\n * the first `tools()` so repeat calls don't re-handshake.\n *\n * Constructed via {@link mcp}; the class itself is internal.\n */\nclass McpClientImpl implements McpClient {\n /** The JSON-RPC client over the transport. */\n private readonly rpc: JsonRpcClientHandle;\n\n /** Construction-time options (prefix / filter / timeout). */\n private readonly options: McpClientOptions;\n\n /** Resolved + cached adapted tools, set after the first `tools()`. */\n private cached: ToolContract[] | undefined;\n\n /** In-flight `tools()` so concurrent callers share one handshake. */\n private pending: Promise<ToolContract[]> | undefined;\n\n /** Flipped once the handshake completes so we only do it once. */\n private initialized = false;\n\n public constructor(\n source: McpTransport | McpTransportClient,\n options: McpClientOptions = {},\n ) {\n this.rpc = createJsonRpcClient(source);\n this.options = options;\n }\n\n /**\n * Run the MCP `initialize` handshake exactly once, then send the\n * `notifications/initialized` notification the protocol requires before\n * any other request. Wraps a handshake failure as a typed\n * {@link McpTransportError} of type `\"connect\"`.\n */\n private async handshake(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n try {\n await this.rpc.call(\"initialize\", {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: {},\n clientInfo: { name: \"@warlock.js/ai-tools\", version: \"4.4.0\" },\n });\n\n await this.rpc.notify(\"notifications/initialized\");\n } catch (cause) {\n if (cause instanceof McpTransportError) {\n throw cause;\n }\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n throw new McpTransportError(`MCP initialize handshake failed: ${message}`, {\n type: \"connect\",\n method: \"initialize\",\n cause,\n });\n }\n\n this.initialized = true;\n }\n\n public tools(): Promise<ToolContract[]> {\n if (this.cached) {\n return Promise.resolve(this.cached);\n }\n\n if (this.pending) {\n return this.pending;\n }\n\n this.pending = this.listAndAdapt()\n .then((tools) => {\n this.cached = tools;\n\n return tools;\n })\n .finally(() => {\n this.pending = undefined;\n });\n\n return this.pending;\n }\n\n /**\n * Handshake, `tools/list`, and adapt each descriptor into a\n * {@link ToolContract}, applying the `filter` and `namePrefix` options.\n */\n private async listAndAdapt(): Promise<ToolContract[]> {\n await this.handshake();\n\n const result = await this.rpc.call<ToolsListResult>(\"tools/list\");\n const descriptors = result.tools ?? [];\n\n const filter = this.options.filter;\n const selected = filter ? descriptors.filter((d) => filter(d.name)) : descriptors;\n\n return selected.map((descriptor) => this.adapt(descriptor));\n }\n\n /**\n * Adapt one remote tool descriptor into a {@link ToolContract}: build\n * the input schema from its JSON Schema via {@link jsonSchemaToStandard},\n * prefix the name, and route `execute` through a `tools/call` that\n * honors `ctx.signal`, unwraps the content blocks, and throws on an\n * `isError` result so `tool()` wraps it.\n */\n private adapt(descriptor: McpToolDescriptor): ToolContract {\n const prefixedName = `${this.options.namePrefix ?? \"\"}${descriptor.name}`;\n const remoteName = descriptor.name;\n const timeoutMs = this.options.timeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;\n const input = jsonSchemaToStandard(descriptor.inputSchema);\n\n return tool<unknown, unknown>({\n name: prefixedName,\n description:\n descriptor.description ?? `Invoke the remote MCP tool \"${remoteName}\".`,\n input,\n execute: async (args, ctx) => {\n const result = await this.rpc.call<McpToolCallResult>(\n \"tools/call\",\n { name: remoteName, arguments: args ?? {} },\n { signal: ctx?.signal, timeoutMs },\n );\n\n // An `isError` result is a tool-level failure — throw it so the\n // surrounding `tool()` wraps it as a `ToolExecutionError` and the\n // agent reads it as `{ error }` data and self-corrects.\n if (result.isError) {\n throw new McpTransportError(\n `MCP tool \"${remoteName}\" returned an error: ${unwrapContent(result.content)}`,\n { type: \"protocol\", method: \"tools/call\" },\n );\n }\n\n return unwrapResult(result.content);\n },\n });\n }\n\n public close(): Promise<void> {\n return this.rpc.close();\n }\n}\n\n/**\n * Flatten an MCP `tools/call` result's content blocks into the value a\n * tool returns. Text blocks are concatenated; a single block whose text is\n * valid JSON is parsed so structured tool output flows back as an object\n * rather than a string. Non-text blocks are preserved as `type`-tagged\n * objects (MCP's wire `type` is kept; any inbound `kind` is normalized to\n * `type`).\n */\nfunction unwrapResult(content: McpContentBlock[] | undefined): unknown {\n const blocks = content ?? [];\n\n // The overwhelmingly common case: a single text block. Parse JSON when\n // it is one, so structured results come back typed; otherwise the string.\n if (blocks.length === 1 && blocks[0].type === \"text\") {\n const text = blocks[0].text ?? \"\";\n\n return tryParseJson(text);\n }\n\n // Multiple / mixed blocks: return a normalized array, each tagged by\n // `type` (never `kind`).\n return blocks.map((block) => normalizeBlock(block));\n}\n\n/**\n * Render content blocks to a short human string for error messages — the\n * concatenated text of every text block.\n */\nfunction unwrapContent(content: McpContentBlock[] | undefined): string {\n return (content ?? [])\n .filter((block) => block.type === \"text\" && typeof block.text === \"string\")\n .map((block) => block.text)\n .join(\" \")\n .trim();\n}\n\n/**\n * Normalize one content block onto our `type`-only shape: translate an\n * inbound `kind` discriminator to `type` (and strip `kind`) so the value a\n * tool returns never carries MCP's `kind` vocabulary.\n */\nfunction normalizeBlock(block: McpContentBlock): Record<string, unknown> {\n const { kind, ...rest } = block as McpContentBlock & { kind?: string };\n const type = block.type ?? kind ?? \"unknown\";\n\n return { ...rest, type };\n}\n\n/**\n * Parse a string as JSON, returning the parsed value on success or the\n * original string when it is not JSON — so a plain-text tool result stays\n * a string while a JSON tool result becomes an object.\n */\nfunction tryParseJson(text: string): unknown {\n const trimmed = text.trim();\n\n if (!trimmed) {\n return text;\n }\n\n const first = trimmed[0];\n\n // Only attempt a parse for plausibly-structured payloads, so a bare\n // sentence isn't mangled by a lenient parse.\n if (first !== \"{\" && first !== \"[\") {\n return text;\n }\n\n try {\n return JSON.parse(trimmed);\n } catch {\n return text;\n }\n}\n\n/**\n * Connect to an external MCP server and adapt its tools as agent tools\n * (Direction A: server → local agent tools).\n *\n * Opens the transport lazily and exposes {@link McpClient.tools}, which on\n * first call runs the `initialize` handshake, lists the server's tools via\n * `tools/list`, and maps each into a {@link ToolContract}:\n * - **input schema** — the remote tool's JSON Schema is wrapped as a\n * Standard Schema via {@link jsonSchemaToStandard} (Ajv-backed, an\n * optional peer);\n * - **execute** — issues `tools/call` honoring `ctx.signal` and the\n * configured `timeoutMs`, unwraps the result content, and throws on an\n * `isError` result so the `tool()` wrapper surfaces it as `{ error }`;\n * - **name** — prefixed with `options.namePrefix` to avoid local\n * collisions; only tools passing `options.filter` are adapted.\n *\n * The adapted contracts are cached after the first `tools()` call, so the\n * handshake + list happen exactly once. The returned contracts drop\n * straight into `ai.agent({ tools: [...] })`.\n *\n * @param server - The transport config (`{ type: \"stdio\" }` /\n * `{ type: \"http\" }`). A pre-built transport client may be injected for\n * testing.\n * @param options - Prefix / filter / per-call timeout.\n * @returns An {@link McpClient} handle.\n *\n * @example\n * const github = mcp(\n * { type: \"stdio\", command: \"npx\", args: [\"-y\", \"@modelcontextprotocol/server-github\"] },\n * { namePrefix: \"github.\" },\n * );\n * const dev = ai.agent({ model, tools: [...(await github.tools())] });\n */\nexport function mcp(\n server: McpTransport | McpTransportClient,\n options?: McpClientOptions,\n): McpClient {\n return new McpClientImpl(server, options);\n}\n"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { McpClient, McpClientOptions, McpServeOptions, McpServeSource, McpServer, McpTransport } from "../contracts/mcp.type.mjs";
2
+ import { createServeHandler, serve } from "./serve.mjs";
3
+ import { jsonSchemaToStandard } from "./json-schema-to-standard.mjs";
4
+ import { McpTransportClient } from "./transport.type.mjs";
5
+ import { JsonRpcClientHandle, createJsonRpcClient, createTransport } from "./transport.mjs";
6
+
7
+ //#region ../@warlock.js/ai-tools/src/mcp/index.d.ts
8
+ /**
9
+ * The callable `ai.mcp` surface — a factory that connects to an external
10
+ * MCP server (Direction A) and also carries `.serve` to expose a local
11
+ * primitive AS an MCP server (Direction B). Modeled as a function with an
12
+ * attached `serve` property, mirroring how `ai.mcp(server).tools()` and
13
+ * `ai.mcp.serve(source, options).start()` read in the design.
14
+ */
15
+ interface McpFactory {
16
+ /**
17
+ * Connect to an external MCP server and adapt its tools as agent tools.
18
+ *
19
+ * @param server - The transport config (`{ type: "stdio" }` / `{ type: "http" }`).
20
+ * @param options - Prefix / filter / per-call timeout.
21
+ */
22
+ (server: McpTransport, options?: McpClientOptions): McpClient;
23
+ /**
24
+ * Expose a built agent / supervisor / orchestrator (or a literal
25
+ * `ToolContract[]`) AS an MCP server.
26
+ *
27
+ * @param source - The tools to expose.
28
+ * @param options - Server name, version, transport, and schema dialect.
29
+ */
30
+ serve(source: McpServeSource, options: McpServeOptions): McpServer;
31
+ }
32
+ /**
33
+ * The `ai.mcp` factory value: the client factory with `.serve` attached.
34
+ * `Object.assign` keeps `mcp` callable (Direction A) while widening it with
35
+ * the `serve` member (Direction B) — one object, both directions. The
36
+ * `declare module "@warlock.js/ai"` augmentation and the runtime
37
+ * registration (`ai.mcp = mcp`) live in `../register`, so this barrel is a
38
+ * pure value/type module the registrar consumes.
39
+ */
40
+ declare const mcp: McpFactory;
41
+ //#endregion
42
+ export { McpFactory, mcp };
43
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/mcp/index.ts"],"mappings":";;;;;;;;;AAwBA;;;;;UAAiB,UAAA;EAeD;;;;;;EAAA,CARb,MAAA,EAAQ,YAAA,EAAc,OAAA,GAAU,gBAAA,GAAmB,SAAA;EAAnB;;;;;;;EAQjC,KAAA,CAAM,MAAA,EAAQ,cAAA,EAAgB,OAAA,EAAS,eAAA,GAAkB,SAAA;AAAA;;AAAS;AAWpE;;;;AAGC;;cAHY,GAAA,EAAK,UAGjB"}
@@ -0,0 +1,19 @@
1
+ import { jsonSchemaToStandard } from "./json-schema-to-standard.mjs";
2
+ import { createJsonRpcClient, createTransport } from "./transport.mjs";
3
+ import { mcp as mcp$1 } from "./client.mjs";
4
+ import { createServeHandler, serve } from "./serve.mjs";
5
+
6
+ //#region ../@warlock.js/ai-tools/src/mcp/index.ts
7
+ /**
8
+ * The `ai.mcp` factory value: the client factory with `.serve` attached.
9
+ * `Object.assign` keeps `mcp` callable (Direction A) while widening it with
10
+ * the `serve` member (Direction B) — one object, both directions. The
11
+ * `declare module "@warlock.js/ai"` augmentation and the runtime
12
+ * registration (`ai.mcp = mcp`) live in `../register`, so this barrel is a
13
+ * pure value/type module the registrar consumes.
14
+ */
15
+ const mcp = Object.assign((server, options) => mcp$1(server, options), { serve });
16
+
17
+ //#endregion
18
+ export { mcp };
19
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["mcpClient"],"sources":["../../../../../../../@warlock.js/ai-tools/src/mcp/index.ts"],"sourcesContent":["import type {\n McpClient,\n McpClientOptions,\n McpServeOptions,\n McpServeSource,\n McpServer,\n McpTransport,\n} from \"../contracts\";\nimport { mcp as mcpClient } from \"./client\";\nimport { serve } from \"./serve\";\n\nexport { serve, createServeHandler } from \"./serve\";\nexport { jsonSchemaToStandard } from \"./json-schema-to-standard\";\nexport { createJsonRpcClient, createTransport } from \"./transport\";\nexport type { JsonRpcClientHandle } from \"./transport\";\nexport type { McpTransportClient } from \"./transport.type\";\n\n/**\n * The callable `ai.mcp` surface — a factory that connects to an external\n * MCP server (Direction A) and also carries `.serve` to expose a local\n * primitive AS an MCP server (Direction B). Modeled as a function with an\n * attached `serve` property, mirroring how `ai.mcp(server).tools()` and\n * `ai.mcp.serve(source, options).start()` read in the design.\n */\nexport interface McpFactory {\n /**\n * Connect to an external MCP server and adapt its tools as agent tools.\n *\n * @param server - The transport config (`{ type: \"stdio\" }` / `{ type: \"http\" }`).\n * @param options - Prefix / filter / per-call timeout.\n */\n (server: McpTransport, options?: McpClientOptions): McpClient;\n /**\n * Expose a built agent / supervisor / orchestrator (or a literal\n * `ToolContract[]`) AS an MCP server.\n *\n * @param source - The tools to expose.\n * @param options - Server name, version, transport, and schema dialect.\n */\n serve(source: McpServeSource, options: McpServeOptions): McpServer;\n}\n\n/**\n * The `ai.mcp` factory value: the client factory with `.serve` attached.\n * `Object.assign` keeps `mcp` callable (Direction A) while widening it with\n * the `serve` member (Direction B) — one object, both directions. The\n * `declare module \"@warlock.js/ai\"` augmentation and the runtime\n * registration (`ai.mcp = mcp`) live in `../register`, so this barrel is a\n * pure value/type module the registrar consumes.\n */\nexport const mcp: McpFactory = Object.assign(\n (server: McpTransport, options?: McpClientOptions): McpClient => mcpClient(server, options),\n { serve },\n);\n"],"mappings":";;;;;;;;;;;;;;AAkDA,MAAa,MAAkB,OAAO,QACnC,QAAsB,YAA0CA,MAAU,QAAQ,OAAO,GAC1F,EAAE,MAAM,CACV"}
@@ -0,0 +1,34 @@
1
+ import { StandardSchemaV1 } from "../node_modules/@standard-schema/spec/dist/index.mjs";
2
+
3
+ //#region ../@warlock.js/ai-tools/src/mcp/json-schema-to-standard.d.ts
4
+ /**
5
+ * Wrap a raw JSON Schema as a {@link StandardSchemaV1} whose
6
+ * `~standard.validate` runs Ajv. The shape mirrors `passthroughSchema()`
7
+ * (`{ "~standard": { version: 1, vendor, validate } }`) so it drops into
8
+ * `tool({ input })` exactly like a native seal schema.
9
+ *
10
+ * Validation behavior:
11
+ * - **Valid input** → `{ value }` (the input is passed through unchanged;
12
+ * Ajv validates, it does not transform).
13
+ * - **Invalid input** → `{ issues }` carrying Ajv's `instancePath` +
14
+ * message per failure, so `tool()` produces a `SchemaValidationError`.
15
+ * - **Missing `ajv` peer** → a single issue carrying the curated install
16
+ * string, surfaced the same way (a developer-facing message in logs).
17
+ * - **No / empty schema** → an accept-all passthrough (an MCP tool may
18
+ * advertise no `inputSchema`).
19
+ *
20
+ * @param schema - The JSON Schema (an MCP tool's `inputSchema`), or
21
+ * `undefined` for a no-argument tool.
22
+ * @returns A `StandardSchemaV1<TInput>` ready for `tool({ input })`.
23
+ *
24
+ * @example
25
+ * const input = jsonSchemaToStandard<{ q: string }>({
26
+ * type: "object",
27
+ * properties: { q: { type: "string" } },
28
+ * required: ["q"],
29
+ * });
30
+ */
31
+ declare function jsonSchemaToStandard<TInput = unknown>(schema: Record<string, unknown> | undefined): StandardSchemaV1<TInput>;
32
+ //#endregion
33
+ export { jsonSchemaToStandard };
34
+ //# sourceMappingURL=json-schema-to-standard.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-schema-to-standard.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/mcp/json-schema-to-standard.ts"],"mappings":";;;;;AA+JA;;;;;;;;;;;;;;AAE0B;;;;;;;;;;;iBAFV,oBAAA,mBACd,MAAA,EAAQ,MAAA,gCACP,gBAAA,CAAiB,MAAA"}
@@ -0,0 +1,147 @@
1
+ //#region ../@warlock.js/ai-tools/src/mcp/json-schema-to-standard.ts
2
+ /**
3
+ * The inverse of `@warlock.js/ai`'s `extractJsonSchema` (which goes
4
+ * Standard Schema → JSON Schema). Here we wrap a raw JSON Schema as a
5
+ * {@link StandardSchemaV1} whose `~standard.validate` runs a lazily-imported
6
+ * Ajv validator — so an MCP server's `inputSchema` (JSON Schema) becomes a
7
+ * `ToolConfig.input` the `tool()` factory can validate against.
8
+ *
9
+ * Ajv is an OPTIONAL peer, lazy-imported on first validate following the
10
+ * langfuse/readability pattern: a missing peer surfaces a curated install
11
+ * string (via the returned issues), never a raw module-resolution stack.
12
+ */
13
+ /** The structural vendor template, mirroring `passthroughSchema()`. */
14
+ const VENDOR = "warlock-ai";
15
+ let AjvSdk;
16
+ let ajvInstance;
17
+ let isAjvAvailable;
18
+ let loadingPromise;
19
+ const AJV_INSTALL_INSTRUCTIONS = `
20
+ The MCP client's JSON-Schema validation requires the ajv package.
21
+ Install it with:
22
+
23
+ npm install ajv
24
+
25
+ Or with your preferred package manager:
26
+
27
+ pnpm add ajv
28
+ yarn add ajv
29
+ `.trim();
30
+ /**
31
+ * Settle the lazy import of `ajv` once, concurrency-safe. A bare `catch`
32
+ * flips the availability flag to `false`; the curated install string then
33
+ * surfaces at validate time as a Standard Schema issue, never a raw
34
+ * module-resolution error. The constructed `Ajv` instance is cached and
35
+ * reused for every schema compile.
36
+ */
37
+ async function loadAjv() {
38
+ if (isAjvAvailable !== void 0) return;
39
+ if (loadingPromise) return loadingPromise;
40
+ loadingPromise = (async () => {
41
+ try {
42
+ AjvSdk = await import("ajv");
43
+ ajvInstance = new (AjvSdk.default ?? AjvSdk)({
44
+ allErrors: true,
45
+ strict: false
46
+ });
47
+ isAjvAvailable = true;
48
+ } catch {
49
+ isAjvAvailable = false;
50
+ }
51
+ })();
52
+ return loadingPromise;
53
+ }
54
+ /**
55
+ * Compile a JSON Schema with the shared Ajv instance, caching the compiled
56
+ * validator on a closure so repeated validations don't recompile. A schema
57
+ * Ajv itself rejects at compile time (an invalid meta-schema) degrades to
58
+ * an accept-all validator so a malformed remote schema can't wedge the
59
+ * tool — the server, not us, owns its schema's correctness.
60
+ */
61
+ function makeCompiler(schema) {
62
+ let compiled;
63
+ let attempted = false;
64
+ return () => {
65
+ if (attempted) return compiled;
66
+ attempted = true;
67
+ if (!ajvInstance) return;
68
+ try {
69
+ compiled = ajvInstance.compile(schema);
70
+ } catch {
71
+ compiled = void 0;
72
+ }
73
+ return compiled;
74
+ };
75
+ }
76
+ /**
77
+ * Wrap a raw JSON Schema as a {@link StandardSchemaV1} whose
78
+ * `~standard.validate` runs Ajv. The shape mirrors `passthroughSchema()`
79
+ * (`{ "~standard": { version: 1, vendor, validate } }`) so it drops into
80
+ * `tool({ input })` exactly like a native seal schema.
81
+ *
82
+ * Validation behavior:
83
+ * - **Valid input** → `{ value }` (the input is passed through unchanged;
84
+ * Ajv validates, it does not transform).
85
+ * - **Invalid input** → `{ issues }` carrying Ajv's `instancePath` +
86
+ * message per failure, so `tool()` produces a `SchemaValidationError`.
87
+ * - **Missing `ajv` peer** → a single issue carrying the curated install
88
+ * string, surfaced the same way (a developer-facing message in logs).
89
+ * - **No / empty schema** → an accept-all passthrough (an MCP tool may
90
+ * advertise no `inputSchema`).
91
+ *
92
+ * @param schema - The JSON Schema (an MCP tool's `inputSchema`), or
93
+ * `undefined` for a no-argument tool.
94
+ * @returns A `StandardSchemaV1<TInput>` ready for `tool({ input })`.
95
+ *
96
+ * @example
97
+ * const input = jsonSchemaToStandard<{ q: string }>({
98
+ * type: "object",
99
+ * properties: { q: { type: "string" } },
100
+ * required: ["q"],
101
+ * });
102
+ */
103
+ function jsonSchemaToStandard(schema) {
104
+ if (!schema || Object.keys(schema).length === 0) return { "~standard": {
105
+ version: 1,
106
+ vendor: VENDOR,
107
+ validate: (value) => ({ value })
108
+ } };
109
+ const compile = makeCompiler(schema);
110
+ return { "~standard": {
111
+ version: 1,
112
+ vendor: VENDOR,
113
+ async validate(value) {
114
+ await loadAjv();
115
+ if (!isAjvAvailable) return { issues: [{ message: AJV_INSTALL_INSTRUCTIONS }] };
116
+ const validator = compile();
117
+ if (!validator) return { value };
118
+ if (validator(value)) return { value };
119
+ const issues = (validator.errors ?? []).map((error) => ({
120
+ message: formatAjvError(error),
121
+ path: pathFromInstancePath(error.instancePath)
122
+ }));
123
+ return { issues: issues.length > 0 ? issues : [{ message: "input failed JSON Schema validation" }] };
124
+ }
125
+ } };
126
+ }
127
+ /**
128
+ * Render one Ajv error into a human-readable issue message. Prefixes the
129
+ * failing instance path (when present) so the model can see WHICH field
130
+ * was wrong, e.g. `/query: must be string`.
131
+ */
132
+ function formatAjvError(error) {
133
+ return `${error.instancePath ? `${error.instancePath}: ` : ""}${error.message ?? `failed "${error.keyword ?? "validation"}"`}`;
134
+ }
135
+ /**
136
+ * Convert an Ajv `instancePath` (a JSON-Pointer like `/items/0/name`) into
137
+ * the Standard Schema `path` segment array (`["items", "0", "name"]`).
138
+ * Empty paths (a root-level failure) become an empty array.
139
+ */
140
+ function pathFromInstancePath(instancePath) {
141
+ if (!instancePath) return [];
142
+ return instancePath.split("/").filter((segment) => segment.length > 0).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
143
+ }
144
+
145
+ //#endregion
146
+ export { jsonSchemaToStandard };
147
+ //# sourceMappingURL=json-schema-to-standard.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-schema-to-standard.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/mcp/json-schema-to-standard.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * The inverse of `@warlock.js/ai`'s `extractJsonSchema` (which goes\n * Standard Schema → JSON Schema). Here we wrap a raw JSON Schema as a\n * {@link StandardSchemaV1} whose `~standard.validate` runs a lazily-imported\n * Ajv validator — so an MCP server's `inputSchema` (JSON Schema) becomes a\n * `ToolConfig.input` the `tool()` factory can validate against.\n *\n * Ajv is an OPTIONAL peer, lazy-imported on first validate following the\n * langfuse/readability pattern: a missing peer surfaces a curated install\n * string (via the returned issues), never a raw module-resolution stack.\n */\n\n/** The structural vendor template, mirroring `passthroughSchema()`. */\nconst VENDOR = \"warlock-ai\";\n\n// ============================================================\n// Lazily-loaded ajv (OPTIONAL peer)\n// ============================================================\n\n/**\n * Minimal structural view of an Ajv-compiled validator. Ajv is an optional\n * peer that may not be installed, so we model only the surface we touch\n * rather than depending on ajv's own published types. A validator is a\n * callable that returns a boolean and exposes the `errors` it collected.\n */\ninterface AjvValidateFunctionLike {\n (data: unknown): boolean;\n errors?: AjvErrorObject[] | null;\n}\n\n/** Minimal structural view of an `Ajv` instance — only `compile`. */\ninterface AjvInstanceLike {\n compile(schema: Record<string, unknown>): AjvValidateFunctionLike;\n}\n\n/** The `Ajv` constructor, as exposed by both the CJS and ESM builds. */\ntype AjvConstructorLike = new (options?: Record<string, unknown>) => AjvInstanceLike;\n\n/**\n * Minimal structural view of the dynamically imported `ajv` module. Ajv\n * ships its constructor as a `default` export under ESM interop; the older\n * CJS shape exposes the constructor as the module namespace itself, so\n * `default` is optional here and the loader falls back to the namespace.\n */\ninterface AjvModuleLike {\n default?: AjvConstructorLike;\n}\n\nlet AjvSdk: AjvModuleLike | undefined;\nlet ajvInstance: AjvInstanceLike | undefined;\nlet isAjvAvailable: boolean | undefined;\nlet loadingPromise: Promise<void> | undefined;\n\nconst AJV_INSTALL_INSTRUCTIONS = `\nThe MCP client's JSON-Schema validation requires the ajv package.\nInstall it with:\n\n npm install ajv\n\nOr with your preferred package manager:\n\n pnpm add ajv\n yarn add ajv\n`.trim();\n\n/**\n * Settle the lazy import of `ajv` once, concurrency-safe. A bare `catch`\n * flips the availability flag to `false`; the curated install string then\n * surfaces at validate time as a Standard Schema issue, never a raw\n * module-resolution error. The constructed `Ajv` instance is cached and\n * reused for every schema compile.\n */\nasync function loadAjv(): Promise<void> {\n if (isAjvAvailable !== undefined) {\n return;\n }\n\n if (loadingPromise) {\n return loadingPromise;\n }\n\n loadingPromise = (async () => {\n try {\n AjvSdk = (await import(\"ajv\")) as AjvModuleLike;\n // Ajv ships as a default export under both CJS and ESM interop; the\n // older CJS shape exposes the constructor as the namespace itself.\n const AjvCtor: AjvConstructorLike =\n AjvSdk.default ?? (AjvSdk as unknown as AjvConstructorLike);\n ajvInstance = new AjvCtor({ allErrors: true, strict: false });\n isAjvAvailable = true;\n } catch {\n isAjvAvailable = false;\n }\n })();\n\n return loadingPromise;\n}\n\n/**\n * Compile a JSON Schema with the shared Ajv instance, caching the compiled\n * validator on a closure so repeated validations don't recompile. A schema\n * Ajv itself rejects at compile time (an invalid meta-schema) degrades to\n * an accept-all validator so a malformed remote schema can't wedge the\n * tool — the server, not us, owns its schema's correctness.\n */\nfunction makeCompiler(schema: Record<string, unknown>): () => AjvValidateFunctionLike | undefined {\n let compiled: AjvValidateFunctionLike | undefined;\n let attempted = false;\n\n return () => {\n if (attempted) {\n return compiled;\n }\n\n attempted = true;\n\n if (!ajvInstance) {\n return undefined;\n }\n\n try {\n compiled = ajvInstance.compile(schema);\n } catch {\n compiled = undefined;\n }\n\n return compiled;\n };\n}\n\n/**\n * Wrap a raw JSON Schema as a {@link StandardSchemaV1} whose\n * `~standard.validate` runs Ajv. The shape mirrors `passthroughSchema()`\n * (`{ \"~standard\": { version: 1, vendor, validate } }`) so it drops into\n * `tool({ input })` exactly like a native seal schema.\n *\n * Validation behavior:\n * - **Valid input** → `{ value }` (the input is passed through unchanged;\n * Ajv validates, it does not transform).\n * - **Invalid input** → `{ issues }` carrying Ajv's `instancePath` +\n * message per failure, so `tool()` produces a `SchemaValidationError`.\n * - **Missing `ajv` peer** → a single issue carrying the curated install\n * string, surfaced the same way (a developer-facing message in logs).\n * - **No / empty schema** → an accept-all passthrough (an MCP tool may\n * advertise no `inputSchema`).\n *\n * @param schema - The JSON Schema (an MCP tool's `inputSchema`), or\n * `undefined` for a no-argument tool.\n * @returns A `StandardSchemaV1<TInput>` ready for `tool({ input })`.\n *\n * @example\n * const input = jsonSchemaToStandard<{ q: string }>({\n * type: \"object\",\n * properties: { q: { type: \"string\" } },\n * required: [\"q\"],\n * });\n */\nexport function jsonSchemaToStandard<TInput = unknown>(\n schema: Record<string, unknown> | undefined,\n): StandardSchemaV1<TInput> {\n // A tool with no schema (or an empty object schema) validates everything\n // — return an accept-all passthrough and never touch Ajv.\n if (!schema || Object.keys(schema).length === 0) {\n return {\n \"~standard\": {\n version: 1,\n vendor: VENDOR,\n validate: (value: unknown) => ({ value: value as TInput }),\n },\n };\n }\n\n const compile = makeCompiler(schema);\n\n return {\n \"~standard\": {\n version: 1,\n vendor: VENDOR,\n async validate(value: unknown): Promise<StandardSchemaV1.Result<TInput>> {\n await loadAjv();\n\n if (!isAjvAvailable) {\n return {\n issues: [{ message: AJV_INSTALL_INSTRUCTIONS }],\n };\n }\n\n const validator = compile();\n\n // A schema Ajv could not compile degrades to accept-all rather\n // than failing every call — the remote server owns its schema.\n if (!validator) {\n return { value: value as TInput };\n }\n\n const ok = validator(value);\n\n if (ok) {\n return { value: value as TInput };\n }\n\n const issues: StandardSchemaV1.Issue[] = (validator.errors ?? []).map((error) => ({\n message: formatAjvError(error),\n path: pathFromInstancePath(error.instancePath),\n }));\n\n return {\n issues: issues.length > 0 ? issues : [{ message: \"input failed JSON Schema validation\" }],\n };\n },\n },\n };\n}\n\n/** A single Ajv error object — narrowed to the fields we read. */\ninterface AjvErrorObject {\n instancePath?: string;\n message?: string;\n keyword?: string;\n}\n\n/**\n * Render one Ajv error into a human-readable issue message. Prefixes the\n * failing instance path (when present) so the model can see WHICH field\n * was wrong, e.g. `/query: must be string`.\n */\nfunction formatAjvError(error: AjvErrorObject): string {\n const where = error.instancePath ? `${error.instancePath}: ` : \"\";\n const message = error.message ?? `failed \"${error.keyword ?? \"validation\"}\"`;\n\n return `${where}${message}`;\n}\n\n/**\n * Convert an Ajv `instancePath` (a JSON-Pointer like `/items/0/name`) into\n * the Standard Schema `path` segment array (`[\"items\", \"0\", \"name\"]`).\n * Empty paths (a root-level failure) become an empty array.\n */\nfunction pathFromInstancePath(instancePath: string | undefined): string[] {\n if (!instancePath) {\n return [];\n }\n\n return instancePath\n .split(\"/\")\n .filter((segment) => segment.length > 0)\n .map((segment) => segment.replace(/~1/g, \"/\").replace(/~0/g, \"~\"));\n}\n\n/**\n * Reset the cached lazy-import state. Test-only seam so a spec can mock\n * `ajv` as present/absent across cases without module-cache bleed.\n *\n * @internal\n */\nexport function resetAjvCacheForTests(): void {\n AjvSdk = undefined;\n ajvInstance = undefined;\n isAjvAvailable = undefined;\n loadingPromise = undefined;\n}\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,SAAS;AAmCf,IAAI;AACJ,IAAI;AACJ,IAAI;AACJ,IAAI;AAEJ,MAAM,2BAA2B;;;;;;;;;;EAU/B,KAAK;;;;;;;;AASP,eAAe,UAAyB;CACtC,IAAI,mBAAmB,QACrB;CAGF,IAAI,gBACF,OAAO;CAGT,kBAAkB,YAAY;EAC5B,IAAI;GACF,SAAU,MAAM,OAAO;GAKvB,cAAc,KADZ,OAAO,WAAY,QACK;IAAE,WAAW;IAAM,QAAQ;GAAM,CAAC;GAC5D,iBAAiB;EACnB,QAAQ;GACN,iBAAiB;EACnB;CACF,EAAC,CAAE;CAEH,OAAO;AACT;;;;;;;;AASA,SAAS,aAAa,QAA4E;CAChG,IAAI;CACJ,IAAI,YAAY;CAEhB,aAAa;EACX,IAAI,WACF,OAAO;EAGT,YAAY;EAEZ,IAAI,CAAC,aACH;EAGF,IAAI;GACF,WAAW,YAAY,QAAQ,MAAM;EACvC,QAAQ;GACN,WAAW;EACb;EAEA,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,qBACd,QAC0B;CAG1B,IAAI,CAAC,UAAU,OAAO,KAAK,MAAM,CAAC,CAAC,WAAW,GAC5C,OAAO,EACL,aAAa;EACX,SAAS;EACT,QAAQ;EACR,WAAW,WAAoB,EAAS,MAAgB;CAC1D,EACF;CAGF,MAAM,UAAU,aAAa,MAAM;CAEnC,OAAO,EACL,aAAa;EACX,SAAS;EACT,QAAQ;EACR,MAAM,SAAS,OAA0D;GACvE,MAAM,QAAQ;GAEd,IAAI,CAAC,gBACH,OAAO,EACL,QAAQ,CAAC,EAAE,SAAS,yBAAyB,CAAC,EAChD;GAGF,MAAM,YAAY,QAAQ;GAI1B,IAAI,CAAC,WACH,OAAO,EAAS,MAAgB;GAKlC,IAFW,UAAU,KAEhB,GACH,OAAO,EAAS,MAAgB;GAGlC,MAAM,UAAoC,UAAU,UAAU,CAAC,EAAC,CAAE,KAAK,WAAW;IAChF,SAAS,eAAe,KAAK;IAC7B,MAAM,qBAAqB,MAAM,YAAY;GAC/C,EAAE;GAEF,OAAO,EACL,QAAQ,OAAO,SAAS,IAAI,SAAS,CAAC,EAAE,SAAS,sCAAsC,CAAC,EAC1F;EACF;CACF,EACF;AACF;;;;;;AAcA,SAAS,eAAe,OAA+B;CAIrD,OAAO,GAHO,MAAM,eAAe,GAAG,MAAM,aAAa,MAAM,KAC/C,MAAM,WAAW,WAAW,MAAM,WAAW,aAAa;AAG5E;;;;;;AAOA,SAAS,qBAAqB,cAA4C;CACxE,IAAI,CAAC,cACH,OAAO,CAAC;CAGV,OAAO,aACJ,MAAM,GAAG,CAAC,CACV,QAAQ,YAAY,QAAQ,SAAS,CAAC,CAAC,CACvC,KAAK,YAAY,QAAQ,QAAQ,OAAO,GAAG,CAAC,CAAC,QAAQ,OAAO,GAAG,CAAC;AACrE"}
@@ -0,0 +1,46 @@
1
+ import { JsonRpcRequest, JsonRpcResponse, McpServeOptions, McpServeSource, McpServer } from "../contracts/mcp.type.mjs";
2
+ //#region ../@warlock.js/ai-tools/src/mcp/serve.d.ts
3
+ /**
4
+ * Build the pure protocol handler for a serve source. Exposed (alongside
5
+ * {@link serve}) so callers and tests can drive the MCP protocol without an
6
+ * actual transport — feed it a JSON-RPC request, get the response.
7
+ *
8
+ * @param source - The tools to expose (an object with `tools()` or a literal array).
9
+ * @param options - Serve options (name / version / schema target).
10
+ * @returns An object whose `handle(request)` maps a request to a response.
11
+ */
12
+ declare function createServeHandler(source: McpServeSource, options: McpServeOptions): {
13
+ handle(request: JsonRpcRequest): Promise<JsonRpcResponse>;
14
+ };
15
+ /**
16
+ * Expose a built agent / supervisor / orchestrator (or a raw
17
+ * `ToolContract[]`) AS an MCP server (Direction B: local primitive → MCP
18
+ * server other clients consume).
19
+ *
20
+ * Enumerates `source.tools()` (or the literal array) once at construction.
21
+ * `tools/list` answers with each tool's `inputSchema` extracted via
22
+ * `extractJsonSchema` at the configured `schemaTarget` (default
23
+ * `draft-2020-12`). `tools/call` routes to the named contract's
24
+ * `invoke()` and maps the never-throwing result — `data` becomes a text
25
+ * content block, `error` becomes an `isError: true` result — so a failing
26
+ * tool surfaces as a normal MCP tool error rather than crashing the server.
27
+ *
28
+ * The default transport is `stdio`, pumped over `process.stdin` /
29
+ * `process.stdout`. Serving over HTTP is left to a host-owned server;
30
+ * `start()` rejects an `http` transport (the protocol core is available
31
+ * via {@link createServeHandler} for a caller's own HTTP wiring).
32
+ *
33
+ * @param source - The tools to expose.
34
+ * @param options - Server name, version, transport, and schema dialect.
35
+ * @returns An {@link McpServer} with `start()` / `stop()`.
36
+ *
37
+ * @example
38
+ * serve(
39
+ * { tools: () => ws.allTools() },
40
+ * { name: "warlock-workspace", transport: { type: "stdio" } },
41
+ * ).start();
42
+ */
43
+ declare function serve(source: McpServeSource, options: McpServeOptions): McpServer;
44
+ //#endregion
45
+ export { createServeHandler, serve };
46
+ //# sourceMappingURL=serve.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.mts","names":[],"sources":["../../../../../../../@warlock.js/ai-tools/src/mcp/serve.ts"],"mappings":";;;;;AAiOA;;;;;;iBAAgB,kBAAA,CACd,MAAA,EAAQ,cAAA,EACR,OAAA,EAAS,eAAA;EACN,MAAA,CAAO,OAAA,EAAS,cAAA,GAAiB,OAAA,CAAQ,eAAA;AAAA;;;;;;;;;;;;AAAe;AAqH7D;;;;;;;;;;;;;;AAAkF;;iBAAlE,KAAA,CAAM,MAAA,EAAQ,cAAA,EAAgB,OAAA,EAAS,eAAA,GAAkB,SAAA"}