@agent-native/core 0.22.4 → 0.22.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  2. package/dist/agent/engine/builder-engine.js +10 -9
  3. package/dist/agent/engine/builder-engine.js.map +1 -1
  4. package/dist/agent/engine/builder-gateway-headers.d.ts +10 -0
  5. package/dist/agent/engine/builder-gateway-headers.d.ts.map +1 -0
  6. package/dist/agent/engine/builder-gateway-headers.js +44 -0
  7. package/dist/agent/engine/builder-gateway-headers.js.map +1 -0
  8. package/dist/agent/engine/index.d.ts +1 -1
  9. package/dist/agent/engine/index.d.ts.map +1 -1
  10. package/dist/agent/engine/index.js +1 -1
  11. package/dist/agent/engine/index.js.map +1 -1
  12. package/dist/agent/engine/registry.d.ts +1 -0
  13. package/dist/agent/engine/registry.d.ts.map +1 -1
  14. package/dist/agent/engine/registry.js +60 -1
  15. package/dist/agent/engine/registry.js.map +1 -1
  16. package/dist/agent/engine/translate-ai-sdk.d.ts.map +1 -1
  17. package/dist/agent/engine/translate-ai-sdk.js +3 -2
  18. package/dist/agent/engine/translate-ai-sdk.js.map +1 -1
  19. package/dist/agent/engine/translate-anthropic.d.ts +36 -2
  20. package/dist/agent/engine/translate-anthropic.d.ts.map +1 -1
  21. package/dist/agent/engine/translate-anthropic.js +159 -6
  22. package/dist/agent/engine/translate-anthropic.js.map +1 -1
  23. package/dist/agent/engine/types.d.ts +4 -2
  24. package/dist/agent/engine/types.d.ts.map +1 -1
  25. package/dist/agent/engine/types.js.map +1 -1
  26. package/dist/agent/production-agent.d.ts.map +1 -1
  27. package/dist/agent/production-agent.js +69 -9
  28. package/dist/agent/production-agent.js.map +1 -1
  29. package/dist/agent/types.d.ts +2 -0
  30. package/dist/agent/types.d.ts.map +1 -1
  31. package/dist/agent/types.js.map +1 -1
  32. package/dist/cli/connect.d.ts +1 -1
  33. package/dist/cli/connect.d.ts.map +1 -1
  34. package/dist/cli/connect.js +5 -2
  35. package/dist/cli/connect.js.map +1 -1
  36. package/dist/cli/create.d.ts.map +1 -1
  37. package/dist/cli/create.js +48 -6
  38. package/dist/cli/create.js.map +1 -1
  39. package/dist/client/AssistantChat.d.ts.map +1 -1
  40. package/dist/client/AssistantChat.js +7 -1
  41. package/dist/client/AssistantChat.js.map +1 -1
  42. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  43. package/dist/client/MultiTabAssistantChat.js +4 -3
  44. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  45. package/dist/client/NewWorkspaceAppFlow.d.ts.map +1 -1
  46. package/dist/client/NewWorkspaceAppFlow.js +1 -0
  47. package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
  48. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  49. package/dist/client/agent-chat-adapter.js +11 -5
  50. package/dist/client/agent-chat-adapter.js.map +1 -1
  51. package/dist/client/api-path.d.ts.map +1 -1
  52. package/dist/client/api-path.js +2 -0
  53. package/dist/client/api-path.js.map +1 -1
  54. package/dist/client/embed-auth.d.ts +4 -0
  55. package/dist/client/embed-auth.d.ts.map +1 -0
  56. package/dist/client/embed-auth.js +102 -0
  57. package/dist/client/embed-auth.js.map +1 -0
  58. package/dist/client/index.d.ts +1 -0
  59. package/dist/client/index.d.ts.map +1 -1
  60. package/dist/client/index.js +1 -0
  61. package/dist/client/index.js.map +1 -1
  62. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  63. package/dist/client/settings/SettingsPanel.js +7 -3
  64. package/dist/client/settings/SettingsPanel.js.map +1 -1
  65. package/dist/client/use-action.d.ts.map +1 -1
  66. package/dist/client/use-action.js +2 -0
  67. package/dist/client/use-action.js.map +1 -1
  68. package/dist/client/use-chat-models.d.ts.map +1 -1
  69. package/dist/client/use-chat-models.js +4 -3
  70. package/dist/client/use-chat-models.js.map +1 -1
  71. package/dist/client/use-chat-threads.d.ts.map +1 -1
  72. package/dist/client/use-chat-threads.js +7 -7
  73. package/dist/client/use-chat-threads.js.map +1 -1
  74. package/dist/client/use-chat-threads.spec.js +70 -0
  75. package/dist/client/use-chat-threads.spec.js.map +1 -1
  76. package/dist/client/use-db-sync.d.ts.map +1 -1
  77. package/dist/client/use-db-sync.js +4 -0
  78. package/dist/client/use-db-sync.js.map +1 -1
  79. package/dist/deploy/route-discovery.js +1 -1
  80. package/dist/deploy/route-discovery.js.map +1 -1
  81. package/dist/index.d.ts +2 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +2 -0
  84. package/dist/index.js.map +1 -1
  85. package/dist/mcp/build-server.d.ts.map +1 -1
  86. package/dist/mcp/build-server.js +49 -21
  87. package/dist/mcp/build-server.js.map +1 -1
  88. package/dist/mcp/builtin-tools.d.ts +1 -0
  89. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  90. package/dist/mcp/builtin-tools.js +147 -8
  91. package/dist/mcp/builtin-tools.js.map +1 -1
  92. package/dist/mcp/connect-route.d.ts.map +1 -1
  93. package/dist/mcp/connect-route.js +79 -51
  94. package/dist/mcp/connect-route.js.map +1 -1
  95. package/dist/mcp/embed-app.d.ts +14 -0
  96. package/dist/mcp/embed-app.d.ts.map +1 -0
  97. package/dist/mcp/embed-app.js +191 -0
  98. package/dist/mcp/embed-app.js.map +1 -0
  99. package/dist/mcp/index.d.ts +1 -0
  100. package/dist/mcp/index.d.ts.map +1 -1
  101. package/dist/mcp/index.js +1 -0
  102. package/dist/mcp/index.js.map +1 -1
  103. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  104. package/dist/scripts/agent-engines/list-agent-engines.js +2 -1
  105. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  106. package/dist/scripts/agent-engines/manage-agent-engine.d.ts.map +1 -1
  107. package/dist/scripts/agent-engines/manage-agent-engine.js +4 -1
  108. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  109. package/dist/scripts/agent-engines/set-agent-engine.d.ts.map +1 -1
  110. package/dist/scripts/agent-engines/set-agent-engine.js +4 -1
  111. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  112. package/dist/server/action-discovery.d.ts.map +1 -1
  113. package/dist/server/action-discovery.js +10 -1
  114. package/dist/server/action-discovery.js.map +1 -1
  115. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  116. package/dist/server/agent-chat-plugin.js +9 -1
  117. package/dist/server/agent-chat-plugin.js.map +1 -1
  118. package/dist/server/auth.d.ts +7 -6
  119. package/dist/server/auth.d.ts.map +1 -1
  120. package/dist/server/auth.js +64 -15
  121. package/dist/server/auth.js.map +1 -1
  122. package/dist/server/core-routes-plugin.d.ts +2 -0
  123. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  124. package/dist/server/core-routes-plugin.js +7 -0
  125. package/dist/server/core-routes-plugin.js.map +1 -1
  126. package/dist/server/credential-provider.d.ts +6 -4
  127. package/dist/server/credential-provider.d.ts.map +1 -1
  128. package/dist/server/credential-provider.js +91 -12
  129. package/dist/server/credential-provider.js.map +1 -1
  130. package/dist/server/embed-route.d.ts +8 -0
  131. package/dist/server/embed-route.d.ts.map +1 -0
  132. package/dist/server/embed-route.js +71 -0
  133. package/dist/server/embed-route.js.map +1 -0
  134. package/dist/server/embed-session.d.ts +65 -0
  135. package/dist/server/embed-session.d.ts.map +1 -0
  136. package/dist/server/embed-session.js +433 -0
  137. package/dist/server/embed-session.js.map +1 -0
  138. package/dist/server/index.d.ts +2 -0
  139. package/dist/server/index.d.ts.map +1 -1
  140. package/dist/server/index.js +2 -0
  141. package/dist/server/index.js.map +1 -1
  142. package/dist/server/open-route.d.ts.map +1 -1
  143. package/dist/server/open-route.js +10 -0
  144. package/dist/server/open-route.js.map +1 -1
  145. package/dist/server/security-headers.d.ts.map +1 -1
  146. package/dist/server/security-headers.js +4 -2
  147. package/dist/server/security-headers.js.map +1 -1
  148. package/dist/shared/embed-auth.d.ts +6 -0
  149. package/dist/shared/embed-auth.d.ts.map +1 -0
  150. package/dist/shared/embed-auth.js +6 -0
  151. package/dist/shared/embed-auth.js.map +1 -0
  152. package/dist/shared/index.d.ts +1 -0
  153. package/dist/shared/index.d.ts.map +1 -1
  154. package/dist/shared/index.js +1 -0
  155. package/dist/shared/index.js.map +1 -1
  156. package/dist/templates/workspace-core/AGENTS.md +14 -5
  157. package/dist/templates/workspace-root/AGENTS.md +5 -0
  158. package/dist/templates/workspace-root/README.md +3 -0
  159. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  160. package/dist/vite/action-types-plugin.js +10 -1
  161. package/dist/vite/action-types-plugin.js.map +1 -1
  162. package/docs/content/a2a-protocol.md +5 -1
  163. package/docs/content/actions.md +19 -4
  164. package/docs/content/cli-adapters.md +5 -0
  165. package/docs/content/client.md +35 -1
  166. package/docs/content/database.md +29 -0
  167. package/docs/content/dispatch.md +7 -1
  168. package/docs/content/external-agents.md +37 -13
  169. package/docs/content/key-concepts.md +3 -3
  170. package/docs/content/messaging.md +1 -1
  171. package/docs/content/onboarding.md +26 -0
  172. package/docs/content/template-content.md +1 -1
  173. package/docs/content/template-dispatch.md +9 -0
  174. package/docs/content/template-starter.md +2 -2
  175. package/package.json +1 -1
  176. package/src/templates/workspace-core/AGENTS.md +14 -5
  177. package/src/templates/workspace-root/AGENTS.md +5 -0
  178. package/src/templates/workspace-root/README.md +3 -0
  179. /package/docs/content/{template-video.md → template-videos.md} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"connect-route.js","sourceRoot":"","sources":["../../src/mcp/connect-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/mcp/connect` — frictionless external-agent connection.\n *\n * A logged-in user on a deployed agent-native app (e.g. mail.agent-native.com)\n * mints a per-user, scoped, revocable MCP bearer token WITHOUT ever copying a\n * shared deployment secret. Two surfaces:\n *\n * 1. Browser — `GET /connect` renders a minimal in-app page (same inline\n * HTML approach as the auth pages). The Authorize button POSTs to\n * `/connect/token`, then shows the ready-to-paste `.mcp.json` entry, the\n * `agent-native connect <origin>` one-liner, and the user's existing\n * tokens with Revoke buttons.\n * 2. CLI — an OAuth-2.0-device-authorization-style flow:\n * POST /connect/device/start (unauth) → device_code + user_code\n * GET /connect?user_code=… (browser) → user signs in & approves\n * POST /connect/device/authorize (session) → binds user to the code\n * POST /connect/device/poll (unauth) → mints + returns the token\n *\n * The minted token reuses the existing A2A signer (`signA2AToken`) — no new\n * crypto. We only add a random `jti` + `scope: \"mcp-connect\"` claim so it can\n * be revoked. `verifyAuth` already verifies A2A_SECRET JWTs and extracts\n * `sub`/`org_domain`, so a minted token works against `/_agent-native/mcp`\n * with no verify changes for the happy path (the revoke check is the only\n * addition there).\n *\n * Node-only (crypto + the A2A signer), bundled alongside the other framework\n * routes. Dialect-agnostic SQL lives in `connect-store.ts`.\n */\n\nimport type { H3Event } from \"h3\";\nimport { getMethod, getHeader } from \"h3\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n getSession,\n getConfiguredLoginHtml,\n isLoopbackRequest,\n} from \"../server/auth.js\";\nimport { signA2AToken } from \"../a2a/client.js\";\nimport { getOrgDomain } from \"../org/context.js\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n recordMintedToken,\n listTokens,\n revokeToken,\n createDeviceCode,\n getDeviceCode,\n approveDeviceCode,\n consumeDeviceCode,\n claimDeviceCodeForMint,\n finishDeviceCodeMint,\n releaseDeviceCodeMint,\n expireDeviceCode,\n MCP_CONNECT_SCOPE,\n DEFAULT_TOKEN_TTL_DAYS,\n MIN_TOKEN_TTL_DAYS,\n MAX_TOKEN_TTL_DAYS,\n DEVICE_CODE_TTL_MS,\n} from \"./connect-store.js\";\n\n/** Device-flow poll interval hint (seconds). */\nconst DEVICE_POLL_INTERVAL_S = 3;\n\n// Human-typable user code: 8 base32 chars, dashed XXXX-XXXX.\nconst USER_CODE_RE = /^[A-Z2-7]{4}-[A-Z2-7]{4}$/;\n\nexport interface McpConnectRouteOptions {\n /** App id (directory under apps/, e.g. `mail`). Used for the server name. */\n appId?: string;\n /** Human app name shown on the connect page. */\n appName?: string;\n}\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction html(body: string, status = 200): Response {\n return new Response(body, {\n status,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n}\n\n/** Derive the running app's origin from request headers (same logic mountMCP\n * uses) — `https` in prod / for non-loopback hosts, `http` for localhost. */\nfunction deriveOrigin(event: H3Event): string {\n const forwardedProto = getHeader(event, \"x-forwarded-proto\");\n const host = getHeader(event, \"x-forwarded-host\") || getHeader(event, \"host\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (host && /^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host) ? \"http\" : \"https\");\n return host ? `${proto}://${host}` : \"\";\n}\n\nfunction normalizeBasePath(raw: string | undefined): string {\n const trimmed = (raw ?? \"\").trim();\n if (!trimmed || trimmed === \"/\") return \"\";\n const withSlash = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n return withSlash.replace(/\\/+$/, \"\");\n}\n\nfunction configuredBasePath(): string {\n return normalizeBasePath(\n process.env.APP_BASE_PATH || process.env.VITE_APP_BASE_PATH,\n );\n}\n\nfunction joinAppPath(basePath: string, path: string): string {\n if (!basePath) return path;\n if (path === \"/\") return basePath;\n return `${basePath}${path.startsWith(\"/\") ? path : `/${path}`}`;\n}\n\nfunction appLabel(origin: string, options: McpConnectRouteOptions): string {\n if (options.appId) return options.appId;\n try {\n const h = new URL(origin).hostname;\n return h.split(\".\")[0] || h;\n } catch {\n return options.appName || \"app\";\n }\n}\n\nfunction serverName(origin: string, options: McpConnectRouteOptions): string {\n return `agent-native-${appLabel(origin, options)}`;\n}\n\nfunction canUseDevOpenConnect(event: H3Event): boolean {\n // Loopback determined from the real socket peer (isLoopbackRequest →\n // getRequestIP without xForwardedFor), NOT a parsed `Host` header — the\n // header is client-controlled, and it also handles IPv6 `::1`. A\n // misconfigured public deploy with no secret thus can't unlock dev-open\n // by spoofing `Host: localhost`.\n return (\n isLoopbackRequest(event) &&\n !process.env.A2A_SECRET?.trim() &&\n !process.env.ACCESS_TOKEN?.trim() &&\n !process.env.ACCESS_TOKENS?.trim()\n );\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Resolve the org domain for a session. Used as the JWT `org_domain` claim so\n * the receiving MCP endpoint can map it back to an org id (same as A2A). Best\n * effort — a missing org just yields a user-scoped (no-org) token.\n */\nasync function resolveOrgDomain(\n orgId: string | undefined,\n): Promise<string | undefined> {\n if (!orgId) return undefined;\n try {\n return (await getOrgDomain(orgId)) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction clampTtlDays(input: unknown): number {\n const n = Number(input);\n if (!Number.isFinite(n)) return DEFAULT_TOKEN_TTL_DAYS;\n return Math.min(\n MAX_TOKEN_TTL_DAYS,\n Math.max(MIN_TOKEN_TTL_DAYS, Math.floor(n)),\n );\n}\n\n/**\n * Mint a connect-scoped JWT and record it. The JWT is signed by the existing\n * A2A signer (HS256 over A2A_SECRET); we add a random `jti` and\n * `scope: \"mcp-connect\"` so the token is individually revocable. The token\n * value is returned to the caller exactly once and never persisted.\n */\nasync function mintConnectToken(params: {\n email: string;\n orgId: string | undefined;\n label: string | null;\n ttlDays: number;\n}): Promise<{ token: string; jti: string }> {\n const orgDomain = await resolveOrgDomain(params.orgId);\n const jti = randomUUID();\n // signA2AToken signs { sub: email, org_domain? } over A2A_SECRET (global)\n // or the org secret. We extend its claims via the standard jose builder by\n // re-using the same signer with extra claims threaded through `options`.\n const token = await signA2AToken(params.email, orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: `${params.ttlDays}d`,\n extraClaims: { jti, scope: MCP_CONNECT_SCOPE },\n });\n await recordMintedToken({\n jti,\n ownerEmail: params.email,\n orgId: params.orgId ?? null,\n label: params.label,\n });\n return { token, jti };\n}\n\nfunction mcpResultPayload(\n appUrl: string,\n options: McpConnectRouteOptions,\n auth: { token?: string; ownerEmail?: string },\n) {\n const mcpUrl = `${appUrl}/_agent-native/mcp`;\n const name = serverName(appUrl, options);\n const headers: Record<string, string> = {};\n if (auth.token) headers.Authorization = `Bearer ${auth.token}`;\n if (!auth.token && auth.ownerEmail) {\n headers[\"X-Agent-Native-Owner-Email\"] = auth.ownerEmail;\n }\n return {\n token: auth.token ?? \"\",\n mcpUrl,\n serverName: name,\n mcpServerEntry: {\n type: \"http\" as const,\n url: mcpUrl,\n ...(Object.keys(headers).length ? { headers } : {}),\n },\n cli: `agent-native connect ${appUrl}`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Connect page (server-rendered HTML string)\n// ---------------------------------------------------------------------------\n\nfunction agentNativeMarkSvg(className: string, gradientId: string): string {\n return `<svg class=\"${className}\" width=\"114\" height=\"66\" viewBox=\"0 0 114 66\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M24.5537 65.7695H0L15.0859 39.4619L37.708 0L60.4912 39.4619H39.6396L24.5537 65.7695Z\" fill=\"white\"/>\n <path d=\"M89.446 0H114L76.2921 65.7704H51.7383L89.446 0Z\" fill=\"url(#${gradientId})\"/>\n <defs>\n <linearGradient id=\"${gradientId}\" x1=\"101.702\" y1=\"67.4791\" x2=\"113.672\" y2=\"-37.4275\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#00B5FF\"/>\n <stop offset=\"1\" stop-color=\"#48FFE4\"/>\n </linearGradient>\n </defs>\n</svg>`;\n}\n\nfunction renderConnectPage(params: {\n connectBasePath: string;\n email: string;\n appName: string;\n appUrl: string;\n serverId: string;\n userCode: string | null;\n}): string {\n const { connectBasePath, email, appName, appUrl, serverId, userCode } =\n params;\n const safeEmail = escapeHtml(email);\n const safeApp = escapeHtml(appName);\n const mcpUrl = `${appUrl}/_agent-native/mcp`;\n const safeMcpUrl = escapeHtml(mcpUrl);\n const safeServerId = escapeHtml(serverId);\n const safeClaudeCodeCmd = escapeHtml(\n `claude mcp add --transport http ${serverId} ${mcpUrl}`,\n );\n const safeCodexCmd = escapeHtml(`npx @agent-native/core connect ${appUrl}`);\n const safeGenericConfig = escapeHtml(\n `{\\n \"mcpServers\": {\\n \"${serverId}\": {\\n \"type\": \"http\",\\n \"url\": \"${mcpUrl}\"\\n }\\n }\\n}`,\n );\n const brandMarkSvg = agentNativeMarkSvg(\n \"brand-mark\",\n \"agent-native-connect-brand-gradient\",\n );\n const flowMarkSvg = agentNativeMarkSvg(\n \"flow-mark\",\n \"agent-native-connect-flow-gradient\",\n );\n const safeUserCode =\n userCode && USER_CODE_RE.test(userCode) ? escapeHtml(userCode) : \"\";\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Connect ${safeApp}</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n :root {\n color-scheme: dark;\n --bg: #09090b; --panel: #121214; --panel-2: #0c0c0e;\n --panel-soft: rgba(255,255,255,0.025);\n --border: rgba(255,255,255,0.075); --border-strong: rgba(255,255,255,0.14);\n --text: #f7f7f8; --muted: #a1a1aa; --subtle: #74747d;\n --accent: #f4f4f5; --accent-fg: #09090b;\n --ring: rgba(250,250,250,0.55);\n --error: #fca5a5; --error-bg: rgba(127,29,29,0.18);\n --ok: #86efac; --ok-bg: rgba(20,83,45,0.12); --ok-border: rgba(134,239,172,0.18);\n }\n html, body { -webkit-font-smoothing: antialiased; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: linear-gradient(180deg, #101013 0%, var(--bg) 58%);\n color: var(--text); display: flex; align-items: center;\n justify-content: center; min-height: 100vh; padding: 1.5rem 1rem;\n }\n .card {\n width: 100%; max-width: 440px;\n background: var(--panel); border: 1px solid var(--border);\n border-radius: 8px; box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset,\n 0 30px 90px rgba(0,0,0,0.5);\n padding: 1.25rem;\n }\n .topbar {\n display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; margin-bottom: 1.75rem;\n }\n .brand-lockup {\n display: flex; align-items: center; gap: 0.55rem;\n color: var(--muted); font-size: 0.78rem; font-weight: 600;\n }\n .brand-mark { width: 18px; height: auto; display: block; }\n .app-pill {\n max-width: 50%; border: 1px solid var(--border);\n border-radius: 999px; padding: 0.28rem 0.55rem;\n color: var(--subtle); font-size: 0.72rem; line-height: 1;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n }\n .hero { padding: 0 0.75rem; text-align: center; }\n .flow {\n display: flex; align-items: center; justify-content: center;\n gap: 0; margin: 0 auto 1.1rem; width: fit-content;\n }\n .flow .tile {\n width: 42px; height: 42px; border-radius: 8px;\n display: flex; align-items: center; justify-content: center;\n background: var(--panel-2); border: 1px solid var(--border-strong);\n color: var(--text); flex-shrink: 0;\n }\n .flow-mark { width: 26px; height: auto; display: block; }\n .flow .agent-symbol {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.95rem; font-weight: 700; letter-spacing: -0.04em;\n }\n .flow .conn {\n width: 30px; height: 1px; flex-shrink: 0;\n background: linear-gradient(90deg, transparent, var(--border-strong), transparent);\n background-position: center;\n }\n .eyebrow {\n text-align: center; font-size: 0.72rem; font-weight: 600;\n letter-spacing: 0.08em; text-transform: uppercase;\n color: var(--subtle); margin-bottom: 0.55rem;\n }\n h1 {\n text-align: center; font-size: 1.45rem; font-weight: 680;\n line-height: 1.25; margin-bottom: 0.7rem;\n letter-spacing: -0.01em;\n }\n .identity {\n display: flex; flex-wrap: wrap; align-items: center; justify-content: center;\n gap: 0.25rem 0.45rem; color: var(--subtle); font-size: 0.78rem;\n line-height: 1.35; margin: 0 auto 1.5rem; max-width: 34ch;\n }\n .identity strong { color: var(--muted); font-weight: 600; }\n .device-strip {\n display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; border: 1px solid var(--border);\n border-radius: 8px; padding: 0.5rem 0.65rem; margin: 0 0 0.9rem;\n background: var(--panel-soft); color: var(--muted);\n }\n .device-strip .label {\n font-size: 0.76rem; font-weight: 560; color: var(--subtle);\n }\n .device-strip .value {\n font-size: 0.78rem; font-weight: 650;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n letter-spacing: 0.08em; color: var(--muted);\n }\n button {\n cursor: pointer; font: inherit; font-weight: 600; border: none;\n border-radius: 8px; padding: 0.78rem 1rem;\n }\n button:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }\n .primary {\n background: var(--accent); color: var(--accent-fg); width: 100%;\n font-size: 0.95rem;\n }\n .primary:hover:not(:disabled) { background: #e4e4e7; }\n .primary:disabled { opacity: 0.55; cursor: default; }\n .ghost {\n background: transparent; color: var(--muted);\n border: 1px solid var(--border-strong); padding: 0.35rem 0.7rem;\n font-size: 0.78rem; font-weight: 500; border-radius: 8px;\n }\n .ghost:hover:not(:disabled) { color: var(--text); border-color: var(--subtle); }\n pre {\n background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;\n padding: 0.9rem; font-size: 0.78rem; line-height: 1.5; overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #d4d4d8; margin: 0.5rem 0 1rem;\n }\n /* Advanced disclosure */\n .advanced { margin: 0 0 1rem; }\n .advanced > summary {\n list-style: none; cursor: pointer; user-select: none;\n display: flex; align-items: center; justify-content: center; gap: 0.35rem;\n color: var(--subtle); font-size: 0.8rem; font-weight: 500;\n padding: 0.5rem 0; text-align: center;\n }\n .advanced > summary::-webkit-details-marker { display: none; }\n .advanced > summary:hover { color: var(--muted); }\n .advanced > summary:focus-visible { outline: 2px solid var(--ring);\n outline-offset: 2px; border-radius: 6px; }\n .advanced > summary .chev {\n width: 7px; height: 7px; border-right: 1.5px solid currentColor;\n border-bottom: 1.5px solid currentColor; transform: rotate(45deg);\n transition: transform 0.15s ease; margin-top: -3px;\n }\n .advanced[open] > summary .chev { transform: rotate(225deg); margin-top: 2px; }\n .advanced-body {\n padding: 0.85rem 0.1rem 0.25rem;\n }\n .field { margin-bottom: 0.9rem; }\n .field:last-child { margin-bottom: 0; }\n .field label { display: block; font-size: 0.78rem; color: var(--muted);\n margin-bottom: 0.35rem; }\n .field input {\n width: 100%; padding: 0.6rem 0.7rem; font: inherit; color: var(--text);\n background: var(--panel-2); border: 1px solid var(--border-strong);\n border-radius: 8px;\n }\n .field input:focus-visible {\n outline: none; border-color: var(--ring);\n box-shadow: 0 0 0 3px rgba(250,250,250,0.12);\n }\n .connections {\n margin-top: 1.1rem; border-top: 1px solid var(--border);\n padding-top: 0.35rem;\n }\n .connections > summary {\n list-style: none; cursor: pointer; user-select: none;\n display: flex; align-items: center; gap: 0.55rem;\n min-height: 2.2rem; color: var(--muted); font-size: 0.82rem;\n }\n .connections > summary::-webkit-details-marker { display: none; }\n .connections > summary:focus-visible {\n outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 6px;\n }\n .connections-title { font-weight: 600; color: var(--muted); }\n .connections-state {\n margin-left: auto; color: var(--subtle); font-size: 0.73rem;\n border: 1px solid var(--border); border-radius: 999px;\n padding: 0.18rem 0.45rem; line-height: 1;\n }\n .connections .chev {\n width: 7px; height: 7px; border-right: 1.5px solid currentColor;\n border-bottom: 1.5px solid currentColor; transform: rotate(45deg);\n transition: transform 0.15s ease; margin: -3px 0 0 0.15rem;\n }\n .connections[open] .chev { transform: rotate(225deg); margin-top: 2px; }\n .token-list { padding-top: 0.4rem; }\n .tok { display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border);\n font-size: 0.83rem; }\n .tok:last-child { border-bottom: none; }\n .tok .meta { color: var(--subtle); font-size: 0.74rem; margin-top: 0.1rem; }\n .tok.revoked { opacity: 0.45; }\n .empty-state {\n color: var(--subtle); font-size: 0.78rem; line-height: 1.45;\n padding: 0.3rem 0 0.45rem;\n }\n .msg { font-size: 0.83rem; padding: 0.7rem 0.8rem; border-radius: 8px;\n margin-bottom: 0.9rem; display: none; line-height: 1.4; }\n .msg.err { display: block; color: var(--error); background: var(--error-bg);\n border: 1px solid rgba(252,165,165,0.16); }\n .msg.ok { display: block; color: var(--ok); background: var(--ok-bg);\n border: 1px solid var(--ok-border); }\n .result-panel { padding-top: 0.15rem; }\n .result-title {\n color: var(--text); font-size: 0.95rem; font-weight: 650;\n text-align: center; margin-bottom: 0.35rem;\n }\n .result-copy {\n color: var(--muted); font-size: 0.83rem; line-height: 1.45;\n text-align: center; margin: 0 auto 0.85rem; max-width: 34ch;\n }\n .section-label {\n color: var(--subtle); font-size: 0.7rem; font-weight: 650;\n letter-spacing: 0.08em; text-transform: uppercase; margin-top: 0.85rem;\n }\n @media (max-width: 480px) {\n body { align-items: flex-start; padding: 0.75rem; }\n .card { padding: 1rem; }\n .hero { padding: 0; }\n .topbar { margin-bottom: 1.35rem; }\n h1 { font-size: 1.3rem; }\n .app-pill { max-width: 46%; }\n pre { font-size: 0.72rem; }\n }\n /* MCP URL display + per-host tabs (the non-dev path). */\n .mcp-url-block { margin: 0 0 1rem; }\n .url-row {\n display: flex; align-items: center; gap: 0.5rem;\n background: var(--panel-2); border: 1px solid var(--border-strong);\n border-radius: 8px; padding: 0.45rem 0.5rem 0.45rem 0.75rem;\n }\n .url-row code {\n flex: 1 1 auto; min-width: 0; overflow-x: auto; white-space: nowrap;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.78rem; color: var(--text);\n }\n .url-row .ghost { flex: 0 0 auto; }\n .hosts { margin: 0 0 1rem; }\n .tabs {\n display: flex; flex-wrap: wrap; gap: 0.25rem;\n border-bottom: 1px solid var(--border); margin-bottom: 0.75rem;\n padding-bottom: 0.4rem;\n }\n .tab {\n background: transparent; color: var(--subtle);\n border: 1px solid transparent;\n padding: 0.35rem 0.65rem; font-size: 0.8rem; font-weight: 600;\n border-radius: 6px;\n }\n .tab:hover { color: var(--muted); background: var(--panel-soft); }\n .tab.is-active {\n color: var(--text); background: var(--panel-2);\n border-color: var(--border-strong);\n }\n .tab-panel { display: none; }\n .tab-panel.is-active { display: block; }\n .tab-panel ol { margin: 0 0 0.6rem 1.1rem; padding: 0; }\n .tab-panel li {\n margin-bottom: 0.3rem; font-size: 0.86rem; line-height: 1.5;\n color: var(--muted);\n }\n .tab-panel li strong { color: var(--text); font-weight: 650; }\n .tab-panel a {\n color: var(--text); text-decoration: underline;\n text-underline-offset: 2px;\n }\n .tab-panel p {\n font-size: 0.84rem; color: var(--muted); margin: 0.4rem 0;\n line-height: 1.5;\n }\n .tab-panel .hint {\n font-size: 0.78rem; color: var(--subtle); margin-top: 0.5rem;\n }\n .tab-panel code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.78rem; color: var(--text);\n background: var(--panel-2); padding: 0.05rem 0.3rem;\n border-radius: 4px;\n }\n .tab-panel pre { margin: 0.4rem 0 0.5rem; }\n .copy-flash {\n color: var(--ok) !important;\n border-color: var(--ok-border) !important;\n }\n @media (min-width: 560px) {\n .card { max-width: 580px; }\n }\n .hidden { display: none !important; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <div class=\"topbar\">\n <div class=\"brand-lockup\">\n ${brandMarkSvg}\n <span>Agent Native</span>\n </div>\n <div class=\"app-pill\" title=\"${safeApp}\">${safeApp}</div>\n </div>\n\n <div class=\"hero\">\n <!-- \"Connect an external agent\" is kept as the accessible consent label. -->\n <div class=\"flow\" role=\"img\" aria-label=\"Connect an external agent to ${safeApp}\">\n <span class=\"tile\" aria-hidden=\"true\">\n ${flowMarkSvg}\n </span>\n <span class=\"conn\" aria-hidden=\"true\"></span>\n <span class=\"tile\" aria-hidden=\"true\">\n <span class=\"agent-symbol\">&lt;/&gt;</span>\n </span>\n </div>\n\n <div class=\"eyebrow\">Connect an external agent</div>\n <h1>${safeUserCode ? `Authorize ${safeApp} from your terminal?` : `Connect ${safeApp} to an agent`}</h1>\n <p class=\"identity\">\n <span>Signed in as <strong>${safeEmail}</strong></span>\n </p>\n </div>\n\n <div id=\"codeCallout\" class=\"device-strip ${safeUserCode ? \"\" : \"hidden\"}\">\n <span class=\"label\">Device code</span>\n <span class=\"value\" id=\"userCodeValue\">${safeUserCode}</span>\n </div>\n\n <div class=\"mcp-url-block\">\n <div class=\"section-label\">Your MCP URL</div>\n <div class=\"url-row\">\n <code id=\"mcpUrlValue\">${safeMcpUrl}</code>\n <button type=\"button\" class=\"ghost\" data-copy=\"mcpUrlValue\" aria-label=\"Copy MCP URL\">Copy</button>\n </div>\n </div>\n\n <div class=\"hosts\">\n <div class=\"section-label\">Pick your AI assistant</div>\n <div class=\"tabs\" role=\"tablist\" aria-label=\"Choose your AI assistant\">\n <button type=\"button\" class=\"tab is-active\" role=\"tab\" data-tab=\"claude\" aria-selected=\"true\">Claude</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"chatgpt\" aria-selected=\"false\">ChatGPT</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"cursor\" aria-selected=\"false\">Cursor</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"claude-code\" aria-selected=\"false\">Claude Code</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"codex\" aria-selected=\"false\">Codex</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"other\" aria-selected=\"false\">Other</button>\n </div>\n <div class=\"tab-panel is-active\" role=\"tabpanel\" data-panel=\"claude\">\n <ol>\n <li>Open <a href=\"https://claude.ai/customize/connectors\" target=\"_blank\" rel=\"noopener noreferrer\">claude.ai → Customize → Connectors</a> (or Settings → Connectors in Claude Desktop).</li>\n <li>Click the <strong>+</strong> button → <strong>Add custom connector</strong>.</li>\n <li>Paste the MCP URL above, name it <strong>${safeApp}</strong>, click <strong>Connect</strong>.</li>\n <li>On the consent page, click <strong>Authorize</strong> to approve <code>mcp:read</code>, <code>mcp:write</code>, <code>mcp:apps</code>.</li>\n </ol>\n <p class=\"hint\">Inline MCP Apps (charts, dashboards, drafts) render automatically inside Claude chats.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"chatgpt\">\n <ol>\n <li>In ChatGPT, open <strong>Settings → Connectors → Add</strong> (Business/Enterprise/Edu workspaces with developer mode enabled).</li>\n <li>Paste the MCP URL above, name it <strong>${safeApp}</strong>, click <strong>Connect</strong>.</li>\n <li>Sign in with your Agent-Native account and approve <code>mcp:read</code>, <code>mcp:write</code>, <code>mcp:apps</code>.</li>\n </ol>\n <p class=\"hint\">Workspace admins may need to add the connector under organization settings first; each member still authorizes their own account.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"cursor\">\n <ol>\n <li>Open Cursor → <strong>Settings → MCP</strong>.</li>\n <li>Click <strong>Add MCP Server</strong>, paste the MCP URL above, save.</li>\n <li>When prompted, sign in with your Agent-Native account.</li>\n </ol>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"claude-code\">\n <p>In your terminal, run:</p>\n <pre id=\"claudeCodeCmd\">${safeClaudeCodeCmd}</pre>\n <button type=\"button\" class=\"ghost\" data-copy=\"claudeCodeCmd\">Copy command</button>\n <p>Then inside Claude Code, type <code>/mcp</code>, choose <strong>${safeServerId}</strong>, and click <strong>Authenticate</strong>.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"codex\">\n <p>For Codex (and other CLI agents), run:</p>\n <pre id=\"codexCmd\">${safeCodexCmd}</pre>\n <button type=\"button\" class=\"ghost\" data-copy=\"codexCmd\">Copy command</button>\n <p class=\"hint\">Mints a per-user token (revocable under <strong>Existing connections</strong> below) and writes the right config file for Codex automatically. For Cowork or Goose, the same command writes the right config.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"other\">\n <p>For any MCP-compatible client with remote-OAuth support, paste the MCP URL above. For clients without OAuth, this generic HTTP config snippet works once paired with a static token (generated below):</p>\n <pre id=\"genericConfig\">${safeGenericConfig}</pre>\n <button type=\"button\" class=\"ghost\" data-copy=\"genericConfig\">Copy config</button>\n </div>\n </div>\n\n <div id=\"msg\" class=\"msg\"></div>\n\n <div id=\"mintForm\">\n <button id=\"authorizeBtn\" class=\"primary\">${safeUserCode ? \"Authorize device\" : \"Create connection token\"}</button>\n <details class=\"advanced\">\n <summary>\n Advanced options\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"advanced-body\">\n <div class=\"field\">\n <label for=\"label\">Label (optional)</label>\n <input id=\"label\" type=\"text\" placeholder=\"e.g. Claude Code on my laptop\" maxlength=\"120\" />\n </div>\n <div class=\"field\">\n <label for=\"ttl\">Expires in (days, 1–365)</label>\n <input id=\"ttl\" type=\"number\" min=\"1\" max=\"365\" value=\"${DEFAULT_TOKEN_TTL_DAYS}\" />\n </div>\n </div>\n </details>\n </div>\n\n <div id=\"result\" class=\"result-panel hidden\">\n <div class=\"result-title\">Connection token created</div>\n <p class=\"result-copy\" id=\"resultMsg\">Paste this into your agent's MCP config. The token is shown only once.</p>\n <div class=\"section-label\">MCP config</div>\n <pre id=\"mcpJson\"></pre>\n <details class=\"advanced\">\n <summary>\n Terminal alternative\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"advanced-body\">\n <pre id=\"cliLine\"></pre>\n </div>\n </details>\n </div>\n\n <details id=\"connections\" class=\"connections\">\n <summary>\n <span class=\"connections-title\">Existing connections</span>\n <span id=\"connectionsState\" class=\"connections-state\">Checking</span>\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div id=\"tokenList\" class=\"token-list\"><div class=\"empty-state\">Checking connections...</div></div>\n </details>\n</div>\n<script>\n(function () {\n var BASE = ${JSON.stringify(joinAppPath(connectBasePath, \"/_agent-native/mcp/connect\"))};\n var USER_CODE = ${JSON.stringify(safeUserCode || null)};\n var msgEl = document.getElementById(\"msg\");\n var connectionsEl = document.getElementById(\"connections\");\n var connectionsStateEl = document.getElementById(\"connectionsState\");\n\n // Tab switching for the per-host instructions block.\n var tabBtns = document.querySelectorAll(\".tabs .tab\");\n var tabPanels = document.querySelectorAll(\".tab-panel\");\n for (var i = 0; i < tabBtns.length; i++) {\n tabBtns[i].addEventListener(\"click\", function (ev) {\n var btn = ev.currentTarget;\n var name = btn.getAttribute(\"data-tab\");\n for (var j = 0; j < tabBtns.length; j++) {\n var active = tabBtns[j] === btn;\n tabBtns[j].classList.toggle(\"is-active\", active);\n tabBtns[j].setAttribute(\"aria-selected\", active ? \"true\" : \"false\");\n }\n for (var k = 0; k < tabPanels.length; k++) {\n tabPanels[k].classList.toggle(\n \"is-active\",\n tabPanels[k].getAttribute(\"data-panel\") === name,\n );\n }\n });\n }\n\n // Copy buttons — any element with data-copy=\"<id>\" copies that node's text.\n document.addEventListener(\"click\", function (ev) {\n var btn = ev.target && ev.target.closest && ev.target.closest(\"[data-copy]\");\n if (!btn) return;\n var node = document.getElementById(btn.getAttribute(\"data-copy\"));\n if (!node || !navigator.clipboard) return;\n navigator.clipboard.writeText(node.textContent || \"\").then(function () {\n var prev = btn.textContent;\n btn.textContent = \"Copied\";\n btn.classList.add(\"copy-flash\");\n setTimeout(function () {\n btn.textContent = prev;\n btn.classList.remove(\"copy-flash\");\n }, 1400);\n });\n });\n function showMsg(text, kind) {\n msgEl.textContent = text;\n msgEl.className = \"msg \" + (kind || \"err\");\n }\n function clearMsg() { msgEl.className = \"msg\"; msgEl.textContent = \"\"; }\n\n function renderResult(data) {\n document.getElementById(\"mintForm\").classList.add(\"hidden\");\n var entry = {};\n entry[data.serverName] = data.mcpServerEntry;\n document.getElementById(\"mcpJson\").textContent =\n JSON.stringify({ mcpServers: entry }, null, 2);\n document.getElementById(\"cliLine\").textContent = data.cli;\n document.getElementById(\"result\").classList.remove(\"hidden\");\n }\n\n async function postJson(path, body) {\n var res = await fetch(BASE + path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"same-origin\",\n body: JSON.stringify(body || {})\n });\n var data = null;\n try { data = await res.json(); } catch (e) {}\n return { ok: res.ok, status: res.status, data: data };\n }\n\n async function loadTokens() {\n var listEl = document.getElementById(\"tokenList\");\n try {\n var res = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (!res.ok) {\n connectionsStateEl.textContent = \"Unavailable\";\n listEl.innerHTML = '<div class=\"empty-state\">Could not load connections.</div>';\n return;\n }\n var data = await res.json();\n var tokens = (data && data.tokens) || [];\n if (!tokens.length) {\n connectionsStateEl.textContent = \"None\";\n connectionsEl.open = false;\n listEl.innerHTML = '<div class=\"empty-state\">Created connections will appear here for revoking later.</div>';\n return;\n }\n var activeCount = tokens.filter(function (t) { return !t.revokedAt; }).length;\n connectionsStateEl.textContent = activeCount === 1 ? \"1 active\" : activeCount + \" active\";\n listEl.innerHTML = \"\";\n tokens.forEach(function (t) {\n var div = document.createElement(\"div\");\n div.className = \"tok\" + (t.revokedAt ? \" revoked\" : \"\");\n var when = t.createdAt ? new Date(t.createdAt).toLocaleString() : \"\";\n var used = t.lastUsedAt ? \" · last used \" + new Date(t.lastUsedAt).toLocaleString() : \"\";\n var left = document.createElement(\"div\");\n var label = document.createElement(\"div\");\n label.textContent = t.label || \"(unlabeled)\";\n var meta = document.createElement(\"div\");\n meta.className = \"meta\";\n meta.textContent = (t.revokedAt ? \"Revoked · \" : \"Created \") + when + used;\n left.appendChild(label); left.appendChild(meta);\n div.appendChild(left);\n if (!t.revokedAt) {\n var btn = document.createElement(\"button\");\n btn.className = \"ghost\";\n btn.textContent = \"Revoke\";\n btn.onclick = async function () {\n btn.disabled = true;\n var r = await postJson(\"/tokens/revoke\", { id: t.id });\n if (r.ok) { loadTokens(); }\n else { btn.disabled = false; showMsg(\"Could not revoke token.\"); }\n };\n div.appendChild(btn);\n }\n listEl.appendChild(div);\n });\n } catch (e) {\n connectionsStateEl.textContent = \"Unavailable\";\n listEl.innerHTML = '<div class=\"empty-state\">Could not load connections.</div>';\n }\n }\n\n document.getElementById(\"authorizeBtn\").onclick = async function () {\n var btn = this;\n btn.disabled = true;\n clearMsg();\n var label = document.getElementById(\"label\").value || undefined;\n var ttlDays = parseInt(document.getElementById(\"ttl\").value, 10) || undefined;\n try {\n if (USER_CODE) {\n var a = await postJson(\"/device/authorize\", { user_code: USER_CODE });\n if (!a.ok) {\n btn.disabled = false;\n showMsg((a.data && a.data.error) || \"Could not authorize this device code.\");\n return;\n }\n showMsg(\"Device authorized — finishing connection… you can return to your terminal.\", \"ok\");\n btn.classList.add(\"hidden\");\n document.getElementById(\"mintForm\").classList.add(\"hidden\");\n var cc = document.getElementById(\"codeCallout\");\n if (cc) cc.classList.add(\"hidden\");\n // The token is minted a few seconds later, when the CLI next polls\n // /device/poll — so a single loadTokens() here runs BEFORE the row\n // exists and the list would wrongly read \"No connections yet\" until\n // a manual reload. Snapshot the EXISTING non-revoked token ids first\n // so we announce \"Connected\" only when THIS device's freshly-minted\n // token appears — a user who already has tokens must not get a false\n // success the instant they authorize.\n var priorIds = {};\n try {\n var pr = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (pr.ok) {\n var pd = await pr.json();\n ((pd && pd.tokens) || []).forEach(function (t) {\n if (!t.revokedAt) priorIds[t.id] = true;\n });\n }\n } catch (e) {}\n loadTokens();\n var tries = 0;\n var iv = setInterval(async function () {\n tries++;\n try {\n var res = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (res.ok) {\n var data = await res.json();\n var fresh = ((data && data.tokens) || []).filter(function (t) {\n return !t.revokedAt && !priorIds[t.id];\n });\n if (fresh.length > 0) {\n clearInterval(iv);\n showMsg(\"Connected. This device can now act as you — manage or revoke it below.\", \"ok\");\n loadTokens();\n return;\n }\n }\n } catch (e) {}\n if (tries >= 30) {\n // No new token appeared in the window — e.g. the loopback\n // dev-open path writes a header-only config and never mints.\n // Don't claim \"Connected\" (we couldn't confirm a device token);\n // keep the \"authorized\" message and just refresh the list.\n clearInterval(iv);\n loadTokens();\n }\n }, 2000);\n return;\n } else {\n var m = await postJson(\"/token\", { label: label, ttlDays: ttlDays });\n if (!m.ok) {\n btn.disabled = false;\n showMsg((m.data && m.data.error) || \"Could not create token.\");\n return;\n }\n renderResult(m.data);\n }\n loadTokens();\n } catch (e) {\n btn.disabled = false;\n showMsg(\"Network error. Please try again.\");\n }\n };\n\n loadTokens();\n})();\n</script>\n</body>\n</html>`;\n}\n\n// ---------------------------------------------------------------------------\n// Handler — single entry point; core-routes-plugin dispatches the subpath.\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a `/_agent-native/mcp/connect[...]` request. `subpath` is the part\n * after `/connect` (empty string = the page itself, otherwise e.g.\n * `/token`, `/device/start`). The core-routes-plugin computes it from the\n * stripped event path so this module stays mount-agnostic.\n */\nexport async function handleMcpConnect(\n event: H3Event,\n subpath: string,\n options: McpConnectRouteOptions = {},\n): Promise<Response> {\n const method = getMethod(event);\n const origin = deriveOrigin(event);\n const basePath = configuredBasePath();\n const appUrl = `${origin}${basePath}`;\n const sub = (\"/\" + subpath.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\")).replace(\n /^\\/$/,\n \"\",\n );\n\n // ---- The connect page (GET) ------------------------------------------\n if (sub === \"\") {\n if (method !== \"GET\" && method !== \"HEAD\") {\n return json({ error: \"Method not allowed\" }, 405);\n }\n const session = await getSession(event);\n if (!session?.email) {\n // Serve the SAME login form the guard would, at this same URL — the\n // login form reloads window.location so we re-enter here authed.\n const loginHtml = getConfiguredLoginHtml(event);\n if (loginHtml) return html(loginHtml, 200);\n // Fully-open app (no auth guard): nothing to scope a mint to.\n return html(\n renderConnectPage({\n connectBasePath: basePath,\n email: \"(no auth configured)\",\n appName: options.appName || appLabel(appUrl, options),\n appUrl,\n serverId: serverName(appUrl, options),\n userCode: null,\n }),\n );\n }\n let userCode: string | null = null;\n try {\n const u = new URL(\n event.node?.req?.url ?? event.path ?? \"/\",\n \"http://an.invalid\",\n );\n const raw = u.searchParams.get(\"user_code\");\n if (raw && USER_CODE_RE.test(raw)) userCode = raw;\n } catch {\n userCode = null;\n }\n return html(\n renderConnectPage({\n connectBasePath: basePath,\n email: session.email,\n appName: options.appName || appLabel(appUrl, options),\n appUrl,\n serverId: serverName(appUrl, options),\n userCode,\n }),\n );\n }\n\n // ---- POST /token (session-required) ---------------------------------\n if (sub === \"/token\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n if (!process.env.A2A_SECRET) {\n if (canUseDevOpenConnect(event)) {\n return json(\n mcpResultPayload(appUrl, options, { ownerEmail: session.email }),\n );\n }\n return json(\n {\n error:\n \"This deployment has no A2A_SECRET configured, so connect tokens cannot be minted.\",\n },\n 503,\n );\n }\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n label?: unknown;\n ttlDays?: unknown;\n };\n const label =\n typeof body.label === \"string\" && body.label.trim()\n ? body.label.trim().slice(0, 120)\n : null;\n const ttlDays = clampTtlDays(body.ttlDays);\n try {\n const { token } = await mintConnectToken({\n email: session.email,\n orgId: session.orgId,\n label,\n ttlDays,\n });\n return json(mcpResultPayload(appUrl, options, { token }));\n } catch {\n return json({ error: \"Failed to mint token.\" }, 500);\n }\n }\n\n // ---- POST /device/start (UNAUTH) ------------------------------------\n if (sub === \"/device/start\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n try {\n const row = await createDeviceCode();\n const verificationUri = `${appUrl}/_agent-native/mcp/connect`;\n return json({\n device_code: row.deviceCode,\n user_code: row.userCode,\n verification_uri: verificationUri,\n verification_uri_complete: `${verificationUri}?user_code=${row.userCode}`,\n interval: DEVICE_POLL_INTERVAL_S,\n expires_in: Math.floor(DEVICE_CODE_TTL_MS / 1000),\n });\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") {\n return json({ error: \"Rate limited. Try again shortly.\" }, 429);\n }\n return json({ error: \"Could not start device flow.\" }, 500);\n }\n }\n\n // ---- POST /device/authorize (session-required) ----------------------\n if (sub === \"/device/authorize\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n user_code?: unknown;\n };\n const userCode =\n typeof body.user_code === \"string\" ? body.user_code.trim() : \"\";\n if (!USER_CODE_RE.test(userCode)) {\n return json({ error: \"Invalid user code.\" }, 400);\n }\n const orgId =\n typeof session.orgId === \"string\" && session.orgId.trim()\n ? session.orgId.trim()\n : null;\n const result = await approveDeviceCode(userCode, session.email, orgId);\n if (result === \"not_found\") {\n return json({ error: \"Unknown device code.\" }, 404);\n }\n if (result === \"expired\") {\n return json({ error: \"This device code has expired.\" }, 410);\n }\n if (result === \"already\") {\n return json({ error: \"This device code was already used.\" }, 409);\n }\n return json({ status: \"approved\" });\n }\n\n // ---- POST /device/poll (UNAUTH) -------------------------------------\n if (sub === \"/device/poll\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n device_code?: unknown;\n };\n const deviceCode =\n typeof body.device_code === \"string\" ? body.device_code : \"\";\n if (!deviceCode) return json({ error: \"device_code required\" }, 400);\n const row = await getDeviceCode(deviceCode);\n if (!row) return json({ status: \"not_found\" }, 404);\n if (row.status === \"consumed\") return json({ status: \"consumed\" });\n if (\n row.status === \"expired\" ||\n (row.expiresAt != null && row.expiresAt < Date.now())\n ) {\n if (row.status !== \"expired\") void expireDeviceCode(deviceCode);\n return json({ status: \"expired\" });\n }\n if (\n row.status === \"pending\" ||\n row.status === \"minting\" ||\n !row.ownerEmail\n ) {\n return json({ status: \"pending\" });\n }\n // status === \"approved\" && ownerEmail bound → mint exactly once.\n if (!process.env.A2A_SECRET) {\n if (canUseDevOpenConnect(event)) {\n const consumed = await consumeDeviceCode(\n deviceCode,\n `dev-open-${randomUUID()}`,\n );\n if (!consumed) {\n const fresh = await getDeviceCode(deviceCode);\n if (fresh?.status === \"consumed\") return json({ status: \"consumed\" });\n return json({ status: \"pending\" });\n }\n return json({\n status: \"approved\",\n ...mcpResultPayload(appUrl, options, {\n ownerEmail: row.ownerEmail,\n }),\n });\n }\n return json({ status: \"error\", error: \"A2A_SECRET not configured\" }, 503);\n }\n try {\n const jti = randomUUID();\n // Claim a retryable minting state first. If signing or recording fails,\n // release the row back to approved so the CLI can poll again.\n const claimed = await claimDeviceCodeForMint(deviceCode, jti);\n if (!claimed) {\n const fresh = await getDeviceCode(deviceCode);\n if (fresh?.status === \"consumed\") return json({ status: \"consumed\" });\n return json({ status: \"pending\" });\n }\n let token: string;\n try {\n const orgDomain = await resolveOrgDomain(claimed.orgId ?? undefined);\n token = await signA2AToken(claimed.ownerEmail!, orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: `${DEFAULT_TOKEN_TTL_DAYS}d`,\n extraClaims: { jti, scope: MCP_CONNECT_SCOPE },\n });\n await recordMintedToken({\n jti,\n ownerEmail: claimed.ownerEmail!,\n orgId: claimed.orgId,\n label: \"Device connection\",\n });\n if (!(await finishDeviceCodeMint(deviceCode, jti))) {\n return json({ status: \"pending\" });\n }\n } catch (err) {\n await releaseDeviceCodeMint(deviceCode, jti);\n throw err;\n }\n return json({\n status: \"approved\",\n ...mcpResultPayload(appUrl, options, { token }),\n });\n } catch {\n return json({ status: \"error\", error: \"Failed to mint token.\" }, 500);\n }\n }\n\n // ---- GET /tokens (session-required) ---------------------------------\n if (sub === \"/tokens\") {\n if (method !== \"GET\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const rows = await listTokens(session.email);\n return json({\n tokens: rows.map((r) => ({\n id: r.id,\n label: r.label,\n createdAt: r.createdAt,\n lastUsedAt: r.lastUsedAt,\n revokedAt: r.revokedAt,\n })),\n });\n }\n\n // ---- POST /tokens/revoke (session-required) -------------------------\n if (sub === \"/tokens/revoke\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n id?: unknown;\n };\n const id = typeof body.id === \"string\" ? body.id : \"\";\n if (!id) return json({ error: \"id required\" }, 400);\n const revoked = await revokeToken(session.email, id);\n return json({ ok: revoked });\n }\n\n return json({ error: \"Not found\" }, 404);\n}\n"]}
1
+ {"version":3,"file":"connect-route.js","sourceRoot":"","sources":["../../src/mcp/connect-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/mcp/connect` — frictionless external-agent connection.\n *\n * A logged-in user on a deployed agent-native app (e.g. mail.agent-native.com)\n * mints a per-user, scoped, revocable MCP bearer token WITHOUT ever copying a\n * shared deployment secret. Two surfaces:\n *\n * 1. Browser — `GET /connect` renders a minimal in-app page (same inline\n * HTML approach as the auth pages). The Authorize button POSTs to\n * `/connect/token`, then shows the ready-to-paste `.mcp.json` entry, the\n * `agent-native connect <origin>` one-liner, and the user's existing\n * tokens with Revoke buttons.\n * 2. CLI — an OAuth-2.0-device-authorization-style flow:\n * POST /connect/device/start (unauth) → device_code + user_code\n * GET /connect?user_code=… (browser) → user signs in & approves\n * POST /connect/device/authorize (session) → binds user to the code\n * POST /connect/device/poll (unauth) → mints + returns the token\n *\n * The minted token reuses the existing A2A signer (`signA2AToken`) — no new\n * crypto. We only add a random `jti` + `scope: \"mcp-connect\"` claim so it can\n * be revoked. `verifyAuth` already verifies A2A_SECRET JWTs and extracts\n * `sub`/`org_domain`, so a minted token works against `/_agent-native/mcp`\n * with no verify changes for the happy path (the revoke check is the only\n * addition there).\n *\n * Node-only (crypto + the A2A signer), bundled alongside the other framework\n * routes. Dialect-agnostic SQL lives in `connect-store.ts`.\n */\n\nimport type { H3Event } from \"h3\";\nimport { getMethod, getHeader } from \"h3\";\nimport { readBody } from \"../server/h3-helpers.js\";\nimport {\n getSession,\n getConfiguredLoginHtml,\n isLoopbackRequest,\n} from \"../server/auth.js\";\nimport { signA2AToken } from \"../a2a/client.js\";\nimport { getOrgDomain } from \"../org/context.js\";\nimport { randomUUID } from \"node:crypto\";\nimport {\n recordMintedToken,\n listTokens,\n revokeToken,\n createDeviceCode,\n getDeviceCode,\n approveDeviceCode,\n consumeDeviceCode,\n claimDeviceCodeForMint,\n finishDeviceCodeMint,\n releaseDeviceCodeMint,\n expireDeviceCode,\n MCP_CONNECT_SCOPE,\n DEFAULT_TOKEN_TTL_DAYS,\n MIN_TOKEN_TTL_DAYS,\n MAX_TOKEN_TTL_DAYS,\n DEVICE_CODE_TTL_MS,\n} from \"./connect-store.js\";\n\n/** Device-flow poll interval hint (seconds). */\nconst DEVICE_POLL_INTERVAL_S = 3;\n\n// Human-typable user code: 8 base32 chars, dashed XXXX-XXXX.\nconst USER_CODE_RE = /^[A-Z2-7]{4}-[A-Z2-7]{4}$/;\n\nexport interface McpConnectRouteOptions {\n /** App id (directory under apps/, e.g. `mail`). Used for the server name. */\n appId?: string;\n /** Human app name shown on the connect page. */\n appName?: string;\n}\n\nfunction json(body: unknown, status = 200): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction html(body: string, status = 200): Response {\n return new Response(body, {\n status,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n}\n\n/** Derive the running app's origin from request headers (same logic mountMCP\n * uses) — `https` in prod / for non-loopback hosts, `http` for localhost. */\nfunction deriveOrigin(event: H3Event): string {\n const forwardedProto = getHeader(event, \"x-forwarded-proto\");\n const host = getHeader(event, \"x-forwarded-host\") || getHeader(event, \"host\");\n const proto =\n forwardedProto?.split(\",\")[0]?.trim() ||\n (host && /^(localhost|127\\.0\\.0\\.1)(:|$)/.test(host) ? \"http\" : \"https\");\n return host ? `${proto}://${host}` : \"\";\n}\n\nfunction normalizeBasePath(raw: string | undefined): string {\n const trimmed = (raw ?? \"\").trim();\n if (!trimmed || trimmed === \"/\") return \"\";\n const withSlash = trimmed.startsWith(\"/\") ? trimmed : `/${trimmed}`;\n return withSlash.replace(/\\/+$/, \"\");\n}\n\nfunction configuredBasePath(): string {\n return normalizeBasePath(\n process.env.APP_BASE_PATH || process.env.VITE_APP_BASE_PATH,\n );\n}\n\nfunction joinAppPath(basePath: string, path: string): string {\n if (!basePath) return path;\n if (path === \"/\") return basePath;\n return `${basePath}${path.startsWith(\"/\") ? path : `/${path}`}`;\n}\n\nfunction appLabel(origin: string, options: McpConnectRouteOptions): string {\n if (options.appId) return options.appId;\n try {\n const h = new URL(origin).hostname;\n return h.split(\".\")[0] || h;\n } catch {\n return options.appName || \"app\";\n }\n}\n\nfunction serverName(origin: string, options: McpConnectRouteOptions): string {\n return `agent-native-${appLabel(origin, options)}`;\n}\n\nfunction canUseDevOpenConnect(event: H3Event): boolean {\n // Loopback determined from the real socket peer (isLoopbackRequest →\n // getRequestIP without xForwardedFor), NOT a parsed `Host` header — the\n // header is client-controlled, and it also handles IPv6 `::1`. A\n // misconfigured public deploy with no secret thus can't unlock dev-open\n // by spoofing `Host: localhost`.\n return (\n isLoopbackRequest(event) &&\n !process.env.A2A_SECRET?.trim() &&\n !process.env.ACCESS_TOKEN?.trim() &&\n !process.env.ACCESS_TOKENS?.trim()\n );\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\n/**\n * Resolve the org domain for a session. Used as the JWT `org_domain` claim so\n * the receiving MCP endpoint can map it back to an org id (same as A2A). Best\n * effort — a missing org just yields a user-scoped (no-org) token.\n */\nasync function resolveOrgDomain(\n orgId: string | undefined,\n): Promise<string | undefined> {\n if (!orgId) return undefined;\n try {\n return (await getOrgDomain(orgId)) ?? undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction clampTtlDays(input: unknown): number {\n const n = Number(input);\n if (!Number.isFinite(n)) return DEFAULT_TOKEN_TTL_DAYS;\n return Math.min(\n MAX_TOKEN_TTL_DAYS,\n Math.max(MIN_TOKEN_TTL_DAYS, Math.floor(n)),\n );\n}\n\n/**\n * Mint a connect-scoped JWT and record it. The JWT is signed by the existing\n * A2A signer (HS256 over A2A_SECRET); we add a random `jti` and\n * `scope: \"mcp-connect\"` so the token is individually revocable. The token\n * value is returned to the caller exactly once and never persisted.\n */\nasync function mintConnectToken(params: {\n email: string;\n orgId: string | undefined;\n label: string | null;\n ttlDays: number;\n}): Promise<{ token: string; jti: string }> {\n const orgDomain = await resolveOrgDomain(params.orgId);\n const jti = randomUUID();\n // signA2AToken signs { sub: email, org_domain? } over A2A_SECRET (global)\n // or the org secret. We extend its claims via the standard jose builder by\n // re-using the same signer with extra claims threaded through `options`.\n const token = await signA2AToken(params.email, orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: `${params.ttlDays}d`,\n extraClaims: { jti, scope: MCP_CONNECT_SCOPE },\n });\n await recordMintedToken({\n jti,\n ownerEmail: params.email,\n orgId: params.orgId ?? null,\n label: params.label,\n });\n return { token, jti };\n}\n\nfunction mcpResultPayload(\n appUrl: string,\n options: McpConnectRouteOptions,\n auth: { token?: string; ownerEmail?: string },\n) {\n const mcpUrl = `${appUrl}/_agent-native/mcp`;\n const name = serverName(appUrl, options);\n const headers: Record<string, string> = {};\n if (auth.token) headers.Authorization = `Bearer ${auth.token}`;\n if (!auth.token && auth.ownerEmail) {\n headers[\"X-Agent-Native-Owner-Email\"] = auth.ownerEmail;\n }\n return {\n token: auth.token ?? \"\",\n mcpUrl,\n serverName: name,\n mcpServerEntry: {\n type: \"http\" as const,\n url: mcpUrl,\n ...(Object.keys(headers).length ? { headers } : {}),\n },\n cli: `agent-native connect ${appUrl}`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Connect page (server-rendered HTML string)\n// ---------------------------------------------------------------------------\n\nfunction agentNativeMarkSvg(className: string, gradientId: string): string {\n return `<svg class=\"${className}\" width=\"114\" height=\"66\" viewBox=\"0 0 114 66\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\" focusable=\"false\">\n <path d=\"M24.5537 65.7695H0L15.0859 39.4619L37.708 0L60.4912 39.4619H39.6396L24.5537 65.7695Z\" fill=\"white\"/>\n <path d=\"M89.446 0H114L76.2921 65.7704H51.7383L89.446 0Z\" fill=\"url(#${gradientId})\"/>\n <defs>\n <linearGradient id=\"${gradientId}\" x1=\"101.702\" y1=\"67.4791\" x2=\"113.672\" y2=\"-37.4275\" gradientUnits=\"userSpaceOnUse\">\n <stop stop-color=\"#00B5FF\"/>\n <stop offset=\"1\" stop-color=\"#48FFE4\"/>\n </linearGradient>\n </defs>\n</svg>`;\n}\n\nfunction renderConnectPage(params: {\n connectBasePath: string;\n email: string;\n appName: string;\n appUrl: string;\n serverId: string;\n userCode: string | null;\n}): string {\n const { connectBasePath, email, appName, appUrl, serverId, userCode } =\n params;\n const safeEmail = escapeHtml(email);\n const safeApp = escapeHtml(appName);\n const mcpUrl = `${appUrl}/_agent-native/mcp`;\n const safeMcpUrl = escapeHtml(mcpUrl);\n const safeServerId = escapeHtml(serverId);\n const safeClaudeCodeCmd = escapeHtml(\n `claude mcp add --transport http ${serverId} ${mcpUrl}`,\n );\n const safeCodexCmd = escapeHtml(`npx @agent-native/core connect ${appUrl}`);\n const safeGenericConfig = escapeHtml(\n `{\\n \"mcpServers\": {\\n \"${serverId}\": {\\n \"type\": \"http\",\\n \"url\": \"${mcpUrl}\"\\n }\\n }\\n}`,\n );\n const brandMarkSvg = agentNativeMarkSvg(\n \"brand-mark\",\n \"agent-native-connect-brand-gradient\",\n );\n const flowMarkSvg = agentNativeMarkSvg(\n \"flow-mark\",\n \"agent-native-connect-flow-gradient\",\n );\n const safeUserCode =\n userCode && USER_CODE_RE.test(userCode) ? escapeHtml(userCode) : \"\";\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Connect ${safeApp}</title>\n<style>\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n :root {\n color-scheme: dark;\n --bg: #09090b; --panel: #121214; --panel-2: #0c0c0e;\n --panel-soft: rgba(255,255,255,0.025);\n --border: rgba(255,255,255,0.075); --border-strong: rgba(255,255,255,0.14);\n --text: #f7f7f8; --muted: #a1a1aa; --subtle: #74747d;\n --accent: #f4f4f5; --accent-fg: #09090b;\n --ring: rgba(250,250,250,0.55);\n --error: #fca5a5; --error-bg: rgba(127,29,29,0.18);\n --ok: #86efac; --ok-bg: rgba(20,83,45,0.12); --ok-border: rgba(134,239,172,0.18);\n }\n html, body { -webkit-font-smoothing: antialiased; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n background: linear-gradient(180deg, #101013 0%, var(--bg) 58%);\n color: var(--text); display: flex; align-items: center;\n justify-content: center; min-height: 100vh; padding: 1.5rem 1rem;\n }\n .card {\n width: 100%; max-width: 440px;\n background: var(--panel); border: 1px solid var(--border);\n border-radius: 8px; box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset,\n 0 30px 90px rgba(0,0,0,0.5);\n padding: 1.25rem;\n }\n .topbar {\n display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; margin-bottom: 1.75rem;\n }\n .brand-lockup {\n display: flex; align-items: center; gap: 0.55rem;\n color: var(--muted); font-size: 0.78rem; font-weight: 600;\n }\n .brand-mark { width: 18px; height: auto; display: block; }\n .app-pill {\n max-width: 50%; border: 1px solid var(--border);\n border-radius: 999px; padding: 0.28rem 0.55rem;\n color: var(--subtle); font-size: 0.72rem; line-height: 1;\n overflow: hidden; text-overflow: ellipsis; white-space: nowrap;\n }\n .hero { padding: 0 0.75rem; text-align: center; }\n .flow {\n display: flex; align-items: center; justify-content: center;\n gap: 0; margin: 0 auto 1.1rem; width: fit-content;\n }\n .flow .tile {\n width: 42px; height: 42px; border-radius: 8px;\n display: flex; align-items: center; justify-content: center;\n background: var(--panel-2); border: 1px solid var(--border-strong);\n color: var(--text); flex-shrink: 0;\n }\n .flow-mark { width: 26px; height: auto; display: block; }\n .flow .agent-symbol {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.95rem; font-weight: 700; letter-spacing: -0.04em;\n }\n .flow .conn {\n width: 30px; height: 1px; flex-shrink: 0;\n background: linear-gradient(90deg, transparent, var(--border-strong), transparent);\n background-position: center;\n }\n .eyebrow {\n text-align: center; font-size: 0.72rem; font-weight: 600;\n letter-spacing: 0.08em; text-transform: uppercase;\n color: var(--subtle); margin-bottom: 0.55rem;\n }\n h1 {\n text-align: center; font-size: 1.45rem; font-weight: 680;\n line-height: 1.25; margin-bottom: 0.7rem;\n letter-spacing: -0.01em;\n }\n .identity {\n display: flex; flex-wrap: wrap; align-items: center; justify-content: center;\n gap: 0.25rem 0.45rem; color: var(--subtle); font-size: 0.78rem;\n line-height: 1.35; margin: 0 auto 1.5rem; max-width: 34ch;\n }\n .identity strong { color: var(--muted); font-weight: 600; }\n .device-strip {\n display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; border: 1px solid var(--border);\n border-radius: 8px; padding: 0.5rem 0.65rem; margin: 0 0 0.9rem;\n background: var(--panel-soft); color: var(--muted);\n }\n .device-strip .label {\n font-size: 0.76rem; font-weight: 560; color: var(--subtle);\n }\n .device-strip .value {\n font-size: 0.78rem; font-weight: 650;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n letter-spacing: 0.08em; color: var(--muted);\n }\n button {\n cursor: pointer; font: inherit; font-weight: 600; border: none;\n border-radius: 8px; padding: 0.78rem 1rem;\n }\n button:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }\n .primary {\n background: var(--accent); color: var(--accent-fg); width: 100%;\n font-size: 0.95rem;\n }\n .primary:hover:not(:disabled) { background: #e4e4e7; }\n .primary:disabled { opacity: 0.55; cursor: default; }\n .ghost {\n background: transparent; color: var(--muted);\n border: 1px solid var(--border-strong); padding: 0.35rem 0.7rem;\n font-size: 0.78rem; font-weight: 500; border-radius: 8px;\n }\n .ghost:hover:not(:disabled) { color: var(--text); border-color: var(--subtle); }\n pre {\n background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;\n padding: 0.9rem; font-size: 0.78rem; line-height: 1.5; overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n color: #d4d4d8; margin: 0.5rem 0 1rem;\n }\n /* Advanced disclosure */\n .advanced { margin: 0 0 1rem; }\n .advanced > summary {\n list-style: none; cursor: pointer; user-select: none;\n display: flex; align-items: center; justify-content: center; gap: 0.35rem;\n color: var(--subtle); font-size: 0.8rem; font-weight: 500;\n padding: 0.5rem 0; text-align: center;\n }\n .advanced > summary::-webkit-details-marker { display: none; }\n .advanced > summary:hover { color: var(--muted); }\n .advanced > summary:focus-visible { outline: 2px solid var(--ring);\n outline-offset: 2px; border-radius: 6px; }\n .advanced > summary .chev {\n width: 7px; height: 7px; border-right: 1.5px solid currentColor;\n border-bottom: 1.5px solid currentColor; transform: rotate(45deg);\n transition: transform 0.15s ease; margin-top: -3px;\n }\n .advanced[open] > summary .chev { transform: rotate(225deg); margin-top: 2px; }\n .advanced-body {\n padding: 0.85rem 0.1rem 0.25rem;\n }\n .field { margin-bottom: 0.9rem; }\n .field:last-child { margin-bottom: 0; }\n .field label { display: block; font-size: 0.78rem; color: var(--muted);\n margin-bottom: 0.35rem; }\n .field input {\n width: 100%; padding: 0.6rem 0.7rem; font: inherit; color: var(--text);\n background: var(--panel-2); border: 1px solid var(--border-strong);\n border-radius: 8px;\n }\n .field input:focus-visible {\n outline: none; border-color: var(--ring);\n box-shadow: 0 0 0 3px rgba(250,250,250,0.12);\n }\n .connections {\n margin-top: 1.1rem; border-top: 1px solid var(--border);\n padding-top: 0.35rem;\n }\n .connections > summary {\n list-style: none; cursor: pointer; user-select: none;\n display: flex; align-items: center; gap: 0.55rem;\n min-height: 2.2rem; color: var(--muted); font-size: 0.82rem;\n }\n .connections > summary::-webkit-details-marker { display: none; }\n .connections > summary:focus-visible {\n outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 6px;\n }\n .connections-title { font-weight: 600; color: var(--muted); }\n .connections-state {\n margin-left: auto; color: var(--subtle); font-size: 0.73rem;\n border: 1px solid var(--border); border-radius: 999px;\n padding: 0.18rem 0.45rem; line-height: 1;\n }\n .connections .chev {\n width: 7px; height: 7px; border-right: 1.5px solid currentColor;\n border-bottom: 1.5px solid currentColor; transform: rotate(45deg);\n transition: transform 0.15s ease; margin: -3px 0 0 0.15rem;\n }\n .connections[open] .chev { transform: rotate(225deg); margin-top: 2px; }\n .token-list { padding-top: 0.4rem; }\n .tok { display: flex; align-items: center; justify-content: space-between;\n gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border);\n font-size: 0.83rem; }\n .tok:last-child { border-bottom: none; }\n .tok .meta { color: var(--subtle); font-size: 0.74rem; margin-top: 0.1rem; }\n .tok.revoked { opacity: 0.45; }\n .empty-state {\n color: var(--subtle); font-size: 0.78rem; line-height: 1.45;\n padding: 0.3rem 0 0.45rem;\n }\n .msg { font-size: 0.83rem; padding: 0.7rem 0.8rem; border-radius: 8px;\n margin-bottom: 0.9rem; display: none; line-height: 1.4; }\n .msg.err { display: block; color: var(--error); background: var(--error-bg);\n border: 1px solid rgba(252,165,165,0.16); }\n .msg.ok { display: block; color: var(--ok); background: var(--ok-bg);\n border: 1px solid var(--ok-border); }\n .result-panel { padding-top: 0.15rem; }\n .result-title {\n color: var(--text); font-size: 0.95rem; font-weight: 650;\n text-align: center; margin-bottom: 0.35rem;\n }\n .result-copy {\n color: var(--muted); font-size: 0.83rem; line-height: 1.45;\n text-align: center; margin: 0 auto 0.85rem; max-width: 34ch;\n }\n .section-label {\n color: var(--subtle); font-size: 0.7rem; font-weight: 650;\n letter-spacing: 0.08em; text-transform: uppercase; margin-top: 0.85rem;\n }\n @media (max-width: 480px) {\n body { align-items: flex-start; padding: 0.75rem; }\n .card { padding: 1rem; }\n .hero { padding: 0; }\n .topbar { margin-bottom: 1.35rem; }\n h1 { font-size: 1.3rem; }\n .app-pill { max-width: 46%; }\n pre { font-size: 0.72rem; }\n }\n /* MCP URL display + per-host tabs (the non-dev path). */\n .mcp-url-block { margin: 0 0 1rem; }\n .url-row {\n display: flex; align-items: center; gap: 0.5rem;\n background: var(--panel-2); border: 1px solid var(--border-strong);\n border-radius: 8px; padding: 0.45rem 0.5rem 0.45rem 0.75rem;\n }\n .url-row code {\n flex: 1 1 auto; min-width: 0; overflow-x: auto; white-space: nowrap;\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.78rem; color: var(--text);\n }\n .url-row .ghost { flex: 0 0 auto; }\n .hosts { margin: 0 0 1rem; }\n .tabs {\n display: flex; flex-wrap: wrap; gap: 0.25rem;\n border-bottom: 1px solid var(--border); margin-bottom: 0.75rem;\n padding-bottom: 0.4rem;\n }\n .tab {\n background: transparent; color: var(--subtle);\n border: 1px solid transparent;\n padding: 0.35rem 0.65rem; font-size: 0.8rem; font-weight: 600;\n border-radius: 6px;\n }\n .tab:hover { color: var(--muted); background: var(--panel-soft); }\n .tab.is-active {\n color: var(--text); background: var(--panel-2);\n border-color: var(--border-strong);\n }\n .tab-panel { display: none; }\n .tab-panel.is-active { display: block; }\n .tab-panel ol { margin: 0 0 0.6rem 1.1rem; padding: 0; }\n .tab-panel li {\n margin-bottom: 0.3rem; font-size: 0.86rem; line-height: 1.5;\n color: var(--muted);\n }\n .tab-panel li strong { color: var(--text); font-weight: 650; }\n .tab-panel a {\n color: var(--text); text-decoration: underline;\n text-underline-offset: 2px;\n }\n .tab-panel p {\n font-size: 0.84rem; color: var(--muted); margin: 0.4rem 0;\n line-height: 1.5;\n }\n .tab-panel .hint {\n font-size: 0.78rem; color: var(--subtle); margin-top: 0.5rem;\n }\n .tab-panel code {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 0.78rem; color: var(--text);\n background: var(--panel-2); padding: 0.05rem 0.3rem;\n border-radius: 4px;\n }\n .tab-panel pre { margin: 0.4rem 0 0.5rem; }\n /* Per-tab primary CTA — visually distinct from the static-token mint\n * button below. Either a link (Open Claude →) or a copy command button.\n */\n .primary-link {\n display: inline-flex; align-items: center; justify-content: center;\n gap: 0.35rem; min-height: 36px; padding: 0.45rem 0.85rem;\n background: var(--accent); color: var(--accent-fg);\n border: 1px solid var(--accent); border-radius: 8px;\n font-size: 0.86rem; font-weight: 650; text-decoration: none;\n cursor: pointer; width: 100%; text-align: center;\n margin: 0.5rem 0 0.2rem;\n }\n .primary-link:hover { background: #e4e4e7; border-color: #e4e4e7; }\n .primary-link.compact { width: auto; min-width: 0; }\n .copy-flash {\n color: var(--ok) !important;\n border-color: var(--ok-border) !important;\n }\n .static-token-mint .static-token-body { padding-top: 0.5rem; }\n .static-token-mint > summary .connections-state {\n font-style: normal;\n }\n @media (min-width: 560px) {\n .card { max-width: 580px; }\n }\n .hidden { display: none !important; }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <div class=\"topbar\">\n <div class=\"brand-lockup\">\n ${brandMarkSvg}\n <span>Agent Native</span>\n </div>\n <div class=\"app-pill\" title=\"${safeApp}\">${safeApp}</div>\n </div>\n\n <div class=\"hero\">\n <!-- \"Connect an external agent\" is kept as the accessible consent label. -->\n <div class=\"flow\" role=\"img\" aria-label=\"Connect an external agent to ${safeApp}\">\n <span class=\"tile\" aria-hidden=\"true\">\n ${flowMarkSvg}\n </span>\n <span class=\"conn\" aria-hidden=\"true\"></span>\n <span class=\"tile\" aria-hidden=\"true\">\n <span class=\"agent-symbol\">&lt;/&gt;</span>\n </span>\n </div>\n\n <div class=\"eyebrow\">Connect an external agent</div>\n <h1>${safeUserCode ? `Authorize ${safeApp} from your terminal?` : `Use ${safeApp} from your AI assistant`}</h1>\n <p class=\"identity\">\n <span>Signed in as <strong>${safeEmail}</strong></span>\n </p>\n </div>\n\n <div id=\"codeCallout\" class=\"device-strip ${safeUserCode ? \"\" : \"hidden\"}\">\n <span class=\"label\">Device code</span>\n <span class=\"value\" id=\"userCodeValue\">${safeUserCode}</span>\n </div>\n\n <div class=\"mcp-url-block\">\n <div class=\"section-label\">Your MCP URL</div>\n <div class=\"url-row\">\n <code id=\"mcpUrlValue\">${safeMcpUrl}</code>\n <button type=\"button\" class=\"ghost\" data-copy=\"mcpUrlValue\" aria-label=\"Copy MCP URL\">Copy</button>\n </div>\n </div>\n\n <div class=\"hosts\">\n <div class=\"section-label\">Pick your AI assistant</div>\n <div class=\"tabs\" role=\"tablist\" aria-label=\"Choose your AI assistant\">\n <button type=\"button\" class=\"tab is-active\" role=\"tab\" data-tab=\"claude\" aria-selected=\"true\">Claude</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"chatgpt\" aria-selected=\"false\">ChatGPT</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"cursor\" aria-selected=\"false\">Cursor</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"claude-code\" aria-selected=\"false\">Claude Code</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"codex\" aria-selected=\"false\">Codex</button>\n <button type=\"button\" class=\"tab\" role=\"tab\" data-tab=\"other\" aria-selected=\"false\">Other</button>\n </div>\n <div class=\"tab-panel is-active\" role=\"tabpanel\" data-panel=\"claude\">\n <ol>\n <li>Open <strong>Customize → Connectors</strong> in Claude.</li>\n <li>Click the <strong>+</strong> button → <strong>Add custom connector</strong>.</li>\n <li>Paste the MCP URL above, name it <strong>${safeApp}</strong>, click <strong>Connect</strong>.</li>\n <li>On the consent page, click <strong>Authorize</strong> to approve <code>mcp:read</code>, <code>mcp:write</code>, <code>mcp:apps</code>.</li>\n </ol>\n <a class=\"primary-link\" href=\"https://claude.ai/customize/connectors\" target=\"_blank\" rel=\"noopener noreferrer\">Open Claude → Connectors</a>\n <p class=\"hint\">Works in Claude web and Claude Desktop. Inline MCP Apps (charts, dashboards, drafts) render automatically inside the chat.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"chatgpt\">\n <ol>\n <li>In ChatGPT, open <strong>Settings → Apps</strong> (Business/Enterprise/Edu workspaces with developer mode enabled).</li>\n <li>Scroll to <strong>Advanced settings → Create app</strong>, paste the MCP URL above, name it <strong>${safeApp}</strong>.</li>\n <li>Click <strong>Connect</strong>, sign in with your Agent-Native account, and approve <code>mcp:read</code>, <code>mcp:write</code>, <code>mcp:apps</code>.</li>\n </ol>\n <a class=\"primary-link\" href=\"https://chatgpt.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Open ChatGPT</a>\n <p class=\"hint\"><strong>Got \"Connector name already exists\" but don't see it under Enabled apps?</strong> ChatGPT saves a hidden draft the moment you click Create — even if you closed the OAuth popup before approving. In <strong>Settings → Apps</strong>, scroll past Enabled apps to the <strong>Drafts</strong> section (\"Private apps you've created in developer mode\"). Click the draft and either press <strong>Connect</strong> to finish OAuth, or use the <strong>⋯ → Delete</strong> menu and re-create. Workspace admins may also need to enable custom connectors under org settings; each member still authorizes their own account.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"cursor\">\n <ol>\n <li>Open <strong>Cursor → Settings → MCP</strong>.</li>\n <li>Click <strong>Add MCP Server</strong>, paste the MCP URL above, save.</li>\n <li>When prompted, sign in with your Agent-Native account and approve the MCP scopes.</li>\n </ol>\n <p class=\"hint\">Cursor supports remote-OAuth MCP servers, same paste-URL flow as Claude — no terminal needed.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"claude-code\">\n <p>In your terminal, run:</p>\n <pre id=\"claudeCodeCmd\">${safeClaudeCodeCmd}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"claudeCodeCmd\">Copy command</button>\n <p class=\"hint\">Then inside Claude Code type <code>/mcp</code>, choose <strong>${safeServerId}</strong>, and click <strong>Authenticate</strong>. Claude completes the OAuth flow itself — no static token needed.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"codex\">\n <p>In your terminal, run:</p>\n <pre id=\"codexCmd\">${safeCodexCmd}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"codexCmd\">Copy command</button>\n <p class=\"hint\">Opens this page in your browser and writes Codex's <code>~/.codex/config.toml</code> automatically. The same command works for Claude Cowork and Goose.</p>\n </div>\n <div class=\"tab-panel\" role=\"tabpanel\" data-panel=\"other\">\n <p>Any MCP-compatible client with remote-OAuth support: paste the MCP URL above. For clients without OAuth, paste this <code>.mcp.json</code> snippet and generate a static bearer below:</p>\n <pre id=\"genericConfig\">${safeGenericConfig}</pre>\n <button type=\"button\" class=\"primary-link compact\" data-copy=\"genericConfig\">Copy config</button>\n </div>\n </div>\n\n <details id=\"staticTokenMint\" class=\"connections static-token-mint\"${safeUserCode ? \" open\" : \"\"}>\n <summary>\n <span class=\"connections-title\">${safeUserCode ? \"Authorize this device\" : \"Generate a static token\"}</span>\n <span class=\"connections-state\">${safeUserCode ? \"From your terminal\" : \"Advanced — clients without OAuth\"}</span>\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"static-token-body\">\n <div id=\"msg\" class=\"msg\"></div>\n <div id=\"mintForm\">\n <button id=\"authorizeBtn\" class=\"primary\">${safeUserCode ? \"Authorize device\" : \"Create connection token\"}</button>\n <details class=\"advanced\">\n <summary>\n Advanced options\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"advanced-body\">\n <div class=\"field\">\n <label for=\"label\">Label (optional)</label>\n <input id=\"label\" type=\"text\" placeholder=\"e.g. Claude Code on my laptop\" maxlength=\"120\" />\n </div>\n <div class=\"field\">\n <label for=\"ttl\">Expires in (days, 1–365)</label>\n <input id=\"ttl\" type=\"number\" min=\"1\" max=\"365\" value=\"${DEFAULT_TOKEN_TTL_DAYS}\" />\n </div>\n </div>\n </details>\n </div>\n <div id=\"result\" class=\"result-panel hidden\">\n <div class=\"result-title\">Connection token created</div>\n <p class=\"result-copy\" id=\"resultMsg\">Paste this into your agent's MCP config. The token is shown only once.</p>\n <div class=\"section-label\">MCP config</div>\n <pre id=\"mcpJson\"></pre>\n <details class=\"advanced\">\n <summary>\n Terminal alternative\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div class=\"advanced-body\">\n <pre id=\"cliLine\"></pre>\n </div>\n </details>\n </div>\n </div>\n </details>\n\n <details id=\"connections\" class=\"connections\">\n <summary>\n <span class=\"connections-title\">Existing connections</span>\n <span id=\"connectionsState\" class=\"connections-state\">Checking</span>\n <span class=\"chev\" aria-hidden=\"true\"></span>\n </summary>\n <div id=\"tokenList\" class=\"token-list\"><div class=\"empty-state\">Checking connections...</div></div>\n </details>\n</div>\n<script>\n(function () {\n var BASE = ${JSON.stringify(joinAppPath(connectBasePath, \"/_agent-native/mcp/connect\"))};\n var USER_CODE = ${JSON.stringify(safeUserCode || null)};\n var msgEl = document.getElementById(\"msg\");\n var connectionsEl = document.getElementById(\"connections\");\n var connectionsStateEl = document.getElementById(\"connectionsState\");\n\n // Tab switching for the per-host instructions block.\n var tabBtns = document.querySelectorAll(\".tabs .tab\");\n var tabPanels = document.querySelectorAll(\".tab-panel\");\n for (var i = 0; i < tabBtns.length; i++) {\n tabBtns[i].addEventListener(\"click\", function (ev) {\n var btn = ev.currentTarget;\n var name = btn.getAttribute(\"data-tab\");\n for (var j = 0; j < tabBtns.length; j++) {\n var active = tabBtns[j] === btn;\n tabBtns[j].classList.toggle(\"is-active\", active);\n tabBtns[j].setAttribute(\"aria-selected\", active ? \"true\" : \"false\");\n }\n for (var k = 0; k < tabPanels.length; k++) {\n tabPanels[k].classList.toggle(\n \"is-active\",\n tabPanels[k].getAttribute(\"data-panel\") === name,\n );\n }\n });\n }\n\n // Copy buttons — any element with data-copy=\"<id>\" copies that node's text.\n document.addEventListener(\"click\", function (ev) {\n var btn = ev.target && ev.target.closest && ev.target.closest(\"[data-copy]\");\n if (!btn) return;\n var node = document.getElementById(btn.getAttribute(\"data-copy\"));\n if (!node || !navigator.clipboard) return;\n navigator.clipboard.writeText(node.textContent || \"\").then(function () {\n var prev = btn.textContent;\n btn.textContent = \"Copied\";\n btn.classList.add(\"copy-flash\");\n setTimeout(function () {\n btn.textContent = prev;\n btn.classList.remove(\"copy-flash\");\n }, 1400);\n });\n });\n function showMsg(text, kind) {\n msgEl.textContent = text;\n msgEl.className = \"msg \" + (kind || \"err\");\n }\n function clearMsg() { msgEl.className = \"msg\"; msgEl.textContent = \"\"; }\n\n function renderResult(data) {\n document.getElementById(\"mintForm\").classList.add(\"hidden\");\n var entry = {};\n entry[data.serverName] = data.mcpServerEntry;\n document.getElementById(\"mcpJson\").textContent =\n JSON.stringify({ mcpServers: entry }, null, 2);\n document.getElementById(\"cliLine\").textContent = data.cli;\n document.getElementById(\"result\").classList.remove(\"hidden\");\n }\n\n async function postJson(path, body) {\n var res = await fetch(BASE + path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"same-origin\",\n body: JSON.stringify(body || {})\n });\n var data = null;\n try { data = await res.json(); } catch (e) {}\n return { ok: res.ok, status: res.status, data: data };\n }\n\n async function loadTokens() {\n var listEl = document.getElementById(\"tokenList\");\n try {\n var res = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (!res.ok) {\n connectionsStateEl.textContent = \"Unavailable\";\n listEl.innerHTML = '<div class=\"empty-state\">Could not load connections.</div>';\n return;\n }\n var data = await res.json();\n var tokens = (data && data.tokens) || [];\n if (!tokens.length) {\n connectionsStateEl.textContent = \"None\";\n connectionsEl.open = false;\n listEl.innerHTML = '<div class=\"empty-state\">Created connections will appear here for revoking later.</div>';\n return;\n }\n var activeCount = tokens.filter(function (t) { return !t.revokedAt; }).length;\n connectionsStateEl.textContent = activeCount === 1 ? \"1 active\" : activeCount + \" active\";\n listEl.innerHTML = \"\";\n tokens.forEach(function (t) {\n var div = document.createElement(\"div\");\n div.className = \"tok\" + (t.revokedAt ? \" revoked\" : \"\");\n var when = t.createdAt ? new Date(t.createdAt).toLocaleString() : \"\";\n var used = t.lastUsedAt ? \" · last used \" + new Date(t.lastUsedAt).toLocaleString() : \"\";\n var left = document.createElement(\"div\");\n var label = document.createElement(\"div\");\n label.textContent = t.label || \"(unlabeled)\";\n var meta = document.createElement(\"div\");\n meta.className = \"meta\";\n meta.textContent = (t.revokedAt ? \"Revoked · \" : \"Created \") + when + used;\n left.appendChild(label); left.appendChild(meta);\n div.appendChild(left);\n if (!t.revokedAt) {\n var btn = document.createElement(\"button\");\n btn.className = \"ghost\";\n btn.textContent = \"Revoke\";\n btn.onclick = async function () {\n btn.disabled = true;\n var r = await postJson(\"/tokens/revoke\", { id: t.id });\n if (r.ok) { loadTokens(); }\n else { btn.disabled = false; showMsg(\"Could not revoke token.\"); }\n };\n div.appendChild(btn);\n }\n listEl.appendChild(div);\n });\n } catch (e) {\n connectionsStateEl.textContent = \"Unavailable\";\n listEl.innerHTML = '<div class=\"empty-state\">Could not load connections.</div>';\n }\n }\n\n document.getElementById(\"authorizeBtn\").onclick = async function () {\n var btn = this;\n btn.disabled = true;\n clearMsg();\n var label = document.getElementById(\"label\").value || undefined;\n var ttlDays = parseInt(document.getElementById(\"ttl\").value, 10) || undefined;\n try {\n if (USER_CODE) {\n var a = await postJson(\"/device/authorize\", { user_code: USER_CODE });\n if (!a.ok) {\n btn.disabled = false;\n showMsg((a.data && a.data.error) || \"Could not authorize this device code.\");\n return;\n }\n showMsg(\"Device authorized — finishing connection… you can return to your terminal.\", \"ok\");\n btn.classList.add(\"hidden\");\n document.getElementById(\"mintForm\").classList.add(\"hidden\");\n var cc = document.getElementById(\"codeCallout\");\n if (cc) cc.classList.add(\"hidden\");\n // The token is minted a few seconds later, when the CLI next polls\n // /device/poll — so a single loadTokens() here runs BEFORE the row\n // exists and the list would wrongly read \"No connections yet\" until\n // a manual reload. Snapshot the EXISTING non-revoked token ids first\n // so we announce \"Connected\" only when THIS device's freshly-minted\n // token appears — a user who already has tokens must not get a false\n // success the instant they authorize.\n var priorIds = {};\n try {\n var pr = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (pr.ok) {\n var pd = await pr.json();\n ((pd && pd.tokens) || []).forEach(function (t) {\n if (!t.revokedAt) priorIds[t.id] = true;\n });\n }\n } catch (e) {}\n loadTokens();\n var tries = 0;\n var iv = setInterval(async function () {\n tries++;\n try {\n var res = await fetch(BASE + \"/tokens\", { credentials: \"same-origin\" });\n if (res.ok) {\n var data = await res.json();\n var fresh = ((data && data.tokens) || []).filter(function (t) {\n return !t.revokedAt && !priorIds[t.id];\n });\n if (fresh.length > 0) {\n clearInterval(iv);\n showMsg(\"Connected. This device can now act as you — manage or revoke it below.\", \"ok\");\n loadTokens();\n return;\n }\n }\n } catch (e) {}\n if (tries >= 30) {\n // No new token appeared in the window — e.g. the loopback\n // dev-open path writes a header-only config and never mints.\n // Don't claim \"Connected\" (we couldn't confirm a device token);\n // keep the \"authorized\" message and just refresh the list.\n clearInterval(iv);\n loadTokens();\n }\n }, 2000);\n return;\n } else {\n var m = await postJson(\"/token\", { label: label, ttlDays: ttlDays });\n if (!m.ok) {\n btn.disabled = false;\n showMsg((m.data && m.data.error) || \"Could not create token.\");\n return;\n }\n renderResult(m.data);\n }\n loadTokens();\n } catch (e) {\n btn.disabled = false;\n showMsg(\"Network error. Please try again.\");\n }\n };\n\n loadTokens();\n})();\n</script>\n</body>\n</html>`;\n}\n\n// ---------------------------------------------------------------------------\n// Handler — single entry point; core-routes-plugin dispatches the subpath.\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a `/_agent-native/mcp/connect[...]` request. `subpath` is the part\n * after `/connect` (empty string = the page itself, otherwise e.g.\n * `/token`, `/device/start`). The core-routes-plugin computes it from the\n * stripped event path so this module stays mount-agnostic.\n */\nexport async function handleMcpConnect(\n event: H3Event,\n subpath: string,\n options: McpConnectRouteOptions = {},\n): Promise<Response> {\n const method = getMethod(event);\n const origin = deriveOrigin(event);\n const basePath = configuredBasePath();\n const appUrl = `${origin}${basePath}`;\n const sub = (\"/\" + subpath.replace(/^\\/+/, \"\").replace(/\\/+$/, \"\")).replace(\n /^\\/$/,\n \"\",\n );\n\n // ---- The connect page (GET) ------------------------------------------\n if (sub === \"\") {\n if (method !== \"GET\" && method !== \"HEAD\") {\n return json({ error: \"Method not allowed\" }, 405);\n }\n const session = await getSession(event);\n if (!session?.email) {\n // Serve the SAME login form the guard would, at this same URL — the\n // login form reloads window.location so we re-enter here authed.\n const loginHtml = getConfiguredLoginHtml(event);\n if (loginHtml) return html(loginHtml, 200);\n // Fully-open app (no auth guard): nothing to scope a mint to.\n return html(\n renderConnectPage({\n connectBasePath: basePath,\n email: \"(no auth configured)\",\n appName: options.appName || appLabel(appUrl, options),\n appUrl,\n serverId: serverName(appUrl, options),\n userCode: null,\n }),\n );\n }\n let userCode: string | null = null;\n try {\n const u = new URL(\n event.node?.req?.url ?? event.path ?? \"/\",\n \"http://an.invalid\",\n );\n const raw = u.searchParams.get(\"user_code\");\n if (raw && USER_CODE_RE.test(raw)) userCode = raw;\n } catch {\n userCode = null;\n }\n return html(\n renderConnectPage({\n connectBasePath: basePath,\n email: session.email,\n appName: options.appName || appLabel(appUrl, options),\n appUrl,\n serverId: serverName(appUrl, options),\n userCode,\n }),\n );\n }\n\n // ---- POST /token (session-required) ---------------------------------\n if (sub === \"/token\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n if (!process.env.A2A_SECRET) {\n if (canUseDevOpenConnect(event)) {\n return json(\n mcpResultPayload(appUrl, options, { ownerEmail: session.email }),\n );\n }\n return json(\n {\n error:\n \"This deployment has no A2A_SECRET configured, so connect tokens cannot be minted.\",\n },\n 503,\n );\n }\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n label?: unknown;\n ttlDays?: unknown;\n };\n const label =\n typeof body.label === \"string\" && body.label.trim()\n ? body.label.trim().slice(0, 120)\n : null;\n const ttlDays = clampTtlDays(body.ttlDays);\n try {\n const { token } = await mintConnectToken({\n email: session.email,\n orgId: session.orgId,\n label,\n ttlDays,\n });\n return json(mcpResultPayload(appUrl, options, { token }));\n } catch {\n return json({ error: \"Failed to mint token.\" }, 500);\n }\n }\n\n // ---- POST /device/start (UNAUTH) ------------------------------------\n if (sub === \"/device/start\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n try {\n const row = await createDeviceCode();\n const verificationUri = `${appUrl}/_agent-native/mcp/connect`;\n return json({\n device_code: row.deviceCode,\n user_code: row.userCode,\n verification_uri: verificationUri,\n verification_uri_complete: `${verificationUri}?user_code=${row.userCode}`,\n interval: DEVICE_POLL_INTERVAL_S,\n expires_in: Math.floor(DEVICE_CODE_TTL_MS / 1000),\n });\n } catch (err: any) {\n if (err?.message === \"RATE_LIMITED\") {\n return json({ error: \"Rate limited. Try again shortly.\" }, 429);\n }\n return json({ error: \"Could not start device flow.\" }, 500);\n }\n }\n\n // ---- POST /device/authorize (session-required) ----------------------\n if (sub === \"/device/authorize\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n user_code?: unknown;\n };\n const userCode =\n typeof body.user_code === \"string\" ? body.user_code.trim() : \"\";\n if (!USER_CODE_RE.test(userCode)) {\n return json({ error: \"Invalid user code.\" }, 400);\n }\n const orgId =\n typeof session.orgId === \"string\" && session.orgId.trim()\n ? session.orgId.trim()\n : null;\n const result = await approveDeviceCode(userCode, session.email, orgId);\n if (result === \"not_found\") {\n return json({ error: \"Unknown device code.\" }, 404);\n }\n if (result === \"expired\") {\n return json({ error: \"This device code has expired.\" }, 410);\n }\n if (result === \"already\") {\n return json({ error: \"This device code was already used.\" }, 409);\n }\n return json({ status: \"approved\" });\n }\n\n // ---- POST /device/poll (UNAUTH) -------------------------------------\n if (sub === \"/device/poll\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n device_code?: unknown;\n };\n const deviceCode =\n typeof body.device_code === \"string\" ? body.device_code : \"\";\n if (!deviceCode) return json({ error: \"device_code required\" }, 400);\n const row = await getDeviceCode(deviceCode);\n if (!row) return json({ status: \"not_found\" }, 404);\n if (row.status === \"consumed\") return json({ status: \"consumed\" });\n if (\n row.status === \"expired\" ||\n (row.expiresAt != null && row.expiresAt < Date.now())\n ) {\n if (row.status !== \"expired\") void expireDeviceCode(deviceCode);\n return json({ status: \"expired\" });\n }\n if (\n row.status === \"pending\" ||\n row.status === \"minting\" ||\n !row.ownerEmail\n ) {\n return json({ status: \"pending\" });\n }\n // status === \"approved\" && ownerEmail bound → mint exactly once.\n if (!process.env.A2A_SECRET) {\n if (canUseDevOpenConnect(event)) {\n const consumed = await consumeDeviceCode(\n deviceCode,\n `dev-open-${randomUUID()}`,\n );\n if (!consumed) {\n const fresh = await getDeviceCode(deviceCode);\n if (fresh?.status === \"consumed\") return json({ status: \"consumed\" });\n return json({ status: \"pending\" });\n }\n return json({\n status: \"approved\",\n ...mcpResultPayload(appUrl, options, {\n ownerEmail: row.ownerEmail,\n }),\n });\n }\n return json({ status: \"error\", error: \"A2A_SECRET not configured\" }, 503);\n }\n try {\n const jti = randomUUID();\n // Claim a retryable minting state first. If signing or recording fails,\n // release the row back to approved so the CLI can poll again.\n const claimed = await claimDeviceCodeForMint(deviceCode, jti);\n if (!claimed) {\n const fresh = await getDeviceCode(deviceCode);\n if (fresh?.status === \"consumed\") return json({ status: \"consumed\" });\n return json({ status: \"pending\" });\n }\n let token: string;\n try {\n const orgDomain = await resolveOrgDomain(claimed.orgId ?? undefined);\n token = await signA2AToken(claimed.ownerEmail!, orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: `${DEFAULT_TOKEN_TTL_DAYS}d`,\n extraClaims: { jti, scope: MCP_CONNECT_SCOPE },\n });\n await recordMintedToken({\n jti,\n ownerEmail: claimed.ownerEmail!,\n orgId: claimed.orgId,\n label: \"Device connection\",\n });\n if (!(await finishDeviceCodeMint(deviceCode, jti))) {\n return json({ status: \"pending\" });\n }\n } catch (err) {\n await releaseDeviceCodeMint(deviceCode, jti);\n throw err;\n }\n return json({\n status: \"approved\",\n ...mcpResultPayload(appUrl, options, { token }),\n });\n } catch {\n return json({ status: \"error\", error: \"Failed to mint token.\" }, 500);\n }\n }\n\n // ---- GET /tokens (session-required) ---------------------------------\n if (sub === \"/tokens\") {\n if (method !== \"GET\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const rows = await listTokens(session.email);\n return json({\n tokens: rows.map((r) => ({\n id: r.id,\n label: r.label,\n createdAt: r.createdAt,\n lastUsedAt: r.lastUsedAt,\n revokedAt: r.revokedAt,\n })),\n });\n }\n\n // ---- POST /tokens/revoke (session-required) -------------------------\n if (sub === \"/tokens/revoke\") {\n if (method !== \"POST\") return json({ error: \"Method not allowed\" }, 405);\n const session = await getSession(event);\n if (!session?.email) return json({ error: \"Unauthorized\" }, 401);\n const body = ((await readBody(event).catch(() => ({}))) ?? {}) as {\n id?: unknown;\n };\n const id = typeof body.id === \"string\" ? body.id : \"\";\n if (!id) return json({ error: \"id required\" }, 400);\n const revoked = await revokeToken(session.email, id);\n return json({ ok: revoked });\n }\n\n return json({ error: \"Not found\" }, 404);\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ActionMcpAppResourceConfig } from "../action.js";
2
+ export declare const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = "$requestOrigin";
3
+ export interface EmbedAppOptions {
4
+ title?: string;
5
+ description?: string;
6
+ iframeTitle?: string;
7
+ openLabel?: string;
8
+ embedByDefault?: boolean;
9
+ startToolName?: string;
10
+ frameDomains?: string[];
11
+ height?: number;
12
+ }
13
+ export declare function embedApp(options?: EmbedAppOptions): ActionMcpAppResourceConfig;
14
+ //# sourceMappingURL=embed-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed-app.d.ts","sourceRoot":"","sources":["../../src/mcp/embed-app.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAK/D,eAAO,MAAM,iCAAiC,mBAAmB,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAUD,wBAAgB,QAAQ,CACtB,OAAO,GAAE,eAAoB,GAC5B,0BAA0B,CAqL5B"}
@@ -0,0 +1,191 @@
1
+ const MCP_APP_IMPORT = "https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps";
2
+ export const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = "$requestOrigin";
3
+ function attr(value) {
4
+ return String(value ?? "")
5
+ .replace(/&/g, "&amp;")
6
+ .replace(/"/g, "&quot;")
7
+ .replace(/</g, "&lt;")
8
+ .replace(/>/g, "&gt;");
9
+ }
10
+ export function embedApp(options = {}) {
11
+ const title = options.title ?? "Open app";
12
+ const iframeTitle = options.iframeTitle ?? "Agent Native app";
13
+ const openLabel = options.openLabel ?? "Open in app";
14
+ const startToolName = options.startToolName ?? "create_embed_session";
15
+ const embedByDefault = options.embedByDefault !== false;
16
+ const height = Math.max(320, Math.min(680, options.height ?? 640));
17
+ return {
18
+ title,
19
+ ...(options.description ? { description: options.description } : {}),
20
+ html: () => `<!doctype html>
21
+ <html lang="en">
22
+ <head>
23
+ <meta charset="utf-8">
24
+ <meta name="viewport" content="width=device-width, initial-scale=1">
25
+ <style>
26
+ :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: Canvas; color: CanvasText; }
27
+ * { box-sizing: border-box; }
28
+ body { margin: 0; }
29
+ .shell { display: grid; gap: 8px; min-height: ${height}px; padding: 0; }
30
+ .bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 6px 8px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, Canvas); }
31
+ .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }
32
+ .actions { display: flex; align-items: center; gap: 6px; }
33
+ button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }
34
+ button:disabled { opacity: .55; cursor: default; }
35
+ .stage { position: relative; min-height: ${height - 44}px; }
36
+ iframe { display: block; width: 100%; height: ${height - 44}px; border: 0; background: Canvas; }
37
+ .message { display: grid; place-items: center; min-height: ${height - 44}px; padding: 18px; color: color-mix(in srgb, CanvasText 62%, Canvas); font-size: 13px; line-height: 1.45; text-align: center; }
38
+ </style>
39
+ </head>
40
+ <body
41
+ data-title="${attr(title)}"
42
+ data-iframe-title="${attr(iframeTitle)}"
43
+ data-open-label="${attr(openLabel)}"
44
+ data-start-tool="${attr(startToolName)}"
45
+ data-embed-default="${embedByDefault ? "1" : "0"}"
46
+ >
47
+ <main class="shell">
48
+ <div class="bar">
49
+ <div class="title" data-title>${attr(title)}</div>
50
+ <div class="actions">
51
+ <button type="button" data-open disabled>${attr(openLabel)}</button>
52
+ </div>
53
+ </div>
54
+ <section class="stage" data-stage>
55
+ <div class="message">Preparing app</div>
56
+ </section>
57
+ </main>
58
+ <script type="module">
59
+ import { App } from "${MCP_APP_IMPORT}";
60
+
61
+ const app = new App({ name: "Agent Native Embed", version: "1.0.0" }, {});
62
+ const body = document.body;
63
+ const stage = document.querySelector("[data-stage]");
64
+ const titleEl = document.querySelector("[data-title]");
65
+ const openButton = document.querySelector("[data-open]");
66
+ const startTool = body.dataset.startTool || "create_embed_session";
67
+ const embedByDefault = body.dataset.embedDefault !== "0";
68
+ let toolInput = {};
69
+ let openUrl = "";
70
+ let startedFor = "";
71
+
72
+ function esc(value) {
73
+ return String(value ?? "")
74
+ .replace(/&/g, "&amp;")
75
+ .replace(/</g, "&lt;")
76
+ .replace(/>/g, "&gt;")
77
+ .replace(/"/g, "&quot;");
78
+ }
79
+
80
+ function parseJson(value, fallback) {
81
+ if (value && typeof value === "object") return value;
82
+ if (typeof value !== "string" || !value.trim()) return fallback;
83
+ try { return JSON.parse(value); } catch { return fallback; }
84
+ }
85
+
86
+ function parseToolResult(params) {
87
+ if (!params) return {};
88
+ if (params.structuredContent && typeof params.structuredContent === "object") {
89
+ return params.structuredContent;
90
+ }
91
+ const parts = Array.isArray(params.content) ? params.content : [];
92
+ const textPart = parts.find((part) => part && part.type === "text" && typeof part.text === "string");
93
+ return parseJson(textPart ? textPart.text : "", {});
94
+ }
95
+
96
+ function openLinkFrom(params, data) {
97
+ const metaUrl = params && params._meta && params._meta["agent-native/openLink"]
98
+ ? params._meta["agent-native/openLink"].webUrl
99
+ : "";
100
+ return metaUrl || data.url || data.deepLink || data.openUrl || "";
101
+ }
102
+
103
+ function wantsEmbed() {
104
+ if (toolInput.embed === false) return false;
105
+ if (toolInput.embed === true) return true;
106
+ return embedByDefault;
107
+ }
108
+
109
+ function setMessage(message) {
110
+ stage.innerHTML = '<div class="message">' + esc(message) + '</div>';
111
+ }
112
+
113
+ function renderFrame(src) {
114
+ const frame = document.createElement("iframe");
115
+ frame.title = body.dataset.iframeTitle || "Agent Native app";
116
+ frame.src = src;
117
+ frame.allow = "clipboard-read; clipboard-write";
118
+ stage.replaceChildren(frame);
119
+ }
120
+
121
+ async function launchEmbed() {
122
+ if (!openUrl) {
123
+ setMessage("Open link was not available.");
124
+ return;
125
+ }
126
+ if (!wantsEmbed()) {
127
+ setMessage("Ready to open.");
128
+ return;
129
+ }
130
+ if (startedFor === openUrl) return;
131
+ startedFor = openUrl;
132
+ setMessage("Loading app");
133
+ try {
134
+ const result = await app.callServerTool({
135
+ name: startTool,
136
+ arguments: {
137
+ url: openUrl,
138
+ chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
139
+ }
140
+ });
141
+ const data = parseToolResult(result);
142
+ if (!data.startUrl) {
143
+ startedFor = "";
144
+ setMessage(data.error || "This app can be opened, but not embedded from this MCP server.");
145
+ return;
146
+ }
147
+ renderFrame(data.startUrl);
148
+ } catch (err) {
149
+ startedFor = "";
150
+ setMessage(err && err.message ? err.message : "Could not launch embedded app.");
151
+ }
152
+ }
153
+
154
+ function updateOpenButton() {
155
+ openButton.disabled = !openUrl;
156
+ openButton.onclick = () => {
157
+ if (openUrl) void app.openLink({ url: openUrl });
158
+ };
159
+ }
160
+
161
+ function updateTitle(data) {
162
+ const label = data.label || data.app || data.view || body.dataset.title || "App";
163
+ titleEl.textContent = String(label);
164
+ }
165
+
166
+ app.ontoolinput = (params) => {
167
+ toolInput = params.arguments || {};
168
+ };
169
+ app.ontoolresult = (params) => {
170
+ const data = parseToolResult(params);
171
+ openUrl = openLinkFrom(params, data);
172
+ updateTitle(data);
173
+ updateOpenButton();
174
+ void launchEmbed();
175
+ };
176
+ await app.connect();
177
+ </script>
178
+ </body>
179
+ </html>`,
180
+ csp: {
181
+ connectDomains: ["https://esm.sh"],
182
+ resourceDomains: ["https://esm.sh"],
183
+ frameDomains: [
184
+ MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
185
+ ...(options.frameDomains ?? []),
186
+ ],
187
+ },
188
+ prefersBorder: false,
189
+ };
190
+ }
191
+ //# sourceMappingURL=embed-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed-app.js","sourceRoot":"","sources":["../../src/mcp/embed-app.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAClB,mEAAmE,CAAC;AAEtE,MAAM,CAAC,MAAM,iCAAiC,GAAG,gBAAgB,CAAC;AAalE,SAAS,IAAI,CAAC,KAAyB;IACrC,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,UAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC;IAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IACrD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACtE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,KAAK,KAAK,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC;IAEnE,OAAO;QACL,KAAK;QACL,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,IAAI,EAAE,GAAG,EAAE,CAAC;;;;;;;;;oDASoC,MAAM;;;;;;+CAMX,MAAM,GAAG,EAAE;oDACN,MAAM,GAAG,EAAE;iEACE,MAAM,GAAG,EAAE;;;;gBAI5D,IAAI,CAAC,KAAK,CAAC;uBACJ,IAAI,CAAC,WAAW,CAAC;qBACnB,IAAI,CAAC,SAAS,CAAC;qBACf,IAAI,CAAC,aAAa,CAAC;wBAChB,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;;;;sCAIZ,IAAI,CAAC,KAAK,CAAC;;mDAEE,IAAI,CAAC,SAAS,CAAC;;;;;;;;2BAQvC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwHjC;QACJ,GAAG,EAAE;YACH,cAAc,EAAE,CAAC,gBAAgB,CAAC;YAClC,eAAe,EAAE,CAAC,gBAAgB,CAAC;YACnC,YAAY,EAAE;gBACZ,iCAAiC;gBACjC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;aAChC;SACF;QACD,aAAa,EAAE,KAAK;KACrB,CAAC;AACJ,CAAC","sourcesContent":["import type { ActionMcpAppResourceConfig } from \"../action.js\";\n\nconst MCP_APP_IMPORT =\n \"https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps\";\n\nexport const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = \"$requestOrigin\";\n\nexport interface EmbedAppOptions {\n title?: string;\n description?: string;\n iframeTitle?: string;\n openLabel?: string;\n embedByDefault?: boolean;\n startToolName?: string;\n frameDomains?: string[];\n height?: number;\n}\n\nfunction attr(value: string | undefined): string {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nexport function embedApp(\n options: EmbedAppOptions = {},\n): ActionMcpAppResourceConfig {\n const title = options.title ?? \"Open app\";\n const iframeTitle = options.iframeTitle ?? \"Agent Native app\";\n const openLabel = options.openLabel ?? \"Open in app\";\n const startToolName = options.startToolName ?? \"create_embed_session\";\n const embedByDefault = options.embedByDefault !== false;\n const height = Math.max(320, Math.min(680, options.height ?? 640));\n\n return {\n title,\n ...(options.description ? { description: options.description } : {}),\n html: () => `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style>\n :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: Canvas; color: CanvasText; }\n * { box-sizing: border-box; }\n body { margin: 0; }\n .shell { display: grid; gap: 8px; min-height: ${height}px; padding: 0; }\n .bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 6px 8px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, Canvas); }\n .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }\n .actions { display: flex; align-items: center; gap: 6px; }\n button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }\n button:disabled { opacity: .55; cursor: default; }\n .stage { position: relative; min-height: ${height - 44}px; }\n iframe { display: block; width: 100%; height: ${height - 44}px; border: 0; background: Canvas; }\n .message { display: grid; place-items: center; min-height: ${height - 44}px; padding: 18px; color: color-mix(in srgb, CanvasText 62%, Canvas); font-size: 13px; line-height: 1.45; text-align: center; }\n </style>\n</head>\n<body\n data-title=\"${attr(title)}\"\n data-iframe-title=\"${attr(iframeTitle)}\"\n data-open-label=\"${attr(openLabel)}\"\n data-start-tool=\"${attr(startToolName)}\"\n data-embed-default=\"${embedByDefault ? \"1\" : \"0\"}\"\n>\n <main class=\"shell\">\n <div class=\"bar\">\n <div class=\"title\" data-title>${attr(title)}</div>\n <div class=\"actions\">\n <button type=\"button\" data-open disabled>${attr(openLabel)}</button>\n </div>\n </div>\n <section class=\"stage\" data-stage>\n <div class=\"message\">Preparing app</div>\n </section>\n </main>\n <script type=\"module\">\n import { App } from \"${MCP_APP_IMPORT}\";\n\n const app = new App({ name: \"Agent Native Embed\", version: \"1.0.0\" }, {});\n const body = document.body;\n const stage = document.querySelector(\"[data-stage]\");\n const titleEl = document.querySelector(\"[data-title]\");\n const openButton = document.querySelector(\"[data-open]\");\n const startTool = body.dataset.startTool || \"create_embed_session\";\n const embedByDefault = body.dataset.embedDefault !== \"0\";\n let toolInput = {};\n let openUrl = \"\";\n let startedFor = \"\";\n\n function esc(value) {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n }\n\n function parseJson(value, fallback) {\n if (value && typeof value === \"object\") return value;\n if (typeof value !== \"string\" || !value.trim()) return fallback;\n try { return JSON.parse(value); } catch { return fallback; }\n }\n\n function parseToolResult(params) {\n if (!params) return {};\n if (params.structuredContent && typeof params.structuredContent === \"object\") {\n return params.structuredContent;\n }\n const parts = Array.isArray(params.content) ? params.content : [];\n const textPart = parts.find((part) => part && part.type === \"text\" && typeof part.text === \"string\");\n return parseJson(textPart ? textPart.text : \"\", {});\n }\n\n function openLinkFrom(params, data) {\n const metaUrl = params && params._meta && params._meta[\"agent-native/openLink\"]\n ? params._meta[\"agent-native/openLink\"].webUrl\n : \"\";\n return metaUrl || data.url || data.deepLink || data.openUrl || \"\";\n }\n\n function wantsEmbed() {\n if (toolInput.embed === false) return false;\n if (toolInput.embed === true) return true;\n return embedByDefault;\n }\n\n function setMessage(message) {\n stage.innerHTML = '<div class=\"message\">' + esc(message) + '</div>';\n }\n\n function renderFrame(src) {\n const frame = document.createElement(\"iframe\");\n frame.title = body.dataset.iframeTitle || \"Agent Native app\";\n frame.src = src;\n frame.allow = \"clipboard-read; clipboard-write\";\n stage.replaceChildren(frame);\n }\n\n async function launchEmbed() {\n if (!openUrl) {\n setMessage(\"Open link was not available.\");\n return;\n }\n if (!wantsEmbed()) {\n setMessage(\"Ready to open.\");\n return;\n }\n if (startedFor === openUrl) return;\n startedFor = openUrl;\n setMessage(\"Loading app\");\n try {\n const result = await app.callServerTool({\n name: startTool,\n arguments: {\n url: openUrl,\n chrome: typeof toolInput.chrome === \"string\" ? toolInput.chrome : \"full\"\n }\n });\n const data = parseToolResult(result);\n if (!data.startUrl) {\n startedFor = \"\";\n setMessage(data.error || \"This app can be opened, but not embedded from this MCP server.\");\n return;\n }\n renderFrame(data.startUrl);\n } catch (err) {\n startedFor = \"\";\n setMessage(err && err.message ? err.message : \"Could not launch embedded app.\");\n }\n }\n\n function updateOpenButton() {\n openButton.disabled = !openUrl;\n openButton.onclick = () => {\n if (openUrl) void app.openLink({ url: openUrl });\n };\n }\n\n function updateTitle(data) {\n const label = data.label || data.app || data.view || body.dataset.title || \"App\";\n titleEl.textContent = String(label);\n }\n\n app.ontoolinput = (params) => {\n toolInput = params.arguments || {};\n };\n app.ontoolresult = (params) => {\n const data = parseToolResult(params);\n openUrl = openLinkFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n void launchEmbed();\n };\n await app.connect();\n </script>\n</body>\n</html>`,\n csp: {\n connectDomains: [\"https://esm.sh\"],\n resourceDomains: [\"https://esm.sh\"],\n frameDomains: [\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.frameDomains ?? []),\n ],\n },\n prefersBorder: false,\n };\n}\n"]}
@@ -5,6 +5,7 @@ export type { MCPCallerIdentity, MCPRequestMeta } from "./build-server.js";
5
5
  export { runMCPStdio } from "./stdio.js";
6
6
  export type { RunMCPStdioOptions } from "./stdio.js";
7
7
  export { getBuiltinCrossAppTools } from "./builtin-tools.js";
8
+ export { embedApp, MCP_APP_REQUEST_ORIGIN_CSP_SOURCE, type EmbedAppOptions, } from "./embed-app.js";
8
9
  export { resolveWorkspace, resolveLocalAppOrigin, findWorkspaceRoot, } from "./workspace-resolve.js";
9
10
  export type { ResolvedApp, ResolvedWorkspace } from "./workspace-resolve.js";
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG7D,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7C,OAAO,EACL,yBAAyB,EACzB,UAAU,EACV,eAAe,EACf,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAG3E,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EACL,QAAQ,EACR,iCAAiC,EACjC,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/mcp/index.js CHANGED
@@ -5,6 +5,7 @@ export { createMCPServerForRequest, verifyAuth, getAccessTokens, resolveOrgIdFro
5
5
  export { runMCPStdio } from "./stdio.js";
6
6
  // Generic cross-app builtin tools (merged into the registry, template wins).
7
7
  export { getBuiltinCrossAppTools } from "./builtin-tools.js";
8
+ export { embedApp, MCP_APP_REQUEST_ORIGIN_CSP_SOURCE, } from "./embed-app.js";
8
9
  // Workspace / app resolution helpers (Node-only).
9
10
  export { resolveWorkspace, resolveLocalAppOrigin, findWorkspaceRoot, } from "./workspace-resolve.js";
10
11
  //# sourceMappingURL=index.js.map