@agent-native/core 0.22.7 → 0.22.9
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.
- package/dist/agent/engine/builder-gateway-headers.d.ts +1 -1
- package/dist/agent/engine/builder-gateway-headers.d.ts.map +1 -1
- package/dist/agent/engine/builder-gateway-headers.js +1 -3
- package/dist/agent/engine/builder-gateway-headers.js.map +1 -1
- package/dist/agent/engine/translate-anthropic.d.ts +2 -0
- package/dist/agent/engine/translate-anthropic.d.ts.map +1 -1
- package/dist/agent/engine/translate-anthropic.js +71 -9
- package/dist/agent/engine/translate-anthropic.js.map +1 -1
- package/dist/client/mcp-apps/McpAppRenderer.js +6 -1
- package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/index.browser.d.ts +1 -0
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -0
- package/dist/index.browser.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +7 -3
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +10 -7
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/embed-app.js +4 -4
- package/dist/mcp/embed-app.js.map +1 -1
- package/docs/content/actions.md +20 -20
- package/docs/content/external-agents.md +19 -14
- package/docs/content/mcp-protocol.md +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-app.js","sourceRoot":"","sources":["../../src/mcp/embed-app.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAClB,mEAAmE,CAAC;AAEtE,MAAM,CAAC,MAAM,iCAAiC,GAAG,gBAAgB,CAAC;AAalE,SAAS,IAAI,CAAC,KAAyB;IACrC,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,UAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC;IAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IACrD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACtE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,KAAK,KAAK,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC;IAEnE,OAAO;QACL,KAAK;QACL,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,IAAI,EAAE,GAAG,EAAE,CAAC;;;;;;;;;oDASoC,MAAM;;;;;;+CAMX,MAAM,GAAG,EAAE;oDACN,MAAM,GAAG,EAAE;iEACE,MAAM,GAAG,EAAE;;;;gBAI5D,IAAI,CAAC,KAAK,CAAC;uBACJ,IAAI,CAAC,WAAW,CAAC;qBACnB,IAAI,CAAC,SAAS,CAAC;qBACf,IAAI,CAAC,aAAa,CAAC;wBAChB,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;;;;sCAIZ,IAAI,CAAC,KAAK,CAAC;;mDAEE,IAAI,CAAC,SAAS,CAAC;;;;;;;;2BAQvC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwHjC;QACJ,GAAG,EAAE;YACH,cAAc,EAAE,CAAC,gBAAgB,CAAC;YAClC,eAAe,EAAE,CAAC,gBAAgB,CAAC;YACnC,YAAY,EAAE;gBACZ,iCAAiC;gBACjC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;aAChC;SACF;QACD,aAAa,EAAE,KAAK;KACrB,CAAC;AACJ,CAAC","sourcesContent":["import type { ActionMcpAppResourceConfig } from \"../action.js\";\n\nconst MCP_APP_IMPORT =\n \"https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps\";\n\nexport const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = \"$requestOrigin\";\n\nexport interface EmbedAppOptions {\n title?: string;\n description?: string;\n iframeTitle?: string;\n openLabel?: string;\n embedByDefault?: boolean;\n startToolName?: string;\n frameDomains?: string[];\n height?: number;\n}\n\nfunction attr(value: string | undefined): string {\n return String(value ?? \"\")\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\nexport function embedApp(\n options: EmbedAppOptions = {},\n): ActionMcpAppResourceConfig {\n const title = options.title ?? \"Open app\";\n const iframeTitle = options.iframeTitle ?? \"Agent Native app\";\n const openLabel = options.openLabel ?? \"Open in app\";\n const startToolName = options.startToolName ?? \"create_embed_session\";\n const embedByDefault = options.embedByDefault !== false;\n const height = Math.max(320, Math.min(
|
|
1
|
+
{"version":3,"file":"embed-app.js","sourceRoot":"","sources":["../../src/mcp/embed-app.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAClB,mEAAmE,CAAC;AAEtE,MAAM,CAAC,MAAM,iCAAiC,GAAG,gBAAgB,CAAC;AAalE,SAAS,IAAI,CAAC,KAAyB;IACrC,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,UAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC;IAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IACrD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACtE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,KAAK,KAAK,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC;IAEnE,OAAO;QACL,KAAK;QACL,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,IAAI,EAAE,GAAG,EAAE,CAAC;;;;;;;;;oDASoC,MAAM;;;;;;+CAMX,MAAM,GAAG,EAAE;oDACN,MAAM,GAAG,EAAE;iEACE,MAAM,GAAG,EAAE;;;;gBAI5D,IAAI,CAAC,KAAK,CAAC;uBACJ,IAAI,CAAC,WAAW,CAAC;qBACnB,IAAI,CAAC,SAAS,CAAC;qBACf,IAAI,CAAC,aAAa,CAAC;wBAChB,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;;;;sCAIZ,IAAI,CAAC,KAAK,CAAC;;mDAEE,IAAI,CAAC,SAAS,CAAC;;;;;;;;2BAQvC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAwHjC;QACJ,GAAG,EAAE;YACH,cAAc,EAAE,CAAC,gBAAgB,CAAC;YAClC,eAAe,EAAE,CAAC,gBAAgB,CAAC;YACnC,YAAY,EAAE;gBACZ,iCAAiC;gBACjC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;aAChC;SACF;QACD,aAAa,EAAE,KAAK;KACrB,CAAC;AACJ,CAAC","sourcesContent":["import type { ActionMcpAppResourceConfig } from \"../action.js\";\n\nconst MCP_APP_IMPORT =\n \"https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps\";\n\nexport const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = \"$requestOrigin\";\n\nexport interface EmbedAppOptions {\n title?: string;\n description?: string;\n iframeTitle?: string;\n openLabel?: string;\n embedByDefault?: boolean;\n startToolName?: string;\n frameDomains?: string[];\n height?: number;\n}\n\nfunction attr(value: string | undefined): string {\n return String(value ?? \"\")\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\nexport function embedApp(\n options: EmbedAppOptions = {},\n): ActionMcpAppResourceConfig {\n const title = options.title ?? \"Open app\";\n const iframeTitle = options.iframeTitle ?? \"Agent Native app\";\n const openLabel = options.openLabel ?? \"Open in app\";\n const startToolName = options.startToolName ?? \"create_embed_session\";\n const embedByDefault = options.embedByDefault !== false;\n const height = Math.max(320, Math.min(900, options.height ?? 900));\n\n return {\n title,\n ...(options.description ? { description: options.description } : {}),\n html: () => `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style>\n :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: Canvas; color: CanvasText; }\n * { box-sizing: border-box; }\n body { margin: 0; }\n .shell { display: grid; gap: 8px; min-height: ${height}px; padding: 0; }\n .bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 6px 8px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, Canvas); }\n .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }\n .actions { display: flex; align-items: center; gap: 6px; }\n button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }\n button:disabled { opacity: .55; cursor: default; }\n .stage { position: relative; min-height: ${height - 44}px; }\n iframe { display: block; width: 100%; height: ${height - 44}px; border: 0; background: Canvas; }\n .message { display: grid; place-items: center; min-height: ${height - 44}px; padding: 18px; color: color-mix(in srgb, CanvasText 62%, Canvas); font-size: 13px; line-height: 1.45; text-align: center; }\n </style>\n</head>\n<body\n data-title=\"${attr(title)}\"\n data-iframe-title=\"${attr(iframeTitle)}\"\n data-open-label=\"${attr(openLabel)}\"\n data-start-tool=\"${attr(startToolName)}\"\n data-embed-default=\"${embedByDefault ? \"1\" : \"0\"}\"\n>\n <main class=\"shell\">\n <div class=\"bar\">\n <div class=\"title\" data-title>${attr(title)}</div>\n <div class=\"actions\">\n <button type=\"button\" data-open disabled>${attr(openLabel)}</button>\n </div>\n </div>\n <section class=\"stage\" data-stage>\n <div class=\"message\">Preparing app</div>\n </section>\n </main>\n <script type=\"module\">\n import { App } from \"${MCP_APP_IMPORT}\";\n\n const app = new App({ name: \"Agent Native Embed\", version: \"1.0.0\" }, {});\n const body = document.body;\n const stage = document.querySelector(\"[data-stage]\");\n const titleEl = document.querySelector(\"[data-title]\");\n const openButton = document.querySelector(\"[data-open]\");\n const startTool = body.dataset.startTool || \"create_embed_session\";\n const embedByDefault = body.dataset.embedDefault !== \"0\";\n let toolInput = {};\n let openUrl = \"\";\n let startedFor = \"\";\n\n function esc(value) {\n return String(value ?? \"\")\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n }\n\n function parseJson(value, fallback) {\n if (value && typeof value === \"object\") return value;\n if (typeof value !== \"string\" || !value.trim()) return fallback;\n try { return JSON.parse(value); } catch { return fallback; }\n }\n\n function parseToolResult(params) {\n if (!params) return {};\n if (params.structuredContent && typeof params.structuredContent === \"object\") {\n return params.structuredContent;\n }\n const parts = Array.isArray(params.content) ? params.content : [];\n const textPart = parts.find((part) => part && part.type === \"text\" && typeof part.text === \"string\");\n return parseJson(textPart ? textPart.text : \"\", {});\n }\n\n function openLinkFrom(params, data) {\n const metaUrl = params && params._meta && params._meta[\"agent-native/openLink\"]\n ? params._meta[\"agent-native/openLink\"].webUrl\n : \"\";\n return metaUrl || data.url || data.deepLink || data.openUrl || \"\";\n }\n\n function wantsEmbed() {\n if (toolInput.embed === false || toolInput.embed === \"false\") return false;\n if (embedByDefault) return true;\n return toolInput.embed === true || toolInput.embed === \"true\";\n }\n\n function setMessage(message) {\n stage.innerHTML = '<div class=\"message\">' + esc(message) + '</div>';\n }\n\n function renderFrame(src) {\n const frame = document.createElement(\"iframe\");\n frame.title = body.dataset.iframeTitle || \"Agent Native app\";\n frame.src = src;\n frame.allow = \"clipboard-read; clipboard-write\";\n stage.replaceChildren(frame);\n }\n\n async function launchEmbed() {\n if (!openUrl) {\n setMessage(\"Open link was not available.\");\n return;\n }\n if (!wantsEmbed()) {\n setMessage(\"Ready to open.\");\n return;\n }\n if (startedFor === openUrl) return;\n startedFor = openUrl;\n setMessage(\"Loading app\");\n try {\n const result = await app.callServerTool({\n name: startTool,\n arguments: {\n url: openUrl,\n chrome: typeof toolInput.chrome === \"string\" ? toolInput.chrome : \"full\"\n }\n });\n const data = parseToolResult(result);\n if (!data.startUrl) {\n startedFor = \"\";\n setMessage(data.error || \"This app can be opened, but not embedded from this MCP server.\");\n return;\n }\n renderFrame(data.startUrl);\n } catch (err) {\n startedFor = \"\";\n setMessage(err && err.message ? err.message : \"Could not launch embedded app.\");\n }\n }\n\n function updateOpenButton() {\n openButton.disabled = !openUrl;\n openButton.onclick = () => {\n if (openUrl) void app.openLink({ url: openUrl });\n };\n }\n\n function updateTitle(data) {\n const label = data.label || data.app || data.view || body.dataset.title || \"App\";\n titleEl.textContent = String(label);\n }\n\n app.ontoolinput = (params) => {\n toolInput = params.arguments || {};\n };\n app.ontoolresult = (params) => {\n const data = parseToolResult(params);\n openUrl = openLinkFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n void launchEmbed();\n };\n await app.connect();\n </script>\n</body>\n</html>`,\n csp: {\n connectDomains: [\"https://esm.sh\"],\n resourceDomains: [\"https://esm.sh\"],\n frameDomains: [\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.frameDomains ?? []),\n ],\n },\n prefersBorder: false,\n };\n}\n"]}
|
package/docs/content/actions.md
CHANGED
|
@@ -154,9 +154,26 @@ If your app is an [A2A](/docs/a2a-protocol) peer, other agent-native apps discov
|
|
|
154
154
|
|
|
155
155
|
With MCP enabled, your actions show up in the framework's MCP server at `/_agent-native/mcp`. Any MCP client — Claude, ChatGPT custom MCP apps, Claude Desktop/Code, Cursor, Codex, etc. — can connect and see them as tools. See [MCP Protocol](/docs/mcp-protocol).
|
|
156
156
|
|
|
157
|
-
For UI-capable MCP hosts, actions can also attach an optional MCP Apps resource
|
|
157
|
+
For UI-capable MCP hosts, actions can also attach an optional MCP Apps resource.
|
|
158
|
+
Use the shared full-app embed helper when the action needs an inline experience.
|
|
159
|
+
MCP Apps should embed the real React route; do not hand-write a separate plain
|
|
160
|
+
HTML product UI.
|
|
161
|
+
|
|
162
|
+
The pattern is the same focused link we already return for external agents:
|
|
163
|
+
the action exposes the operation, `link` points at the route with the right URL
|
|
164
|
+
or deep-link params, and `embedApp()` uses that same target as the inline app.
|
|
165
|
+
This works for draft emails, filtered inboxes, calendar event drafts, full
|
|
166
|
+
dashboards, saved analyses, extension routes, decks, design editors, and any
|
|
167
|
+
other state the app can load from a route.
|
|
168
|
+
|
|
169
|
+
When a whole app surface is too much, embed a narrow route that renders a real
|
|
170
|
+
shared React component instead. For example, Analytics can render `/chart` with
|
|
171
|
+
a compact `SqlPanel` URL payload so the MCP host shows one live chart while the
|
|
172
|
+
implementation still reuses the dashboard chart component.
|
|
158
173
|
|
|
159
174
|
```ts
|
|
175
|
+
import { embedApp } from "@agent-native/core";
|
|
176
|
+
|
|
160
177
|
export default defineAction({
|
|
161
178
|
description: "Create an email draft for review.",
|
|
162
179
|
schema: z.object({ body: z.string() }),
|
|
@@ -166,31 +183,14 @@ export default defineAction({
|
|
|
166
183
|
url: "/_agent-native/open?app=mail&view=inbox",
|
|
167
184
|
}),
|
|
168
185
|
mcpApp: {
|
|
169
|
-
resource: {
|
|
170
|
-
title: "Review draft",
|
|
171
|
-
html: '<!doctype html><html><body><main id="app"></main></body></html>',
|
|
172
|
-
csp: { connectDomains: ["https://mail.agent-native.com"] },
|
|
173
|
-
},
|
|
186
|
+
resource: embedApp({ title: "Review draft", openLabel: "Open in Mail" }),
|
|
174
187
|
},
|
|
175
188
|
});
|
|
176
189
|
```
|
|
177
190
|
|
|
178
191
|
This advertises the MCP Apps extension (`io.modelcontextprotocol/ui`), exposes the HTML via MCP resources, and includes both current and legacy UI resource metadata for compatible hosts. Keep `link` as the fallback for CLI and non-UI MCP clients; see [External Agents](/docs/external-agents#mcp-apps).
|
|
179
192
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
import { embedApp } from "@agent-native/core/mcp";
|
|
184
|
-
|
|
185
|
-
export default defineAction({
|
|
186
|
-
// ...description, schema, run, link...
|
|
187
|
-
mcpApp: {
|
|
188
|
-
resource: embedApp({ title: "Open dashboard" }),
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
The helper launches the action's `link` target through `/_agent-native/embed/start` with a short-lived browser session, so routes such as dashboards, filtered inboxes, drafts, and extension pages can reuse the app's React components directly.
|
|
193
|
+
The helper launches the action's `link` target through `/_agent-native/embed/start` with a short-lived browser session, so routes such as full dashboards, filtered inboxes, drafts, and extension pages can reuse the app's React components directly.
|
|
194
194
|
|
|
195
195
|
## Standard actions {#standard-actions}
|
|
196
196
|
|
|
@@ -102,7 +102,7 @@ Use Agent-Native Analytics to generate a weekly conversion-rate bar chart and sh
|
|
|
102
102
|
Use Agent-Native Mail to draft a short follow-up email to me, but do not send it.
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
In hosts that support MCP Apps, Analytics can render
|
|
105
|
+
In hosts that support MCP Apps, Analytics can render real dashboard and analysis routes inline, and Mail can render the real compose UI inline for draft review. In hosts that do not render MCP Apps, the same tool call still returns a deep link such as **Open draft in Mail →** or **Open dashboard in Analytics →**.
|
|
106
106
|
|
|
107
107
|
## Advanced setup: local agents {#connect}
|
|
108
108
|
|
|
@@ -279,31 +279,36 @@ List/search actions point at a record-focused view the same way — e.g. calenda
|
|
|
279
279
|
|
|
280
280
|
## Authoring: optional MCP Apps UI {#mcp-apps}
|
|
281
281
|
|
|
282
|
-
For hosts that support the MCP Apps extension, an action can also advertise an inline
|
|
282
|
+
For hosts that support the MCP Apps extension, an action can also advertise an inline UI resource with `mcpApp`. This is a progressive enhancement for flows where the external agent should hand the user an interactive surface instead of only text — for example reviewing an email draft, editing a calendar invite, or choosing between generated dashboard variants.
|
|
283
|
+
|
|
284
|
+
Use the real React app with `embedApp()` whenever the user needs UI. The mental model is simple: the action's `link` target is also the MCP App embed target. Expose the operation as a normal action/tool, return a focused deep link with `link`, and add `mcpApp.resource = embedApp(...)` so capable hosts load that same route inline instead of opening a new tab.
|
|
285
|
+
|
|
286
|
+
That means full-app embeds can do anything the route can do once opened: review or edit an email draft, show a filtered inbox/search, open a calendar event or event draft, load an extension page, inspect a full analytics dashboard or saved analysis, continue a deck in the Slides editor, or open a Design project/editor. Prefer URL/deep-link params and the existing `/_agent-native/open` navigation/app-state bridge over inventing a second state protocol for MCP Apps.
|
|
287
|
+
|
|
288
|
+
On rare occasions the right target is a focused app route that renders one shared React component instead of the whole app shell. Analytics' `/chart` route is the model: it takes a compact `SqlPanel` payload in the URL and renders the same chart component the dashboard uses. This is still an app embed, not a plain HTML MCP App. Expose or call it through a normal action / `open_app({ path, embed: true })`, keep the URL deterministic, and let `embedApp()` render that route inline.
|
|
289
|
+
|
|
290
|
+
Do not hand-write one-off plain HTML MCP Apps for product UI; if the action needs a custom surface, add or reuse a real app route/component first and embed that route.
|
|
283
291
|
|
|
284
292
|
```ts
|
|
293
|
+
import { embedApp } from "@agent-native/core";
|
|
294
|
+
|
|
285
295
|
export default defineAction({
|
|
286
296
|
// ...description, schema, run, link...
|
|
287
297
|
mcpApp: {
|
|
288
|
-
resource: {
|
|
298
|
+
resource: embedApp({
|
|
289
299
|
title: "Review draft",
|
|
290
|
-
description: "
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
csp: { connectDomains: ["https://mail.agent-native.com"] },
|
|
296
|
-
prefersBorder: true,
|
|
297
|
-
},
|
|
300
|
+
description: "Open the generated draft in the real Mail compose UI.",
|
|
301
|
+
iframeTitle: "Agent-Native Mail",
|
|
302
|
+
openLabel: "Open in Mail",
|
|
303
|
+
frameDomains: ["https:", "http://localhost:*", "http://127.0.0.1:*"],
|
|
304
|
+
}),
|
|
298
305
|
},
|
|
299
306
|
});
|
|
300
307
|
```
|
|
301
308
|
|
|
302
309
|
The MCP server advertises extension `io.modelcontextprotocol/ui`, adds `_meta.ui.resourceUri` plus `_meta["ui/resourceUri"]` to `tools/list`, and exposes the HTML through `resources/list` + `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.
|
|
303
310
|
|
|
304
|
-
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.
|
|
305
|
-
|
|
306
|
-
For heavyweight authenticated workflows, reuse the real React app instead of rebuilding a plain-HTML mini UI. Core exports `embedApp()` from `@agent-native/core/mcp` and `@agent-native/core`; attach it to an action that already has a `link` builder. The embedded MCP App 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 dashboards, filtered inboxes, calendar draft views, and extension pages.
|
|
311
|
+
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.
|
|
307
312
|
|
|
308
313
|
### The `link` contract {#link-contract}
|
|
309
314
|
|
|
@@ -72,7 +72,7 @@ POST https://your-app.example.com/_agent-native/mcp
|
|
|
72
72
|
|
|
73
73
|
The server supports the standard MCP handshake: `initialize` → `initialized` → `tools/list` → `tools/call`.
|
|
74
74
|
|
|
75
|
-
If an action declares `mcpApp`, the server also advertises the official MCP Apps extension (`io.modelcontextprotocol/ui`) and supports `resources/list`, `resources/templates/list`, and `resources/read` for the app
|
|
75
|
+
If an action declares `mcpApp`, the server also advertises the official MCP Apps extension (`io.modelcontextprotocol/ui`) and supports `resources/list`, `resources/templates/list`, and `resources/read` for the app resource. Hosts that render MCP Apps can show the UI inline; hosts that do not can still call the tool and use the deep-link fallback. Product UIs should use `embedApp()` so the inline surface is the real React app route, or a focused route that renders a shared React component such as an Analytics chart, not a separate plain HTML implementation. The current official extension matrix includes Claude, Claude Desktop, VS Code GitHub Copilot, Goose, Postman, MCPJam, ChatGPT, and Cursor; host support varies by version and plan, so use the [External Agents MCP Apps notes](/docs/external-agents#mcp-apps-compatibility) for the user-facing guidance.
|
|
76
76
|
|
|
77
77
|
## Tools {#tools}
|
|
78
78
|
|