/** * MCP eager-load helper. * * The Claude Code SDK marks every MCP tool as deferred-by-default: the model * cannot invoke an MCP tool until it has first called `ToolSearch` to load * the schema, which costs one extra turn per unique schema. The SDK's per- * tool override is `_meta["anthropic/alwaysLoad"]: true` on the tool's * `tools/list` entry, which `isDeferredTool` reads to flip the default. * * The MCP SDK exposes that channel only via `server.registerTool(name, * config, handler)`; the older `server.tool(name, desc, schema, handler)` * has no `_meta` parameter. This helper wraps the registerTool call so * every plugin's index.ts can opt a tool into eager-load with one line * matching the shape of the existing four-arg `server.tool` overload. * * Use for every tool that appears in `ADMIN_CORE_TOOLS` — the permission * allow-list IS the curation per the CEO review. Tools the * admin should not see should be removed from the allow-list, not merely * left deferred. */ import { wrapWithLifeline } from "../../mcp-lifeline/dist/index.js"; /** Wire-format metadata key the Claude Code SDK reads. */ export const ALWAYS_LOAD_META_KEY = "anthropic/alwaysLoad" as const; /** * Loose structural type for any object exposing a `registerTool` method. The * MCP SDK ships dual ESM/CJS packages with conditional exports, so importing * the concrete `McpServer` type here would force every consumer to compile * under the same resolution mode — they don't. Keep this contract minimal: * we only need the method to exist; we cast internally to drive the * upstream generic overloads which differ between SDK versions. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any type EagerCapableServer = { registerTool: (...args: any[]) => any }; /** * Local structural type for a Zod raw shape. We deliberately do NOT import * `ZodRawShape` from zod itself — plugin workspaces ship pinned zod v3 in * their nested `node_modules/zod`, while `platform/node_modules/zod` is v4, * and the two `ZodRawShape` types are structurally incompatible (v4 * introduced the `_zod` brand on `ZodType`). A typed-by-value `unknown` * member is enough: TypeScript treats it as compatible with both v3 and v4 * shapes, while still constraining the generic to "an object whose values * are zod-like" so the caller's destructured handler args stay typed via * the SDK's own overload resolution downstream. */ type LocalZodRawShape = Record; /** * Register an MCP tool that the Claude Code SDK eager-loads into the * model's prompt at session boot, bypassing the ToolSearch round-trip. * * Mirrors the four-arg `server.tool(name, description, inputSchema, handler)` * call shape — the only overload in use across our plugins — so callers * replace `server.tool(` with `eagerTool(server, ` mechanically. * * The helper deliberately does not constrain handler-arg types itself; the * downstream `server.registerTool` invocation inside this function passes * the shape through, and the upstream MCP SDK's overload (resolved against * the consumer's own zod version) infers the handler args. */ export function eagerTool( server: EagerCapableServer, name: string, description: string, inputSchema: Args, // eslint-disable-next-line @typescript-eslint/no-explicit-any handler: (...args: any[]) => any, ): void { server.registerTool( name, { description, inputSchema, _meta: { [ALWAYS_LOAD_META_KEY]: true }, }, wrapWithLifeline(name, handler), ); }