| 1 | import type { AppBridge } from "@modelcontextprotocol/ext-apps/app-bridge"; |
| 2 | |
| 3 | export interface MCPAppPresentation { |
| 4 | server: string; |
| 5 | tool: string; |
| 6 | generation: number; |
| 7 | resourceUri?: string; |
| 8 | csp?: Record<string, string[]>; |
| 9 | rawResult?: unknown; |
| 10 | structured?: unknown; |
| 11 | } |
| 12 | |
| 13 | export interface MCPAppInstanceView { |
| 14 | instanceToken: string; |
| 15 | tabId: string; |
| 16 | server: string; |
| 17 | tool: string; |
| 18 | outerUrl: string; |
| 19 | resourceQuery: string; |
| 20 | resourceDigest: string; |
| 21 | } |
| 22 | |
| 23 | export function parseMCPAppArguments(raw: string): Record<string, unknown> { |
| 24 | try { |
| 25 | const parsed = JSON.parse(raw) as unknown; |
| 26 | return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) |
| 27 | ? parsed as Record<string, unknown> |
| 28 | : {}; |
| 29 | } catch { |
| 30 | return {}; |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | export function normalizeMCPAppResult( |
| 35 | presentation: MCPAppPresentation, |
| 36 | fallbackOutput: string | undefined, |
| 37 | ): Parameters<AppBridge["sendToolResult"]>[0] { |
| 38 | const raw = presentation.rawResult; |
| 39 | const result = raw !== null && typeof raw === "object" && !Array.isArray(raw) |
| 40 | ? { ...(raw as Record<string, unknown>) } |
| 41 | : {}; |
| 42 | if (!Array.isArray(result.content)) { |
| 43 | result.content = fallbackOutput ? [{ type: "text", text: fallbackOutput }] : []; |
| 44 | } |
| 45 | if (result.structuredContent === undefined && presentation.structured !== undefined) { |
| 46 | result.structuredContent = presentation.structured; |
| 47 | } |
| 48 | return result as Parameters<AppBridge["sendToolResult"]>[0]; |
| 49 | } |
| 50 | |
| 51 | export function parseMCPAppCallResult( |
| 52 | raw: string, |
| 53 | ): Parameters<AppBridge["sendToolResult"]>[0] { |
| 54 | const parsed = JSON.parse(raw) as unknown; |
| 55 | if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { |
| 56 | throw new Error("MCP App tool result is not an object"); |
| 57 | } |
| 58 | const result = parsed as Record<string, unknown>; |
| 59 | if (!Array.isArray(result.content)) { |
| 60 | throw new Error("MCP App tool result has invalid content"); |
| 61 | } |
| 62 | return result as Parameters<AppBridge["sendToolResult"]>[0]; |
| 63 | } |
| 64 | |
| 65 | export function validatedMCPAppLinkOrigin(rawURL: string): string | null { |
| 66 | try { |
| 67 | const target = new URL(rawURL); |
| 68 | if ((target.protocol !== "https:" && target.protocol !== "http:") || target.username || target.password) { |
| 69 | return null; |
| 70 | } |
| 71 | return target.origin; |
| 72 | } catch { |
| 73 | return null; |
| 74 | } |
| 75 | } |
| 76 |