@agent-native/core 0.22.17 → 0.22.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/client/agent-chat.d.ts.map +1 -1
  2. package/dist/client/agent-chat.js +19 -6
  3. package/dist/client/agent-chat.js.map +1 -1
  4. package/dist/client/embed-auth.d.ts +5 -0
  5. package/dist/client/embed-auth.d.ts.map +1 -1
  6. package/dist/client/embed-auth.js +100 -13
  7. package/dist/client/embed-auth.js.map +1 -1
  8. package/dist/client/frame.d.ts.map +1 -1
  9. package/dist/client/frame.js +1 -0
  10. package/dist/client/frame.js.map +1 -1
  11. package/dist/client/index.d.ts +1 -1
  12. package/dist/client/index.d.ts.map +1 -1
  13. package/dist/client/index.js +1 -1
  14. package/dist/client/index.js.map +1 -1
  15. package/dist/client/mcp-app-host.d.ts +5 -0
  16. package/dist/client/mcp-app-host.d.ts.map +1 -1
  17. package/dist/client/mcp-app-host.js +267 -2
  18. package/dist/client/mcp-app-host.js.map +1 -1
  19. package/dist/client/theme.js +1 -1
  20. package/dist/client/theme.js.map +1 -1
  21. package/dist/client/vite-dev-recovery-script.d.ts.map +1 -1
  22. package/dist/client/vite-dev-recovery-script.js +9 -0
  23. package/dist/client/vite-dev-recovery-script.js.map +1 -1
  24. package/dist/index.browser.d.ts +1 -1
  25. package/dist/index.browser.d.ts.map +1 -1
  26. package/dist/index.browser.js +1 -1
  27. package/dist/index.browser.js.map +1 -1
  28. package/dist/mcp/build-server.d.ts.map +1 -1
  29. package/dist/mcp/build-server.js +63 -9
  30. package/dist/mcp/build-server.js.map +1 -1
  31. package/dist/mcp/embed-app.d.ts.map +1 -1
  32. package/dist/mcp/embed-app.js +332 -34
  33. package/dist/mcp/embed-app.js.map +1 -1
  34. package/dist/server/open-route.d.ts.map +1 -1
  35. package/dist/server/open-route.js +7 -2
  36. package/dist/server/open-route.js.map +1 -1
  37. package/docs/content/client.md +6 -3
  38. package/docs/content/external-agents.md +34 -16
  39. package/docs/content/mcp-protocol.md +17 -7
  40. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"open-route.js","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/open` — the stable deep-link route.\n *\n * An external coding agent (Claude Code / Cowork / Codex) surfaces an\n * \"Open in <app> →\" link (built by an action's `link` builder, see\n * `deep-link.ts`). When the user clicks it in any browser / inline webview,\n * this route:\n * 1. Resolves the *browser* session (NOT the agent token) — so the record\n * always lands where the human is logged in.\n * 2. When unauthenticated, serves the same sign-in form the auth guard\n * would, *at this same URL*. The login form's success handler reloads\n * `window.location.href`, so the now-authenticated request re-enters\n * this route and proceeds. No `?next=` plumbing needed.\n * 3. Writes the existing one-shot `navigate` application-state command (the\n * exact key the UI already drains every 2s — we don't invent a new\n * navigation mechanism, we bridge to it), plus an optional `compose-<id>`\n * draft.\n * 4. 302-redirects to the rendered SPA view so the page loads immediately;\n * the polled `navigate` command then applies record-level focus.\n *\n * The link itself is a pure pointer (view + record ids + filters) and carries\n * no privileged state.\n */\nimport type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod } from \"h3\";\nimport { getSession, getConfiguredLoginHtml } from \"./auth.js\";\nimport { appStatePut, appStateGet } from \"../application-state/store.js\";\nimport {\n AGENT_SIDEBAR_QUERY_PARAM,\n withCollapsedAgentSidebarParam,\n} from \"../shared/agent-sidebar-url.js\";\nimport {\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n} from \"../shared/embed-auth.js\";\nimport { getConfiguredAppBasePath } from \"./app-base-path.js\";\n\n/** Query keys that are route control, not navigation payload. */\nconst RESERVED = new Set([\n \"app\",\n \"view\",\n \"to\",\n \"compose\",\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n AGENT_SIDEBAR_QUERY_PARAM,\n]);\n\n// Control-char guard (NUL..US + DEL). Defined via codepoints so the source\n// file stays plain ASCII.\nconst CONTROL_CHARS = new RegExp(\"[\\\\u0000-\\\\u001f\\\\u007f]\");\n\n// Compose-draft id charset. Mirrors `sanitizeDraftId` in\n// templates/mail/actions/manage-draft.ts so the id we concatenate into the\n// `compose-<id>` application-state key can't escape the key namespace\n// (path-traversal / key injection guard).\nconst COMPOSE_ID = /^[a-zA-Z0-9_-]{1,64}$/;\n\nexport interface OpenRouteOptions {\n /** Per-template override that turns the parsed deep-link params into the\n * client-side SPA path to redirect to. Return `null` to use the default\n * (`/<view>`). Filter params (`f_*`) are appended automatically. */\n resolveOpenPath?: (params: {\n app?: string;\n view?: string;\n params: Record<string, string>;\n }) => string | null | undefined;\n}\n\nfunction getRequestUrl(event: H3Event): string {\n const mountedPathname = (event as any).context?._mountedPathname;\n if (typeof mountedPathname === \"string\" && mountedPathname) {\n return `${mountedPathname}${(event as any).url?.search ?? \"\"}`;\n }\n return (event as any).node?.req?.url ?? (event as any).path ?? \"/\";\n}\n\n/** Decode a base64url string to UTF-8 (Node Buffer; this route is Node-only). */\nfunction decodeBase64Url(input: string): string {\n return Buffer.from(input, \"base64url\").toString(\"utf8\");\n}\n\n/**\n * Normalize a candidate redirect path to a safe, same-origin, leading-slash\n * relative path. Rejects absolute URLs, scheme-relative `//host`, and control\n * chars (open-redirect guard). Returns `null` when unsafe.\n */\nfunction safeRelativePath(raw: string | undefined | null): string | null {\n if (!raw) return null;\n if (CONTROL_CHARS.test(raw)) return null;\n if (!raw.startsWith(\"/\")) return null;\n if (raw.startsWith(\"//\") || raw.startsWith(\"/\\\\\")) return null;\n if (/^\\/[a-z][a-z0-9+.-]*:/i.test(raw)) return null;\n return raw;\n}\n\nfunction redirect(location: string): Response {\n // Native web Response (not h3 v2's reworked sendRedirect) — matches the\n // redirect pattern used elsewhere in auth.ts.\n return new Response(\"\", { status: 302, headers: { Location: location } });\n}\n\nfunction appendSearchParams(target: string, params: URLSearchParams): string {\n if (!params.toString()) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n for (const [k, v] of params.entries()) url.searchParams.set(k, v);\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nfunction withConfiguredRedirectBasePath(target: string): string {\n const base = getConfiguredAppBasePath();\n if (!base) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n if (url.pathname === base || url.pathname.startsWith(`${base}/`)) {\n return `${url.pathname}${url.search}${url.hash}`;\n }\n url.pathname = url.pathname === \"/\" ? base : `${base}${url.pathname}`;\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nexport function createOpenRouteHandler(options: OpenRouteOptions = {}) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method !== \"GET\" && method !== \"HEAD\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n const rawUrl = getRequestUrl(event);\n let search: URLSearchParams;\n try {\n search = new URL(rawUrl, \"http://an.invalid\").searchParams;\n } catch {\n search = new URLSearchParams();\n }\n\n const app = search.get(\"app\") ?? undefined;\n const view = search.get(\"view\") ?? undefined;\n const toParam = search.get(\"to\") ?? undefined;\n const compose = search.get(\"compose\") ?? undefined;\n\n // Resolve the BROWSER session. When unauthenticated, serve the same login\n // form the guard would — at this URL — so the post-login reload returns\n // here authenticated.\n const session = await getSession(event);\n if (!session?.email) {\n const html = getConfiguredLoginHtml(event);\n if (html) {\n return new Response(html, {\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n // No auth guard configured (fully open app) — best effort: still send\n // the user to the view; nothing to scope the navigate write to.\n }\n\n // Build the navigation payload from every non-reserved query param\n // (record ids + filters: threadId, eventId, dashboardId, f_*, ...).\n const navParams: Record<string, string> = {};\n for (const [k, v] of search.entries()) {\n if (RESERVED.has(k)) continue;\n navParams[k] = v;\n }\n const navPayload: Record<string, unknown> = { ...navParams };\n if (view) navPayload.view = view;\n\n if (session?.email) {\n try {\n await appStatePut(session.email, \"navigate\", navPayload, {\n requestSource: \"deep-link\",\n });\n if (compose) {\n try {\n const draft = JSON.parse(decodeBase64Url(compose));\n // Validate the id before using it as a key segment. An unsafe id\n // could escape the `compose-` namespace and clobber an unrelated\n // application-state key; skip the write (the view still opens),\n // mirroring the malformed-payload branch below.\n if (\n draft &&\n typeof draft === \"object\" &&\n typeof draft.id === \"string\" &&\n COMPOSE_ID.test(draft.id)\n ) {\n const composeKey = `compose-${draft.id}`;\n // A compact deep link may carry only `{ id, subject }` when the\n // full draft was too large to inline in the URL. The complete\n // draft is already persisted at `compose-<id>` by manage-draft\n // on create/update. Never let the truncated stub overwrite that\n // richer saved draft (would silently lose body / recipients /\n // reply metadata). Only write when the payload actually carries\n // content, or when nothing is saved yet (composer still opens).\n const hasContent =\n (typeof draft.body === \"string\" && draft.body.length > 0) ||\n !!draft.to ||\n !!draft.cc ||\n !!draft.bcc ||\n !!draft.html ||\n !!draft.replyToThreadId;\n const existing = hasContent\n ? null\n : await appStateGet(session.email, composeKey);\n if (hasContent || !existing) {\n await appStatePut(session.email, composeKey, draft, {\n requestSource: \"deep-link\",\n });\n }\n }\n } catch {\n // Malformed compose payload — skip; the view still opens.\n }\n }\n } catch {\n // App-state write failure shouldn't 500 the click; the redirect\n // below still lands the user on the right view.\n }\n }\n\n // Resolve the SPA path to redirect to.\n let target =\n safeRelativePath(toParam) ??\n safeRelativePath(\n options.resolveOpenPath?.({ app, view, params: navParams }) ??\n (view ? `/${view}` : null),\n ) ??\n \"/\";\n\n // Forward filter params (f_*) onto the redirect so dashboards/lists open\n // pre-filtered even before the navigate command is drained.\n const filters = new URLSearchParams();\n for (const [k, v] of search.entries()) {\n if (k.startsWith(\"f_\")) filters.set(k, v);\n }\n target = appendSearchParams(target, filters);\n const embedParams = new URLSearchParams();\n for (const key of [EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM]) {\n const value = search.get(key);\n if (value) embedParams.set(key, value);\n }\n target = appendSearchParams(target, embedParams);\n target = withCollapsedAgentSidebarParam(target);\n target = withConfiguredRedirectBasePath(target);\n\n return redirect(target);\n });\n}\n"]}
1
+ {"version":3,"file":"open-route.js","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `/_agent-native/open` — the stable deep-link route.\n *\n * An external coding agent (Claude Code / Cowork / Codex) surfaces an\n * \"Open in <app> →\" link (built by an action's `link` builder, see\n * `deep-link.ts`). When the user clicks it in any browser / inline webview,\n * this route:\n * 1. Resolves the *browser* session (NOT the agent token) — so the record\n * always lands where the human is logged in.\n * 2. When unauthenticated, serves the same sign-in form the auth guard\n * would, *at this same URL*. The login form's success handler reloads\n * `window.location.href`, so the now-authenticated request re-enters\n * this route and proceeds. No `?next=` plumbing needed.\n * 3. Writes the existing one-shot `navigate` application-state command (the\n * exact key the UI already drains every 2s — we don't invent a new\n * navigation mechanism, we bridge to it), plus an optional `compose-<id>`\n * draft.\n * 4. 302-redirects to the rendered SPA view so the page loads immediately;\n * the polled `navigate` command then applies record-level focus.\n *\n * The link itself is a pure pointer (view + record ids + filters) and carries\n * no privileged state.\n */\nimport type { H3Event } from \"h3\";\nimport { defineEventHandler, getMethod } from \"h3\";\nimport { getSession, getConfiguredLoginHtml } from \"./auth.js\";\nimport { appStatePut, appStateGet } from \"../application-state/store.js\";\nimport {\n AGENT_SIDEBAR_QUERY_PARAM,\n withCollapsedAgentSidebarParam,\n} from \"../shared/agent-sidebar-url.js\";\nimport {\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n MCP_APP_CHAT_BRIDGE_QUERY_PARAM,\n} from \"../shared/embed-auth.js\";\nimport { getConfiguredAppBasePath } from \"./app-base-path.js\";\n\n/** Query keys that are route control, not navigation payload. */\nconst RESERVED = new Set([\n \"app\",\n \"view\",\n \"to\",\n \"compose\",\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n MCP_APP_CHAT_BRIDGE_QUERY_PARAM,\n AGENT_SIDEBAR_QUERY_PARAM,\n]);\n\n// Control-char guard (NUL..US + DEL). Defined via codepoints so the source\n// file stays plain ASCII.\nconst CONTROL_CHARS = new RegExp(\"[\\\\u0000-\\\\u001f\\\\u007f]\");\n\n// Compose-draft id charset. Mirrors `sanitizeDraftId` in\n// templates/mail/actions/manage-draft.ts so the id we concatenate into the\n// `compose-<id>` application-state key can't escape the key namespace\n// (path-traversal / key injection guard).\nconst COMPOSE_ID = /^[a-zA-Z0-9_-]{1,64}$/;\n\nexport interface OpenRouteOptions {\n /** Per-template override that turns the parsed deep-link params into the\n * client-side SPA path to redirect to. Return `null` to use the default\n * (`/<view>`). Filter params (`f_*`) are appended automatically. */\n resolveOpenPath?: (params: {\n app?: string;\n view?: string;\n params: Record<string, string>;\n }) => string | null | undefined;\n}\n\nfunction getRequestUrl(event: H3Event): string {\n const mountedPathname = (event as any).context?._mountedPathname;\n if (typeof mountedPathname === \"string\" && mountedPathname) {\n return `${mountedPathname}${(event as any).url?.search ?? \"\"}`;\n }\n return (event as any).node?.req?.url ?? (event as any).path ?? \"/\";\n}\n\n/** Decode a base64url string to UTF-8 (Node Buffer; this route is Node-only). */\nfunction decodeBase64Url(input: string): string {\n return Buffer.from(input, \"base64url\").toString(\"utf8\");\n}\n\n/**\n * Normalize a candidate redirect path to a safe, same-origin, leading-slash\n * relative path. Rejects absolute URLs, scheme-relative `//host`, and control\n * chars (open-redirect guard). Returns `null` when unsafe.\n */\nfunction safeRelativePath(raw: string | undefined | null): string | null {\n if (!raw) return null;\n if (CONTROL_CHARS.test(raw)) return null;\n if (!raw.startsWith(\"/\")) return null;\n if (raw.startsWith(\"//\") || raw.startsWith(\"/\\\\\")) return null;\n if (/^\\/[a-z][a-z0-9+.-]*:/i.test(raw)) return null;\n return raw;\n}\n\nfunction redirect(location: string): Response {\n // Native web Response (not h3 v2's reworked sendRedirect) — matches the\n // redirect pattern used elsewhere in auth.ts.\n return new Response(\"\", { status: 302, headers: { Location: location } });\n}\n\nfunction appendSearchParams(target: string, params: URLSearchParams): string {\n if (!params.toString()) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n for (const [k, v] of params.entries()) url.searchParams.set(k, v);\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nfunction withConfiguredRedirectBasePath(target: string): string {\n const base = getConfiguredAppBasePath();\n if (!base) return target;\n try {\n const url = new URL(target, \"http://an.invalid\");\n if (url.pathname === base || url.pathname.startsWith(`${base}/`)) {\n return `${url.pathname}${url.search}${url.hash}`;\n }\n url.pathname = url.pathname === \"/\" ? base : `${base}${url.pathname}`;\n return `${url.pathname}${url.search}${url.hash}`;\n } catch {\n return target;\n }\n}\n\nexport function createOpenRouteHandler(options: OpenRouteOptions = {}) {\n return defineEventHandler(async (event: H3Event) => {\n const method = getMethod(event);\n if (method !== \"GET\" && method !== \"HEAD\") {\n return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n status: 405,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n const rawUrl = getRequestUrl(event);\n let search: URLSearchParams;\n try {\n search = new URL(rawUrl, \"http://an.invalid\").searchParams;\n } catch {\n search = new URLSearchParams();\n }\n\n const app = search.get(\"app\") ?? undefined;\n const view = search.get(\"view\") ?? undefined;\n const toParam = search.get(\"to\") ?? undefined;\n const compose = search.get(\"compose\") ?? undefined;\n\n // Resolve the BROWSER session. When unauthenticated, serve the same login\n // form the guard would — at this URL — so the post-login reload returns\n // here authenticated.\n const session = await getSession(event);\n if (!session?.email) {\n const html = getConfiguredLoginHtml(event);\n if (html) {\n return new Response(html, {\n status: 200,\n headers: { \"Content-Type\": \"text/html; charset=utf-8\" },\n });\n }\n // No auth guard configured (fully open app) — best effort: still send\n // the user to the view; nothing to scope the navigate write to.\n }\n\n // Build the navigation payload from every non-reserved query param\n // (record ids + filters: threadId, eventId, dashboardId, f_*, ...).\n const navParams: Record<string, string> = {};\n for (const [k, v] of search.entries()) {\n if (RESERVED.has(k)) continue;\n navParams[k] = v;\n }\n const navPayload: Record<string, unknown> = { ...navParams };\n if (view) navPayload.view = view;\n\n if (session?.email) {\n try {\n await appStatePut(session.email, \"navigate\", navPayload, {\n requestSource: \"deep-link\",\n });\n if (compose) {\n try {\n const draft = JSON.parse(decodeBase64Url(compose));\n // Validate the id before using it as a key segment. An unsafe id\n // could escape the `compose-` namespace and clobber an unrelated\n // application-state key; skip the write (the view still opens),\n // mirroring the malformed-payload branch below.\n if (\n draft &&\n typeof draft === \"object\" &&\n typeof draft.id === \"string\" &&\n COMPOSE_ID.test(draft.id)\n ) {\n const composeKey = `compose-${draft.id}`;\n // A compact deep link may carry only `{ id, subject }` when the\n // full draft was too large to inline in the URL. The complete\n // draft is already persisted at `compose-<id>` by manage-draft\n // on create/update. Never let the truncated stub overwrite that\n // richer saved draft (would silently lose body / recipients /\n // reply metadata). Only write when the payload actually carries\n // content, or when nothing is saved yet (composer still opens).\n const hasContent =\n (typeof draft.body === \"string\" && draft.body.length > 0) ||\n !!draft.to ||\n !!draft.cc ||\n !!draft.bcc ||\n !!draft.html ||\n !!draft.replyToThreadId;\n const existing = hasContent\n ? null\n : await appStateGet(session.email, composeKey);\n if (hasContent || !existing) {\n await appStatePut(session.email, composeKey, draft, {\n requestSource: \"deep-link\",\n });\n }\n }\n } catch {\n // Malformed compose payload — skip; the view still opens.\n }\n }\n } catch {\n // App-state write failure shouldn't 500 the click; the redirect\n // below still lands the user on the right view.\n }\n }\n\n // Resolve the SPA path to redirect to.\n let target =\n safeRelativePath(toParam) ??\n safeRelativePath(\n options.resolveOpenPath?.({ app, view, params: navParams }) ??\n (view ? `/${view}` : null),\n ) ??\n \"/\";\n\n // Forward filter params (f_*) onto the redirect so dashboards/lists open\n // pre-filtered even before the navigate command is drained.\n const filters = new URLSearchParams();\n for (const [k, v] of search.entries()) {\n if (k.startsWith(\"f_\")) filters.set(k, v);\n }\n target = appendSearchParams(target, filters);\n const embedParams = new URLSearchParams();\n for (const key of [\n EMBED_MODE_QUERY_PARAM,\n EMBED_TOKEN_QUERY_PARAM,\n MCP_APP_CHAT_BRIDGE_QUERY_PARAM,\n ]) {\n const value = search.get(key);\n if (value) embedParams.set(key, value);\n }\n target = appendSearchParams(target, embedParams);\n target = withCollapsedAgentSidebarParam(target);\n target = withConfiguredRedirectBasePath(target);\n\n return redirect(target);\n });\n}\n"]}
@@ -75,8 +75,8 @@ Send a message to the agent chat via postMessage. Used to delegate AI tasks from
75
75
 
76
76
  When the app route is running inside an MCP App embed created with `embedApp()`,
77
77
  auto-submitted messages (`submit` omitted or `true`) are forwarded to the MCP
78
- App wrapper, which asks the containing host to add hidden context and send the
79
- visible user turn. `context` is sent as model context before the visible
78
+ App host bridge, which asks the containing host to add hidden context and send
79
+ the visible user turn. `context` is sent as model context before the visible
80
80
  message, so it stays model-visible without being posted as user-facing chat.
81
81
  `submit: false` keeps the local prefill/review behavior because MCP Apps do not
82
82
  define a standard draft-prefill API.
@@ -116,7 +116,10 @@ Routes embedded by `embedApp()` should be URL-first: load the current artifact
116
116
  from path/query params, render the real React route or a focused shared
117
117
  component, and use host bridge messages only for host-owned behavior.
118
118
 
119
- The wire contract is:
119
+ Default direct route embeds use the standard MCP Apps `ui/*` JSON-RPC bridge.
120
+ The exported helpers below send `ui/update-model-context`, `ui/open-link`, and
121
+ `ui/request-display-mode` directly to the host. The explicit nested diagnostic
122
+ mode still uses the wrapper relay:
120
123
 
121
124
  | Direction | Message type | Purpose |
122
125
  | --------------- | ---------------------------------------- | ------------------------------------------------- |
@@ -219,18 +219,38 @@ Claude Code and other CLI-first clients still receive the same resources and met
219
219
 
220
220
  MCP App embeds are route embeds, not separate mini-products. `embedApp()`
221
221
  starts from the action's `link` target, creates a short-lived embed session,
222
- and loads that URL in an iframe. Design embedded routes so a reload with the
223
- same URL reconstructs the same view.
222
+ and by default navigates the MCP App frame itself to that app route. That keeps
223
+ Claude and ChatGPT on a single iframe instead of relying on a nested app iframe.
224
+ Design embedded routes so a reload with the same URL reconstructs the same view.
225
+
226
+ ChatGPT gets a dedicated compatibility path through `window.openai`: the launch
227
+ document reads `toolInput`, `toolOutput`, and `toolResponseMetadata` directly,
228
+ then calls `create_embed_session` via `window.openai.callTool(...)`. Standard
229
+ MCP Apps hosts use the `ui/*` JSON-RPC bridge. After the direct navigation, the
230
+ loaded app route can still call `ui/update-model-context`, `ui/message`,
231
+ `ui/open-link`, and `ui/request-display-mode` through the host bridge helpers.
232
+ Keep the result shape identical for both paths: return a focused `link` and
233
+ concise structured content.
234
+
235
+ An explicit nested iframe diagnostic path remains available with
236
+ `embedMode: "iframe"`, `renderMode: "iframe"`, `nested: true`, or
237
+ `frame: "iframe"`. Use it only when debugging host behavior. If that diagnostic
238
+ iframe is blocked, `embedApp()` replaces it with an open-app fallback: the user
239
+ can retry inline, open a freshly minted embed session through the host, or use
240
+ the visible route URL. Keep the action's `link` target useful on its own because
241
+ it is still the universal escape hatch.
224
242
 
225
243
  The host bridge is deliberately small:
226
244
 
227
- | Direction | Message type | Use it for |
228
- | --------------- | ---------------------------------------- | ---------------------------------------- |
229
- | wrapper → route | `agentNative.mcpHostContext` | Theme, locale, host platform, dimensions |
230
- | route → wrapper | `agentNative.mcpHost.updateModelContext` | Hidden context for the host model |
231
- | route → wrapper | `agentNative.mcpHost.openLink` | Open an external or app URL via the host |
232
- | route → wrapper | `agentNative.mcpHost.requestDisplayMode` | Request `inline`, `fullscreen`, or `pip` |
233
- | wrapper route | `agentNative.mcpHost.response` | Correlate async request results |
245
+ | Mode | Message type | Use it for |
246
+ | ----------------- | ------------------------------------- | ---------------------------------------- |
247
+ | direct | `ui/update-model-context` | Hidden context for the host model |
248
+ | direct | `ui/message` | Post a visible user turn into the host |
249
+ | direct | `ui/open-link` | Open an external or app URL via the host |
250
+ | direct | `ui/request-display-mode` | Request `inline`, `fullscreen`, or `pip` |
251
+ | nested diagnostic | `agentNative.mcpHostContext` | Theme, locale, host platform, dimensions |
252
+ | nested diagnostic | `agentNative.embeddedAppReady` | Confirm the route iframe loaded |
253
+ | nested diagnostic | `agentNative.mcpHost.*` / `.response` | Wrapper relay for host requests |
234
254
 
235
255
  Embedded routes can use `updateMcpAppModelContext()`,
236
256
  `openMcpAppHostLink()`, `requestMcpAppDisplayMode()`,
@@ -259,7 +279,7 @@ On top of the per-action tools the MCP server exposes a stable verb set, so an e
259
279
 
260
280
  For OAuth callers that request `mcp:apps`, the server intentionally advertises a compact `tools/list` catalog so app hosts do not ingest every internal action schema. The model sees app-facing builtins (`list_apps`, `open_app`, app-only `create_embed_session`) and actions with `mcpApp`. Stdio/static-token developer clients still get the full connected action surface, and `publicAgent.expose` remains the opt-in for safe read/ingest tools outside the compact app catalog. If a UI-capable host should be able to call a new action from an MCP App conversation, mark it with `mcpApp`; use `publicAgent` for non-UI read/ingest handoff tools.
261
281
 
262
- For fast ChatGPT/Claude handoffs, the ideal path is direct: call the action that creates or opens the artifact, then let the MCP App widget launch the iframe. A Mail request should call `manage_draft` and render the real compose route. A dashboard request should call `open_app({ path, embed: true })` or a dashboard action with `mcpApp` and render the full Analytics route. Calendar, Forms, Content, Slides, Design, and Clips should follow the same pattern with their draft/create/search actions. `list_apps` is useful when the model must choose among granted apps; broad `resources/list`, full-catalog discovery, or `ask_app` delegation should not be the normal route for an obvious UI handoff.
282
+ For fast ChatGPT/Claude handoffs, the ideal path is direct: call the action that creates or opens the artifact, then let the MCP App launch the route. A Mail request should call `manage_draft` and render the real compose route. A dashboard request should call `open_app({ path, embed: true })` or a dashboard action with `mcpApp` and render the full Analytics route. Calendar, Forms, Content, Slides, Design, and Clips should follow the same pattern with their draft/create/search actions. `list_apps` is useful when the model must choose among granted apps; broad `resources/list`, full-catalog discovery, or `ask_app` delegation should not be the normal route for an obvious UI handoff.
263
283
 
264
284
  ### Per-app tour {#tour}
265
285
 
@@ -340,13 +360,11 @@ export default defineAction({
340
360
 
341
361
  The MCP server advertises extension `io.modelcontextprotocol/ui`, adds `_meta.ui.resourceUri` plus `_meta["ui/resourceUri"]` to `tools/list`, and also emits ChatGPT Apps SDK compatibility metadata (`openai/outputTemplate`, widget CSP/description/accessibility). It exposes the HTML through `resources/list`, `resources/templates/list`, and `resources/read` using MIME `text/html;profile=mcp-app`. The stdio proxy forwards those resource handlers from the live app, so desktop and CLI clients see the same resources as HTTP clients.
342
362
 
343
- Keep the existing `link` builder even when adding `mcpApp`. CLI-only clients, older hosts, and any host that does not render MCP Apps will ignore the UI metadata and still need the `"Open in … →"` link. `embedApp()` uses that link as its launch target, calls the app-only `create_embed_session` helper, exchanges a one-time SQL ticket at `/_agent-native/embed/start`, and loads the target route in an iframe with a short-lived browser session plus a bearer fallback for same-origin fetches. `open_app({ app, path, embed: true })` is the generic escape hatch for routes such as full dashboards, filtered inboxes, calendar draft views, analyses, and extension pages, and should be used liberally when the full app is the clearest review/edit surface.
363
+ Keep the existing `link` builder even when adding `mcpApp`. CLI-only clients, older hosts, and any host that does not render MCP Apps will ignore the UI metadata and still need the `"Open in … →"` link. `embedApp()` uses that link as its launch target, calls the app-only `create_embed_session` helper, exchanges a one-time SQL ticket at `/_agent-native/embed/start`, and navigates the MCP App frame to the target route with a short-lived browser session plus a bearer fallback for same-origin fetches. `open_app({ app, path, embed: true })` is the generic escape hatch for routes such as full dashboards, filtered inboxes, calendar draft views, analyses, and extension pages, and should be used liberally when the full app is the clearest review/edit surface.
344
364
 
345
- Inside those `embedApp()` full-app iframes, `sendToAgentChat()` is host-aware:
346
- auto-submitted prompts are relayed to the wrapper, which uses the host's model
347
- context and message APIs for hidden context plus the visible user turn. Hosts
348
- without MCP Apps messaging support simply ignore the bridge, and
349
- `submit: false` remains a local review/prefill path.
365
+ Inside those `embedApp()` routes, `sendToAgentChat()` is embed-aware.
366
+ Auto-submitted prompts relay to the MCP host as `ui/update-model-context` plus
367
+ `ui/message`; `submit: false` remains local prefill/review behavior.
350
368
 
351
369
  ### The `link` contract {#link-contract}
352
370
 
@@ -78,13 +78,23 @@ If an action declares `mcpApp`, the server also advertises the official MCP Apps
78
78
 
79
79
  `embedApp()` is the low-level URL-first MCP App helper. It reads the action
80
80
  result's open link, asks the app-only `create_embed_session` tool to mint a
81
- route-scoped session, then loads the resulting route in an iframe. For normal
82
- action authoring, use `embedRoute()` when the action's `link` and `mcpApp`
83
- should come from the same pure route builder. The route itself should derive
84
- state from the URL and normal app data fetching.
85
-
86
- The bridge between the wrapper and the embedded route uses Agent-Native
87
- postMessage types:
81
+ route-scoped session, then navigates the MCP App frame itself to the resulting
82
+ app route. For normal action authoring, use `embedRoute()` when the action's
83
+ `link` and `mcpApp` should come from the same pure route builder. The route
84
+ itself should derive state from the URL and normal app data fetching.
85
+
86
+ Default direct embeds talk to the MCP Apps host through standard `ui/*`
87
+ JSON-RPC messages:
88
+
89
+ | Type | Payload shape |
90
+ | ------------------------- | ---------------------------------- |
91
+ | `ui/update-model-context` | `{ content?, structuredContent? }` |
92
+ | `ui/message` | `{ role: "user", content }` |
93
+ | `ui/open-link` | `{ url }` |
94
+ | `ui/request-display-mode` | `{ mode }` |
95
+
96
+ An explicit `embedMode: "iframe"` / `renderMode: "iframe"` diagnostic path
97
+ keeps the old wrapper-to-route postMessage relay:
88
98
 
89
99
  | Direction | Type | Payload shape |
90
100
  | --------------- | ---------------------------------------- | --------------------------------------------- |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.22.17",
3
+ "version": "0.22.19",
4
4
  "type": "module",
5
5
  "description": "Framework for agent-native application development — where AI agents and UI share state via files",
6
6
  "license": "MIT",