import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; export type ToolCallHost = { readonly callServerTool: (params: { name: string; arguments?: Record; }) => Promise; }; export type TrustedInteraction = { executionId: string; interaction: { kind?: unknown; message?: unknown; url?: unknown; requestedSchema?: unknown; }; }; export type TrustedInteractionResponse = { action: "accept" | "decline" | "cancel"; content?: Record; }; export type RequestTrustedInteraction = ( interaction: TrustedInteraction, ) => Promise; /** * Creates a tRPC-style recursive proxy that maps dotted tool paths * to execute-action calls through the MCP Apps bridge. * * Usage: tools.github.issues.create({ title: "Bug" }) * becomes: app.callServerTool("execute-action", { code: "return await tools.github.issues.create({\"title\":\"Bug\"})" }) */ export function createToolsProxy( app: ToolCallHost, requestTrustedInteraction: RequestTrustedInteraction, ): Record { function nest(path: string[]): unknown { return new Proxy(function () {}, { get(_target, key: string) { if (key === "then" || key === "toJSON" || key === (Symbol.toPrimitive as unknown)) { return undefined; } return nest([...path, key]); }, apply(_target, _thisArg, args: unknown[]) { const toolPath = path.join("."); const serializedArgs = args.length > 0 ? JSON.stringify(args[0]) : "{}"; const code = `return await tools.${toolPath}(${serializedArgs})`; return app .callServerTool({ name: "execute-action", arguments: { code }, }) .then((r) => resolveToolResult(app, r, requestTrustedInteraction)); }, }); } return nest([]) as Record; } /** * Creates the `run()` escape hatch for multi-step tool composition. * * Usage: const result = await run(` * const me = await tools.github.users.me() * return await tools.github.issues.create({ assignee: me.login, ... }) * `) */ export function createRunFn( app: ToolCallHost, requestTrustedInteraction: RequestTrustedInteraction, ): (code: string) => Promise { return (code: string) => app .callServerTool({ name: "execute-action", arguments: { code }, }) .then((r) => resolveToolResult(app, r, requestTrustedInteraction)); } async function resolveToolResult( app: ToolCallHost, result: CallToolResult, requestTrustedInteraction: RequestTrustedInteraction, ): Promise { if (result.isError) { const msg = result.content?.find((c) => c.type === "text")?.text ?? "Tool call failed"; throw new Error(msg); } const structured = result.structuredContent as Record | undefined; const pending = parseTrustedInteraction(structured); if (pending) { const response = await requestTrustedInteraction(pending); const resumed = await app.callServerTool({ name: "execute-action-resume", arguments: { executionId: pending.executionId, action: response.action, content: JSON.stringify(response.content ?? {}), }, }); return resolveToolResult(app, resumed, requestTrustedInteraction); } return unwrapResult(structured) ?? parseTextContent(result); } function parseTrustedInteraction( structured: Record | undefined, ): TrustedInteraction | null { if (!structured || structured.status !== "waiting_for_interaction") return null; if (typeof structured.executionId !== "string") return null; const interaction = typeof structured.interaction === "object" && structured.interaction !== null && !Array.isArray(structured.interaction) ? (structured.interaction as TrustedInteraction["interaction"]) : {}; return { executionId: structured.executionId, interaction }; } /** * Unwrap execution result. The kernel wraps results as * `{ status: "completed", result: , logs: [...] }`. * Return just the inner result value. */ function unwrapResult(structured: Record | undefined | null): unknown { if ( structured && typeof structured === "object" && "status" in structured && "result" in structured ) { return structured.result; } return structured; } function parseTextContent(r: { content?: Array<{ type: string; text?: string }> }): unknown { const text = r.content?.find((c) => c.type === "text")?.text; if (!text) return null; try { return JSON.parse(text); } catch { return text; } }