import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import type { CloudClient } from "../cloud/client.js"; import type { AgentLlm } from "../cloud/types.js"; import { SessionState } from "../session/state.js"; import type { UpstreamSession } from "./upstream.js"; import type { Logger, SnapshotTelemetryDraft, TraceConfig } from "./snapshot.js"; /** * Context handed to every interceptor. Mirrors the slice of `ProxyDeps` that * an interceptor actually needs — keeps the per-tool implementation honest * about its dependencies. */ export interface InterceptDeps { upstream: UpstreamSession; cloud: CloudClient | null; session: SessionState; bypass: boolean; /** Privacy Shield bypass engaged (off-switch + ack); forwarded per snapshot. */ privacyShieldBypass?: boolean; /** Off-switch without the ack; surfaces the one-time incomplete warning. */ privacyShieldBypassIncomplete?: boolean; log: Logger; trace?: TraceConfig; /** Customer's agent-LLM metadata; forwarded to the cloud per snapshot. */ agentLlm?: AgentLlm; } /** Result shape mirrors the MCP SDK `CallToolResult`, plus an optional * telemetry draft the request handler uses to fire a /v1/telemetry POST * after the call returns. The telemetry field is stripped from the * surface the MCP SDK sees. */ export interface ToolInterceptResult { content: Array<{ type: "text"; text: string; }>; isError?: boolean; telemetry?: SnapshotTelemetryDraft; } /** * Tool-name interceptor. Receives the upstream call's already-fetched * result so the interceptor doesn't decide *whether* to call upstream — * that's the registry's job. The interceptor decides what to do with the * response (e.g. run Privacy Shield + cloud POST for `browser_snapshot`). */ export type ToolInterceptor = (args: Record, upstreamResult: { text: string; isError?: boolean; }, deps: InterceptDeps) => Promise; /** * Default tool-interceptor registry. One entry today; the registry shape * exists so future framework adapters (browser-use, Stagehand, direct CDP) * can add interceptors without editing the request handler. * * Production code reads from this default. Tests inject their own registry * via `ProxyDeps.interceptors` to verify the dispatch path with synthetic * tool names. */ export declare const DEFAULT_INTERCEPTORS: ReadonlyMap; export interface ProxyDeps { upstream: UpstreamSession; cloud: CloudClient | null; bypass: boolean; /** Privacy Shield bypass engaged (off-switch + ack); forwarded per snapshot. */ privacyShieldBypass?: boolean; /** Off-switch without the ack; surfaces the one-time incomplete warning. */ privacyShieldBypassIncomplete?: boolean; log: Logger; /** When set, snapshot interceptor writes per-match span JSONL. JDC_TRACE=1 only. */ trace?: TraceConfig; /** * Customer's agent-LLM metadata (provider + optional model). When set, * forwarded with every cloud snapshot POST so the service-side * tokenizer picks the right family; when absent, the service falls * back to an approximate tokenizer. */ agentLlm?: AgentLlm; transport?: Transport; /** * Optional interceptor registry override. Defaults to * `DEFAULT_INTERCEPTORS` (which today contains only `browser_snapshot`). * Tests pass synthetic registries to exercise the dispatch path. */ interceptors?: ReadonlyMap; } export interface ProxyHandle { server: Server; close(): Promise; } /** * Assemble the proxy server. Tools listed in the interceptor registry * (default: `browser_snapshot` → `handleSnapshot`) get their upstream * response handed to the matching interceptor — typically Privacy Shield * + the cloud codec POST. Everything else passes through verbatim: * upstream's actual `inputSchema` is preserved on `tools/list` so the * agent knows what args to send, and arguments are forwarded untouched * on `tools/call`. Using the low-level `Server` (rather than `McpServer`) * is what lets us pass arbitrary args without forcing every upstream JSON * Schema through a Zod conversion — that conversion would silently strip * passthrough tool arguments. */ export declare function startProxy(deps: ProxyDeps): Promise;