/** * Tool registry * * All MCP tools are registered here. Each tool module exports: * - Tool definitions (McpToolDefinition[]) * - Executors (Record) */ import type { McpToolDefinition, ToolContext, ToolResult } from "../types.js"; import { type ToolAnnotations } from "./annotations.js"; export { TOOL_ANNOTATIONS, EXTERNAL_REACH, buildJustifications, type ToolAnnotations, type ExternalReach, type ToolJustifications, } from "./annotations.js"; /** * All available MCP tools */ export declare const ALL_TOOLS: McpToolDefinition[]; /** * Tools advertised over the remote HTTP transport ONLY. * * This is a BUDGET split, not a policy one — the same distinction `ToWireOptions` * draws below for annotations. `tools/list` ships as one newline-delimited * message, and on Windows + npx a line over the ~64KB anonymous-pipe buffer * never completes — the failure that broke Windows Desktop and the reason this * split exists; `test/tools-list-budget.test.ts` guards the stdio * wire at 62KB against a surface already measuring ~61KB. A new tool costs * ~700-950B, which stdio cannot afford without first consolidating an existing * one. Remote HTTP has no pipe, and its own ceiling has ~19KB spare. * * `import_content` used to sit here. It is now on the default surface, paid for * by the three webhook tools above — a straight swap of 1,436 unused bytes for * 975 used ones. That swap matters beyond the budget: a stdio agent asked to * import an article could not see `import_content`, so it reached for * `write_cms_post` instead and produced a FraseCMS post. Not an error the user * could act on — a plausible wrong answer, and precisely what the customer * asking for this feature said they did not want. * * Two consequences of withholding anything, worth stating: * * - The EXECUTOR stays registered on both transports. Withholding is about * what we advertise, not what we permit; a stdio client that knows the name * (from docs, or a prior remote session) gets the same behaviour rather than * a confusing "unknown tool". Nothing here is a security boundary — the API * key's scopes are. * - A stdio client cannot DISCOVER a withheld tool, and an agent that cannot * see a tool does not report it missing — it substitutes the nearest thing * it can see. So `SERVER_INSTRUCTIONS` names the hosted endpoint and what * lives only there, because instructions are the one channel that reaches * the agent at connect time regardless of the tool list. */ export declare const REMOTE_ONLY_TOOL_NAMES: ReadonlySet; /** * The tools a given transport advertises in `tools/list`. * * One chokepoint, so the two servers cannot drift: `src/index.ts` (stdio) and * `src/lib/mcp/server-factory.ts` (remote HTTP) both call this rather than * filtering ALL_TOOLS by hand. */ export declare function toolsForTransport(transport: "stdio" | "remote"): McpToolDefinition[]; /** * Tool executor mapping. * * Every executor is wrapped with `withCleanRender` so a malformed render (an * object coerced to `[object Object]`, a leaked `undefined`/`NaN`) is caught in * tests and recorded in prod instead of silently shipping to users. This is the * single chokepoint both MCP surfaces dispatch through — the stdio server and * the in-app HTTP endpoint — so wrapping here instruments every tool on both. */ export declare const TOOL_EXECUTORS: Record Promise>; /** * The whole tool surface ships in one `tools/list` message — no pagination, no * on-demand gateway. Both were tried and abandoned: Claude Desktop ignores * `tools/list` cursors (anything past page 1 is invisible), and tools enabled * mid-turn via `tools/list_changed` don't become callable until a later turn, * so an "enable then use" flow dead-ends. The only thing that reliably works is * a single message with every tool already callable. To stay under the ~64KB * Windows anonymous-pipe buffer that caused the original -32001 stall, the * per-tool schemas are kept lean and the total is guarded in CI (see * `test/tools-list-budget.test.ts`). */ /** A tool in its advertised `tools/list` wire shape. */ export interface WireTool { name: string; description: string; inputSchema: McpToolDefinition["inputSchema"]; /** Present only when serialized with `{ outputSchema: true }` — see `toWire`. */ outputSchema?: typeof TOOL_RESULT_OUTPUT_SCHEMA; /** Present only when serialized with `{ annotations: true }` — see `toWire`. */ title?: string; annotations?: ToolAnnotations; } /** * Stable structured result exposed by the remote MCP transport. * * `markdown` carries the tool's actual answer and is REQUIRED. Declaring an * output schema tells a client that `structuredContent` is the authoritative * result, and clients act on that: they render it in preference to the * `content` text block. A schema of only `{ success }` therefore reduced every * successful call to `{"success":true}` at the client — no sites, no SERP rows, * no research id — while the tool itself had succeeded. The payload has to * travel in the thing we told the client to read. * * This duplicates the markdown (it stays in `content` for clients that predate * structured output). That cost is deliberate and bounded — the alternative is * a correct-looking call that renders empty. * * Executors may also return a raw `data` payload, but that is intentionally not * advertised or forwarded: some tools handle delivery tokens, webhook secrets, * or more API data than the model needs. The optional hidden-state fields let * clients distinguish a successful empty state without parsing prose. */ export declare const TOOL_RESULT_OUTPUT_SCHEMA: { readonly type: "object"; readonly properties: { readonly success: { readonly type: "boolean"; }; readonly markdown: { readonly type: "string"; }; readonly hidden: { readonly type: "boolean"; }; readonly reason: { readonly type: "string"; }; }; readonly required: readonly ["success", "markdown"]; readonly additionalProperties: false; }; export interface ToWireOptions { /** * Emit `title` + `annotations` (Connectors Directory requirement). * * OFF BY DEFAULT, AND THAT IS DELIBERATE — not an oversight. * * Measured on the full JSON-RPC `tools/list` line (121 tools), which is what * the pipe actually carries: * * plain 62,984 B (61.5 KiB) * annotated 76,518 B (74.7 KiB) * delta 13,534 B (13.2 KiB) — over the 64 KiB limit by 10,982 B * * Over HTTP that is free. Over stdio it is fatal: a single line above 64 KiB * is the -32001 `tools/list` stall on Windows + npx that * `test/tools-list-budget.test.ts` exists to prevent. So the remote HTTP * server opts in and the stdio server does not. (Re-measure with * `scripts/measure-tools-list.ts` rather than trusting these numbers — an * earlier version of this comment quoted figures that were ~25% low because * they omitted the JSON-RPC envelope.) * * The margin is what makes this robust: even the leanest annotation shape — * dropping the redundant top-level `title`, which saves ~3.7 KB — still * exceeds the limit. Annotations cannot ship over stdio without a surface * trim, full stop. * * This is a SERIALIZATION difference, not a classification difference. Both * transports read the same `TOOL_ANNOTATIONS` table, so the two can never * disagree about whether a tool is destructive — one of them simply omits a * presentational field for a transport-layer reason. * * THE OMISSION FAILS SAFE, which is easy to get backwards. Per the MCP spec, * an absent `readOnlyHint` defaults false and an absent `destructiveHint` * defaults TRUE — so on stdio every tool, `delete_content` included, is * treated as destructive and prompts. The unannotated transport is the more * conservative one. What stdio loses is purely UX: the 63 read-only tools * give up their no-prompt affordance. Do not "fix" the omission thinking it * is the dangerous side. * * WHY NOT PAGINATE INSTEAD? `tools/list` is a paginated operation in the * spec, and chunking would let BOTH transports ship annotations with one wire * shape — genuinely the cleaner design. It is rejected for client-compat, not * purity: Claude Desktop ignores `tools/list` cursors, so anything past page 1 * is invisible to exactly the stdio cohort this split protects (see the * ALL_TOOLS header above, where paging and gateway approaches were both tried * and abandoned). If that client behaviour is ever fixed, pagination beats * this split. * * Note for the future: a Desktop Extension (MCPB) submission would bundle the * stdio server and require annotations on that wire too, which would force a * tool-surface trim to get back under 64 KiB. This defers that, it does not * remove it. */ annotations?: boolean; /** * Emit the shared structured-result schema. * * The remote HTTP server opts in for ChatGPT review. Stdio leaves it out to * preserve the guarded single-message tools/list budget below the Windows * anonymous-pipe limit. */ outputSchema?: boolean; } export declare function toWire(t: McpToolDefinition, options?: ToWireOptions): WireTool; /** * Server `instructions` (MCP `initialize` result). Modern clients (Claude Code * tool-search) read this to route by task and defer-load tool schemas, cutting * the upfront context cost of the full surface. It names EVERY domain so no * area is invisible to task-based routing — an under-named domain won't be * deferred-loaded on demand. * * Single source of truth: imported by BOTH the stdio server (`src/index.ts`) * and the HTTP factory (`src/lib/mcp/server-factory.ts` in the main app) so the * two transports never drift. Keep it under ~2KB — it ships on every connect. */ export declare const SERVER_INSTRUCTIONS: string; /** * Get tool definition by name */ export declare function getToolByName(name: string): McpToolDefinition | undefined; /** * Get tool executor by name */ export declare function getToolExecutor(name: string): ((input: unknown, context: ToolContext) => Promise) | undefined; /** * Get total tool count */ export declare function getToolCount(): number; //# sourceMappingURL=index.d.ts.map