/** * MCP tool response hardening (spec-10). * * A single set of guards every tool runs through in the CallTool handler, so the * whole surface has one uniform input-validation, timeout, output-cap, and * error-normalization path: * * - Input validation BEFORE the handler runs, against the tool's own declared * `inputSchema` (reusing the hand-written JSON-Schema-subset validator from * spec-05 — no Ajv). Invalid args are a Tool Execution Error (`isError: true`) whose text * names the parameter, the expected shape, and a corrected example call, so the calling * model can self-correct (change: adopt-mcp-protocol-conformance; SEP-1303). * - Per-tool timeout via Promise.race, with slow tools overridden. * - Output size cap: oversized results are truncated DETERMINISTICALLY with a * `truncated: true` note telling the agent how to narrow the query — never a * silent drop. * - Error normalization to a stable code taxonomy, distinguishing "repo not * analyzed yet" (actionable) from real failures. */ /** Stable MCP tool error-code taxonomy. */ export type McpToolErrorCode = 'INVALID_ARGS' | 'NOT_ANALYZED' | 'TIMEOUT' | 'OUTPUT_TRUNCATED' | 'INTERNAL'; /** Reject the first unknown own property with a bounded, deterministic hint. */ export declare function validateKnownProperties(value: unknown, knownKeys: readonly string[]): string | null; /** * A minimal argument object that satisfies a schema's `required` list, each value an example of its * declared shape — the "corrected example" half of an actionable validation error. */ export declare function exampleToolArguments(inputSchema: unknown, depth?: number): Record; /** * The text of an actionable Tool Execution Error for rejected arguments: the tool, what is wrong * (parameter path and expected shape), and a corrected example call to retry with. The detail echoes * caller-supplied values, so it is bounded, secret-redacted, and stripped of terminal controls like * every other error result. */ export declare function invalidArgumentsMessage(toolName: string, detail: string, inputSchema: unknown, exampleOverrides?: Record): string; type InvalidArgumentsResult = { content: Array<{ type: 'text'; text: string; }>; isError: true; }; /** * Check a tool call's arguments before anything runs (change: adopt-mcp-protocol-conformance): schema * validation, then the `directory`. A rejection is a Tool Execution Error (`isError: true`) the model * can act on, never a JSON-RPC protocol error; it creates nothing, so no telemetry is written for it. */ export declare function checkToolArguments(toolName: string, args: unknown, inputSchema: unknown, options: { hadExplicitDirectory: boolean; validateDirectory: (directory: string) => Promise; }): Promise<{ ok: true; directory: string; } | { ok: false; result: InvalidArgumentsResult; }>; /** * Validate args against a tool's inputSchema. Returns a human-readable message on * failure, or null when valid (or when no schema is declared). */ export declare function validateToolArgs(args: unknown, inputSchema: unknown): string | null; /** Thrown when a tool exceeds its timeout — classified as TIMEOUT downstream. */ export declare class ToolTimeoutError extends Error { readonly toolName: string; readonly ms: number; constructor(toolName: string, ms: number); } /** The timeout budget for a tool (per-tool override or the default). */ export declare function toolTimeoutMs(toolName: string): number; /** Race a tool's work against its timeout. Rejects with ToolTimeoutError on expiry. */ export declare function withToolTimeout(work: Promise, toolName: string, msOverride?: number): Promise; /** * Deterministically cap a result string to a byte budget. When over budget, cut on * a UTF-8-safe boundary and append a note explaining how to narrow the query. */ export declare function capOutput(text: string, maxBytes: number): { text: string; truncated: boolean; }; /** * The MCP response body for a tool result: capped to the byte budget, then SCREENED. * * This is the choke point where a structured tool result becomes the text an agent reads on * the stdio MCP transport, so it is where repository-derived bytes are neutralized. (The * serve daemon serializes its own HTTP body and screens it in its own `sendJson`.) It used to screen * only the string branch, which looked sufficient because `JSON.stringify` escapes C0 * controls to `\uXXXX` by itself: ESC therefore appeared handled, while U+202E bidi * overrides and U+200B zero-width characters passed through verbatim into orient, * search_code, get_subgraph, find_path, find_dead_code and get_map responses. * * Screening runs AFTER capping, on the serialized document: the class only ever REMOVES * characters inside string literals, so the JSON stays valid and parseable and the byte * budget still holds (the text can only get smaller). */ export declare function capStructuredResult(result: unknown, maxBytes: number): { text: string; truncated: boolean; }; /** Map an error to the stable taxonomy code (actionable vs real failure). */ export declare function classifyToolError(err: unknown): McpToolErrorCode; export {}; //# sourceMappingURL=tool-guard.d.ts.map