@warlock.js/ai-tools 5.33.0 → 5.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -103
- package/cjs/index.cjs +123 -14
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/index.d.mts +2 -2
- package/esm/contracts/mcp-serve.type.d.mts +48 -3
- package/esm/contracts/mcp-serve.type.d.mts.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/mcp/error.d.mts +9 -0
- package/esm/mcp/error.d.mts.map +1 -0
- package/esm/mcp/error.mjs +24 -0
- package/esm/mcp/error.mjs.map +1 -0
- package/esm/mcp/index.d.mts +2 -1
- package/esm/mcp/index.d.mts.map +1 -1
- package/esm/mcp/index.mjs +2 -1
- package/esm/mcp/index.mjs.map +1 -1
- package/esm/mcp/serve-http.mjs +73 -6
- package/esm/mcp/serve-http.mjs.map +1 -1
- package/esm/mcp/serve.d.mts.map +1 -1
- package/esm/mcp/serve.mjs +25 -4
- package/esm/mcp/serve.mjs.map +1 -1
- package/esm/register.d.mts +13 -52
- package/esm/register.d.mts.map +1 -1
- package/esm/register.mjs +4 -5
- package/esm/register.mjs.map +1 -1
- package/llms-full.txt +123 -2
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/skills/expose-as-mcp-server/SKILL.md +102 -2
- package/skills/use-web-and-http-tools/SKILL.md +21 -0
package/esm/mcp/serve.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { McpServeError, McpTransportError } from "../errors.mjs";
|
|
2
|
+
import { isMcpError } from "./error.mjs";
|
|
2
3
|
import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
|
|
3
4
|
import { assertMcpResult, isMcpResult, isRecord, looksLikeMcpResult, unwrapMcpResult } from "./result.mjs";
|
|
4
5
|
import { McpServeRequests } from "./serve-requests.mjs";
|
|
@@ -138,7 +139,10 @@ var McpServeHandler = class {
|
|
|
138
139
|
switch (request.method) {
|
|
139
140
|
case "initialize": return this.initializeResult(request.params);
|
|
140
141
|
case "ping": return {};
|
|
141
|
-
case "tools/list": return { tools: this.listTools(
|
|
142
|
+
case "tools/list": return { tools: await this.listTools({
|
|
143
|
+
...context,
|
|
144
|
+
requestId: request.id
|
|
145
|
+
}) };
|
|
142
146
|
case "tools/call": return this.callTool(request.params, {
|
|
143
147
|
...context,
|
|
144
148
|
requestId: request.id
|
|
@@ -181,9 +185,9 @@ var McpServeHandler = class {
|
|
|
181
185
|
* the configured dialect (default `draft-2020-12` — overriding
|
|
182
186
|
* `extractJsonSchema`'s own `openai-strict` default to a neutral MCP draft).
|
|
183
187
|
*/
|
|
184
|
-
listTools() {
|
|
188
|
+
async listTools(context) {
|
|
185
189
|
const target = this.options.schemaTarget ?? DEFAULT_SCHEMA_TARGET;
|
|
186
|
-
|
|
190
|
+
const tools = this.tools.map(({ name, contract }) => {
|
|
187
191
|
const inputSchema = extractJsonSchema(contract.input, { target }) ?? {
|
|
188
192
|
type: "object",
|
|
189
193
|
properties: {}
|
|
@@ -195,6 +199,8 @@ var McpServeHandler = class {
|
|
|
195
199
|
...isRecord(contract.meta?.outputSchema) ? { outputSchema: contract.meta.outputSchema } : {}
|
|
196
200
|
};
|
|
197
201
|
});
|
|
202
|
+
context.signal?.throwIfAborted();
|
|
203
|
+
return this.options.listFilter ? this.options.listFilter(context, tools) : tools;
|
|
198
204
|
}
|
|
199
205
|
/**
|
|
200
206
|
* Route a `tools/call` to the named contract's `invoke()` and map the
|
|
@@ -208,6 +214,15 @@ var McpServeHandler = class {
|
|
|
208
214
|
*/
|
|
209
215
|
async callTool(params, context) {
|
|
210
216
|
const { name, args } = readCallParams(params);
|
|
217
|
+
if (this.options.validateCallParams) {
|
|
218
|
+
context.signal?.throwIfAborted();
|
|
219
|
+
try {
|
|
220
|
+
await this.options.validateCallParams(context, readRecord(params));
|
|
221
|
+
} catch (cause) {
|
|
222
|
+
if (isMcpError(cause)) throw new RpcFailure(cause.code, cause.message, cause.data);
|
|
223
|
+
throw cause;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
211
226
|
const contract = this.byName.get(name);
|
|
212
227
|
if (!contract) throw new McpTransportError(`Unknown tool "${name}".`, {
|
|
213
228
|
type: "protocol",
|
|
@@ -251,7 +266,13 @@ var McpServeHandler = class {
|
|
|
251
266
|
input = validation.value;
|
|
252
267
|
}
|
|
253
268
|
context.signal?.throwIfAborted();
|
|
254
|
-
|
|
269
|
+
let refusal;
|
|
270
|
+
try {
|
|
271
|
+
refusal = await this.options.beforeCall(context, contract, input);
|
|
272
|
+
} catch (cause) {
|
|
273
|
+
if (isMcpError(cause)) throw new RpcFailure(cause.code, cause.message, cause.data);
|
|
274
|
+
throw cause;
|
|
275
|
+
}
|
|
255
276
|
if (refusal !== void 0) {
|
|
256
277
|
try {
|
|
257
278
|
assertMcpResult(refusal);
|
package/esm/mcp/serve.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve.ts"],"sourcesContent":["import { createInterface, type Interface } from \"node:readline\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { extractJsonSchema, type ToolContext, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n McpGroupedToolSource,\n McpHttpServeHandle,\n McpHttpServeTransport,\n McpPromptMessage,\n McpServeCallContext,\n McpServeHandle,\n McpServeOptions,\n McpServePrompt,\n McpServeResource,\n McpServeSource,\n McpServeToolContext,\n McpStdioServeTransport,\n McpServer,\n McpToolCallResult,\n McpToolDescriptor,\n McpToolSource,\n} from \"../contracts\";\nimport { McpServeError, McpTransportError } from \"../errors\";\nimport { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from \"./protocol\";\nimport { startHttpServer } from \"./serve-http\";\nimport { McpServeRequests } from \"./serve-requests\";\nimport {\n assertMcpResult,\n isMcpResult,\n isRecord,\n looksLikeMcpResult,\n unwrapMcpResult,\n} from \"./result\";\n\n/** JSON-RPC version literal every outbound message carries. */\nconst JSONRPC_VERSION = \"2.0\";\n\n/** Default JSON-Schema dialect emitted for each tool's `inputSchema`. */\nconst DEFAULT_SCHEMA_TARGET = \"draft-2020-12\";\n\n/** Default advertised server version when the caller omits one. */\nconst DEFAULT_VERSION = \"4.4.0\";\n\n/** JSON-RPC standard error codes we emit. */\nconst JSON_RPC_METHOD_NOT_FOUND = -32601;\nconst JSON_RPC_INVALID_PARAMS = -32602;\n\n/** MCP's \"resource not found\" error code (server/resources, error handling). */\nconst MCP_RESOURCE_NOT_FOUND = -32002;\n\n/** One tool as exposed on the wire: its public name plus the contract behind it. */\ntype ExposedTool = {\n /** The name clients see in `tools/list` and send in `tools/call`. */\n name: string;\n /** The contract that runs. */\n contract: ToolContract;\n};\n\n/**\n * A JSON-RPC failure raised inside a method handler, carrying the exact\n * code to answer with (unknown resource, missing prompt argument, …).\n */\nclass RpcFailure extends Error {\n public readonly code: number;\n\n public readonly data: unknown;\n\n public constructor(code: number, message: string, data?: unknown) {\n super(message);\n this.code = code;\n this.data = data;\n }\n}\n\n/** Enumerate one tool set into a flat contract array. */\nfunction toolsOf(source: McpToolSource): ToolContract[] {\n return Array.isArray(source) ? source : source.tools();\n}\n\n/** Narrow a serve source to its grouped shape. */\nfunction isGroupedSource(source: McpServeSource): source is McpGroupedToolSource {\n return !Array.isArray(source) && \"groups\" in source;\n}\n\n/**\n * Resolve the serve source into the exposed tool list, applying\n * `prefixToolNames` and refusing duplicate names. Throws\n * {@link McpServeError} — `\"config\"` for `prefixToolNames` on an ungrouped\n * source, `\"collision\"` when two tools would share a name.\n */\nfunction resolveTools(source: McpServeSource, options: McpServeOptions): ExposedTool[] {\n const prefix = options.prefixToolNames ?? false;\n const exposed: Array<ExposedTool & { origin: string }> = [];\n\n if (isGroupedSource(source)) {\n for (const [group, groupSource] of Object.entries(source.groups)) {\n for (const contract of toolsOf(groupSource)) {\n let name = contract.name;\n\n if (prefix === true) {\n name = `${group}_${contract.name}`;\n } else if (typeof prefix === \"function\") {\n name = prefix(group, contract.name);\n }\n\n exposed.push({ name, contract, origin: `group \"${group}\"` });\n }\n }\n } else {\n if (prefix !== false) {\n throw new McpServeError(\n \"prefixToolNames needs a grouped source: serve({ groups: { name: tools } }, …).\",\n { type: \"config\" },\n );\n }\n\n for (const contract of toolsOf(source)) {\n exposed.push({ name: contract.name, contract, origin: \"the source\" });\n }\n }\n\n const seen = new Map<string, string>();\n\n for (const tool of exposed) {\n const previous = seen.get(tool.name);\n\n if (previous !== undefined) {\n throw new McpServeError(\n `MCP tool name \"${tool.name}\" is exposed twice (from ${previous} and ${tool.origin}). ` +\n \"Rename one tool, or serve a grouped source with prefixToolNames.\",\n { type: \"collision\", context: { name: tool.name } },\n );\n }\n\n seen.set(tool.name, tool.origin);\n }\n\n return exposed.map(({ name, contract }) => ({ name, contract }));\n}\n\n/** Throw a collision error when two entries share the same key. */\nfunction assertUnique<T>(\n entries: readonly T[] | undefined,\n key: (entry: T) => string,\n label: string,\n): void {\n const seen = new Set<string>();\n\n for (const entry of entries ?? []) {\n const value = key(entry);\n\n if (seen.has(value)) {\n throw new McpServeError(`MCP ${label} \"${value}\" is declared twice.`, {\n type: \"collision\",\n context: { [label]: value },\n });\n }\n\n seen.add(value);\n }\n}\n\n/**\n * The pure protocol core of `serve` — maps one JSON-RPC request to its\n * response, with no I/O. Both the stdio and HTTP serve-transports pump\n * their inbound requests through this, and specs can drive it directly.\n *\n * Handles `initialize`, `ping`, `tools/list`, `tools/call`, and — when\n * configured — `resources/list`, `resources/templates/list`,\n * `resources/read`, `prompts/list` and `prompts/get`. Any other method\n * answers with a JSON-RPC `method not found` error.\n *\n * Constructed via {@link createServeHandler}.\n */\nclass McpServeHandler {\n /** The tools this server exposes (snapshotted at construction). */\n private readonly tools: ExposedTool[];\n\n /** Fast lookup by exposed tool name for `tools/call` dispatch. */\n private readonly byName: Map<string, ToolContract>;\n\n /** Resources by URI, or `undefined` when the capability is off. */\n private readonly resources: Map<string, McpServeResource> | undefined;\n\n /** Prompts by name, or `undefined` when the capability is off. */\n private readonly prompts: Map<string, McpServePrompt> | undefined;\n\n /** Construction-time serve options (name / version / schema target). */\n private readonly options: McpServeOptions;\n\n public constructor(source: McpServeSource, options: McpServeOptions) {\n this.tools = resolveTools(source, options);\n this.byName = new Map(this.tools.map((tool) => [tool.name, tool.contract]));\n\n assertUnique(options.resources, (resource) => resource.uri, \"resource\");\n assertUnique(options.prompts, (prompt) => prompt.name, \"prompt\");\n\n this.resources = options.resources\n ? new Map(options.resources.map((resource) => [resource.uri, resource]))\n : undefined;\n this.prompts = options.prompts\n ? new Map(options.prompts.map((prompt) => [prompt.name, prompt]))\n : undefined;\n this.options = options;\n }\n\n /**\n * Dispatch one inbound JSON-RPC request to its handler and produce the\n * response. A handler that throws is mapped to a JSON-RPC error response\n * — the serve loop never crashes on a bad request.\n *\n * @param request - The JSON-RPC request.\n * @param context - Per-call context: the inbound HTTP request (headers\n * reach tools as `ctx.request`) and an abort signal.\n */\n public async handle(\n request: JsonRpcRequest,\n context: McpServeCallContext = {},\n ): Promise<JsonRpcResponse> {\n try {\n return this.ok(\n request.id,\n await this.dispatch(request, { ...context, requestId: request.id }),\n );\n } catch (cause) {\n if (cause instanceof RpcFailure) {\n return this.error(request.id, cause.code, cause.message, cause.data);\n }\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n return this.error(request.id, JSON_RPC_INVALID_PARAMS, message);\n }\n }\n\n /** Route a method to its result builder. */\n private async dispatch(request: JsonRpcRequest, context: McpServeCallContext): Promise<unknown> {\n switch (request.method) {\n case \"initialize\":\n return this.initializeResult(request.params);\n case \"ping\":\n return {};\n case \"tools/list\":\n return { tools: this.listTools() };\n case \"tools/call\":\n return this.callTool(request.params, { ...context, requestId: request.id });\n default:\n break;\n }\n\n if (this.resources) {\n switch (request.method) {\n case \"resources/list\":\n return { resources: this.listResources(this.resources) };\n case \"resources/templates/list\":\n return { resourceTemplates: [] };\n case \"resources/read\":\n return this.readResource(this.resources, request.params, context);\n default:\n break;\n }\n }\n\n if (this.prompts) {\n switch (request.method) {\n case \"prompts/list\":\n return { prompts: this.listPrompts(this.prompts) };\n case \"prompts/get\":\n return this.getPrompt(this.prompts, request.params, context);\n default:\n break;\n }\n }\n\n throw new RpcFailure(\n JSON_RPC_METHOD_NOT_FOUND,\n `Method \"${request.method}\" is not supported by this MCP server.`,\n );\n }\n\n /**\n * Build the `initialize` result advertising name / version /\n * capabilities, negotiating the protocol version: the client's requested\n * version when supported, otherwise the newest this server speaks.\n */\n private initializeResult(params: unknown): Record<string, unknown> {\n const requested = readRecord(params).protocolVersion;\n const protocolVersion =\n typeof requested === \"string\" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)\n ? requested\n : LATEST_PROTOCOL_VERSION;\n\n const capabilities: Record<string, unknown> = { tools: {} };\n\n if (this.resources) {\n capabilities.resources = {};\n }\n\n if (this.prompts) {\n capabilities.prompts = {};\n }\n\n return {\n protocolVersion,\n capabilities,\n serverInfo: {\n name: this.options.name,\n version: this.options.version ?? DEFAULT_VERSION,\n },\n };\n }\n\n /**\n * Build the `tools/list` payload: one {@link McpToolDescriptor} per\n * exposed tool, its `inputSchema` extracted via `extractJsonSchema` at\n * the configured dialect (default `draft-2020-12` — overriding\n * `extractJsonSchema`'s own `openai-strict` default to a neutral MCP draft).\n */\n private listTools(): McpToolDescriptor[] {\n const target = this.options.schemaTarget ?? DEFAULT_SCHEMA_TARGET;\n\n return this.tools.map(({ name, contract }) => {\n const inputSchema = extractJsonSchema(contract.input, { target }) ?? {\n type: \"object\",\n properties: {},\n };\n\n return {\n name,\n description: contract.description,\n inputSchema,\n ...(isRecord(contract.meta?.outputSchema)\n ? { outputSchema: contract.meta.outputSchema }\n : {}),\n };\n });\n }\n\n /**\n * Route a `tools/call` to the named contract's `invoke()` and map the\n * never-throwing {@link import(\"@warlock.js/ai\").ToolInvokeResult}: `data`\n * → a text content block (or an opted-in native result), `error` → an\n * `isError: true` result. An unknown tool name throws (mapped to a\n * JSON-RPC error by {@link handle}).\n *\n * The contract receives a `ToolContext` carrying the abort signal and,\n * over HTTP, the inbound request's headers on `ctx.request`.\n */\n private async callTool(\n params: unknown,\n context: McpServeToolContext,\n ): Promise<McpToolCallResult> {\n const { name, args } = readCallParams(params);\n const contract = this.byName.get(name);\n\n if (!contract) {\n throw new McpTransportError(`Unknown tool \"${name}\".`, {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n const toolContext: ToolContext = {\n artifacts: {},\n requestId: context.requestId,\n principal: context.principal,\n };\n\n if (context.signal) {\n toolContext.signal = context.signal;\n }\n\n if (context.request) {\n const { headers, remoteAddress } = context.request;\n\n toolContext.request = remoteAddress === undefined ? { headers } : { headers, remoteAddress };\n }\n\n if (this.options.beforeCall) {\n let input = args;\n\n // Validate at the policy boundary even for custom ToolContracts. Keep\n // invoke as the execution boundary so custom accounting/error handling\n // remains intact; invoke still owns its own input validation.\n if (contract.input) {\n let validation: StandardSchemaV1.Result<unknown>;\n\n try {\n validation = await contract.input[\"~standard\"].validate(args);\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n\n return {\n content: [\n { type: \"text\", text: `Schema validation threw for tool \"${name}\": ${message}` },\n ],\n isError: true,\n };\n }\n\n if (validation.issues) {\n return {\n content: [\n {\n type: \"text\",\n text: `Validation failed: ${validation.issues.map((issue) => issue.message).join(\"; \")}`,\n },\n ],\n isError: true,\n };\n }\n\n input = validation.value;\n }\n\n context.signal?.throwIfAborted();\n const refusal = await this.options.beforeCall(context, contract, input);\n\n if (refusal !== undefined) {\n try {\n assertMcpResult(refusal);\n\n if (refusal.isError !== true) {\n throw new Error(\"A refusal must be an error result.\");\n }\n } catch {\n throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, \"Invalid MCP beforeCall refusal.\");\n }\n\n return refusal;\n }\n }\n\n context.signal?.throwIfAborted();\n const result = await contract.invoke(args, toolContext);\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: result.error.message }],\n isError: true,\n };\n }\n\n const mode = contract.meta?.mcpResults ?? this.options.results ?? \"text\";\n const branded = isMcpResult(result.data);\n\n if (branded || (mode === \"native\" && looksLikeMcpResult(result.data))) {\n const native = isMcpResult(result.data) ? unwrapMcpResult(result.data) : result.data;\n\n try {\n assertMcpResult(native);\n } catch {\n throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, \"Invalid native MCP CallToolResult.\");\n }\n\n return native;\n }\n\n return {\n content: [{ type: \"text\", text: serializeData(result.data) }],\n isError: false,\n };\n }\n\n /** Build the `resources/list` payload (metadata only, no contents). */\n private listResources(resources: Map<string, McpServeResource>): Record<string, unknown>[] {\n return [...resources.values()].map((resource) =>\n compact({\n uri: resource.uri,\n name: resource.name,\n title: resource.title,\n description: resource.description,\n mimeType: resource.mimeType,\n }),\n );\n }\n\n /**\n * Answer `resources/read`: run the resource's `read()` and wrap the\n * result as one text or base64 blob content entry.\n */\n private async readResource(\n resources: Map<string, McpServeResource>,\n params: unknown,\n context: McpServeCallContext,\n ): Promise<Record<string, unknown>> {\n const uri = readRecord(params).uri;\n\n if (typeof uri !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, \"resources/read requires a string `uri`.\");\n }\n\n const resource = resources.get(uri);\n\n if (!resource) {\n throw new RpcFailure(MCP_RESOURCE_NOT_FOUND, \"Resource not found\", { uri });\n }\n\n const body = await resource.read(context);\n const entry =\n typeof body === \"string\"\n ? { uri, mimeType: resource.mimeType, text: body }\n : { uri, mimeType: resource.mimeType, blob: Buffer.from(body).toString(\"base64\") };\n\n return { contents: [compact(entry)] };\n }\n\n /** Build the `prompts/list` payload. */\n private listPrompts(prompts: Map<string, McpServePrompt>): Record<string, unknown>[] {\n return [...prompts.values()].map((prompt) =>\n compact({\n name: prompt.name,\n title: prompt.title,\n description: prompt.description,\n arguments: prompt.arguments,\n }),\n );\n }\n\n /**\n * Answer `prompts/get`: validate the arguments (strings only; required\n * ones present) and build the messages. An unknown prompt or a missing\n * required argument answers invalid-params, per the MCP prompts spec.\n */\n private async getPrompt(\n prompts: Map<string, McpServePrompt>,\n params: unknown,\n context: McpServeCallContext,\n ): Promise<Record<string, unknown>> {\n const record = readRecord(params);\n const name = record.name;\n\n if (typeof name !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, \"prompts/get requires a string `name`.\");\n }\n\n const prompt = prompts.get(name);\n\n if (!prompt) {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, `Unknown prompt \"${name}\".`);\n }\n\n const args: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(readRecord(record.arguments))) {\n if (typeof value !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, `Prompt argument \"${key}\" must be a string.`);\n }\n\n args[key] = value;\n }\n\n for (const argument of prompt.arguments ?? []) {\n if (argument.required && args[argument.name] === undefined) {\n throw new RpcFailure(\n JSON_RPC_INVALID_PARAMS,\n `Prompt \"${name}\" requires the \"${argument.name}\" argument.`,\n );\n }\n }\n\n const built = await prompt.get(args, context);\n const messages: McpPromptMessage[] =\n typeof built === \"string\"\n ? [{ role: \"user\", content: { type: \"text\", text: built } }]\n : built;\n\n return compact({ description: prompt.description, messages });\n }\n\n /** Build a JSON-RPC success response. */\n private ok(id: JsonRpcRequest[\"id\"], result: unknown): JsonRpcResponse {\n return { jsonrpc: JSONRPC_VERSION, id, result };\n }\n\n /** Build a JSON-RPC error response. */\n private error(\n id: JsonRpcRequest[\"id\"],\n code: number,\n message: string,\n data?: unknown,\n ): JsonRpcResponse {\n const error = data === undefined ? { code, message } : { code, message, data };\n\n return { jsonrpc: JSONRPC_VERSION, id, error };\n }\n}\n\n/** JSON-RPC \"Invalid Request\" — the line is JSON but not a request object. */\nconst JSON_RPC_INVALID_REQUEST = -32600;\n\n/** JSON-RPC \"Internal error\" — answering the request failed unexpectedly. */\nconst JSON_RPC_INTERNAL_ERROR = -32603;\n\n/**\n * Turn one stdio line into the line to write back, or `undefined` when\n * nothing is answered: blank or non-JSON lines and notifications (no `id`).\n * A JSON value that is not a request object (`null`, a number, an array) is\n * answered with a JSON-RPC \"Invalid Request\" error whose `id` is `null`.\n * Never rejects: a dispatch failure is answered as an internal error.\n */\nexport async function answerStdioLine(\n line: string,\n dispatch: (request: JsonRpcRequest) => Promise<JsonRpcResponse | undefined>,\n notify?: (message: unknown) => void,\n): Promise<string | undefined> {\n const trimmed = line.trim();\n\n if (!trimmed) {\n return undefined;\n }\n\n let message: unknown;\n\n try {\n message = JSON.parse(trimmed);\n } catch {\n return undefined;\n }\n\n if (typeof message !== \"object\" || message === null || Array.isArray(message)) {\n const invalid = {\n jsonrpc: JSONRPC_VERSION,\n id: null,\n error: { code: JSON_RPC_INVALID_REQUEST, message: \"Invalid request: not a JSON-RPC object.\" },\n };\n\n return `${JSON.stringify(invalid)}\\n`;\n }\n\n const request = message as JsonRpcRequest;\n const id: unknown = request.id;\n\n if (id === undefined || id === null) {\n notify?.(message);\n\n return undefined;\n }\n\n if (typeof id !== \"string\" && typeof id !== \"number\") {\n return `${JSON.stringify({\n jsonrpc: JSONRPC_VERSION,\n id: null,\n error: {\n code: JSON_RPC_INVALID_REQUEST,\n message: \"Invalid request: id must be a string or number.\",\n },\n })}\\n`;\n }\n\n try {\n const response = await dispatch(request);\n\n return response === undefined ? undefined : `${JSON.stringify(response)}\\n`;\n } catch {\n const failed = {\n jsonrpc: JSONRPC_VERSION,\n id,\n error: { code: JSON_RPC_INTERNAL_ERROR, message: \"Internal error.\" },\n };\n\n return `${JSON.stringify(failed)}\\n`;\n }\n}\n\n/** Read an unknown value as a plain record (non-objects read as empty). */\nfunction readRecord(value: unknown): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {};\n }\n\n return value as Record<string, unknown>;\n}\n\n/** Drop `undefined` members so optional wire fields are omitted, not `null`. */\nfunction compact(value: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, member] of Object.entries(value)) {\n if (member !== undefined) {\n result[key] = member;\n }\n }\n\n return result;\n}\n\n/**\n * Read and validate the `tools/call` params into `{ name, args }`. Throws\n * a typed {@link McpTransportError} when `name` is missing — mapped to a\n * JSON-RPC `invalid params` error by the handler.\n */\nfunction readCallParams(params: unknown): { name: string; args: unknown } {\n if (typeof params !== \"object\" || params === null) {\n throw new McpTransportError(\"tools/call params must be an object.\", {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n const record = params as { name?: unknown; arguments?: unknown };\n\n if (typeof record.name !== \"string\") {\n throw new McpTransportError(\"tools/call requires a string `name`.\", {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n return { name: record.name, args: record.arguments ?? {} };\n}\n\n/**\n * Serialize a tool's `data` for an MCP text content block — a string is\n * passed verbatim, everything else is JSON-stringified so structured\n * output crosses the wire as text the consuming client can re-parse.\n */\nfunction serializeData(data: unknown): string {\n if (typeof data === \"string\") {\n return data;\n }\n\n if (data === undefined) {\n return \"\";\n }\n\n return JSON.stringify(data);\n}\n\n/**\n * The pure protocol handler: one JSON-RPC request in, one response out.\n */\nexport interface McpServeRequestHandler {\n /**\n * Map one request to its response.\n *\n * @param request - The JSON-RPC request.\n * @param context - Optional per-call context (inbound HTTP request, abort signal).\n */\n handle(request: JsonRpcRequest, context?: McpServeCallContext): Promise<JsonRpcResponse>;\n}\n\n/**\n * Build the pure protocol handler for a serve source. Exposed (alongside\n * {@link serve}) so callers and tests can drive the MCP protocol without an\n * actual transport — feed it a JSON-RPC request, get the response.\n *\n * Throws {@link McpServeError} on duplicate tool names / resource URIs /\n * prompt names, or `prefixToolNames` on an ungrouped source.\n *\n * @param source - The tools to expose (one tool set or `{ groups }`).\n * @param options - Serve options (name / version / schema target / resources / prompts).\n * @returns An object whose `handle(request, context?)` maps a request to a response.\n */\nexport function createServeHandler(\n source: McpServeSource,\n options: McpServeOptions,\n): McpServeRequestHandler {\n return new McpServeHandler(source, options);\n}\n\n/**\n * The internal {@link McpServer} — owns a {@link McpServeHandler} and a\n * transport pump. For `stdio` it reads newline-delimited JSON-RPC requests\n * from `process.stdin` and writes responses to `process.stdout`; for\n * `http` it binds a Streamable HTTP endpoint (see `./serve-http`).\n *\n * Constructed via {@link serve}; the class itself is internal.\n */\nclass McpServerImpl implements McpServer {\n /** The pure protocol handler. */\n private readonly handler: McpServeHandler;\n\n /** Serve options (transport selection lives here). */\n private readonly options: McpServeOptions;\n\n /** The running handle (stdio or http), while started or starting. */\n private running: Promise<McpServeHandle> | undefined;\n\n public constructor(source: McpServeSource, options: McpServeOptions) {\n this.handler = new McpServeHandler(source, options);\n this.options = options;\n }\n\n public start(): Promise<McpServeHandle> {\n if (!this.running) {\n const transport = this.options.transport ?? { type: \"stdio\" };\n const starting =\n transport.type === \"http\"\n ? this.startHttp(transport)\n : Promise.resolve(this.startStdio(transport));\n\n // A failed start leaves the server startable again.\n this.running = starting.catch((error: unknown) => {\n this.running = undefined;\n throw error;\n });\n }\n\n return this.running;\n }\n\n /** Bind the HTTP endpoint; its `close()` also makes this server startable again. */\n private async startHttp(transport: McpHttpServeTransport): Promise<McpHttpServeHandle> {\n const http = await startHttpServer(transport, (request, context) =>\n this.handler.handle(request, context),\n );\n\n return {\n url: http.url,\n close: async () => {\n await http.close();\n this.running = undefined;\n },\n };\n }\n\n /** Wire the stdin line reader and answer each request on stdout. */\n private startStdio(transport: McpStdioServeTransport): McpServeHandle {\n const shutdown = new AbortController();\n const requests = new McpServeRequests();\n const reader: Interface = createInterface({ input: process.stdin });\n reader.once(\"close\", () => shutdown.abort());\n\n reader.on(\"line\", (line) => {\n // A failure answering one line must never become an unhandled\n // rejection: that would take the whole server process down.\n void this.onLine(line, transport, requests, shutdown.signal).catch(() => {});\n });\n\n return {\n close: async () => {\n shutdown.abort();\n reader.close();\n this.running = undefined;\n },\n };\n }\n\n /** Answer one stdin line on stdout (see {@link answerStdioLine}). */\n private async onLine(\n line: string,\n transport: McpStdioServeTransport,\n requests: McpServeRequests,\n signal: AbortSignal,\n ): Promise<void> {\n const reply = await answerStdioLine(\n line,\n (request) =>\n requests.run(request, { signal }, async (context) => {\n const principal = transport.principalProvider\n ? await transport.principalProvider(context)\n : transport.principal;\n\n return this.handler.handle(request, { ...context, principal });\n }),\n (message) => requests.cancel(message),\n );\n\n if (reply !== undefined) {\n process.stdout.write(reply);\n }\n }\n\n public async stop(): Promise<void> {\n const running = this.running;\n\n if (!running) {\n return;\n }\n\n let handle: McpServeHandle;\n\n try {\n handle = await running;\n } catch {\n // A start that failed has nothing to close.\n return;\n }\n\n await handle.close();\n }\n}\n\n/**\n * Expose a built agent / supervisor / orchestrator (or a raw\n * `ToolContract[]`, or several named groups) AS an MCP server (Direction B:\n * local primitive → MCP server other clients consume).\n *\n * Enumerates the tools once, here. Every exposed name must be unique — a\n * duplicate throws {@link McpServeError} (`type: \"collision\"`) right away;\n * serve `{ groups: { … } }` with `prefixToolNames: true` to namespace\n * same-named tools from different sets. `tools/list` answers with each\n * tool's `inputSchema` extracted via `extractJsonSchema` at the configured\n * `schemaTarget` (default `draft-2020-12`). `tools/call` routes to the\n * contract's `invoke()` and maps the never-throwing result — `data`\n * becomes a text content block, `error` an `isError: true` result.\n *\n * Transports: `stdio` (default) pumps `process.stdin` / `process.stdout`;\n * `http` binds an MCP Streamable HTTP endpoint on `127.0.0.1` by default,\n * with bearer / custom auth (required off loopback), `Origin` and `Host`\n * validation against DNS rebinding, a body-size cap, and a draining\n * `close()`. `start()` resolves `{ url, close }`.\n *\n * @param source - The tools to expose.\n * @param options - Server name, version, transport, schema dialect, resources, prompts.\n * @returns An {@link McpServer} with `start()` / `stop()`.\n *\n * @example\n * const { url, close } = await serve([calc, clock], {\n * name: \"session-tools\",\n * transport: { type: \"http\", auth: { bearer: sessionToken } },\n * }).start();\n */\nexport function serve(\n source: McpServeSource,\n options: McpServeOptions & { transport: McpHttpServeTransport },\n): McpServer<McpHttpServeHandle>;\nexport function serve(source: McpServeSource, options: McpServeOptions): McpServer;\nexport function serve(source: McpServeSource, options: McpServeOptions): McpServer {\n return new McpServerImpl(source, options);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serve.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve.ts"],"sourcesContent":["import { createInterface, type Interface } from \"node:readline\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { extractJsonSchema, type ToolContext, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n McpCallRefusal,\n McpGroupedToolSource,\n McpHttpServeHandle,\n McpHttpServeTransport,\n McpPromptMessage,\n McpServeCallContext,\n McpServeHandle,\n McpServeOptions,\n McpServePrompt,\n McpServeResource,\n McpServeSource,\n McpServeToolContext,\n McpStdioServeTransport,\n McpServer,\n McpToolCallResult,\n McpToolDescriptor,\n McpToolSource,\n} from \"../contracts\";\nimport { McpServeError, McpTransportError } from \"../errors\";\nimport { isMcpError } from \"./error\";\nimport { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from \"./protocol\";\nimport { startHttpServer } from \"./serve-http\";\nimport { McpServeRequests } from \"./serve-requests\";\nimport {\n assertMcpResult,\n isMcpResult,\n isRecord,\n looksLikeMcpResult,\n unwrapMcpResult,\n} from \"./result\";\n\n/** JSON-RPC version literal every outbound message carries. */\nconst JSONRPC_VERSION = \"2.0\";\n\n/** Default JSON-Schema dialect emitted for each tool's `inputSchema`. */\nconst DEFAULT_SCHEMA_TARGET = \"draft-2020-12\";\n\n/** Default advertised server version when the caller omits one. */\nconst DEFAULT_VERSION = \"4.4.0\";\n\n/** JSON-RPC standard error codes we emit. */\nconst JSON_RPC_METHOD_NOT_FOUND = -32601;\nconst JSON_RPC_INVALID_PARAMS = -32602;\n\n/** MCP's \"resource not found\" error code (server/resources, error handling). */\nconst MCP_RESOURCE_NOT_FOUND = -32002;\n\n/** One tool as exposed on the wire: its public name plus the contract behind it. */\ntype ExposedTool = {\n /** The name clients see in `tools/list` and send in `tools/call`. */\n name: string;\n /** The contract that runs. */\n contract: ToolContract;\n};\n\n/**\n * A JSON-RPC failure raised inside a method handler, carrying the exact\n * code to answer with (unknown resource, missing prompt argument, …).\n */\nclass RpcFailure extends Error {\n public readonly code: number;\n\n public readonly data: unknown;\n\n public constructor(code: number, message: string, data?: unknown) {\n super(message);\n this.code = code;\n this.data = data;\n }\n}\n\n/** Enumerate one tool set into a flat contract array. */\nfunction toolsOf(source: McpToolSource): ToolContract[] {\n return Array.isArray(source) ? source : source.tools();\n}\n\n/** Narrow a serve source to its grouped shape. */\nfunction isGroupedSource(source: McpServeSource): source is McpGroupedToolSource {\n return !Array.isArray(source) && \"groups\" in source;\n}\n\n/**\n * Resolve the serve source into the exposed tool list, applying\n * `prefixToolNames` and refusing duplicate names. Throws\n * {@link McpServeError} — `\"config\"` for `prefixToolNames` on an ungrouped\n * source, `\"collision\"` when two tools would share a name.\n */\nfunction resolveTools(source: McpServeSource, options: McpServeOptions): ExposedTool[] {\n const prefix = options.prefixToolNames ?? false;\n const exposed: Array<ExposedTool & { origin: string }> = [];\n\n if (isGroupedSource(source)) {\n for (const [group, groupSource] of Object.entries(source.groups)) {\n for (const contract of toolsOf(groupSource)) {\n let name = contract.name;\n\n if (prefix === true) {\n name = `${group}_${contract.name}`;\n } else if (typeof prefix === \"function\") {\n name = prefix(group, contract.name);\n }\n\n exposed.push({ name, contract, origin: `group \"${group}\"` });\n }\n }\n } else {\n if (prefix !== false) {\n throw new McpServeError(\n \"prefixToolNames needs a grouped source: serve({ groups: { name: tools } }, …).\",\n { type: \"config\" },\n );\n }\n\n for (const contract of toolsOf(source)) {\n exposed.push({ name: contract.name, contract, origin: \"the source\" });\n }\n }\n\n const seen = new Map<string, string>();\n\n for (const tool of exposed) {\n const previous = seen.get(tool.name);\n\n if (previous !== undefined) {\n throw new McpServeError(\n `MCP tool name \"${tool.name}\" is exposed twice (from ${previous} and ${tool.origin}). ` +\n \"Rename one tool, or serve a grouped source with prefixToolNames.\",\n { type: \"collision\", context: { name: tool.name } },\n );\n }\n\n seen.set(tool.name, tool.origin);\n }\n\n return exposed.map(({ name, contract }) => ({ name, contract }));\n}\n\n/** Throw a collision error when two entries share the same key. */\nfunction assertUnique<T>(\n entries: readonly T[] | undefined,\n key: (entry: T) => string,\n label: string,\n): void {\n const seen = new Set<string>();\n\n for (const entry of entries ?? []) {\n const value = key(entry);\n\n if (seen.has(value)) {\n throw new McpServeError(`MCP ${label} \"${value}\" is declared twice.`, {\n type: \"collision\",\n context: { [label]: value },\n });\n }\n\n seen.add(value);\n }\n}\n\n/**\n * The pure protocol core of `serve` — maps one JSON-RPC request to its\n * response, with no I/O. Both the stdio and HTTP serve-transports pump\n * their inbound requests through this, and specs can drive it directly.\n *\n * Handles `initialize`, `ping`, `tools/list`, `tools/call`, and — when\n * configured — `resources/list`, `resources/templates/list`,\n * `resources/read`, `prompts/list` and `prompts/get`. Any other method\n * answers with a JSON-RPC `method not found` error.\n *\n * Constructed via {@link createServeHandler}.\n */\nclass McpServeHandler {\n /** The tools this server exposes (snapshotted at construction). */\n private readonly tools: ExposedTool[];\n\n /** Fast lookup by exposed tool name for `tools/call` dispatch. */\n private readonly byName: Map<string, ToolContract>;\n\n /** Resources by URI, or `undefined` when the capability is off. */\n private readonly resources: Map<string, McpServeResource> | undefined;\n\n /** Prompts by name, or `undefined` when the capability is off. */\n private readonly prompts: Map<string, McpServePrompt> | undefined;\n\n /** Construction-time serve options (name / version / schema target). */\n private readonly options: McpServeOptions;\n\n public constructor(source: McpServeSource, options: McpServeOptions) {\n this.tools = resolveTools(source, options);\n this.byName = new Map(this.tools.map((tool) => [tool.name, tool.contract]));\n\n assertUnique(options.resources, (resource) => resource.uri, \"resource\");\n assertUnique(options.prompts, (prompt) => prompt.name, \"prompt\");\n\n this.resources = options.resources\n ? new Map(options.resources.map((resource) => [resource.uri, resource]))\n : undefined;\n this.prompts = options.prompts\n ? new Map(options.prompts.map((prompt) => [prompt.name, prompt]))\n : undefined;\n this.options = options;\n }\n\n /**\n * Dispatch one inbound JSON-RPC request to its handler and produce the\n * response. A handler that throws is mapped to a JSON-RPC error response\n * — the serve loop never crashes on a bad request.\n *\n * @param request - The JSON-RPC request.\n * @param context - Per-call context: the inbound HTTP request (headers\n * reach tools as `ctx.request`) and an abort signal.\n */\n public async handle(\n request: JsonRpcRequest,\n context: McpServeCallContext = {},\n ): Promise<JsonRpcResponse> {\n try {\n return this.ok(\n request.id,\n await this.dispatch(request, { ...context, requestId: request.id }),\n );\n } catch (cause) {\n if (cause instanceof RpcFailure) {\n return this.error(request.id, cause.code, cause.message, cause.data);\n }\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n return this.error(request.id, JSON_RPC_INVALID_PARAMS, message);\n }\n }\n\n /** Route a method to its result builder. */\n private async dispatch(request: JsonRpcRequest, context: McpServeCallContext): Promise<unknown> {\n switch (request.method) {\n case \"initialize\":\n return this.initializeResult(request.params);\n case \"ping\":\n return {};\n case \"tools/list\":\n return { tools: await this.listTools({ ...context, requestId: request.id }) };\n case \"tools/call\":\n return this.callTool(request.params, { ...context, requestId: request.id });\n default:\n break;\n }\n\n if (this.resources) {\n switch (request.method) {\n case \"resources/list\":\n return { resources: this.listResources(this.resources) };\n case \"resources/templates/list\":\n return { resourceTemplates: [] };\n case \"resources/read\":\n return this.readResource(this.resources, request.params, context);\n default:\n break;\n }\n }\n\n if (this.prompts) {\n switch (request.method) {\n case \"prompts/list\":\n return { prompts: this.listPrompts(this.prompts) };\n case \"prompts/get\":\n return this.getPrompt(this.prompts, request.params, context);\n default:\n break;\n }\n }\n\n throw new RpcFailure(\n JSON_RPC_METHOD_NOT_FOUND,\n `Method \"${request.method}\" is not supported by this MCP server.`,\n );\n }\n\n /**\n * Build the `initialize` result advertising name / version /\n * capabilities, negotiating the protocol version: the client's requested\n * version when supported, otherwise the newest this server speaks.\n */\n private initializeResult(params: unknown): Record<string, unknown> {\n const requested = readRecord(params).protocolVersion;\n const protocolVersion =\n typeof requested === \"string\" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)\n ? requested\n : LATEST_PROTOCOL_VERSION;\n\n const capabilities: Record<string, unknown> = { tools: {} };\n\n if (this.resources) {\n capabilities.resources = {};\n }\n\n if (this.prompts) {\n capabilities.prompts = {};\n }\n\n return {\n protocolVersion,\n capabilities,\n serverInfo: {\n name: this.options.name,\n version: this.options.version ?? DEFAULT_VERSION,\n },\n };\n }\n\n /**\n * Build the `tools/list` payload: one {@link McpToolDescriptor} per\n * exposed tool, its `inputSchema` extracted via `extractJsonSchema` at\n * the configured dialect (default `draft-2020-12` — overriding\n * `extractJsonSchema`'s own `openai-strict` default to a neutral MCP draft).\n */\n private async listTools(context: McpServeToolContext): Promise<readonly McpToolDescriptor[]> {\n const target = this.options.schemaTarget ?? DEFAULT_SCHEMA_TARGET;\n\n const tools = this.tools.map(({ name, contract }) => {\n const inputSchema = extractJsonSchema(contract.input, { target }) ?? {\n type: \"object\",\n properties: {},\n };\n\n return {\n name,\n description: contract.description,\n inputSchema,\n ...(isRecord(contract.meta?.outputSchema)\n ? { outputSchema: contract.meta.outputSchema }\n : {}),\n };\n });\n\n context.signal?.throwIfAborted();\n return this.options.listFilter ? this.options.listFilter(context, tools) : tools;\n }\n\n /**\n * Route a `tools/call` to the named contract's `invoke()` and map the\n * never-throwing {@link import(\"@warlock.js/ai\").ToolInvokeResult}: `data`\n * → a text content block (or an opted-in native result), `error` → an\n * `isError: true` result. An unknown tool name throws (mapped to a\n * JSON-RPC error by {@link handle}).\n *\n * The contract receives a `ToolContext` carrying the abort signal and,\n * over HTTP, the inbound request's headers on `ctx.request`.\n */\n private async callTool(\n params: unknown,\n context: McpServeToolContext,\n ): Promise<McpToolCallResult> {\n const { name, args } = readCallParams(params);\n\n if (this.options.validateCallParams) {\n context.signal?.throwIfAborted();\n\n try {\n await this.options.validateCallParams(context, readRecord(params));\n } catch (cause) {\n if (isMcpError(cause)) {\n throw new RpcFailure(cause.code, cause.message, cause.data);\n }\n\n throw cause;\n }\n }\n\n const contract = this.byName.get(name);\n\n if (!contract) {\n throw new McpTransportError(`Unknown tool \"${name}\".`, {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n const toolContext: ToolContext = {\n artifacts: {},\n requestId: context.requestId,\n principal: context.principal,\n };\n\n if (context.signal) {\n toolContext.signal = context.signal;\n }\n\n if (context.request) {\n const { headers, remoteAddress } = context.request;\n\n toolContext.request = remoteAddress === undefined ? { headers } : { headers, remoteAddress };\n }\n\n if (this.options.beforeCall) {\n let input = args;\n\n // Validate at the policy boundary even for custom ToolContracts. Keep\n // invoke as the execution boundary so custom accounting/error handling\n // remains intact; invoke still owns its own input validation.\n if (contract.input) {\n let validation: StandardSchemaV1.Result<unknown>;\n\n try {\n validation = await contract.input[\"~standard\"].validate(args);\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n\n return {\n content: [\n { type: \"text\", text: `Schema validation threw for tool \"${name}\": ${message}` },\n ],\n isError: true,\n };\n }\n\n if (validation.issues) {\n return {\n content: [\n {\n type: \"text\",\n text: `Validation failed: ${validation.issues.map((issue) => issue.message).join(\"; \")}`,\n },\n ],\n isError: true,\n };\n }\n\n input = validation.value;\n }\n\n context.signal?.throwIfAborted();\n let refusal: void | McpCallRefusal;\n\n try {\n refusal = await this.options.beforeCall(context, contract, input);\n } catch (cause) {\n if (isMcpError(cause)) {\n throw new RpcFailure(cause.code, cause.message, cause.data);\n }\n\n throw cause;\n }\n\n if (refusal !== undefined) {\n try {\n assertMcpResult(refusal);\n\n if (refusal.isError !== true) {\n throw new Error(\"A refusal must be an error result.\");\n }\n } catch {\n throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, \"Invalid MCP beforeCall refusal.\");\n }\n\n return refusal;\n }\n }\n\n context.signal?.throwIfAborted();\n const result = await contract.invoke(args, toolContext);\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: result.error.message }],\n isError: true,\n };\n }\n\n const mode = contract.meta?.mcpResults ?? this.options.results ?? \"text\";\n const branded = isMcpResult(result.data);\n\n if (branded || (mode === \"native\" && looksLikeMcpResult(result.data))) {\n const native = isMcpResult(result.data) ? unwrapMcpResult(result.data) : result.data;\n\n try {\n assertMcpResult(native);\n } catch {\n throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, \"Invalid native MCP CallToolResult.\");\n }\n\n return native;\n }\n\n return {\n content: [{ type: \"text\", text: serializeData(result.data) }],\n isError: false,\n };\n }\n\n /** Build the `resources/list` payload (metadata only, no contents). */\n private listResources(resources: Map<string, McpServeResource>): Record<string, unknown>[] {\n return [...resources.values()].map((resource) =>\n compact({\n uri: resource.uri,\n name: resource.name,\n title: resource.title,\n description: resource.description,\n mimeType: resource.mimeType,\n }),\n );\n }\n\n /**\n * Answer `resources/read`: run the resource's `read()` and wrap the\n * result as one text or base64 blob content entry.\n */\n private async readResource(\n resources: Map<string, McpServeResource>,\n params: unknown,\n context: McpServeCallContext,\n ): Promise<Record<string, unknown>> {\n const uri = readRecord(params).uri;\n\n if (typeof uri !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, \"resources/read requires a string `uri`.\");\n }\n\n const resource = resources.get(uri);\n\n if (!resource) {\n throw new RpcFailure(MCP_RESOURCE_NOT_FOUND, \"Resource not found\", { uri });\n }\n\n const body = await resource.read(context);\n const entry =\n typeof body === \"string\"\n ? { uri, mimeType: resource.mimeType, text: body }\n : { uri, mimeType: resource.mimeType, blob: Buffer.from(body).toString(\"base64\") };\n\n return { contents: [compact(entry)] };\n }\n\n /** Build the `prompts/list` payload. */\n private listPrompts(prompts: Map<string, McpServePrompt>): Record<string, unknown>[] {\n return [...prompts.values()].map((prompt) =>\n compact({\n name: prompt.name,\n title: prompt.title,\n description: prompt.description,\n arguments: prompt.arguments,\n }),\n );\n }\n\n /**\n * Answer `prompts/get`: validate the arguments (strings only; required\n * ones present) and build the messages. An unknown prompt or a missing\n * required argument answers invalid-params, per the MCP prompts spec.\n */\n private async getPrompt(\n prompts: Map<string, McpServePrompt>,\n params: unknown,\n context: McpServeCallContext,\n ): Promise<Record<string, unknown>> {\n const record = readRecord(params);\n const name = record.name;\n\n if (typeof name !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, \"prompts/get requires a string `name`.\");\n }\n\n const prompt = prompts.get(name);\n\n if (!prompt) {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, `Unknown prompt \"${name}\".`);\n }\n\n const args: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(readRecord(record.arguments))) {\n if (typeof value !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, `Prompt argument \"${key}\" must be a string.`);\n }\n\n args[key] = value;\n }\n\n for (const argument of prompt.arguments ?? []) {\n if (argument.required && args[argument.name] === undefined) {\n throw new RpcFailure(\n JSON_RPC_INVALID_PARAMS,\n `Prompt \"${name}\" requires the \"${argument.name}\" argument.`,\n );\n }\n }\n\n const built = await prompt.get(args, context);\n const messages: McpPromptMessage[] =\n typeof built === \"string\"\n ? [{ role: \"user\", content: { type: \"text\", text: built } }]\n : built;\n\n return compact({ description: prompt.description, messages });\n }\n\n /** Build a JSON-RPC success response. */\n private ok(id: JsonRpcRequest[\"id\"], result: unknown): JsonRpcResponse {\n return { jsonrpc: JSONRPC_VERSION, id, result };\n }\n\n /** Build a JSON-RPC error response. */\n private error(\n id: JsonRpcRequest[\"id\"],\n code: number,\n message: string,\n data?: unknown,\n ): JsonRpcResponse {\n const error = data === undefined ? { code, message } : { code, message, data };\n\n return { jsonrpc: JSONRPC_VERSION, id, error };\n }\n}\n\n/** JSON-RPC \"Invalid Request\" — the line is JSON but not a request object. */\nconst JSON_RPC_INVALID_REQUEST = -32600;\n\n/** JSON-RPC \"Internal error\" — answering the request failed unexpectedly. */\nconst JSON_RPC_INTERNAL_ERROR = -32603;\n\n/**\n * Turn one stdio line into the line to write back, or `undefined` when\n * nothing is answered: blank or non-JSON lines and notifications (no `id`).\n * A JSON value that is not a request object (`null`, a number, an array) is\n * answered with a JSON-RPC \"Invalid Request\" error whose `id` is `null`.\n * Never rejects: a dispatch failure is answered as an internal error.\n */\nexport async function answerStdioLine(\n line: string,\n dispatch: (request: JsonRpcRequest) => Promise<JsonRpcResponse | undefined>,\n notify?: (message: unknown) => void,\n): Promise<string | undefined> {\n const trimmed = line.trim();\n\n if (!trimmed) {\n return undefined;\n }\n\n let message: unknown;\n\n try {\n message = JSON.parse(trimmed);\n } catch {\n return undefined;\n }\n\n if (typeof message !== \"object\" || message === null || Array.isArray(message)) {\n const invalid = {\n jsonrpc: JSONRPC_VERSION,\n id: null,\n error: { code: JSON_RPC_INVALID_REQUEST, message: \"Invalid request: not a JSON-RPC object.\" },\n };\n\n return `${JSON.stringify(invalid)}\\n`;\n }\n\n const request = message as JsonRpcRequest;\n const id: unknown = request.id;\n\n if (id === undefined || id === null) {\n notify?.(message);\n\n return undefined;\n }\n\n if (typeof id !== \"string\" && typeof id !== \"number\") {\n return `${JSON.stringify({\n jsonrpc: JSONRPC_VERSION,\n id: null,\n error: {\n code: JSON_RPC_INVALID_REQUEST,\n message: \"Invalid request: id must be a string or number.\",\n },\n })}\\n`;\n }\n\n try {\n const response = await dispatch(request);\n\n return response === undefined ? undefined : `${JSON.stringify(response)}\\n`;\n } catch {\n const failed = {\n jsonrpc: JSONRPC_VERSION,\n id,\n error: { code: JSON_RPC_INTERNAL_ERROR, message: \"Internal error.\" },\n };\n\n return `${JSON.stringify(failed)}\\n`;\n }\n}\n\n/** Read an unknown value as a plain record (non-objects read as empty). */\nfunction readRecord(value: unknown): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {};\n }\n\n return value as Record<string, unknown>;\n}\n\n/** Drop `undefined` members so optional wire fields are omitted, not `null`. */\nfunction compact(value: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, member] of Object.entries(value)) {\n if (member !== undefined) {\n result[key] = member;\n }\n }\n\n return result;\n}\n\n/**\n * Read and validate the `tools/call` params into `{ name, args }`. Throws\n * a typed {@link McpTransportError} when `name` is missing — mapped to a\n * JSON-RPC `invalid params` error by the handler.\n */\nfunction readCallParams(params: unknown): { name: string; args: unknown } {\n if (typeof params !== \"object\" || params === null) {\n throw new McpTransportError(\"tools/call params must be an object.\", {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n const record = params as { name?: unknown; arguments?: unknown };\n\n if (typeof record.name !== \"string\") {\n throw new McpTransportError(\"tools/call requires a string `name`.\", {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n return { name: record.name, args: record.arguments ?? {} };\n}\n\n/**\n * Serialize a tool's `data` for an MCP text content block — a string is\n * passed verbatim, everything else is JSON-stringified so structured\n * output crosses the wire as text the consuming client can re-parse.\n */\nfunction serializeData(data: unknown): string {\n if (typeof data === \"string\") {\n return data;\n }\n\n if (data === undefined) {\n return \"\";\n }\n\n return JSON.stringify(data);\n}\n\n/**\n * The pure protocol handler: one JSON-RPC request in, one response out.\n */\nexport interface McpServeRequestHandler {\n /**\n * Map one request to its response.\n *\n * @param request - The JSON-RPC request.\n * @param context - Optional per-call context (inbound HTTP request, abort signal).\n */\n handle(request: JsonRpcRequest, context?: McpServeCallContext): Promise<JsonRpcResponse>;\n}\n\n/**\n * Build the pure protocol handler for a serve source. Exposed (alongside\n * {@link serve}) so callers and tests can drive the MCP protocol without an\n * actual transport — feed it a JSON-RPC request, get the response.\n *\n * Throws {@link McpServeError} on duplicate tool names / resource URIs /\n * prompt names, or `prefixToolNames` on an ungrouped source.\n *\n * @param source - The tools to expose (one tool set or `{ groups }`).\n * @param options - Serve options (name / version / schema target / resources / prompts).\n * @returns An object whose `handle(request, context?)` maps a request to a response.\n */\nexport function createServeHandler(\n source: McpServeSource,\n options: McpServeOptions,\n): McpServeRequestHandler {\n return new McpServeHandler(source, options);\n}\n\n/**\n * The internal {@link McpServer} — owns a {@link McpServeHandler} and a\n * transport pump. For `stdio` it reads newline-delimited JSON-RPC requests\n * from `process.stdin` and writes responses to `process.stdout`; for\n * `http` it binds a Streamable HTTP endpoint (see `./serve-http`).\n *\n * Constructed via {@link serve}; the class itself is internal.\n */\nclass McpServerImpl implements McpServer {\n /** The pure protocol handler. */\n private readonly handler: McpServeHandler;\n\n /** Serve options (transport selection lives here). */\n private readonly options: McpServeOptions;\n\n /** The running handle (stdio or http), while started or starting. */\n private running: Promise<McpServeHandle> | undefined;\n\n public constructor(source: McpServeSource, options: McpServeOptions) {\n this.handler = new McpServeHandler(source, options);\n this.options = options;\n }\n\n public start(): Promise<McpServeHandle> {\n if (!this.running) {\n const transport = this.options.transport ?? { type: \"stdio\" };\n const starting =\n transport.type === \"http\"\n ? this.startHttp(transport)\n : Promise.resolve(this.startStdio(transport));\n\n // A failed start leaves the server startable again.\n this.running = starting.catch((error: unknown) => {\n this.running = undefined;\n throw error;\n });\n }\n\n return this.running;\n }\n\n /** Bind the HTTP endpoint; its `close()` also makes this server startable again. */\n private async startHttp(transport: McpHttpServeTransport): Promise<McpHttpServeHandle> {\n const http = await startHttpServer(transport, (request, context) =>\n this.handler.handle(request, context),\n );\n\n return {\n url: http.url,\n close: async () => {\n await http.close();\n this.running = undefined;\n },\n };\n }\n\n /** Wire the stdin line reader and answer each request on stdout. */\n private startStdio(transport: McpStdioServeTransport): McpServeHandle {\n const shutdown = new AbortController();\n const requests = new McpServeRequests();\n const reader: Interface = createInterface({ input: process.stdin });\n reader.once(\"close\", () => shutdown.abort());\n\n reader.on(\"line\", (line) => {\n // A failure answering one line must never become an unhandled\n // rejection: that would take the whole server process down.\n void this.onLine(line, transport, requests, shutdown.signal).catch(() => {});\n });\n\n return {\n close: async () => {\n shutdown.abort();\n reader.close();\n this.running = undefined;\n },\n };\n }\n\n /** Answer one stdin line on stdout (see {@link answerStdioLine}). */\n private async onLine(\n line: string,\n transport: McpStdioServeTransport,\n requests: McpServeRequests,\n signal: AbortSignal,\n ): Promise<void> {\n const reply = await answerStdioLine(\n line,\n (request) =>\n requests.run(request, { signal }, async (context) => {\n const principal = transport.principalProvider\n ? await transport.principalProvider(context)\n : transport.principal;\n\n return this.handler.handle(request, { ...context, principal });\n }),\n (message) => requests.cancel(message),\n );\n\n if (reply !== undefined) {\n process.stdout.write(reply);\n }\n }\n\n public async stop(): Promise<void> {\n const running = this.running;\n\n if (!running) {\n return;\n }\n\n let handle: McpServeHandle;\n\n try {\n handle = await running;\n } catch {\n // A start that failed has nothing to close.\n return;\n }\n\n await handle.close();\n }\n}\n\n/**\n * Expose a built agent / supervisor / orchestrator (or a raw\n * `ToolContract[]`, or several named groups) AS an MCP server (Direction B:\n * local primitive → MCP server other clients consume).\n *\n * Enumerates the tools once, here. Every exposed name must be unique — a\n * duplicate throws {@link McpServeError} (`type: \"collision\"`) right away;\n * serve `{ groups: { … } }` with `prefixToolNames: true` to namespace\n * same-named tools from different sets. `tools/list` answers with each\n * tool's `inputSchema` extracted via `extractJsonSchema` at the configured\n * `schemaTarget` (default `draft-2020-12`). `tools/call` routes to the\n * contract's `invoke()` and maps the never-throwing result — `data`\n * becomes a text content block, `error` an `isError: true` result.\n *\n * Transports: `stdio` (default) pumps `process.stdin` / `process.stdout`;\n * `http` binds an MCP Streamable HTTP endpoint on `127.0.0.1` by default,\n * with bearer / custom auth (required off loopback), `Origin` and `Host`\n * validation against DNS rebinding, a body-size cap, and a draining\n * `close()`. `start()` resolves `{ url, close }`.\n *\n * @param source - The tools to expose.\n * @param options - Server name, version, transport, schema dialect, resources, prompts.\n * @returns An {@link McpServer} with `start()` / `stop()`.\n *\n * @example\n * const { url, close } = await serve([calc, clock], {\n * name: \"session-tools\",\n * transport: { type: \"http\", auth: { bearer: sessionToken } },\n * }).start();\n */\nexport function serve(\n source: McpServeSource,\n options: McpServeOptions & { transport: McpHttpServeTransport },\n): McpServer<McpHttpServeHandle>;\nexport function serve(source: McpServeSource, options: McpServeOptions): McpServer;\nexport function serve(source: McpServeSource, options: McpServeOptions): McpServer {\n return new McpServerImpl(source, options);\n}\n"],"mappings":"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"}
|
package/esm/register.d.mts
CHANGED
|
@@ -3,63 +3,24 @@ import { HttpRequestInput, HttpRequestOptions, HttpRequestResult } from "./contr
|
|
|
3
3
|
import { CalculatorInput, CalculatorOptions, CalculatorResult, DateTimeInput, DateTimeOptions, DateTimeResult } from "./contracts/utility.type.mjs";
|
|
4
4
|
import "./contracts/index.mjs";
|
|
5
5
|
import { McpFactory } from "./mcp/index.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { AiToolsNamespace, ToolContract } from "@warlock.js/ai";
|
|
7
7
|
//#region ../../packages/ai-tools/src/register.d.ts
|
|
8
|
-
/**
|
|
9
|
-
* The `ai.tools.*` namespace — the five ready-made agent tools this
|
|
10
|
-
* package vends. Each member is a factory returning a {@link ToolContract}
|
|
11
|
-
* that drops straight into `ai.agent({ tools: [...] })`. Declared inline on
|
|
12
|
-
* the `Ai` interface below (one block, one shape) rather than accreted
|
|
13
|
-
* across the tool sub-barrels, mirroring the `ai.workspace` augmentation in
|
|
14
|
-
* `@warlock.js/ai-workspace`.
|
|
15
|
-
*/
|
|
16
|
-
export interface AiToolsNamespace {
|
|
17
|
-
/** Search the web via a chosen provider; returns ranked LLM-ready hits. */
|
|
18
|
-
webSearch(options: WebSearchOptions): ToolContract<WebSearchInput, WebSearchResult>;
|
|
19
|
-
/** Fetch a URL (host-allowlisted, byte-capped) and return its content. */
|
|
20
|
-
fetchUrl(options?: FetchUrlOptions): ToolContract<FetchUrlInput, FetchUrlResult>;
|
|
21
|
-
/** A guarded HTTP/REST client — method + host allowlists, byte/timeout caps. */
|
|
22
|
-
http(options?: HttpRequestOptions): ToolContract<HttpRequestInput, HttpRequestResult>;
|
|
23
|
-
/** A SAFE arithmetic-expression evaluator (no `eval`/`Function`). */
|
|
24
|
-
calculator(options?: CalculatorOptions): ToolContract<CalculatorInput, CalculatorResult>;
|
|
25
|
-
/** Clock/calendar ops — now / add / diff / format over ISO instants. */
|
|
26
|
-
dateTime(options?: DateTimeOptions): ToolContract<DateTimeInput, DateTimeResult>;
|
|
27
|
-
/**
|
|
28
|
-
* Measure the input tokens a tool list adds to every call — natively
|
|
29
|
-
* through the model's count endpoint when it has one, estimated
|
|
30
|
-
* otherwise (`source` says which), per tool and in total, in `"full"`
|
|
31
|
-
* or `"compact"` schema mode. Re-exports `measureTools` from
|
|
32
|
-
* `@warlock.js/ai`.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* const { tokens, perTool, source } = await ai.tools.measure(tools, { model, mode: "compact" });
|
|
36
|
-
*/
|
|
37
|
-
measure: typeof measureTools;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Attach the `tools` namespace and the `mcp` factory to the `ai` namespace
|
|
41
|
-
* via module augmentation, per the `ai.`-namespace convention.
|
|
42
|
-
* `@warlock.js/ai` now exposes a named `Ai` interface for exactly this, so
|
|
43
|
-
* after a bare `import "@warlock.js/ai-tools"`, `ai.tools.webSearch(...)`,
|
|
44
|
-
* `ai.tools.http(...)`, `ai.mcp(server)`, and `ai.mcp.serve(source)` are all
|
|
45
|
-
* globally typed — no view/cast needed.
|
|
46
|
-
*/
|
|
8
|
+
/** Extend the core tool utilities without replacing the catalogue namespace. */
|
|
47
9
|
declare module "@warlock.js/ai" {
|
|
10
|
+
interface AiToolsNamespace {
|
|
11
|
+
/** Search the web via a chosen provider. */
|
|
12
|
+
webSearch(options: WebSearchOptions): ToolContract<WebSearchInput, WebSearchResult>;
|
|
13
|
+
/** Fetch a URL with host, byte and timeout caps. */
|
|
14
|
+
fetchUrl(options?: FetchUrlOptions): ToolContract<FetchUrlInput, FetchUrlResult>;
|
|
15
|
+
/** Guarded HTTP client. */
|
|
16
|
+
http(options?: HttpRequestOptions): ToolContract<HttpRequestInput, HttpRequestResult>;
|
|
17
|
+
calculator(options?: CalculatorOptions): ToolContract<CalculatorInput, CalculatorResult>;
|
|
18
|
+
dateTime(options?: DateTimeOptions): ToolContract<DateTimeInput, DateTimeResult>;
|
|
19
|
+
}
|
|
48
20
|
interface Ai {
|
|
49
|
-
/**
|
|
50
|
-
* Ready-made agent tools from `@warlock.js/ai-tools` — web search,
|
|
51
|
-
* fetch/scrape, HTTP/REST, calculator, and date-time. Each returns a
|
|
52
|
-
* `ToolContract` that slots into `ai.agent({ tools: [...] })`.
|
|
53
|
-
*/
|
|
54
|
-
tools: AiToolsNamespace;
|
|
55
|
-
/**
|
|
56
|
-
* The Model Context Protocol surface — `ai.mcp(server)` connects to an
|
|
57
|
-
* external MCP server and adapts its tools as agent tools (Direction A),
|
|
58
|
-
* while `ai.mcp.serve(source, options)` exposes a local primitive AS an
|
|
59
|
-
* MCP server other clients can consume (Direction B).
|
|
60
|
-
*/
|
|
61
21
|
mcp: McpFactory;
|
|
62
22
|
}
|
|
63
23
|
}
|
|
64
24
|
//#endregion
|
|
25
|
+
export type { AiToolsNamespace };
|
|
65
26
|
//# sourceMappingURL=register.d.mts.map
|
package/esm/register.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.d.mts","names":[],"sources":["../../../../../../../packages/ai-tools/src/register.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"register.d.mts","names":[],"sources":["../../../../../../../packages/ai-tools/src/register.ts"],"mappings":";;;;;;;;;YA6BY;;IAER,UAAU,SAAS,mBAAmB,aAAa,gBAAgB;;IAEnE,SAAS,UAAU,kBAAkB,aAAa,eAAe;;IAEjE,KAAK,UAAU,qBAAqB,aAAa,kBAAkB;IACnE,WAAW,UAAU,oBAAoB,aAAa,iBAAiB;IACvE,SAAS,UAAU,kBAAkB,aAAa,eAAe;;YAEzD;IACR,KAAK"}
|
package/esm/register.mjs
CHANGED
|
@@ -4,17 +4,16 @@ import { calculatorTool } from "./utility/calculator.mjs";
|
|
|
4
4
|
import { dateTimeTool } from "./utility/date-time.mjs";
|
|
5
5
|
import { fetchUrlTool } from "./web/fetch-url.mjs";
|
|
6
6
|
import { webSearchTool } from "./web/web-search.mjs";
|
|
7
|
-
import { ai
|
|
7
|
+
import { ai } from "@warlock.js/ai";
|
|
8
8
|
|
|
9
9
|
//#region ../../packages/ai-tools/src/register.ts
|
|
10
|
-
ai.tools
|
|
10
|
+
Object.assign(ai.tools, {
|
|
11
11
|
webSearch: webSearchTool,
|
|
12
12
|
fetchUrl: fetchUrlTool,
|
|
13
13
|
http: httpRequestTool,
|
|
14
14
|
calculator: calculatorTool,
|
|
15
|
-
dateTime: (options) => dateTimeTool(options)
|
|
16
|
-
|
|
17
|
-
};
|
|
15
|
+
dateTime: (options) => dateTimeTool(options)
|
|
16
|
+
});
|
|
18
17
|
ai.mcp = mcp;
|
|
19
18
|
|
|
20
19
|
//#endregion
|
package/esm/register.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.mjs","names":[],"sources":["../../../../../../../packages/ai-tools/src/register.ts"],"sourcesContent":["import { ai,
|
|
1
|
+
{"version":3,"file":"register.mjs","names":[],"sources":["../../../../../../../packages/ai-tools/src/register.ts"],"sourcesContent":["import { ai, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n CalculatorInput,\n CalculatorOptions,\n CalculatorResult,\n DateTimeInput,\n DateTimeOptions,\n DateTimeResult,\n FetchUrlInput,\n FetchUrlOptions,\n FetchUrlResult,\n HttpRequestInput,\n HttpRequestOptions,\n HttpRequestResult,\n WebSearchInput,\n WebSearchOptions,\n WebSearchResult,\n} from \"./contracts\";\nimport { httpRequestTool } from \"./http/http-request\";\nimport { type McpFactory, mcp } from \"./mcp\";\nimport { calculatorTool } from \"./utility/calculator\";\nimport { dateTimeTool } from \"./utility/date-time\";\nimport { fetchUrlTool } from \"./web/fetch-url\";\nimport { webSearchTool } from \"./web/web-search\";\n\nexport type { AiToolsNamespace } from \"@warlock.js/ai\";\n\n/** Extend the core tool utilities without replacing the catalogue namespace. */\ndeclare module \"@warlock.js/ai\" {\n interface AiToolsNamespace {\n /** Search the web via a chosen provider. */\n webSearch(options: WebSearchOptions): ToolContract<WebSearchInput, WebSearchResult>;\n /** Fetch a URL with host, byte and timeout caps. */\n fetchUrl(options?: FetchUrlOptions): ToolContract<FetchUrlInput, FetchUrlResult>;\n /** Guarded HTTP client. */\n http(options?: HttpRequestOptions): ToolContract<HttpRequestInput, HttpRequestResult>;\n calculator(options?: CalculatorOptions): ToolContract<CalculatorInput, CalculatorResult>;\n dateTime(options?: DateTimeOptions): ToolContract<DateTimeInput, DateTimeResult>;\n }\n interface Ai {\n mcp: McpFactory;\n }\n}\n\n// Runtime registration: attach `tools` and `mcp` onto the shared `ai`\n// object the moment this module is imported (the augmentation above types\n// them). The `dateTime` factory's optional injectable-clock `deps` is a\n// test/advanced seam, omitted from the agent-facing signature — call\n// `dateTimeTool` directly to use it.\nObject.assign(ai.tools, {\n webSearch: webSearchTool,\n fetchUrl: fetchUrlTool,\n http: httpRequestTool,\n calculator: calculatorTool,\n dateTime: (options?: DateTimeOptions) => dateTimeTool(options),\n});\n\nai.mcp = mcp;\n"],"mappings":";;;;;;;;;AAiDA,OAAO,OAAO,GAAG,OAAO;CACtB,WAAW;CACX,UAAU;CACV,MAAM;CACN,YAAY;CACZ,WAAW,YAA8B,aAAa,OAAO;AAC/D,CAAC;AAED,GAAG,MAAM"}
|
package/llms-full.txt
CHANGED
|
@@ -147,7 +147,7 @@ The MCP client does not use `@modelcontextprotocol/sdk`: it speaks MCP's JSON-RP
|
|
|
147
147
|
|
|
148
148
|
---
|
|
149
149
|
name: expose-as-mcp-server
|
|
150
|
-
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.'
|
|
150
|
+
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`, `listFilter`, `validateCallParams`, `McpError`, `routes`, `McpHttpRouteContext`, `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.'
|
|
151
151
|
---
|
|
152
152
|
|
|
153
153
|
# Expose tools AS an MCP server (Direction B)
|
|
@@ -282,7 +282,7 @@ const server = ai.mcp.serve([identity], {
|
|
|
282
282
|
```
|
|
283
283
|
|
|
284
284
|
- `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.
|
|
285
|
-
- `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.
|
|
285
|
+
- `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 `McpError` → its own code/message/data; other throws → 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.
|
|
286
286
|
- 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.
|
|
287
287
|
- Public types: `McpBeforeCall`, `McpCallRefusal`, `McpServeToolContext`, `McpStdioServeTransport`; `ToolContext` in `@warlock.js/ai` adds optional `requestId` and `principal`.
|
|
288
288
|
|
|
@@ -290,6 +290,106 @@ const server = ai.mcp.serve([identity], {
|
|
|
290
290
|
|
|
291
291
|
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.
|
|
292
292
|
|
|
293
|
+
## Control discovery, call envelopes and HTTP side routes
|
|
294
|
+
|
|
295
|
+
Use this complete example for hidden-but-callable tools, custom protocol errors,
|
|
296
|
+
unknown envelope policy, and side routes under the same authentication:
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
import { randomBytes } from "node:crypto";
|
|
300
|
+
import { ai } from "@warlock.js/ai";
|
|
301
|
+
import { McpError } from "@warlock.js/ai-tools";
|
|
302
|
+
|
|
303
|
+
const tools = [ai.tools.calculator(), ai.tools.dateTime()];
|
|
304
|
+
const token = randomBytes(32).toString("hex");
|
|
305
|
+
const canCall = (principal: unknown, name: string) =>
|
|
306
|
+
principal === "member" && tools.some((tool) => tool.name === name);
|
|
307
|
+
|
|
308
|
+
const server = ai.mcp.serve(tools, {
|
|
309
|
+
name: "member-tools",
|
|
310
|
+
listFilter: async (context, descriptors) =>
|
|
311
|
+
context.principal === "member"
|
|
312
|
+
? descriptors.filter((entry) => entry.name === "calculator")
|
|
313
|
+
: [],
|
|
314
|
+
validateCallParams: (_context, params) => {
|
|
315
|
+
const unsupported = Object.keys(params).filter(
|
|
316
|
+
(key) => !["name", "arguments", "_meta"].includes(key),
|
|
317
|
+
);
|
|
318
|
+
if (unsupported.length > 0) {
|
|
319
|
+
throw new McpError(-32600, "Unsupported call envelope", { fields: unsupported });
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
beforeCall: (context, tool) => {
|
|
323
|
+
if (!canCall(context.principal, tool.name)) {
|
|
324
|
+
throw new McpError(-32600, "Denied");
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
transport: {
|
|
328
|
+
type: "http",
|
|
329
|
+
path: "/mcp",
|
|
330
|
+
auth: { bearer: token, authenticate: () => "member" },
|
|
331
|
+
routes: [
|
|
332
|
+
{
|
|
333
|
+
method: "GET",
|
|
334
|
+
path: "/tool-policy",
|
|
335
|
+
handler: (context) => ({
|
|
336
|
+
body: { allowed: canCall(context.principal, context.url.searchParams.get("name") ?? "") },
|
|
337
|
+
}),
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
const { url, close } = await server.start();
|
|
343
|
+
// `${url}/tool-policy?name=date_time` requires the same bearer token.
|
|
344
|
+
// date_time stays callable via tools/call, under beforeCall, despite being hidden.
|
|
345
|
+
await close();
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
- `listFilter(context, tools)` runs per `tools/list`, on fresh wire descriptors
|
|
349
|
+
with schemas and exposed (possibly prefixed) names. Return a descriptor array,
|
|
350
|
+
possibly empty or reordered. It only affects discovery; call lookup and policy
|
|
351
|
+
are independent. A throwing filter fails that list request, leaving tools callable.
|
|
352
|
+
- `validateCallParams(context, params)` runs after the basic object/name check,
|
|
353
|
+
before lookup/schema validation/`beforeCall`/invoke. It sees the full read-only
|
|
354
|
+
params including unknown fields, `_meta` and `task`. Return nothing or throw.
|
|
355
|
+
Both hooks support promises and the same request id/principal/request/signal
|
|
356
|
+
as `beforeCall`, on HTTP and stdio. For stdio, use
|
|
357
|
+
`transport: { type: "stdio", principal: "member" }` with these same hooks.
|
|
358
|
+
- `McpError(code, message, data?)` extends `Error`. `beforeCall` and
|
|
359
|
+
`validateCallParams` preserve its integer code, message and optional data in
|
|
360
|
+
JSON-RPC errors. SDK `McpError` is recognized by Error/name/integer code without
|
|
361
|
+
a runtime SDK dependency; its `.message` is preserved including the SDK prefix.
|
|
362
|
+
Other thrown values stay `-32602`; tool execution errors stay `isError: true`.
|
|
363
|
+
- Defaults continue ignoring extra envelope fields. The server speaks
|
|
364
|
+
`2025-11-25`, `2025-06-18`, `2025-03-26`, and advertises no task capability.
|
|
365
|
+
[MCP 2025-11-25 task handling](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks#task-support-and-handling)
|
|
366
|
+
requires ignoring task metadata without that capability. The example's
|
|
367
|
+
rejection of `task` is an explicit app policy; omit it to retain default behavior.
|
|
368
|
+
Allow `_meta`, which is protocol metadata.
|
|
369
|
+
- `transport.routes` is HTTP-only. Host/Origin and all configured bearer,
|
|
370
|
+
authorize and authenticate checks run before body reads/handlers. Auth is
|
|
371
|
+
optional on loopback and required elsewhere, just as for MCP. `context` has
|
|
372
|
+
`request`, `principal`, parsed `url`, raw UTF-8 `body`, `signal`; no request id.
|
|
373
|
+
Route bodies share `maxBodyBytes`. MCP-only JSON/header checks do not apply.
|
|
374
|
+
A handler returns `{ status?: number, headers?: Record<string, string>, body?: unknown }`;
|
|
375
|
+
status defaults to 200, body is JSON, omitted body is empty, HEAD suppresses it.
|
|
376
|
+
Exceptions return a generic 500. Disconnect and shutdown abort the signal;
|
|
377
|
+
routes share `close()` draining. Reuse your policy function explicitly;
|
|
378
|
+
route requests do not invoke `beforeCall`.
|
|
379
|
+
- Route suffix `/tool-policy` appends to MCP path `/mcp` or `/mcp/` as
|
|
380
|
+
`/mcp/tool-policy` (root MCP path `/` gives `/tool-policy`). Exact methods:
|
|
381
|
+
GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS. No wildcard, automatic HEAD or CORS.
|
|
382
|
+
Query strings do not affect matching; read them with `url.searchParams`.
|
|
383
|
+
Known path + wrong method → 405 with Allow; unknown path → 404.
|
|
384
|
+
- Route suffixes require nonempty segments containing letters/digits/`_-.~`.
|
|
385
|
+
Empty suffix, `/`, dot segments, percent escapes, repeated or trailing slashes,
|
|
386
|
+
queries, fragments and wildcards fail start with `McpServeError.type: "config"`.
|
|
387
|
+
With routes, the MCP base must be a canonical pathname. All methods on the
|
|
388
|
+
MCP endpoint are reserved, so routes cannot override it. Duplicate method/path
|
|
389
|
+
pairs fail start with `"collision"`; different methods on a suffix are allowed.
|
|
390
|
+
- Public types: `McpListFilter`, `McpValidateCallParams`, `McpHttpRoute`,
|
|
391
|
+
`McpHttpRouteContext`, `McpHttpRouteResponse`.
|
|
392
|
+
|
|
293
393
|
## Return native MCP results
|
|
294
394
|
|
|
295
395
|
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:
|
|
@@ -583,4 +683,25 @@ if (error instanceof WebToolError && error.type === "denied-host") {
|
|
|
583
683
|
- The `define-ai-tool` topic of the `warlock-js-ai` skill — the `tool()` / `ToolContract` seam every tool here is built on.
|
|
584
684
|
- The `run-ai-agent` topic of the `warlock-js-ai` skill — running the agent that consumes these tools.
|
|
585
685
|
|
|
686
|
+
## Add the belt to a discovery catalogue
|
|
687
|
+
|
|
688
|
+
Registration extends the core namespace, preserving `ai.tools.catalogue` and `ai.tools.measure`:
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
import "@warlock.js/ai-tools";
|
|
692
|
+
import { ai, type ModelContract } from "@warlock.js/ai";
|
|
693
|
+
|
|
694
|
+
declare const model: ModelContract;
|
|
695
|
+
const surface = ai.tools.catalogue({
|
|
696
|
+
core: [ai.tools.calculator()],
|
|
697
|
+
searchable: [ai.tools.dateTime()],
|
|
698
|
+
limit: 8,
|
|
699
|
+
});
|
|
700
|
+
const assistant = ai.agent({ model, tools: surface.tools });
|
|
701
|
+
const measured = await ai.measure({ model, tools: surface.tools });
|
|
702
|
+
console.log(measured.parts.tools.tokens);
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
The core plus `tool_search` / `tool_call` stays advertised. Hidden calls still traverse target policy, approval, validation, accounting and tracing. The `define-ai-tool` topic covers lazy descriptors, ranking and measurement comparisons. Native provider tools stay alongside the catalogue; eager ones included in its lists pass through unchanged and are excluded from discovery.
|
|
706
|
+
|
|
586
707
|
|
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`, `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.
|
|
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`, `listFilter`, `validateCallParams`, `McpError`, `routes`, `McpHttpRouteContext`, `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.
|
|
25
|
+
"@warlock.js/ai": "5.34.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.
|
|
46
|
+
"version": "5.34.0",
|
|
47
47
|
"main": "./cjs/index.cjs",
|
|
48
48
|
"module": "./esm/index.mjs",
|
|
49
49
|
"types": "./esm/index.d.mts",
|