import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; export interface ErrorHints { /** Appended after `Hint: ` when the SDK rejects the credential. */ authentication: string; /** Appended after `Hint: ` when the platform refuses an authenticated call. */ forbidden: string; } /** * The wrapper every tool handler delegates to. Named because the shared * toolset takes it as an argument: the tools are identical across transports, * the hints inside `call` are not. */ export type CallFn = (fn: () => Promise) => Promise; export interface CallOptions { hints: ErrorHints; /** * Attach a plain-object SUCCESS result as `structuredContent` beside the * text. Hosted-only: it is what feeds the Apps-SDK widget, and the MCP spec * pairs it with an `outputSchema`, which is a hand-maintained zod twin of a * published type. One such twin is worth it for a widget; fifteen would be a * drift surface with no consumer asking for it. See * `cloudflare/mcp/CLAUDE.md`, "What deliberately differs". * * It does NOT gate the ERROR envelope, which every transport carries — see * `toErrorResult`. The objection above is about fifteen success shapes; a * failure has exactly one published shape, and no schema to keep in step. */ structuredContent?: boolean; /** * Called when the API refuses on CREDENTIAL grounds — and only then. * * An HTTP transport has an obligation stdio does not: a client learns it * must authenticate from a real `401` with a `WWW-Authenticate` header, and * ignores that header entirely on a `200`. So a tool error saying "please * authenticate" is, on that transport, a dead end — the caller is told * something it has no way to act on. * * This is the seam that lets the transport answer properly, and the * decision of WHAT counts as a credential failure stays here, beside the * hints, rather than being made a second time by each consumer: * * - **Authentication** always reports. The credential was absent, malformed, * unknown, or expired — the transport cannot tell which, and does not * need to. * - **Forbidden reports only when it carries `requiredScope`.** That field * is the API's own signal that a valid grant simply lacks a permission, * which re-consent can fix. Every other refusal on that arm — plan limits, * a terminated account, an action no scope can authorize — is a genuine * answer to the question asked, and stays an ordinary in-band tool error. * * The result is unchanged either way: the agent still receives the full * text envelope, hints included. This is a notification, not a substitution. * * Why an observer at all, rather than the transport inspecting the request: * peeking at a JSON-RPC body to guess whether a call needs a credential * means parsing it twice — on the deploy path, that is tens of megabytes of * base64 re-parsed before the size caps run — and it can only ever guess at * PRESENCE, so an EXPIRED token would answer in-band and a connected client * would never refresh. Reporting what the API actually answered costs * nothing and is correct for both. */ onAuthFailure?: (failure: AuthFailure) => void; } /** * What a credential refusal was, in the only two shapes a transport acts on * differently. * * Deliberately not an HTTP status or an RFC 6750 error code: those are the * consuming transport's vocabulary, and stdio — which also builds a `call` — * has neither. The presence of `requiredScope` is the whole discriminator, so * there is no second field restating it. */ export interface AuthFailure { /** * The scope the grant is missing, from the API's `details.requiredScope`. * Absent when the credential itself was refused rather than its permissions. */ requiredScope?: string; } /** * Builds the `call()` wrapper both transports use: SDK promise in, MCP * `CallToolResult` out. * * The success envelope, the `'Done.'` sentinel for a void result, the order * the error arms are tested in, and the `Details:` appendix are all wire * facts an agent observes — so they live here once, rather than in two files * kept equal by review. */ export declare function createCall(options: CallOptions): CallFn; export declare const call: CallFn;