@agent-native/core 0.72.3 → 0.73.0

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 (223) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +38 -0
  3. package/corpus/core/docs/content/skills-guide.md +14 -0
  4. package/corpus/core/docs/content/template-clips.md +7 -1
  5. package/corpus/core/package.json +6 -6
  6. package/corpus/core/src/a2a/client.ts +7 -3
  7. package/corpus/core/src/agent/durable-background.ts +25 -12
  8. package/corpus/core/src/cli/create.ts +1 -1
  9. package/corpus/core/src/cli/skills.ts +450 -19
  10. package/corpus/core/src/client/api-path.ts +28 -0
  11. package/corpus/core/src/client/embed-auth.ts +26 -0
  12. package/corpus/core/src/client/mcp-app-host.ts +6 -0
  13. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +227 -33
  14. package/corpus/core/src/client/resources/use-mcp-servers.ts +1 -0
  15. package/corpus/core/src/client/settings/DemoModeSection.tsx +3 -1
  16. package/corpus/core/src/demo/fetch-interceptor.ts +1 -1
  17. package/corpus/core/src/embedding/agent.ts +6 -1
  18. package/corpus/core/src/embedding/bridge.ts +34 -1
  19. package/corpus/core/src/mcp/build-server.ts +38 -10
  20. package/corpus/core/src/mcp/embed-app.ts +120 -45
  21. package/corpus/core/src/mcp/index.ts +5 -0
  22. package/corpus/core/src/mcp/org-directory.ts +70 -11
  23. package/corpus/core/src/mcp/server.ts +14 -0
  24. package/corpus/core/src/mcp-client/config.ts +12 -0
  25. package/corpus/core/src/mcp-client/index.ts +2 -0
  26. package/corpus/core/src/mcp-client/manager.ts +268 -9
  27. package/corpus/core/src/mcp-client/remote-store.ts +154 -0
  28. package/corpus/core/src/mcp-client/routes.ts +2 -0
  29. package/corpus/core/src/server/agent-chat-plugin.ts +8 -0
  30. package/corpus/core/src/server/auth.ts +7 -0
  31. package/corpus/core/src/server/index.ts +1 -0
  32. package/corpus/core/src/server/onboarding-html.ts +95 -9
  33. package/corpus/core/src/shared/mcp-embed-headers.ts +44 -3
  34. package/corpus/core/src/templates/default/AGENTS.md +4 -0
  35. package/corpus/core/src/templates/default/package.json +2 -1
  36. package/corpus/core/src/templates/headless/AGENTS.md +4 -0
  37. package/corpus/core/src/templates/headless/package.json +1 -0
  38. package/corpus/core/src/templates/workspace-root/AGENTS.md +4 -0
  39. package/corpus/core/src/templates/workspace-root/README.md +4 -0
  40. package/corpus/core/src/templates/workspace-root/package.json +1 -0
  41. package/corpus/core/src/vite/client.ts +53 -0
  42. package/corpus/templates/analytics/actions/update-dashboard.ts +3 -4
  43. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +144 -40
  44. package/corpus/templates/analytics/app/global.css +15 -13
  45. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +9 -32
  46. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +0 -26
  47. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +0 -23
  48. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/pivot.ts +65 -2
  49. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +3 -4
  50. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-charts-now-fill-sparse-template-time-series-suppor.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-rows-now-auto-fit-panels-so-one-two-or-three-chart.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-06-24-dashboard-traffic-tables-now-show-top-urls-and-shared-clips-.md +6 -0
  53. package/corpus/templates/analytics/changelog/2026-06-24-first-party-signup-and-pageview-panels-now-honor-dashboard-filters.md +5 -0
  54. package/corpus/templates/analytics/changelog/2026-06-24-first-party-template-dashboards-now-include-repeat-users-ret.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-06-24-retention-charts-now-break-out-1-day-and-7-day-return-rates-.md +6 -0
  56. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +256 -44
  57. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +7 -2
  58. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +35 -8
  59. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +287 -42
  60. package/corpus/templates/assets/.agents/skills/a2a-assets/SKILL.md +17 -10
  61. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +15 -6
  62. package/corpus/templates/assets/AGENTS.md +7 -5
  63. package/corpus/templates/assets/actions/generate-asset.ts +161 -0
  64. package/corpus/templates/assets/actions/list-assets.ts +4 -2
  65. package/corpus/templates/assets/actions/open-asset-picker.ts +84 -3
  66. package/corpus/templates/assets/app/routes/library.tsx +243 -40
  67. package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +1 -1
  68. package/corpus/templates/calendar/server/plugins/auth.ts +12 -0
  69. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +7 -7
  70. package/corpus/templates/clips/AGENTS.md +6 -2
  71. package/corpus/templates/clips/app/components/capture-install-options.tsx +20 -4
  72. package/corpus/templates/clips/app/components/player/player-controls.tsx +62 -32
  73. package/corpus/templates/clips/app/lib/capture-install-options.ts +39 -5
  74. package/corpus/templates/clips/app/routes/download.tsx +2 -2
  75. package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -1
  76. package/corpus/templates/clips/changelog/2026-06-24-inline-slack-playback-controls-no-longer-shift-when-adjustin.md +6 -0
  77. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-console-log-stream-all-le.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-06-24-shared-clips-now-expose-their-full-fetch-xhr-network-request.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-06-24-the-chrome-extension-option-now-only-appears-on-supported-hosts.md +5 -0
  80. package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +13 -8
  81. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  82. package/corpus/templates/clips/chrome-extension/scripts/package.ts +15 -2
  83. package/corpus/templates/clips/chrome-extension/src/overlay.css +4 -25
  84. package/corpus/templates/clips/chrome-extension/src/overlay.ts +1 -3
  85. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +9 -0
  86. package/corpus/templates/clips/server/lib/public-agent-context.ts +67 -24
  87. package/corpus/templates/design/AGENTS.md +10 -5
  88. package/corpus/templates/design/actions/connect-assets-mcp.ts +131 -0
  89. package/corpus/templates/design/actions/insert-asset.ts +219 -0
  90. package/corpus/templates/design/actions/view-screen.ts +3 -1
  91. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
  92. package/corpus/templates/design/app/pages/DesignEditor.tsx +53 -0
  93. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -1
  94. package/corpus/templates/design/server/plugins/assets-mcp.ts +60 -0
  95. package/corpus/templates/mail/server/plugins/auth.ts +4 -1
  96. package/corpus/templates/plan/app/pages/PlansPage.tsx +271 -197
  97. package/corpus/templates/plan/server/plan-content.ts +45 -2
  98. package/dist/a2a/client.d.ts +1 -0
  99. package/dist/a2a/client.d.ts.map +1 -1
  100. package/dist/a2a/client.js +5 -3
  101. package/dist/a2a/client.js.map +1 -1
  102. package/dist/agent/durable-background.d.ts +9 -4
  103. package/dist/agent/durable-background.d.ts.map +1 -1
  104. package/dist/agent/durable-background.js +24 -11
  105. package/dist/agent/durable-background.js.map +1 -1
  106. package/dist/cli/create.js +1 -1
  107. package/dist/cli/create.js.map +1 -1
  108. package/dist/cli/skills.d.ts.map +1 -1
  109. package/dist/cli/skills.js +361 -19
  110. package/dist/cli/skills.js.map +1 -1
  111. package/dist/client/api-path.d.ts.map +1 -1
  112. package/dist/client/api-path.js +27 -0
  113. package/dist/client/api-path.js.map +1 -1
  114. package/dist/client/embed-auth.d.ts.map +1 -1
  115. package/dist/client/embed-auth.js +27 -0
  116. package/dist/client/embed-auth.js.map +1 -1
  117. package/dist/client/mcp-app-host.d.ts.map +1 -1
  118. package/dist/client/mcp-app-host.js +5 -0
  119. package/dist/client/mcp-app-host.js.map +1 -1
  120. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  121. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  122. package/dist/client/mcp-apps/McpAppRenderer.js +179 -33
  123. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  124. package/dist/client/resources/use-mcp-servers.d.ts +1 -0
  125. package/dist/client/resources/use-mcp-servers.d.ts.map +1 -1
  126. package/dist/client/resources/use-mcp-servers.js.map +1 -1
  127. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  128. package/dist/client/settings/DemoModeSection.js +3 -1
  129. package/dist/client/settings/DemoModeSection.js.map +1 -1
  130. package/dist/demo/fetch-interceptor.js +1 -1
  131. package/dist/demo/fetch-interceptor.js.map +1 -1
  132. package/dist/embedding/agent.d.ts +1 -1
  133. package/dist/embedding/agent.d.ts.map +1 -1
  134. package/dist/embedding/agent.js +1 -1
  135. package/dist/embedding/agent.js.map +1 -1
  136. package/dist/embedding/bridge.d.ts.map +1 -1
  137. package/dist/embedding/bridge.js +27 -1
  138. package/dist/embedding/bridge.js.map +1 -1
  139. package/dist/extensions/schema.d.ts +2 -2
  140. package/dist/mcp/build-server.d.ts +2 -0
  141. package/dist/mcp/build-server.d.ts.map +1 -1
  142. package/dist/mcp/build-server.js +32 -9
  143. package/dist/mcp/build-server.js.map +1 -1
  144. package/dist/mcp/embed-app.d.ts.map +1 -1
  145. package/dist/mcp/embed-app.js +120 -45
  146. package/dist/mcp/embed-app.js.map +1 -1
  147. package/dist/mcp/index.d.ts +1 -0
  148. package/dist/mcp/index.d.ts.map +1 -1
  149. package/dist/mcp/index.js +1 -0
  150. package/dist/mcp/index.js.map +1 -1
  151. package/dist/mcp/org-directory.d.ts +1 -0
  152. package/dist/mcp/org-directory.d.ts.map +1 -1
  153. package/dist/mcp/org-directory.js +59 -11
  154. package/dist/mcp/org-directory.js.map +1 -1
  155. package/dist/mcp/server.d.ts.map +1 -1
  156. package/dist/mcp/server.js +9 -0
  157. package/dist/mcp/server.js.map +1 -1
  158. package/dist/mcp-client/config.d.ts +12 -0
  159. package/dist/mcp-client/config.d.ts.map +1 -1
  160. package/dist/mcp-client/config.js.map +1 -1
  161. package/dist/mcp-client/index.d.ts +1 -1
  162. package/dist/mcp-client/index.d.ts.map +1 -1
  163. package/dist/mcp-client/index.js +1 -1
  164. package/dist/mcp-client/index.js.map +1 -1
  165. package/dist/mcp-client/manager.d.ts +2 -0
  166. package/dist/mcp-client/manager.d.ts.map +1 -1
  167. package/dist/mcp-client/manager.js +201 -9
  168. package/dist/mcp-client/manager.js.map +1 -1
  169. package/dist/mcp-client/remote-store.d.ts +27 -0
  170. package/dist/mcp-client/remote-store.d.ts.map +1 -1
  171. package/dist/mcp-client/remote-store.js +105 -0
  172. package/dist/mcp-client/remote-store.js.map +1 -1
  173. package/dist/mcp-client/routes.d.ts +1 -0
  174. package/dist/mcp-client/routes.d.ts.map +1 -1
  175. package/dist/mcp-client/routes.js +1 -0
  176. package/dist/mcp-client/routes.js.map +1 -1
  177. package/dist/provider-api/actions/github-repo-files.d.ts +2 -2
  178. package/dist/provider-api/actions/query-staged-dataset.d.ts +3 -3
  179. package/dist/provider-api/corpus-jobs.d.ts +8 -8
  180. package/dist/server/agent-chat-plugin.d.ts +2 -0
  181. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  182. package/dist/server/agent-chat-plugin.js +8 -0
  183. package/dist/server/agent-chat-plugin.js.map +1 -1
  184. package/dist/server/auth.d.ts +7 -0
  185. package/dist/server/auth.d.ts.map +1 -1
  186. package/dist/server/auth.js +1 -0
  187. package/dist/server/auth.js.map +1 -1
  188. package/dist/server/index.d.ts +1 -1
  189. package/dist/server/index.d.ts.map +1 -1
  190. package/dist/server/index.js +1 -1
  191. package/dist/server/index.js.map +1 -1
  192. package/dist/server/onboarding-html.d.ts +15 -0
  193. package/dist/server/onboarding-html.d.ts.map +1 -1
  194. package/dist/server/onboarding-html.js +73 -9
  195. package/dist/server/onboarding-html.js.map +1 -1
  196. package/dist/shared/mcp-embed-headers.d.ts +3 -1
  197. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  198. package/dist/shared/mcp-embed-headers.js +40 -3
  199. package/dist/shared/mcp-embed-headers.js.map +1 -1
  200. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  201. package/dist/sharing/actions/set-resource-visibility.d.ts +2 -2
  202. package/dist/sharing/actions/share-resource.d.ts +1 -1
  203. package/dist/sharing/schema.d.ts +1 -1
  204. package/dist/templates/default/AGENTS.md +4 -0
  205. package/dist/templates/default/package.json +2 -1
  206. package/dist/templates/headless/AGENTS.md +4 -0
  207. package/dist/templates/headless/package.json +1 -0
  208. package/dist/templates/workspace-root/AGENTS.md +4 -0
  209. package/dist/templates/workspace-root/README.md +4 -0
  210. package/dist/templates/workspace-root/package.json +1 -0
  211. package/dist/vite/client.d.ts.map +1 -1
  212. package/dist/vite/client.js +44 -0
  213. package/dist/vite/client.js.map +1 -1
  214. package/docs/content/skills-guide.md +14 -0
  215. package/docs/content/template-clips.md +7 -1
  216. package/package.json +6 -6
  217. package/src/templates/default/AGENTS.md +4 -0
  218. package/src/templates/default/package.json +2 -1
  219. package/src/templates/headless/AGENTS.md +4 -0
  220. package/src/templates/headless/package.json +1 -0
  221. package/src/templates/workspace-root/AGENTS.md +4 -0
  222. package/src/templates/workspace-root/README.md +4 -0
  223. package/src/templates/workspace-root/package.json +1 -0
@@ -1,4 +1,11 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useLayoutEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from "react";
2
9
  import {
3
10
  AppBridge,
4
11
  PostMessageTransport,
@@ -8,8 +15,14 @@ import {
8
15
  } from "@modelcontextprotocol/ext-apps/app-bridge";
9
16
  import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
10
17
  import { IconAlertTriangle, IconLoader2 } from "@tabler/icons-react";
18
+ import {
19
+ AGENT_NATIVE_EMBED_MESSAGE_TYPES,
20
+ AGENT_NATIVE_EMBED_PROTOCOL,
21
+ AGENT_NATIVE_EMBED_VERSION,
22
+ } from "../../embedding/protocol.js";
11
23
  import type { AgentMcpAppPayload } from "../../mcp-client/app-result.js";
12
24
  import { agentNativePath } from "../api-path.js";
25
+ import { sendToAgentChat, type AgentChatRequestMode } from "../agent-chat.js";
13
26
  import { cn } from "../utils.js";
14
27
 
15
28
  export const DEFAULT_MCP_APP_IFRAME_HEIGHT = 650;
@@ -17,6 +30,8 @@ export const MCP_APP_INITIALIZE_TIMEOUT_MS = 8000;
17
30
  const MIN_IFRAME_HEIGHT = 220;
18
31
  const VIEWPORT_MARGIN = 16;
19
32
  const SANDBOX_FLAGS = "allow-scripts allow-forms allow-popups";
33
+ const useBrowserLayoutEffect =
34
+ typeof window === "undefined" ? useEffect : useLayoutEffect;
20
35
 
21
36
  export interface McpAppRendererProps {
22
37
  app: AgentMcpAppPayload;
@@ -29,10 +44,24 @@ type ResourceUiMeta = {
29
44
  prefersBorder?: boolean;
30
45
  };
31
46
 
47
+ type McpContentPart = {
48
+ type?: unknown;
49
+ text?: unknown;
50
+ data?: unknown;
51
+ mimeType?: unknown;
52
+ url?: unknown;
53
+ };
54
+
55
+ type McpAppModelContext = {
56
+ content?: unknown;
57
+ structuredContent?: unknown;
58
+ };
59
+
32
60
  export function McpAppRenderer({ app, className }: McpAppRendererProps) {
33
61
  const iframeRef = useRef<HTMLIFrameElement | null>(null);
34
62
  const desiredHeightRef = useRef(DEFAULT_MCP_APP_IFRAME_HEIGHT);
35
- const [loadedSrcDoc, setLoadedSrcDoc] = useState<string | null>(null);
63
+ const modelContextRef = useRef<McpAppModelContext | null>(null);
64
+ const readyRef = useRef(false);
36
65
  const [height, setHeight] = useState(DEFAULT_MCP_APP_IFRAME_HEIGHT);
37
66
  const [error, setError] = useState<string | null>(null);
38
67
  const [ready, setReady] = useState(false);
@@ -49,6 +78,23 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
49
78
  );
50
79
  const externalOpenUrl = useMemo(() => openUrlFromMcpApp(app), [app]);
51
80
 
81
+ // Keep the latest payload/permissions/csp reachable from the bridge effect
82
+ // without making them effect dependencies. The embedded resource identity is
83
+ // fully captured by `srcDoc`. The bridge effect must NOT re-run when a benign
84
+ // parent re-render hands us a new `app` object reference with identical
85
+ // content (common during chat streaming/polling): re-running tears down a
86
+ // live, already-initialized MCP App (teardownResource) and re-arms the
87
+ // initialize watchdog against a fresh host AppBridge that the embed shell
88
+ // will never re-handshake (its connect promise is memoized), surfacing a
89
+ // false "MCP App did not finish initializing." error after the app is
90
+ // visibly working.
91
+ const appRef = useRef(app);
92
+ const supportedPermissionsRef = useRef(supportedPermissions);
93
+ const uiCspRef = useRef(uiMeta.csp);
94
+ appRef.current = app;
95
+ supportedPermissionsRef.current = supportedPermissions;
96
+ uiCspRef.current = uiMeta.csp;
97
+
52
98
  useEffect(() => {
53
99
  desiredHeightRef.current = DEFAULT_MCP_APP_IFRAME_HEIGHT;
54
100
  setHeight(
@@ -57,11 +103,18 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
57
103
  availableMcpAppHeight(iframeRef.current),
58
104
  ),
59
105
  );
60
- setLoadedSrcDoc(null);
106
+ readyRef.current = false;
61
107
  setReady(false);
62
108
  setError(null);
109
+ modelContextRef.current = null;
63
110
  }, [srcDoc]);
64
111
 
112
+ const markReady = useCallback(() => {
113
+ readyRef.current = true;
114
+ setReady(true);
115
+ setError(null);
116
+ }, []);
117
+
65
118
  const applyHeight = useCallback((desiredHeight?: number) => {
66
119
  if (
67
120
  typeof desiredHeight === "number" &&
@@ -114,11 +167,26 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
114
167
 
115
168
  useEffect(() => {
116
169
  const iframe = iframeRef.current;
117
- if (!iframe?.contentWindow || !srcDoc || loadedSrcDoc !== srcDoc) return;
170
+ if (!iframe?.contentWindow || !srcDoc) return;
171
+ const frameWindow = iframe.contentWindow;
172
+ const listener = (event: MessageEvent) => {
173
+ if (event.source !== frameWindow) return;
174
+ if (isMcpAppReadyMessage(event.data)) {
175
+ markReady();
176
+ }
177
+ };
178
+ window.addEventListener("message", listener);
179
+ return () => window.removeEventListener("message", listener);
180
+ }, [markReady, srcDoc]);
181
+
182
+ useBrowserLayoutEffect(() => {
183
+ const iframe = iframeRef.current;
184
+ if (!iframe?.contentWindow || !srcDoc) return;
118
185
 
186
+ const currentApp = appRef.current;
119
187
  let closed = false;
120
188
  const initializeTimer = window.setTimeout(() => {
121
- if (closed) return;
189
+ if (closed || readyRef.current) return;
122
190
  setReady(false);
123
191
  setError("MCP App did not finish initializing.");
124
192
  }, MCP_APP_INITIALIZE_TIMEOUT_MS);
@@ -131,13 +199,13 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
131
199
  serverResources: {},
132
200
  logging: {},
133
201
  sandbox: {
134
- permissions: supportedPermissions,
135
- csp: uiMeta.csp ?? {},
202
+ permissions: supportedPermissionsRef.current,
203
+ csp: uiCspRef.current ?? {},
136
204
  },
137
205
  },
138
206
  {
139
207
  hostContext: buildHostContext(
140
- app,
208
+ currentApp,
141
209
  availableMcpAppHeight(iframe),
142
210
  ) as any,
143
211
  },
@@ -152,12 +220,9 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
152
220
  bridge.addEventListener("initialized", () => {
153
221
  if (closed) return;
154
222
  clearTimeout(initializeTimer);
155
- setReady(true);
156
- // Clear any error the initialize timeout may have set before a slow app
157
- // finished otherwise the error overlay stays stuck over a working app.
158
- setError(null);
159
- void bridge.sendToolInput({ arguments: app.toolInput });
160
- void bridge.sendToolResult(app.toolResult as CallToolResult);
223
+ markReady();
224
+ void bridge.sendToolInput({ arguments: appRef.current.toolInput });
225
+ void bridge.sendToolResult(appRef.current.toolResult as CallToolResult);
161
226
  });
162
227
  bridge.addEventListener("loggingmessage", ({ level, data }) => {
163
228
  if (level === "error" || level === "critical" || level === "alert") {
@@ -170,13 +235,16 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
170
235
  return {};
171
236
  };
172
237
  bridge.oncalltool = async ({ name, arguments: toolArguments }) => {
173
- const toolName = normalizeSameServerToolName(app.serverId, name);
238
+ const toolName = normalizeSameServerToolName(
239
+ appRef.current.serverId,
240
+ name,
241
+ );
174
242
  if (!toolName) {
175
243
  return errorToolResult("Cross-server MCP App tool calls are blocked.");
176
244
  }
177
245
  try {
178
246
  return await postMcpAppEndpoint<CallToolResult>("call-tool", {
179
- serverId: app.serverId,
247
+ serverId: appRef.current.serverId,
180
248
  toolName,
181
249
  arguments:
182
250
  toolArguments && typeof toolArguments === "object"
@@ -188,22 +256,40 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
188
256
  }
189
257
  };
190
258
  (bridge as any).onlisttools = async () =>
191
- postMcpAppEndpoint("list-tools", { serverId: app.serverId });
259
+ postMcpAppEndpoint("list-tools", { serverId: appRef.current.serverId });
192
260
  bridge.onreadresource = async ({ uri }) =>
193
- postMcpAppEndpoint("read-resource", { serverId: app.serverId, uri });
261
+ postMcpAppEndpoint("read-resource", {
262
+ serverId: appRef.current.serverId,
263
+ uri,
264
+ });
194
265
  bridge.onlistresources = async () => ({ resources: [] });
195
266
  bridge.onlistresourcetemplates = async () => ({ resourceTemplates: [] });
196
267
  bridge.ondownloadfile = async () => ({ isError: true });
197
- bridge.onmessage = async () => ({ isError: true });
198
- bridge.onupdatemodelcontext = async () => ({});
268
+ bridge.onmessage = async (params) => {
269
+ const message = messageTextFromMcpUiMessage(params);
270
+ if (!message.trim()) return { isError: true };
271
+ const mode = requestModeFromMcpUiMessage(params);
272
+ sendToAgentChat({
273
+ message,
274
+ context: contextTextFromMcpModelContext(modelContextRef.current),
275
+ images: imageDataUrlsFromMcpContent(params.content),
276
+ submit: true,
277
+ openSidebar: true,
278
+ ...(mode ? { mode } : {}),
279
+ });
280
+ return {};
281
+ };
282
+ bridge.onupdatemodelcontext = async (params) => {
283
+ modelContextRef.current = params as McpAppModelContext;
284
+ return {};
285
+ };
199
286
 
200
287
  const transport = new PostMessageTransport(
201
288
  iframe.contentWindow,
202
289
  iframe.contentWindow,
203
290
  );
204
- setReady(false);
205
291
  setError(null);
206
- bridge.connect(transport).catch((err: any) => {
292
+ void bridge.connect(transport).catch((err: any) => {
207
293
  if (!closed) {
208
294
  clearTimeout(initializeTimer);
209
295
  setError(err?.message ?? "Failed to initialize MCP App.");
@@ -212,8 +298,8 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
212
298
 
213
299
  return () => {
214
300
  closed = true;
301
+ modelContextRef.current = null;
215
302
  clearTimeout(initializeTimer);
216
- setReady(false);
217
303
  void bridge
218
304
  .teardownResource({}, { timeout: 500 })
219
305
  .catch(() => undefined)
@@ -221,14 +307,10 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
221
307
  void (bridge as any).close?.().catch?.(() => undefined);
222
308
  });
223
309
  };
224
- }, [
225
- app,
226
- applyHeight,
227
- loadedSrcDoc,
228
- srcDoc,
229
- supportedPermissions,
230
- uiMeta.csp,
231
- ]);
310
+ // The embedded resource identity is captured by `srcDoc`; `app`,
311
+ // `supportedPermissions`, and `uiMeta.csp` are read via refs so a
312
+ // new-but-equal `app` object reference does not tear down a live bridge.
313
+ }, [applyHeight, markReady, srcDoc]);
232
314
 
233
315
  if (!resourceHtml) {
234
316
  return (
@@ -277,12 +359,83 @@ export function McpAppRenderer({ app, className }: McpAppRendererProps) {
277
359
  sandbox={SANDBOX_FLAGS}
278
360
  allow={buildAllowAttribute(supportedPermissions)}
279
361
  style={{ height }}
280
- onLoad={() => setLoadedSrcDoc(srcDoc)}
281
362
  />
282
363
  </div>
283
364
  );
284
365
  }
285
366
 
367
+ function messageTextFromMcpUiMessage(params: { content?: unknown }): string {
368
+ return textPartsFromMcpContent(params.content).join("\n\n").trim();
369
+ }
370
+
371
+ function requestModeFromMcpUiMessage(
372
+ params: unknown,
373
+ ): AgentChatRequestMode | undefined {
374
+ const record =
375
+ params && typeof params === "object" && !Array.isArray(params)
376
+ ? (params as { mode?: unknown; requestMode?: unknown })
377
+ : {};
378
+ const mode = record.requestMode ?? record.mode;
379
+ return mode === "act" || mode === "plan" ? mode : undefined;
380
+ }
381
+
382
+ function contextTextFromMcpModelContext(
383
+ context: McpAppModelContext | null,
384
+ ): string | undefined {
385
+ if (!context) return undefined;
386
+ const parts = textPartsFromMcpContent(context.content);
387
+ if (context.structuredContent !== undefined) {
388
+ parts.push(JSON.stringify(context.structuredContent, null, 2));
389
+ }
390
+ const text = parts.join("\n\n").trim();
391
+ return text || undefined;
392
+ }
393
+
394
+ function textPartsFromMcpContent(content: unknown): string[] {
395
+ if (!Array.isArray(content)) return [];
396
+ return content
397
+ .map((part): string | null => {
398
+ const record = contentPartRecord(part);
399
+ if (
400
+ !record ||
401
+ record.type !== "text" ||
402
+ typeof record.text !== "string"
403
+ ) {
404
+ return null;
405
+ }
406
+ const text = record.text.trim();
407
+ return text || null;
408
+ })
409
+ .filter((text): text is string => Boolean(text));
410
+ }
411
+
412
+ function imageDataUrlsFromMcpContent(content: unknown): string[] | undefined {
413
+ if (!Array.isArray(content)) return undefined;
414
+ const images = content
415
+ .map((part): string | null => {
416
+ const record = contentPartRecord(part);
417
+ if (!record || record.type !== "image") return null;
418
+ if (typeof record.url === "string" && record.url.trim()) {
419
+ return record.url;
420
+ }
421
+ if (typeof record.data !== "string" || !record.data.trim()) return null;
422
+ const mimeType =
423
+ typeof record.mimeType === "string" &&
424
+ record.mimeType.startsWith("image/")
425
+ ? record.mimeType
426
+ : "image/png";
427
+ return `data:${mimeType};base64,${record.data}`;
428
+ })
429
+ .filter((image): image is string => Boolean(image));
430
+ return images.length ? images : undefined;
431
+ }
432
+
433
+ function contentPartRecord(part: unknown): McpContentPart | null {
434
+ return part && typeof part === "object" && !Array.isArray(part)
435
+ ? (part as McpContentPart)
436
+ : null;
437
+ }
438
+
286
439
  function resourceUiMeta(app: AgentMcpAppPayload): ResourceUiMeta {
287
440
  const meta = app.resource?._meta;
288
441
  const ui =
@@ -341,8 +494,31 @@ export function supportedMcpAppPermissions(
341
494
  return permissions?.clipboardWrite ? { clipboardWrite: {} } : {};
342
495
  }
343
496
 
497
+ export function isMcpAppReadyMessage(data: unknown): boolean {
498
+ if (!data || typeof data !== "object" || Array.isArray(data)) return false;
499
+ const record = data as {
500
+ protocol?: unknown;
501
+ version?: unknown;
502
+ type?: unknown;
503
+ };
504
+ if (
505
+ record.protocol === AGENT_NATIVE_EMBED_PROTOCOL &&
506
+ record.version === AGENT_NATIVE_EMBED_VERSION &&
507
+ record.type === AGENT_NATIVE_EMBED_MESSAGE_TYPES.READY
508
+ ) {
509
+ return true;
510
+ }
511
+ const type = record.type;
512
+ return (
513
+ type === "agentNative.embeddedAppReady" ||
514
+ type === "agentNative.frameOrigin"
515
+ );
516
+ }
517
+
344
518
  export function buildMcpAppCsp(csp: McpUiResourceCsp | undefined): string {
345
- const connect = sanitizeCspSources(csp?.connectDomains);
519
+ const connect = withLocalWebSocketSources(
520
+ sanitizeCspSources(csp?.connectDomains),
521
+ );
346
522
  const resources = sanitizeCspSources(csp?.resourceDomains);
347
523
  const frames = sanitizeCspSources(csp?.frameDomains);
348
524
  const base = sanitizeCspSources(csp?.baseUriDomains);
@@ -369,6 +545,24 @@ function sanitizeCspSources(values: string[] | undefined): string[] {
369
545
  return [...new Set(out)];
370
546
  }
371
547
 
548
+ function withLocalWebSocketSources(sources: string[]): string[] {
549
+ const out = [...sources];
550
+ for (const source of sources) {
551
+ try {
552
+ const url = new URL(source);
553
+ if (
554
+ url.protocol === "http:" &&
555
+ ["localhost", "127.0.0.1", "::1"].includes(url.hostname)
556
+ ) {
557
+ out.push(`ws://${url.host}`);
558
+ }
559
+ } catch {
560
+ // Ignore non-URL CSP source expressions.
561
+ }
562
+ }
563
+ return [...new Set(out)];
564
+ }
565
+
372
566
  function sanitizeCspSource(value: string): string | null {
373
567
  const source = value.trim();
374
568
  if (!source || source.includes("'") || /[\s;]/.test(source)) return null;
@@ -20,6 +20,7 @@ export interface McpServer {
20
20
  url: string;
21
21
  headers?: Record<string, { set: true }>;
22
22
  description?: string;
23
+ firstParty?: boolean;
23
24
  createdAt: number;
24
25
  mergedId: string;
25
26
  status:
@@ -33,7 +33,9 @@ export function DemoModeSection() {
33
33
  const { data } = useQuery({
34
34
  queryKey: ["agent-native", "demo-mode"],
35
35
  queryFn: async () => {
36
- const res = await fetch(DEMO_STATUS_URL, { credentials: "include" });
36
+ const res = await fetch(DEMO_STATUS_URL, {
37
+ credentials: "same-origin",
38
+ });
37
39
  if (!res.ok) return null;
38
40
  return (await res.json()) as DemoStatus | null;
39
41
  },
@@ -91,7 +91,7 @@ function methodOf(input: RequestInfo | URL, init?: RequestInit): string {
91
91
  async function refreshDemoFlag(): Promise<void> {
92
92
  const f = originalFetch ?? fetch;
93
93
  try {
94
- const res = await f(STATUS_PATH, { credentials: "include" });
94
+ const res = await f(STATUS_PATH, { credentials: "same-origin" });
95
95
  if (!res.ok) return;
96
96
  const json = (await res.json()) as {
97
97
  enabled?: boolean;
@@ -1,4 +1,9 @@
1
- import { A2AClient, callAgent, type Message, type Task } from "../a2a/index.js";
1
+ import {
2
+ A2AClient,
3
+ callAgent,
4
+ type Message,
5
+ type Task,
6
+ } from "@agent-native/core/a2a";
2
7
 
3
8
  export interface AgentEndpointOptions {
4
9
  /**
@@ -1,3 +1,7 @@
1
+ import {
2
+ MCP_APP_CHAT_BRIDGE_QUERY_PARAM,
3
+ EMBED_MODE_QUERY_PARAM,
4
+ } from "../shared/embed-auth.js";
1
5
  import {
2
6
  AGENT_NATIVE_EMBED_MESSAGE_TYPES,
3
7
  createAgentNativeEmbedEnvelope,
@@ -61,10 +65,38 @@ type PendingRequest = {
61
65
 
62
66
  function resolveParentOrigin(win: Window, explicit?: string): string | null {
63
67
  if (explicit) return explicit;
68
+ if (shouldUseOpaqueParentTarget(win)) return "*";
64
69
  const referrerOrigin = embeddedAppOrigin(win.document?.referrer ?? "");
65
70
  return referrerOrigin;
66
71
  }
67
72
 
73
+ function shouldUseOpaqueParentTarget(win: Window): boolean {
74
+ try {
75
+ const url = new URL(win.location.href);
76
+ const chatBridge = url.searchParams.get(MCP_APP_CHAT_BRIDGE_QUERY_PARAM);
77
+ const embedMode = url.searchParams.get(EMBED_MODE_QUERY_PARAM);
78
+ return (
79
+ chatBridge === "1" ||
80
+ chatBridge === "true" ||
81
+ embedMode === "1" ||
82
+ embedMode === "true"
83
+ );
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ function defaultAllowedOrigins(
90
+ parentOrigin: string | null,
91
+ referrerOrigin: string | null,
92
+ ): string[] {
93
+ if (!parentOrigin) return [];
94
+ if (parentOrigin === "*") {
95
+ return ["null", ...(referrerOrigin ? [referrerOrigin] : [])];
96
+ }
97
+ return [parentOrigin];
98
+ }
99
+
68
100
  function postToParent(
69
101
  win: Window,
70
102
  parent: Window | null,
@@ -83,9 +115,10 @@ export function createEmbeddedAppBridge(
83
115
  ): EmbeddedAppBridge {
84
116
  const win = options.currentWindow ?? window;
85
117
  const parentOrigin = resolveParentOrigin(win, options.parentOrigin);
118
+ const referrerOrigin = embeddedAppOrigin(win.document?.referrer ?? "");
86
119
  const allowedOrigins =
87
120
  options.allowedOrigins ??
88
- (parentOrigin && parentOrigin !== "*" ? [parentOrigin] : []);
121
+ defaultAllowedOrigins(parentOrigin, referrerOrigin);
89
122
  const pending = new Map<string, PendingRequest>();
90
123
  let destroyed = false;
91
124
 
@@ -152,6 +152,8 @@ export interface MCPCallerIdentity {
152
152
  oauthScopes?: string[];
153
153
  /** Present only for standard remote MCP OAuth access tokens. */
154
154
  oauthClientId?: string;
155
+ /** Present only for framework-minted first-party MCP client tokens. */
156
+ firstPartyMcp?: boolean;
155
157
  }
156
158
 
157
159
  /** Per-request context used to turn an action's relative deep link into the
@@ -748,7 +750,7 @@ function safeUiSegment(value: string | undefined, fallback: string): string {
748
750
 
749
751
  // ChatGPT and Claude cache MCP App resource HTML by `ui://` URI. Bump this
750
752
  // when the shared shell changes in a way that must invalidate host caches.
751
- const MCP_APP_RESOURCE_SHELL_VERSION = "shell-v46";
753
+ const MCP_APP_RESOURCE_SHELL_VERSION = "shell-v51";
752
754
 
753
755
  function legacyDefaultMcpAppUri(config: MCPConfig, actionName: string): string {
754
756
  const app = safeUiSegment(config.appId ?? config.name, "agent-native");
@@ -1785,6 +1787,7 @@ function addSecretCandidate(
1785
1787
 
1786
1788
  async function verifyA2AJwtForMcp(
1787
1789
  token: string,
1790
+ resourceUrl?: string | string[],
1788
1791
  ): Promise<Record<string, unknown> | null> {
1789
1792
  const jose = await import("jose");
1790
1793
  let unverifiedPayload: Record<string, unknown> | null = null;
@@ -1813,21 +1816,43 @@ async function verifyA2AJwtForMcp(
1813
1816
  }
1814
1817
  }
1815
1818
 
1819
+ const firstPartyMcp = unverifiedPayload.agent_native_first_party_mcp === true;
1820
+ const audiences = firstPartyMcp ? mcpAudienceList(resourceUrl) : null;
1821
+ if (firstPartyMcp && !audiences?.length) return null;
1822
+
1816
1823
  for (const secret of candidateSecrets) {
1817
- try {
1818
- const { payload } = await jose.jwtVerify(
1819
- token,
1820
- new TextEncoder().encode(secret),
1821
- );
1822
- return payload as Record<string, unknown>;
1823
- } catch {
1824
- // Try the next candidate without exposing which secret matched.
1824
+ const encodedSecret = new TextEncoder().encode(secret);
1825
+ for (const audience of audiences ?? [undefined]) {
1826
+ try {
1827
+ const { payload } = await jose.jwtVerify(
1828
+ token,
1829
+ encodedSecret,
1830
+ audience ? { audience } : undefined,
1831
+ );
1832
+ return payload as Record<string, unknown>;
1833
+ } catch {
1834
+ // Try the next candidate without exposing which secret matched.
1835
+ }
1825
1836
  }
1826
1837
  }
1827
1838
 
1828
1839
  return null;
1829
1840
  }
1830
1841
 
1842
+ function mcpAudienceList(resource: string | string[] | undefined): string[] {
1843
+ const raw = Array.isArray(resource) ? resource : resource ? [resource] : [];
1844
+ const seen = new Set<string>();
1845
+ const out: string[] = [];
1846
+ for (const value of raw) {
1847
+ const normalized = value.replace(/\/+$/, "");
1848
+ if (normalized && !seen.has(normalized)) {
1849
+ seen.add(normalized);
1850
+ out.push(normalized);
1851
+ }
1852
+ }
1853
+ return out;
1854
+ }
1855
+
1831
1856
  async function isConnectTokenAllowed(
1832
1857
  jti: string | undefined,
1833
1858
  ): Promise<boolean> {
@@ -1941,7 +1966,7 @@ export async function verifyAuth(
1941
1966
 
1942
1967
  // Try an A2A JWT via the shared A2A_SECRET first, then the caller org's
1943
1968
  // synced A2A secret when the token carries org_domain.
1944
- const payload = await verifyA2AJwtForMcp(token);
1969
+ const payload = await verifyA2AJwtForMcp(token, options.resourceUrl);
1945
1970
  if (payload) {
1946
1971
  const tokenScope =
1947
1972
  typeof payload.scope === "string" ? payload.scope : undefined;
@@ -1976,6 +2001,9 @@ export async function verifyAuth(
1976
2001
  typeof payload.org_domain === "string"
1977
2002
  ? (payload.org_domain as string)
1978
2003
  : undefined,
2004
+ ...(payload.agent_native_first_party_mcp === true
2005
+ ? { firstPartyMcp: true }
2006
+ : {}),
1979
2007
  },
1980
2008
  // Verified JWT (connect-minted or A2A delegation) — a real caller.
1981
2009
  fullSurface: true,