@agent-native/core 0.30.3 → 0.30.5

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.
@@ -16,12 +16,11 @@
16
16
  * POST /connect/device/authorize (session) → binds user to the code
17
17
  * POST /connect/device/poll (unauth) → mints + returns the token
18
18
  *
19
- * The minted token reuses the existing A2A signer (`signA2AToken`) — no new
20
- * crypto. We only add a random `jti` + `scope: "mcp-connect"` claim so it can
21
- * be revoked. `verifyAuth` already verifies A2A_SECRET JWTs and extracts
22
- * `sub`/`org_domain`, so a minted token works against `/_agent-native/mcp`
23
- * with no verify changes for the happy path (the revoke check is the only
24
- * addition there).
19
+ * When A2A_SECRET exists, the minted token reuses the existing A2A signer
20
+ * (`signA2AToken`) and adds a random `jti` + `scope: "mcp-connect"` claim so
21
+ * it can be revoked. Deployments without A2A_SECRET mint the same standard MCP
22
+ * OAuth access-token format used by remote MCP OAuth, signed with the auth
23
+ * secret fallback and bound to the exact MCP resource URL.
25
24
  *
26
25
  * Node-only (crypto + the A2A signer), bundled alongside the other framework
27
26
  * routes. Dialect-agnostic SQL lives in `connect-store.ts`.
@@ -32,9 +31,11 @@ import { getSession, getConfiguredLoginHtml, isLoopbackRequest, } from "../serve
32
31
  import { signA2AToken } from "../a2a/client.js";
33
32
  import { getOrgDomain } from "../org/context.js";
34
33
  import { randomUUID } from "node:crypto";
35
- import { recordMintedToken, listTokens, revokeToken, createDeviceCode, getDeviceCode, approveDeviceCode, consumeDeviceCode, claimDeviceCodeForMint, finishDeviceCodeMint, releaseDeviceCodeMint, expireDeviceCode, MCP_CONNECT_SCOPE, DEFAULT_TOKEN_TTL_DAYS, MIN_TOKEN_TTL_DAYS, MAX_TOKEN_TTL_DAYS, DEVICE_CODE_TTL_MS, } from "./connect-store.js";
34
+ import { recordMintedToken, listTokens, revokeToken, createDeviceCode, getDeviceCode, approveDeviceCode, consumeDeviceCode, claimDeviceCodeForMint, finishDeviceCodeMint, releaseDeviceCodeMint, expireDeviceCode, MCP_CONNECT_OAUTH_CLIENT_ID, MCP_CONNECT_SCOPE, DEFAULT_TOKEN_TTL_DAYS, MIN_TOKEN_TTL_DAYS, MAX_TOKEN_TTL_DAYS, DEVICE_CODE_TTL_MS, } from "./connect-store.js";
35
+ import { MCP_OAUTH_DEFAULT_SCOPE, signMcpOAuthAccessToken, } from "./oauth-token.js";
36
36
  /** Device-flow poll interval hint (seconds). */
37
37
  const DEVICE_POLL_INTERVAL_S = 3;
38
+ const MCP_FULL_CATALOG_HEADER = "X-Agent-Native-MCP-Full-Catalog";
38
39
  // Human-typable user code: 8 base32 chars, dashed XXXX-XXXX.
39
40
  const USER_CODE_RE = /^[A-Z2-7]{4}-[A-Z2-7]{4}$/;
40
41
  function json(body, status = 200) {
@@ -143,21 +144,20 @@ function clampTtlDays(input) {
143
144
  return Math.min(MAX_TOKEN_TTL_DAYS, Math.max(MIN_TOKEN_TTL_DAYS, Math.floor(n)));
144
145
  }
145
146
  /**
146
- * Mint a connect-scoped JWT and record it. The JWT is signed by the existing
147
- * A2A signer (HS256 over A2A_SECRET); we add a random `jti` and
148
- * `scope: "mcp-connect"` so the token is individually revocable. The token
149
- * value is returned to the caller exactly once and never persisted.
147
+ * Mint a connect-scoped JWT and record it. The token value is returned to the
148
+ * caller exactly once and never persisted; only the random `jti` is stored for
149
+ * revocation.
150
150
  */
151
151
  async function mintConnectToken(params) {
152
152
  const orgDomain = await resolveOrgDomain(params.orgId);
153
153
  const jti = randomUUID();
154
- // signA2AToken signs { sub: email, org_domain? } over A2A_SECRET (global)
155
- // or the org secret. We extend its claims via the standard jose builder by
156
- // re-using the same signer with extra claims threaded through `options`.
157
- const token = await signA2AToken(params.email, orgDomain, undefined, {
158
- preferGlobalSecret: true,
154
+ const token = await signConnectToken({
155
+ ownerEmail: params.email,
156
+ orgId: params.orgId,
157
+ orgDomain,
158
+ appUrl: params.appUrl,
159
159
  expiresIn: `${params.ttlDays}d`,
160
- extraClaims: { jti, scope: MCP_CONNECT_SCOPE },
160
+ jti,
161
161
  });
162
162
  await recordMintedToken({
163
163
  jti,
@@ -167,8 +167,28 @@ async function mintConnectToken(params) {
167
167
  });
168
168
  return { token, jti };
169
169
  }
170
+ async function signConnectToken(params) {
171
+ if (process.env.A2A_SECRET?.trim()) {
172
+ return signA2AToken(params.ownerEmail, params.orgDomain, undefined, {
173
+ preferGlobalSecret: true,
174
+ expiresIn: params.expiresIn,
175
+ extraClaims: { jti: params.jti, scope: MCP_CONNECT_SCOPE },
176
+ });
177
+ }
178
+ return signMcpOAuthAccessToken({
179
+ ownerEmail: params.ownerEmail,
180
+ orgId: params.orgId ?? null,
181
+ orgDomain: params.orgDomain ?? null,
182
+ clientId: MCP_CONNECT_OAUTH_CLIENT_ID,
183
+ scope: MCP_OAUTH_DEFAULT_SCOPE,
184
+ resource: mcpResourceUrl(params.appUrl),
185
+ issuer: params.appUrl,
186
+ jti: params.jti,
187
+ expiresIn: params.expiresIn,
188
+ });
189
+ }
170
190
  function mcpResultPayload(appUrl, options, auth) {
171
- const mcpUrl = `${appUrl}/_agent-native/mcp`;
191
+ const mcpUrl = mcpResourceUrl(appUrl);
172
192
  const name = serverName(appUrl, options);
173
193
  const headers = {};
174
194
  if (auth.token)
@@ -176,6 +196,9 @@ function mcpResultPayload(appUrl, options, auth) {
176
196
  if (!auth.token && auth.ownerEmail) {
177
197
  headers["X-Agent-Native-Owner-Email"] = auth.ownerEmail;
178
198
  }
199
+ if (auth.token || auth.ownerEmail) {
200
+ headers[MCP_FULL_CATALOG_HEADER] = "1";
201
+ }
179
202
  return {
180
203
  token: auth.token ?? "",
181
204
  mcpUrl,
@@ -188,6 +211,9 @@ function mcpResultPayload(appUrl, options, auth) {
188
211
  cli: `agent-native connect ${appUrl}`,
189
212
  };
190
213
  }
214
+ function mcpResourceUrl(appUrl) {
215
+ return `${appUrl}/_agent-native/mcp`;
216
+ }
191
217
  // ---------------------------------------------------------------------------
192
218
  // Connect page (server-rendered HTML string)
193
219
  // ---------------------------------------------------------------------------
@@ -498,14 +524,19 @@ function renderConnectPage(params) {
498
524
  .primary-link {
499
525
  display: inline-flex; align-items: center; justify-content: center;
500
526
  gap: 0.35rem; min-height: 36px; padding: 0.45rem 0.85rem;
501
- background: var(--accent); color: var(--accent-fg);
502
- border: 1px solid var(--accent); border-radius: 8px;
527
+ background: var(--panel-2); color: var(--text);
528
+ border: 1px solid var(--border-strong); border-radius: 8px;
503
529
  font-size: 0.86rem; font-weight: 650; text-decoration: none;
504
- cursor: pointer; width: 100%; text-align: center;
530
+ cursor: pointer; width: auto; max-width: 100%; text-align: center;
505
531
  margin: 0.5rem 0 0.2rem;
506
532
  }
507
- .primary-link:hover { background: #e4e4e7; border-color: #e4e4e7; }
508
- .primary-link.compact { width: auto; min-width: 0; }
533
+ .tab-panel a.primary-link {
534
+ color: var(--text); text-decoration: none;
535
+ }
536
+ .primary-link:hover {
537
+ background: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.2);
538
+ }
539
+ .primary-link.compact { min-width: 0; }
509
540
  .copy-flash {
510
541
  color: var(--ok) !important;
511
542
  border-color: var(--ok-border) !important;
@@ -948,13 +979,8 @@ export async function handleMcpConnect(event, subpath, options = {}) {
948
979
  const session = await getSession(event);
949
980
  if (!session?.email)
950
981
  return json({ error: "Unauthorized" }, 401);
951
- if (!process.env.A2A_SECRET) {
952
- if (canUseDevOpenConnect(event)) {
953
- return json(mcpResultPayload(appUrl, options, { ownerEmail: session.email }));
954
- }
955
- return json({
956
- error: "This deployment has no A2A_SECRET configured, so connect tokens cannot be minted.",
957
- }, 503);
982
+ if (!process.env.A2A_SECRET?.trim() && canUseDevOpenConnect(event)) {
983
+ return json(mcpResultPayload(appUrl, options, { ownerEmail: session.email }));
958
984
  }
959
985
  const body = ((await readBody(event).catch(() => ({}))) ?? {});
960
986
  const label = typeof body.label === "string" && body.label.trim()
@@ -967,6 +993,7 @@ export async function handleMcpConnect(event, subpath, options = {}) {
967
993
  orgId: session.orgId,
968
994
  label,
969
995
  ttlDays,
996
+ appUrl,
970
997
  });
971
998
  return json(mcpResultPayload(appUrl, options, { token }));
972
999
  }
@@ -1049,23 +1076,20 @@ export async function handleMcpConnect(event, subpath, options = {}) {
1049
1076
  return json({ status: "pending" });
1050
1077
  }
1051
1078
  // status === "approved" && ownerEmail bound → mint exactly once.
1052
- if (!process.env.A2A_SECRET) {
1053
- if (canUseDevOpenConnect(event)) {
1054
- const consumed = await consumeDeviceCode(deviceCode, `dev-open-${randomUUID()}`);
1055
- if (!consumed) {
1056
- const fresh = await getDeviceCode(deviceCode);
1057
- if (fresh?.status === "consumed")
1058
- return json({ status: "consumed" });
1059
- return json({ status: "pending" });
1060
- }
1061
- return json({
1062
- status: "approved",
1063
- ...mcpResultPayload(appUrl, options, {
1064
- ownerEmail: row.ownerEmail,
1065
- }),
1066
- });
1079
+ if (!process.env.A2A_SECRET?.trim() && canUseDevOpenConnect(event)) {
1080
+ const consumed = await consumeDeviceCode(deviceCode, `dev-open-${randomUUID()}`);
1081
+ if (!consumed) {
1082
+ const fresh = await getDeviceCode(deviceCode);
1083
+ if (fresh?.status === "consumed")
1084
+ return json({ status: "consumed" });
1085
+ return json({ status: "pending" });
1067
1086
  }
1068
- return json({ status: "error", error: "A2A_SECRET not configured" }, 503);
1087
+ return json({
1088
+ status: "approved",
1089
+ ...mcpResultPayload(appUrl, options, {
1090
+ ownerEmail: row.ownerEmail,
1091
+ }),
1092
+ });
1069
1093
  }
1070
1094
  try {
1071
1095
  const jti = randomUUID();
@@ -1081,10 +1105,13 @@ export async function handleMcpConnect(event, subpath, options = {}) {
1081
1105
  let token;
1082
1106
  try {
1083
1107
  const orgDomain = await resolveOrgDomain(claimed.orgId ?? undefined);
1084
- token = await signA2AToken(claimed.ownerEmail, orgDomain, undefined, {
1085
- preferGlobalSecret: true,
1108
+ token = await signConnectToken({
1109
+ ownerEmail: claimed.ownerEmail,
1110
+ orgId: claimed.orgId,
1111
+ orgDomain,
1112
+ appUrl,
1086
1113
  expiresIn: `${DEFAULT_TOKEN_TTL_DAYS}d`,
1087
- extraClaims: { jti, scope: MCP_CONNECT_SCOPE },
1114
+ jti,
1088
1115
  });
1089
1116
  await recordMintedToken({
1090
1117
  jti,
@@ -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 isLoopbackOrigin(origin: string): boolean {\n try {\n const hostname = new URL(origin).hostname;\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname === \"[::1]\" ||\n hostname.startsWith(\"127.\")\n );\n } catch {\n return false;\n }\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 isLoopbackOrigin(deriveOrigin(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"]}
1
+ {"version":3,"file":"connect-route.js","sourceRoot":"","sources":["../../src/mcp/connect-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EACL,UAAU,EACV,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,gBAAgB,EAChB,2BAA2B,EAC3B,iBAAiB,EACjB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAE1B,gDAAgD;AAChD,MAAM,sBAAsB,GAAG,CAAC,CAAC;AACjC,MAAM,uBAAuB,GAAG,iCAAiC,CAAC;AAElE,6DAA6D;AAC7D,MAAM,YAAY,GAAG,2BAA2B,CAAC;AASjD,SAAS,IAAI,CAAC,IAAa,EAAE,MAAM,GAAG,GAAG;IACvC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,MAAM,GAAG,GAAG;IACtC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE;KACxD,CAAC,CAAC;AACL,CAAC;AAED;8EAC8E;AAC9E,SAAS,YAAY,CAAC,KAAc;IAClC,MAAM,cAAc,GAAG,SAAS,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,EAAE,kBAAkB,CAAC,IAAI,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9E,MAAM,KAAK,GACT,cAAc,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;QACrC,CAAC,IAAI,IAAI,gCAAgC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3E,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAc;IACtC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC;QAC1C,OAAO,CACL,QAAQ,KAAK,WAAW;YACxB,QAAQ,KAAK,WAAW;YACxB,QAAQ,KAAK,KAAK;YAClB,QAAQ,KAAK,OAAO;YACpB,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAC5B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAuB;IAChD,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACnC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;IACpE,OAAO,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,kBAAkB;IACzB,OAAO,iBAAiB,CACtB,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAC5D,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,IAAY;IACjD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,QAAQ,CAAC;IAClC,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AAClE,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc,EAAE,OAA+B;IAC/D,IAAI,OAAO,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC,KAAK,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC;QACnC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,OAAO,IAAI,KAAK,CAAC;IAClC,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,MAAc,EAAE,OAA+B;IACjE,OAAO,gBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAc;IAC1C,qEAAqE;IACrE,wEAAwE;IACxE,iEAAiE;IACjE,wEAAwE;IACxE,iCAAiC;IACjC,OAAO,CACL,iBAAiB,CAAC,KAAK,CAAC;QACxB,gBAAgB,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE;QAC/B,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE;QACjC,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE,CACnC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,gBAAgB,CAC7B,KAAyB;IAEzB,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,YAAY,CAAC,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,sBAAsB,CAAC;IACvD,OAAO,IAAI,CAAC,GAAG,CACb,kBAAkB,EAClB,IAAI,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAC5C,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,gBAAgB,CAAC,MAM/B;IACC,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC;QACnC,UAAU,EAAE,MAAM,CAAC,KAAK;QACxB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS;QACT,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,SAAS,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG;QAC/B,GAAG;KACJ,CAAC,CAAC;IACH,MAAM,iBAAiB,CAAC;QACtB,GAAG;QACH,UAAU,EAAE,MAAM,CAAC,KAAK;QACxB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;QAC3B,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AACxB,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,MAO/B;IACC,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;QACnC,OAAO,YAAY,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE;YAClE,kBAAkB,EAAE,IAAI;YACxB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,iBAAiB,EAAE;SAC3D,CAAC,CAAC;IACL,CAAC;IAED,OAAO,uBAAuB,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,IAAI;QAC3B,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,IAAI;QACnC,QAAQ,EAAE,2BAA2B;QACrC,KAAK,EAAE,uBAAuB;QAC9B,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC;QACvC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC,CAAC;AACL,CAAC;AAED,SAAS,gBAAgB,CACvB,MAAc,EACd,OAA+B,EAC/B,IAA6C;IAE7C,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,aAAa,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC;IAC/D,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACnC,OAAO,CAAC,4BAA4B,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;IAC1D,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,OAAO,CAAC,uBAAuB,CAAC,GAAG,GAAG,CAAC;IACzC,CAAC;IACD,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;QACvB,MAAM;QACN,UAAU,EAAE,IAAI;QAChB,cAAc,EAAE;YACd,IAAI,EAAE,MAAe;YACrB,GAAG,EAAE,MAAM;YACX,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpD;QACD,GAAG,EAAE,wBAAwB,MAAM,EAAE;KACtC,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,MAAc;IACpC,OAAO,GAAG,MAAM,oBAAoB,CAAC;AACvC,CAAC;AAED,8EAA8E;AAC9E,6CAA6C;AAC7C,8EAA8E;AAE9E,SAAS,kBAAkB,CAAC,SAAiB,EAAE,UAAkB;IAC/D,OAAO,eAAe,SAAS;;yEAEwC,UAAU;;0BAEzD,UAAU;;;;;OAK7B,CAAC;AACR,CAAC;AAED,SAAS,iBAAiB,CAAC,MAO1B;IACC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GACnE,MAAM,CAAC;IACT,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IACpC,MAAM,MAAM,GAAG,GAAG,MAAM,oBAAoB,CAAC;IAC7C,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,iBAAiB,GAAG,UAAU,CAClC,mCAAmC,QAAQ,IAAI,MAAM,EAAE,CACxD,CAAC;IACF,MAAM,YAAY,GAAG,UAAU,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAC;IAC5E,MAAM,iBAAiB,GAAG,UAAU,CAClC,8BAA8B,QAAQ,8CAA8C,MAAM,kBAAkB,CAC7G,CAAC;IACF,MAAM,YAAY,GAAG,kBAAkB,CACrC,YAAY,EACZ,qCAAqC,CACtC,CAAC;IACF,MAAM,WAAW,GAAG,kBAAkB,CACpC,WAAW,EACX,oCAAoC,CACrC,CAAC;IACF,MAAM,YAAY,GAChB,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,OAAO;;;;;iBAKQ,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAoThB,YAAY;;;mCAGe,OAAO,KAAK,OAAO;;;;;4EAKsB,OAAO;;UAEzE,WAAW;;;;;;;;;UASX,YAAY,CAAC,CAAC,CAAC,aAAa,OAAO,sBAAsB,CAAC,CAAC,CAAC,OAAO,OAAO,yBAAyB;;mCAE1E,SAAS;;;;8CAIE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ;;6CAE7B,YAAY;;;;;;+BAM1B,UAAU;;;;;;;;;;;;;;;;;;;uDAmBc,OAAO;;;;;;;;;kHASoD,OAAO;;;;;;;;;;;;;;;;gCAgBzF,iBAAiB;;uFAEsC,YAAY;;;;2BAIxE,YAAY;;;;;;gCAMP,iBAAiB;;;;;uEAKsB,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;;wCAE1D,YAAY,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,yBAAyB;wCAClE,YAAY,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,kCAAkC;;;;;;oDAM5D,YAAY,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,yBAAyB;;;;;;;;;;;;;uEAa1C,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;eAkC9E,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,eAAe,EAAE,4BAA4B,CAAC,CAAC;oBACrE,IAAI,CAAC,SAAS,CAAC,YAAY,IAAI,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAgNhD,CAAC;AACT,CAAC;AAED,8EAA8E;AAC9E,2EAA2E;AAC3E,8EAA8E;AAE9E;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAc,EACd,OAAe,EACf,UAAkC,EAAE;IAEpC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,QAAQ,EAAE,CAAC;IACtC,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CACzE,MAAM,EACN,EAAE,CACH,CAAC;IAEF,yEAAyE;IACzE,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;QACf,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1C,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;YACpB,oEAAoE;YACpE,iEAAiE;YACjE,MAAM,SAAS,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;YAChD,IAAI,SAAS;gBAAE,OAAO,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAC3C,8DAA8D;YAC9D,OAAO,IAAI,CACT,iBAAiB,CAAC;gBAChB,eAAe,EAAE,QAAQ;gBACzB,KAAK,EAAE,sBAAsB;gBAC7B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;gBACrD,MAAM;gBACN,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC;gBACrC,QAAQ,EAAE,IAAI;aACf,CAAC,CACH,CAAC;QACJ,CAAC;QACD,IAAI,QAAQ,GAAkB,IAAI,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,IAAI,GAAG,CACf,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,IAAI,IAAI,GAAG,EACzC,mBAAmB,CACpB,CAAC;YACF,MAAM,GAAG,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAC5C,IAAI,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,QAAQ,GAAG,GAAG,CAAC;QACpD,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CACT,iBAAiB,CAAC;YAChB,eAAe,EAAE,QAAQ;YACzB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;YACrD,MAAM;YACN,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC;YACrC,QAAQ;SACT,CAAC,CACH,CAAC;IACJ,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;QACrB,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;YACnE,OAAO,IAAI,CACT,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CACjE,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAG5D,CAAC;QACF,MAAM,KAAK,GACT,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YACjD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;YACjC,CAAC,CAAC,IAAI,CAAC;QACX,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,gBAAgB,CAAC;gBACvC,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,KAAK;gBACL,OAAO;gBACP,MAAM;aACP,CAAC,CAAC;YACH,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,EAAE,GAAG,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,eAAe,EAAE,CAAC;QAC5B,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,gBAAgB,EAAE,CAAC;YACrC,MAAM,eAAe,GAAG,GAAG,MAAM,4BAA4B,CAAC;YAC9D,OAAO,IAAI,CAAC;gBACV,WAAW,EAAE,GAAG,CAAC,UAAU;gBAC3B,SAAS,EAAE,GAAG,CAAC,QAAQ;gBACvB,gBAAgB,EAAE,eAAe;gBACjC,yBAAyB,EAAE,GAAG,eAAe,cAAc,GAAG,CAAC,QAAQ,EAAE;gBACzE,QAAQ,EAAE,sBAAsB;gBAChC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC;aAClD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,IAAI,GAAG,EAAE,OAAO,KAAK,cAAc,EAAE,CAAC;gBACpC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,kCAAkC,EAAE,EAAE,GAAG,CAAC,CAAC;YAClE,CAAC;YACD,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,EAAE,GAAG,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,mBAAmB,EAAE,CAAC;QAChC,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAE5D,CAAC;QACF,MAAM,QAAQ,GACZ,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAClE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,KAAK,GACT,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;YACvD,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;YACtB,CAAC,CAAC,IAAI,CAAC;QACX,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACvE,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,EAAE,GAAG,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,+BAA+B,EAAE,EAAE,GAAG,CAAC,CAAC;QAC/D,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oCAAoC,EAAE,EAAE,GAAG,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;QAC3B,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAE5D,CAAC;QACF,MAAM,UAAU,GACd,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,EAAE,GAAG,CAAC,CAAC;QACrE,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU;YAAE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;QACnE,IACE,GAAG,CAAC,MAAM,KAAK,SAAS;YACxB,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,IAAI,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EACrD,CAAC;YACD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;gBAAE,KAAK,gBAAgB,CAAC,UAAU,CAAC,CAAC;YAChE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,IACE,GAAG,CAAC,MAAM,KAAK,SAAS;YACxB,GAAG,CAAC,MAAM,KAAK,SAAS;YACxB,CAAC,GAAG,CAAC,UAAU,EACf,CAAC;YACD,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;QACrC,CAAC;QACD,iEAAiE;QACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;YACnE,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CACtC,UAAU,EACV,YAAY,UAAU,EAAE,EAAE,CAC3B,CAAC;YACF,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;gBAC9C,IAAI,KAAK,EAAE,MAAM,KAAK,UAAU;oBAAE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;gBACtE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,IAAI,CAAC;gBACV,MAAM,EAAE,UAAU;gBAClB,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE;oBACnC,UAAU,EAAE,GAAG,CAAC,UAAU;iBAC3B,CAAC;aACH,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;YACzB,wEAAwE;YACxE,8DAA8D;YAC9D,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAC9D,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;gBAC9C,IAAI,KAAK,EAAE,MAAM,KAAK,UAAU;oBAAE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;gBACtE,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;YACrC,CAAC;YACD,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;gBACrE,KAAK,GAAG,MAAM,gBAAgB,CAAC;oBAC7B,UAAU,EAAE,OAAO,CAAC,UAAW;oBAC/B,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,SAAS;oBACT,MAAM;oBACN,SAAS,EAAE,GAAG,sBAAsB,GAAG;oBACvC,GAAG;iBACJ,CAAC,CAAC;gBACH,MAAM,iBAAiB,CAAC;oBACtB,GAAG;oBACH,UAAU,EAAE,OAAO,CAAC,UAAW;oBAC/B,KAAK,EAAE,OAAO,CAAC,KAAK;oBACpB,KAAK,EAAE,mBAAmB;iBAC3B,CAAC,CAAC;gBACH,IAAI,CAAC,CAAC,MAAM,oBAAoB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;oBACnD,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,qBAAqB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;gBAC7C,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,OAAO,IAAI,CAAC;gBACV,MAAM,EAAE,UAAU;gBAClB,GAAG,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC;aAChD,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,EAAE,GAAG,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,IAAI,MAAM,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;YACV,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvB,EAAE,EAAE,CAAC,CAAC,EAAE;gBACR,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,SAAS,EAAE,CAAC,CAAC,SAAS;aACvB,CAAC,CAAC;SACJ,CAAC,CAAC;IACL,CAAC;IAED,yEAAyE;IACzE,IAAI,GAAG,KAAK,gBAAgB,EAAE,CAAC;QAC7B,IAAI,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAE5D,CAAC;QACF,MAAM,EAAE,GAAG,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,GAAG,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC","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 * When A2A_SECRET exists, the minted token reuses the existing A2A signer\n * (`signA2AToken`) and adds a random `jti` + `scope: \"mcp-connect\"` claim so\n * it can be revoked. Deployments without A2A_SECRET mint the same standard MCP\n * OAuth access-token format used by remote MCP OAuth, signed with the auth\n * secret fallback and bound to the exact MCP resource URL.\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_OAUTH_CLIENT_ID,\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\";\nimport {\n MCP_OAUTH_DEFAULT_SCOPE,\n signMcpOAuthAccessToken,\n} from \"./oauth-token.js\";\n\n/** Device-flow poll interval hint (seconds). */\nconst DEVICE_POLL_INTERVAL_S = 3;\nconst MCP_FULL_CATALOG_HEADER = \"X-Agent-Native-MCP-Full-Catalog\";\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 isLoopbackOrigin(origin: string): boolean {\n try {\n const hostname = new URL(origin).hostname;\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname === \"[::1]\" ||\n hostname.startsWith(\"127.\")\n );\n } catch {\n return false;\n }\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 isLoopbackOrigin(deriveOrigin(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 token value is returned to the\n * caller exactly once and never persisted; only the random `jti` is stored for\n * revocation.\n */\nasync function mintConnectToken(params: {\n email: string;\n orgId: string | undefined;\n label: string | null;\n ttlDays: number;\n appUrl: string;\n}): Promise<{ token: string; jti: string }> {\n const orgDomain = await resolveOrgDomain(params.orgId);\n const jti = randomUUID();\n const token = await signConnectToken({\n ownerEmail: params.email,\n orgId: params.orgId,\n orgDomain,\n appUrl: params.appUrl,\n expiresIn: `${params.ttlDays}d`,\n jti,\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\nasync function signConnectToken(params: {\n ownerEmail: string;\n orgId: string | null | undefined;\n orgDomain: string | undefined;\n appUrl: string;\n expiresIn: string;\n jti: string;\n}): Promise<string> {\n if (process.env.A2A_SECRET?.trim()) {\n return signA2AToken(params.ownerEmail, params.orgDomain, undefined, {\n preferGlobalSecret: true,\n expiresIn: params.expiresIn,\n extraClaims: { jti: params.jti, scope: MCP_CONNECT_SCOPE },\n });\n }\n\n return signMcpOAuthAccessToken({\n ownerEmail: params.ownerEmail,\n orgId: params.orgId ?? null,\n orgDomain: params.orgDomain ?? null,\n clientId: MCP_CONNECT_OAUTH_CLIENT_ID,\n scope: MCP_OAUTH_DEFAULT_SCOPE,\n resource: mcpResourceUrl(params.appUrl),\n issuer: params.appUrl,\n jti: params.jti,\n expiresIn: params.expiresIn,\n });\n}\n\nfunction mcpResultPayload(\n appUrl: string,\n options: McpConnectRouteOptions,\n auth: { token?: string; ownerEmail?: string },\n) {\n const mcpUrl = mcpResourceUrl(appUrl);\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 if (auth.token || auth.ownerEmail) {\n headers[MCP_FULL_CATALOG_HEADER] = \"1\";\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\nfunction mcpResourceUrl(appUrl: string): string {\n return `${appUrl}/_agent-native/mcp`;\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(--panel-2); color: var(--text);\n border: 1px solid var(--border-strong); border-radius: 8px;\n font-size: 0.86rem; font-weight: 650; text-decoration: none;\n cursor: pointer; width: auto; max-width: 100%; text-align: center;\n margin: 0.5rem 0 0.2rem;\n }\n .tab-panel a.primary-link {\n color: var(--text); text-decoration: none;\n }\n .primary-link:hover {\n background: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.2);\n }\n .primary-link.compact { 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?.trim() && canUseDevOpenConnect(event)) {\n return json(\n mcpResultPayload(appUrl, options, { ownerEmail: session.email }),\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 appUrl,\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?.trim() && 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 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 signConnectToken({\n ownerEmail: claimed.ownerEmail!,\n orgId: claimed.orgId,\n orgDomain,\n appUrl,\n expiresIn: `${DEFAULT_TOKEN_TTL_DAYS}d`,\n jti,\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"]}
@@ -23,6 +23,11 @@
23
23
  * `build-server.ts` import it from the leaf store without a cycle.
24
24
  */
25
25
  export declare const MCP_CONNECT_SCOPE = "mcp-connect";
26
+ /**
27
+ * Client id used when connect/device flows have to mint a standard MCP OAuth
28
+ * access token instead of an A2A JWT (for deployments without A2A_SECRET).
29
+ */
30
+ export declare const MCP_CONNECT_OAUTH_CLIENT_ID = "agent-native-connect";
26
31
  /** Device codes are valid for 10 minutes. */
27
32
  export declare const DEVICE_CODE_TTL_MS: number;
28
33
  /** Default minted-token lifetime. Configurable per-request 1–365 days. */
@@ -1 +1 @@
1
- {"version":3,"file":"connect-store.d.ts","sourceRoot":"","sources":["../../src/mcp/connect-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAYH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAE/C,6CAA6C;AAC7C,eAAO,MAAM,kBAAkB,QAAc,CAAC;AAE9C,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,sBAAsB,QAAS,CAAC;AA+C7C,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBlB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBhE;AAED,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,EAAE,CAAC,CAsB3B;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/D;AAMD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IACpE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAmBD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAgD/D;AAgBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAc/B;AAED,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAc/B;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAAC,aAAa,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC,CAuB9D;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAY/B;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAW/B;AAED,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYxE"}
1
+ {"version":3,"file":"connect-store.d.ts","sourceRoot":"","sources":["../../src/mcp/connect-store.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,11 @@ let _initPromise;
26
26
  * `build-server.ts` import it from the leaf store without a cycle.
27
27
  */
28
28
  export const MCP_CONNECT_SCOPE = "mcp-connect";
29
+ /**
30
+ * Client id used when connect/device flows have to mint a standard MCP OAuth
31
+ * access token instead of an A2A JWT (for deployments without A2A_SECRET).
32
+ */
33
+ export const MCP_CONNECT_OAUTH_CLIENT_ID = "agent-native-connect";
29
34
  /** Device codes are valid for 10 minutes. */
30
35
  export const DEVICE_CODE_TTL_MS = 10 * 60_000;
31
36
  /** Default minted-token lifetime. Configurable per-request 1–365 days. */