import { Transport, JSONRPCMessage, MessageExtraInfo, TransportSendOptions, CallToolResult } from '@modelcontextprotocol/client'; import { AppBridge } from '@modelcontextprotocol/ext-apps/app-bridge'; /** * SEP-1865 tool visibility helpers. * * Dependency-free leaf module so both the React renderer utilities * (`mcp-apps-utils.ts`, re-exported for back-compat) and the framework-free * host bridge (`host-app-bridge.ts`, consumed by the eval harness) share one * source of truth for the model-only visibility check without dragging * heavier dependencies into either consumer. */ /** * Get the visibility array from tool metadata. * Default: ["model", "app"] if not specified (per SEP-1865). */ declare function getToolVisibility(toolMeta: Record | undefined): Array<"model" | "app">; /** * Check if tool is visible to model only (not callable by apps). * True when visibility is exactly ["model"]. */ declare function isVisibleToModelOnly(toolMeta: Record | undefined): boolean; /** * Check if tool is visible to app only (hidden from model). * True when visibility is exactly ["app"]. */ declare function isVisibleToAppOnly(toolMeta: Record | undefined): boolean; /** * The tool's declared `ui://` resource URI, or `null` when it declares none or * declares one that is malformed. Resolves both the nested * `_meta.ui.resourceUri` and the deprecated flat `_meta["ui/resourceUri"]`. * * Only the malformed-URI throw is absorbed. Anything else upstream raises is a * real fault — a changed contract, a bug in `app-bridge` — and propagates, so * a detection regression stays visible instead of reading as "no app UI". */ declare function resolveToolUiResourceUri(toolMeta: Record | undefined): string | null; declare class LoggingTransport implements Transport { private inner; private onSend?; private onReceive?; private _sessionId?; onclose?: () => void; onerror?: (error: Error) => void; onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void; setProtocolVersion?: (version: string) => void; constructor(inner: Transport, handlers: { onSend?: (message: JSONRPCMessage) => void; onReceive?: (message: JSONRPCMessage) => void; }); get sessionId(): string | undefined; set sessionId(value: string | undefined); start(): Promise; send(message: JSONRPCMessage, options?: TransportSendOptions): Promise; close(): Promise; } /** * Pure JSON helpers shared by the widget runtime and the inspector. Framework- * free, browser- and Node-safe. Relocated here from the inspector in Phase * 3d-ii so the (soon-to-relocate) widget renderer and the inspector share one * implementation instead of duplicating it across the package boundary. */ /** Deterministic JSON: deep object-key sort, then `JSON.stringify`. */ declare function stableStringifyJson(value: unknown): string; /** * Extract a method/label from a widget transport message. MCP Apps messages are * JSON-RPC (`method`/`result`/`error`); OpenAI-shim messages carry an * `openai:`-prefixed `type`. `protocol` defaults to the JSON-RPC reading. */ declare function extractMethod(message: unknown, protocol?: "mcp-apps" | "openai-apps"): string; /** * Which halves of a tool result a host relays to a widget that called a tool. * * A tool result has two halves — the `content` block array and * `structuredContent` (MCP spec, "Tool Result" under server/tools) — and real * hosts differ in what they forward. Cursor 3.4 strips `structuredContent`, so * a widget reading it breaks silently there while working everywhere else. * * This is the WIDGET axis, deliberately distinct from the two mcpjam already * models: `modelVisibleMcpToolResults` (what the model sees) and * `mcpToolResultImageRendering` (what mcpjam's own chat UI draws). Same tool * result, three different pipes. * * Absent or `true` means forwarded, matching the omit-when-absent discipline * every probe knob uses: only an explicit `false` drops anything. */ interface ToolResultPolicy { structuredContent?: boolean; content?: { text?: boolean; image?: boolean; audio?: boolean; resource?: boolean; resourceLink?: boolean; }; } /** * Drop the halves of `result` this host does not forward to widgets. * * Returns the ORIGINAL object when nothing is dropped, so the common case * costs no allocation and referential equality is preserved for consumers * that memoize on it. * * A block whose `type` is not in the spec's five kinds is always kept: the * policy cannot describe it, and silently swallowing an unknown block would * make a future spec addition look like host breakage. */ declare function applyToolResultPolicy>(result: T, policy: ToolResultPolicy | null | undefined): T; /** * Which browser storage APIs a widget can reach inside the host's sandbox. * * NOT an MCP concept — the MCP Apps spec's `sandbox` defines only * `permissions` and `csp`. This is an observed consequence of the sandboxed * iframe the spec does mandate, measured because widgets that persist state * break silently on a host that blocks it. * * Named and exported because the same shape crosses five seams (profile * type, widget-host projection, renderer, sandboxed-iframe prop, modal * prop). Adding a fourth API — cookies, OPFS — must be a one-line change, * not five edits in lockstep where a missed copy drops the field silently. * * Absent or `true` means available; only an explicit `false` blocks. */ interface BrowserStoragePolicy { localStorage?: boolean; sessionStorage?: boolean; indexedDB?: boolean; } /** * Iframe sandbox attribute construction (SEP-1865). * * The renderer's `effectiveSandbox` memo resolves *policy* (host profiles, * playground toggles, the capabilities matrix, the hosted clamp) into a * resolved `{ csp, permissions, permissive, sandboxAttrs, allowFeatures, * cspDirectives }` shape using `@mcpjam/sdk/browser`. That policy resolution * stays in the renderer because it depends on inspector-only surface state. * * What lives here is the deterministic *attribute construction* that turns the * resolved policy into the outer iframe's `sandbox=` / `allow=` strings — the * exact logic `SandboxedIframe` used to compute inline. Extracted to a * dependency-free leaf so both the production renderer (via `SandboxedIframe`) * and the eval browser harness build attributes the same way, so a widget * renders against an identical grant surface in either host. * * NOTE: the inner-document CSP `` is built by the cross-origin sandbox * proxy (`server/routes/apps/mcp-apps/sandbox-proxy.html`), not here. PR 3's * harness reuses that builder; `resolveIframeSandboxPolicy` is intentionally * scoped to the outer attributes the renderer constructs. */ /** * Minimal structural shape of the resolved widget permissions this module reads * — the 4 SEP-1865 spec features, checked only for truthiness. Declared locally * rather than importing `McpUiResourcePermissions` from * `@modelcontextprotocol/ext-apps` so this leaf stays dependency-free and the * published `.d.ts` resolves cleanly for NodeNext consumers (ext-apps's * `app-bridge` barrel re-exports types via `export *` that NodeNext can't * follow). The real `McpUiResourcePermissions` is structurally assignable to * this. (Slice 3 reintroduces an ext-apps dependency where `host-app-bridge` * needs `AppBridge` as a value.) */ interface IframeSandboxPermissions { camera?: unknown; microphone?: unknown; geolocation?: unknown; clipboardWrite?: unknown; } /** * Spec-mandated permissive baseline for the outer iframe `sandbox=` attribute. * Mirrors the default `SandboxedIframe` applies when no profile overrides the * token set. */ declare const DEFAULT_IFRAME_SANDBOX = "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"; /** * Build the outer iframe `allow=` (Permissions Policy) attribute. * * The 4 SEP-1865 spec permissions (camera/microphone/geolocation/ * clipboard-write) always flow through from `permissions`. When `allowFeatures` * is provided (even as `{}`) the profile is authoritative for non-spec * Permissions Policy features and the renderer's legacy defaults * (`local-network-access *`, `midi *`) are dropped. */ declare function buildOuterAllowAttribute(input: { permissions?: IframeSandboxPermissions; allowFeatures?: Record; }): string; /** * Build the outer iframe `sandbox=` attribute. * * When `sandboxAttrs` is provided (even as `[]`) the profile is authoritative: * the iframe gets `allow-scripts allow-same-origin` plus exactly the listed * tokens. When undefined, fall back to the permissive `sandbox` baseline. * `undefined` vs `[]` is meaningful: `[]` is the explicit "spec-minimum" intent. */ declare function buildOuterSandboxAttribute(input: { sandbox?: string; sandboxAttrs?: string[]; }): string; /** * Resolve the outer iframe sandbox policy into the literal `sandbox=` / `allow=` * attribute strings. Consumed by both `SandboxedIframe` (production) and the * eval harness so the grant surface is identical in either host. */ declare function resolveIframeSandboxPolicy(input: { sandbox?: string; sandboxAttrs?: string[]; permissions?: IframeSandboxPermissions; allowFeatures?: Record; }): { sandbox: string; allow: string; }; /** * App-tool invocation lifecycle types (SEP-1865). * * The framework-free shape the host bridge produces as an app-initiated * `tools/call` moves through running → success / error. Lives in the SDK * widget-runtime so `host-app-bridge` (which emits these updates) and the * inspector renderer UI that displays them share one definition. The inspector * re-exports these from `client/src/components/chat-v2/thread/app-tool-invocations.ts` * for back-compat. */ type AppToolInvocationStatus = "running" | "success" | "error"; interface AppToolInvocation { id: string; parentToolCallId: string; toolName: string; input?: Record; output?: unknown; errorText?: string; status: AppToolInvocationStatus; startedAt: number; completedAt?: number; } type AppToolInvocationUpdate = AppToolInvocation; /** * host-app-bridge.ts — framework-free MCP Apps host bridge surface (SEP-1865) * * The host-side AppBridge wiring, the capability-gated bridge handler * installation, and the iframe sandbox attribute construction, lifted out of the * inspector's React renderer so they can be reused outside React — specifically * by the eval browser harness, which must behave like the production renderer * when it mounts a widget in headless Chromium. Relocated to * `@mcpjam/sdk/widget-runtime` (Tier B Phase 2); the inspector's * `client/src/components/chat-v2/thread/mcp-apps/host-app-bridge.ts` now * re-exports from this subpath for back-compat. * * Design contract: * - This module is PURE JS: no React, no DOM-effect ownership, no Zustand. * - All host-environment effects (open a link, read a resource, dispatch a * tool call, mutate display mode, animate the iframe) are injected as * callbacks. The renderer binds its `useRef`/`useState`/`useCallback` * machinery to these inputs; the harness binds its own. * - The genuinely-shared *correctness surface* lives here verbatim: * capability gating (advertise = enforce), the model-only visibility * check, the matrix-gated `sendToolCancelled` policy, and the app-tool * invocation lifecycle. The harness needs all of it to match production. * * React-specific concerns (state updates, refs, reconnection) stay in the * renderer. See the inspector's `mcp-apps-renderer.tsx` for the adapter that * consumes this. */ /** * ext-apps host types, derived from the `AppBridge` constructor signature * rather than imported by name. * * `@modelcontextprotocol/ext-apps/app-bridge`'s published `.d.ts` re-exports its * types through an extensionless `export * from "./types"`, which TypeScript's * NodeNext resolver cannot follow — importing `McpUiHostCapabilities` & co. by * name fails even this package's own typecheck (TS2460/TS2305) and would break * external NodeNext consumers of the published declarations. Deriving from the * `AppBridge` value (which IS resolvable — the class is declared inline in * app-bridge.d.ts) keeps the SDK on NodeNext and the emitted `.d.ts` clean. * This mirrors the `Parameters`/`ReturnType` derivation below, which already * pulls the bridge-handler param/result types off the `AppBridge` signature to * avoid importing the MCP v1 SDK types package directly. */ type AppBridgeCtorArgs = ConstructorParameters; /** `McpUiHostCapabilities` — the resolved host capability surface (incl. sandbox). */ type McpUiHostCapabilities = AppBridgeCtorArgs[2]; /** `McpUiResourceCsp` — the sandbox CSP slice. */ type McpUiResourceCsp = NonNullable["csp"]>; /** `McpUiResourcePermissions` — the sandbox permissions slice. */ type McpUiResourcePermissions = NonNullable["permissions"]>; /** `McpUiHostContext` — the host-context options slice. */ type McpUiHostContext = NonNullable["hostContext"]>; /** * Construct a host-side AppBridge with the resolved capabilities + sandbox * slice + host context. Mirrors the inline `new AppBridge(...)` the renderer * builds. The caller wires a transport and calls `bridge.connect(transport)`. */ declare function createHostAppBridge(opts: { hostInfo: { name: string; version: string; }; hostCapabilities: Omit; sandbox?: { csp?: McpUiResourceCsp; permissions?: McpUiResourcePermissions; }; hostContext?: McpUiHostContext; }): AppBridge; type CallToolReturn = Awaited>>; type ReadResourceReturn = Awaited>>; type ListResourcesParams = Parameters>[0]; type ListResourcesReturn = Awaited>>; type ListResourceTemplatesParams = Parameters>[0]; type ListResourceTemplatesReturn = Awaited>>; type ListPromptsReturn = Awaited>>; type LoggingMessageParams = Parameters>[0]; type SizeChangeParams = Parameters>[0]; type RequestDisplayModeParams = Parameters>[0]; type RequestDisplayModeReturn = Awaited>>; type UpdateModelContextParams = Parameters>[0]; type DownloadFileParams = Parameters>[0]; type DownloadFileReturn = Awaited>>; type WidgetDebugDirection = "host-to-ui" | "ui-to-host"; /** * The minimal slice of the MCP Apps capabilities matrix that the shared * correctness surface reads. `null` means "no matrix configured" (treat as * all-allowed), matching the renderer's `mcpAppsCapabilitiesRef.current`. */ interface HostBridgeMatrix { /** When false, suppress the side-channel `sendToolCancelled` notification. */ toolCancelled?: boolean; /** When false, do not install the view-initiated teardown handler. */ requestTeardown?: boolean; /** * Which halves of a tool result this host relays back to the widget. * Unlike the gates above — which decide whether a handler exists at all — * this one shapes the VALUE a live handler returns. */ toolResult?: ToolResultPolicy; } /** * Host-environment effects injected by the consumer. Every callback is * optional; an unset callback means the corresponding host behavior is a no-op * (the bridge handler is still installed when its capability is advertised, so * the widget sees a well-formed response). */ interface HostBridgeCallbacks { /** Fires on `ui/initialize` completion. The renderer drives all of its * init-time state from here (ready flag, display modes, app-provided-tools * detection + auto-promote); the harness records render readiness. */ onAppInitialized?: (bridge: AppBridge) => void; /** `ui/message` text content (host follow-up). */ onSendFollowUp?: (text: string) => void; /** `ui/open-link`. */ onOpenLink?: (url: string) => void; /** App-initiated `tools/call` dispatcher. Resolves to a CallToolResult. */ onCallTool?: (name: string, args: Record) => Promise; /** App-tool invocation lifecycle updates (running/success/error). */ onAppToolInvocation?: (update: AppToolInvocationUpdate) => void; /** `resources/read`. */ onReadResource?: (uri: string) => Promise; /** `resources/list`. */ onListResources?: (params: ListResourcesParams) => Promise; /** `resources/templates/list`. */ onListResourceTemplates?: (params: ListResourceTemplatesParams) => Promise; /** `prompts/list`. */ onListPrompts?: () => Promise; /** `logging/message`. */ onLoggingMessage?: (params: LoggingMessageParams) => void; /** `ui/notifications/size-changed`. */ onSizeChange?: (params: SizeChangeParams) => void; /** `ui/request-display-mode`. Returns the granted mode. */ onRequestDisplayMode?: (params: RequestDisplayModeParams) => Promise | RequestDisplayModeReturn; /** `ui/update-model-context`. */ onUpdateModelContext?: (toolCallId: string, params: UpdateModelContextParams) => void; /** `ui/download-file`. */ onDownloadFile?: (params: DownloadFileParams) => Promise | DownloadFileReturn; /** `ui/notifications/request-teardown` (after host-side teardownResource). */ onRequestTeardown?: (toolCallId: string) => void; } interface RegisterHostBridgeHandlersOptions { /** * Resolved host capabilities (vendor-trait surface advertised in * `ui/initialize`). Gating here is advertise = enforce: a handler is only * installed when its capability is true. */ effectiveHostCapabilities: Omit; /** Read the MCP Apps capabilities matrix at call/registration time. */ getMatrix?: () => HostBridgeMatrix | null; /** Look up tool metadata by name for the model-only visibility check. */ getToolMetadata?: (name: string) => Record | undefined; /** Current parent tool-call id (correlates app-tool invocations + teardown). */ getToolCallId?: () => string; /** Monotonic sequence source for app-tool invocation ids. Defaults to an * internal per-registration counter; the renderer passes a shared ref so * inline + modal bridges never collide on an invocation id. */ nextInvocationSequence?: () => number; /** Host-environment effect callbacks. */ callbacks: HostBridgeCallbacks; /** Diagnostic sink (traffic / widget-debug). */ onWidgetDebug?: (direction: WidgetDebugDirection, method: string, data: Record) => void; } /** * Install the SEP-1865 host bridge handlers on `bridge`, gated by * `effectiveHostCapabilities` and the capabilities matrix. This is the * production correctness surface — capability gating, model-only visibility, * matrix-gated `sendToolCancelled`, and the app-tool invocation lifecycle — * lifted out of the React renderer so the eval harness shares it verbatim. */ declare function registerHostBridgeHandlers(bridge: AppBridge, options: RegisterHostBridgeHandlersOptions): void; export { type AppToolInvocation, type AppToolInvocationStatus, type AppToolInvocationUpdate, type BrowserStoragePolicy, DEFAULT_IFRAME_SANDBOX, type HostBridgeCallbacks, type HostBridgeMatrix, type IframeSandboxPermissions, LoggingTransport, type RegisterHostBridgeHandlersOptions, type ToolResultPolicy, type WidgetDebugDirection, applyToolResultPolicy, buildOuterAllowAttribute, buildOuterSandboxAttribute, createHostAppBridge, extractMethod, getToolVisibility, isVisibleToAppOnly, isVisibleToModelOnly, registerHostBridgeHandlers, resolveIframeSandboxPolicy, resolveToolUiResourceUri, stableStringifyJson };