@agent-native/core 0.22.18 → 0.22.20

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 (72) 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 +181 -12
  7. package/dist/client/embed-auth.js.map +1 -1
  8. package/dist/client/index.d.ts +1 -1
  9. package/dist/client/index.d.ts.map +1 -1
  10. package/dist/client/index.js +1 -1
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/client/mcp-app-host.d.ts +5 -0
  13. package/dist/client/mcp-app-host.d.ts.map +1 -1
  14. package/dist/client/mcp-app-host.js +267 -2
  15. package/dist/client/mcp-app-host.js.map +1 -1
  16. package/dist/client/mcp-apps/McpAppRenderer.d.ts +3 -0
  17. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  18. package/dist/client/mcp-apps/McpAppRenderer.js +86 -9
  19. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  20. package/dist/client/theme.js +1 -1
  21. package/dist/client/theme.js.map +1 -1
  22. package/dist/client/vite-dev-recovery-script.d.ts.map +1 -1
  23. package/dist/client/vite-dev-recovery-script.js +9 -0
  24. package/dist/client/vite-dev-recovery-script.js.map +1 -1
  25. package/dist/deploy/build.d.ts.map +1 -1
  26. package/dist/deploy/build.js +73 -5
  27. package/dist/deploy/build.js.map +1 -1
  28. package/dist/index.browser.d.ts +1 -1
  29. package/dist/index.browser.d.ts.map +1 -1
  30. package/dist/index.browser.js +1 -1
  31. package/dist/index.browser.js.map +1 -1
  32. package/dist/mcp/build-server.d.ts.map +1 -1
  33. package/dist/mcp/build-server.js +73 -8
  34. package/dist/mcp/build-server.js.map +1 -1
  35. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  36. package/dist/mcp/builtin-tools.js +6 -3
  37. package/dist/mcp/builtin-tools.js.map +1 -1
  38. package/dist/mcp/embed-app.d.ts +2 -2
  39. package/dist/mcp/embed-app.d.ts.map +1 -1
  40. package/dist/mcp/embed-app.js +426 -23
  41. package/dist/mcp/embed-app.js.map +1 -1
  42. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  43. package/dist/server/core-routes-plugin.js +37 -10
  44. package/dist/server/core-routes-plugin.js.map +1 -1
  45. package/dist/server/create-server.d.ts.map +1 -1
  46. package/dist/server/create-server.js +21 -7
  47. package/dist/server/create-server.js.map +1 -1
  48. package/dist/server/embed-route.d.ts.map +1 -1
  49. package/dist/server/embed-route.js +62 -21
  50. package/dist/server/embed-route.js.map +1 -1
  51. package/dist/server/open-route.d.ts.map +1 -1
  52. package/dist/server/open-route.js +7 -2
  53. package/dist/server/open-route.js.map +1 -1
  54. package/dist/server/security-headers.d.ts.map +1 -1
  55. package/dist/server/security-headers.js +9 -1
  56. package/dist/server/security-headers.js.map +1 -1
  57. package/dist/server/ssr-handler.d.ts +2 -0
  58. package/dist/server/ssr-handler.d.ts.map +1 -1
  59. package/dist/server/ssr-handler.js +66 -11
  60. package/dist/server/ssr-handler.js.map +1 -1
  61. package/dist/shared/mcp-embed-headers.d.ts +12 -0
  62. package/dist/shared/mcp-embed-headers.d.ts.map +1 -0
  63. package/dist/shared/mcp-embed-headers.js +51 -0
  64. package/dist/shared/mcp-embed-headers.js.map +1 -0
  65. package/dist/vite/client.d.ts.map +1 -1
  66. package/dist/vite/client.js +23 -0
  67. package/dist/vite/client.js.map +1 -1
  68. package/docs/content/actions.md +15 -5
  69. package/docs/content/client.md +6 -3
  70. package/docs/content/external-agents.md +61 -41
  71. package/docs/content/mcp-protocol.md +42 -7
  72. package/package.json +1 -1
@@ -2,8 +2,8 @@ import { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from "../shared/embed-auth.js";
2
2
  const MCP_APP_IMPORT = "https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps";
3
3
  export const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = "$requestOrigin";
4
4
  const MCP_APP_WRAPPER_CHROME_HEIGHT = 44;
5
- export const DEFAULT_MCP_APP_VIEWPORT_HEIGHT = 720;
6
- export const DEFAULT_MCP_APP_SHELL_HEIGHT = DEFAULT_MCP_APP_VIEWPORT_HEIGHT + MCP_APP_WRAPPER_CHROME_HEIGHT;
5
+ export const DEFAULT_MCP_APP_SHELL_HEIGHT = 560;
6
+ export const DEFAULT_MCP_APP_VIEWPORT_HEIGHT = DEFAULT_MCP_APP_SHELL_HEIGHT - MCP_APP_WRAPPER_CHROME_HEIGHT;
7
7
  function attr(value) {
8
8
  return String(value ?? "")
9
9
  .replace(/&/g, "&")
@@ -19,6 +19,10 @@ export function embedApp(options = {}) {
19
19
  const embedByDefault = options.embedByDefault !== false;
20
20
  const height = Math.max(320, Math.min(900, options.height ?? DEFAULT_MCP_APP_SHELL_HEIGHT));
21
21
  const viewportHeight = height - MCP_APP_WRAPPER_CHROME_HEIGHT;
22
+ const frameDomains = [
23
+ MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
24
+ ...(options.frameDomains ?? []),
25
+ ];
22
26
  return {
23
27
  title,
24
28
  ...(options.description ? { description: options.description } : {}),
@@ -28,19 +32,19 @@ export function embedApp(options = {}) {
28
32
  <meta charset="utf-8">
29
33
  <meta name="viewport" content="width=device-width, initial-scale=1">
30
34
  <style>
31
- :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: Canvas; color: CanvasText; }
35
+ :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: Canvas; color: CanvasText; --agent-native-shell-height: ${height}px; --agent-native-viewport-height: ${viewportHeight}px; }
32
36
  * { box-sizing: border-box; }
33
37
  body { margin: 0; }
34
- .shell { display: grid; gap: 8px; min-height: ${height}px; padding: 0; }
38
+ .shell { display: grid; gap: 8px; min-height: var(--agent-native-shell-height); padding: 0; }
35
39
  .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); }
36
40
  .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); }
37
41
  .actions { display: flex; align-items: center; gap: 6px; }
38
42
  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; }
39
43
  button:disabled { opacity: .55; cursor: default; }
40
- .stage { position: relative; min-height: ${viewportHeight}px; }
41
- iframe { display: block; width: 100%; height: ${viewportHeight}px; border: 0; background: Canvas; }
42
- .message { display: grid; place-items: center; min-height: ${viewportHeight}px; padding: 18px; color: color-mix(in srgb, CanvasText 62%, Canvas); font-size: 13px; line-height: 1.45; text-align: center; }
43
- .fallback { display: grid; align-content: center; justify-items: center; gap: 12px; min-height: ${viewportHeight}px; padding: 24px; background: Canvas; color: CanvasText; text-align: center; }
44
+ .stage { position: relative; min-height: var(--agent-native-viewport-height); }
45
+ iframe { display: block; width: 100%; height: var(--agent-native-viewport-height); border: 0; background: Canvas; }
46
+ .message { display: grid; place-items: center; min-height: var(--agent-native-viewport-height); padding: 18px; color: color-mix(in srgb, CanvasText 62%, Canvas); font-size: 13px; line-height: 1.45; text-align: center; }
47
+ .fallback { display: grid; align-content: center; justify-items: center; gap: 12px; min-height: var(--agent-native-viewport-height); padding: 24px; background: Canvas; color: CanvasText; text-align: center; }
44
48
  .fallback-title { max-width: 440px; font-size: 14px; font-weight: 700; }
45
49
  .fallback-copy { max-width: 520px; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.45; }
46
50
  .fallback-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }
@@ -75,7 +79,8 @@ export function embedApp(options = {}) {
75
79
  const startTool = body.dataset.startTool || "create_embed_session";
76
80
  const embedByDefault = body.dataset.embedDefault !== "0";
77
81
  const chatBridgeParam = ${JSON.stringify(MCP_APP_CHAT_BRIDGE_QUERY_PARAM)};
78
- const intrinsicHeight = ${height};
82
+ const defaultIntrinsicHeight = ${height};
83
+ const chromeHeight = ${MCP_APP_WRAPPER_CHROME_HEIGHT};
79
84
  let app = null;
80
85
  let openAiBridge = null;
81
86
  let toolInput = {};
@@ -107,6 +112,40 @@ export function embedApp(options = {}) {
107
112
  : {};
108
113
  }
109
114
 
115
+ function finiteNumber(value) {
116
+ return typeof value === "number" && Number.isFinite(value) && value > 0
117
+ ? value
118
+ : null;
119
+ }
120
+
121
+ function contextMaxHeight(context) {
122
+ if (!context || typeof context !== "object") return null;
123
+ return finiteNumber(context.maxHeight) ||
124
+ finiteNumber(context.containerDimensions && context.containerDimensions.maxHeight);
125
+ }
126
+
127
+ function visibleIntrinsicHeight() {
128
+ const context = hostState().context || {};
129
+ const hostMaxHeight = contextMaxHeight(context);
130
+ if (hostMaxHeight) return Math.floor(hostMaxHeight);
131
+ const viewportHeight = finiteNumber(window.visualViewport && window.visualViewport.height) ||
132
+ finiteNumber(window.innerHeight);
133
+ return Math.floor(viewportHeight || defaultIntrinsicHeight);
134
+ }
135
+
136
+ function applyIntrinsicHeight(nextHeight) {
137
+ const boundedHeight = Math.min(
138
+ defaultIntrinsicHeight,
139
+ Math.floor(nextHeight || defaultIntrinsicHeight)
140
+ );
141
+ const height = Math.max(320, boundedHeight);
142
+ const viewportHeight = Math.max(0, height - chromeHeight);
143
+ document.documentElement.style.setProperty("--agent-native-shell-height", height + "px");
144
+ document.documentElement.style.setProperty("--agent-native-viewport-height", viewportHeight + "px");
145
+ if (appFrame) appFrame.style.height = viewportHeight + "px";
146
+ return height;
147
+ }
148
+
110
149
  function parseToolResult(params) {
111
150
  if (!params) return {};
112
151
  if (params.result && typeof params.result === "object") {
@@ -124,8 +163,9 @@ export function embedApp(options = {}) {
124
163
  }
125
164
 
126
165
  function openLinkFrom(params, data) {
127
- const metaUrl = params && params._meta && params._meta["agent-native/openLink"]
128
- ? params._meta["agent-native/openLink"].webUrl
166
+ const openLink = params && params._meta && params._meta["agent-native/openLink"];
167
+ const metaUrl = openLink && typeof openLink === "object" && typeof openLink.webUrl === "string"
168
+ ? openLink.webUrl
129
169
  : "";
130
170
  return metaUrl || data.url || data.deepLink || data.openUrl || "";
131
171
  }
@@ -187,6 +227,270 @@ export function embedApp(options = {}) {
187
227
  }
188
228
  }
189
229
 
230
+ function isEmbedStartUrl(value) {
231
+ if (typeof value !== "string" || !value) return false;
232
+ try {
233
+ const url = new URL(value, window.location.href);
234
+ return /\\/_agent-native\\/embed\\/start$/.test(url.pathname);
235
+ } catch {
236
+ return false;
237
+ }
238
+ }
239
+
240
+ function localPathFromUrl(url, includeToken) {
241
+ const next = new URL(url.href);
242
+ if (!includeToken) next.searchParams.delete("__an_embed_token");
243
+ return next.pathname + next.search + next.hash;
244
+ }
245
+
246
+ function rewriteRootRelativeHtmlUrls(html, appOrigin) {
247
+ return String(html).replace(
248
+ /\\b(src|href|poster|action)\\s*=\\s*(["'])\\/(?!\\/)/gi,
249
+ (_match, name, quote) => String(name) + "=" + quote + appOrigin + "/"
250
+ );
251
+ }
252
+
253
+ function removeHtmlCspMeta(html) {
254
+ return String(html).replace(
255
+ /<meta\\s+[^>]*http-equiv\\s*=\\s*(["'])?content-security-policy\\1?[^>]*>/gi,
256
+ ""
257
+ );
258
+ }
259
+
260
+ function embedConfigForAppUrl(appUrl) {
261
+ const sanitizedTarget = localPathFromUrl(appUrl, false);
262
+ return {
263
+ origin: appUrl.origin,
264
+ href: appUrl.href,
265
+ baseHref: appUrl.origin + appUrl.pathname,
266
+ target: sanitizedTarget,
267
+ token: appUrl.searchParams.get("__an_embed_token") || "",
268
+ chatBridgeActive: appUrl.searchParams.get(chatBridgeParam) === "1",
269
+ chatBridgeParam,
270
+ embedTokenParam: "__an_embed_token",
271
+ embedTargetHeader: "x-agent-native-embed-target"
272
+ };
273
+ }
274
+
275
+ function installExternalEmbedRuntime(config) {
276
+ window.__AGENT_NATIVE_EXTERNAL_EMBED = config;
277
+ try {
278
+ if (config.target) {
279
+ window.history.replaceState(window.history.state, "", config.target);
280
+ }
281
+ } catch (_err) {}
282
+ try {
283
+ if (config.token) {
284
+ sessionStorage.setItem("agent-native:embed-auth-token", config.token);
285
+ }
286
+ if (config.chatBridgeActive && config.token) {
287
+ sessionStorage.setItem("agent-native:mcp-chat-bridge", config.token);
288
+ }
289
+ } catch (_err) {}
290
+ if (window.__agentNativeExternalEmbedRuntimeInstalled) return;
291
+ window.__agentNativeExternalEmbedRuntimeInstalled = true;
292
+ function appOrigin() {
293
+ try {
294
+ return new URL(config.origin).origin;
295
+ } catch (_err) {
296
+ return "";
297
+ }
298
+ }
299
+ function targetPath() {
300
+ return config.target || location.pathname + location.search;
301
+ }
302
+ function rewrittenUrl(value, appendToken) {
303
+ const origin = appOrigin();
304
+ if (!origin) return null;
305
+ let url;
306
+ try {
307
+ url = new URL(value, location.href);
308
+ } catch (_err) {
309
+ return null;
310
+ }
311
+ if (url.origin !== location.origin && url.origin !== origin) return null;
312
+ if (url.origin !== origin) {
313
+ const app = new URL(origin);
314
+ url.protocol = app.protocol;
315
+ url.host = app.host;
316
+ }
317
+ if (appendToken && config.token && url.pathname === "/_agent-native/events") {
318
+ url.searchParams.set(config.embedTokenParam, config.token);
319
+ }
320
+ return url.toString();
321
+ }
322
+ function authHeaders(input, init) {
323
+ const headers = new Headers(
324
+ init && init.headers ? init.headers : input instanceof Request ? input.headers : undefined
325
+ );
326
+ if (config.token && !headers.has("Authorization")) {
327
+ headers.set("Authorization", "Bearer " + config.token);
328
+ }
329
+ if (!headers.has(config.embedTargetHeader)) {
330
+ headers.set(config.embedTargetHeader, targetPath());
331
+ }
332
+ return headers;
333
+ }
334
+ if (typeof fetch === "function") {
335
+ const originalFetch = fetch.bind(window);
336
+ window.fetch = function(input, init) {
337
+ const raw = input instanceof Request ? input.url : String(input);
338
+ const url = rewrittenUrl(raw, false);
339
+ if (!url) return originalFetch(input, init);
340
+ const nextInit = Object.assign({}, init || {}, {
341
+ headers: authHeaders(input, init),
342
+ credentials: "omit"
343
+ });
344
+ if (input instanceof Request) {
345
+ return originalFetch(new Request(url, input), nextInit);
346
+ }
347
+ return originalFetch(url, nextInit);
348
+ };
349
+ }
350
+ if (typeof XMLHttpRequest !== "undefined") {
351
+ const originalOpen = XMLHttpRequest.prototype.open;
352
+ const originalSend = XMLHttpRequest.prototype.send;
353
+ XMLHttpRequest.prototype.open = function(method, url) {
354
+ const rewritten = rewrittenUrl(url, false);
355
+ this.__agentNativeExternalEmbed = !!rewritten;
356
+ return originalOpen.call(
357
+ this,
358
+ method,
359
+ rewritten || url,
360
+ arguments.length > 2 ? arguments[2] : true,
361
+ arguments[3],
362
+ arguments[4]
363
+ );
364
+ };
365
+ XMLHttpRequest.prototype.send = function(body) {
366
+ if (this.__agentNativeExternalEmbed) {
367
+ try {
368
+ if (config.token) this.setRequestHeader("Authorization", "Bearer " + config.token);
369
+ this.setRequestHeader(config.embedTargetHeader, targetPath());
370
+ } catch (_err) {}
371
+ }
372
+ return originalSend.call(this, body);
373
+ };
374
+ }
375
+ if (typeof EventSource !== "undefined") {
376
+ const OriginalEventSource = EventSource;
377
+ window.EventSource = function(url, options) {
378
+ return new OriginalEventSource(rewrittenUrl(url, true) || url, options);
379
+ };
380
+ window.EventSource.prototype = OriginalEventSource.prototype;
381
+ }
382
+ }
383
+
384
+ function copyDocumentElementAttributes(source) {
385
+ const target = document.documentElement;
386
+ for (const attr of Array.from(target.attributes)) {
387
+ target.removeAttribute(attr.name);
388
+ }
389
+ for (const attr of Array.from(source.attributes)) {
390
+ target.setAttribute(attr.name, attr.value);
391
+ }
392
+ }
393
+
394
+ function importChildren(source, target) {
395
+ target.replaceChildren(
396
+ ...Array.from(source.childNodes).map((node) => document.importNode(node, true))
397
+ );
398
+ }
399
+
400
+ function isModuleScript(script) {
401
+ return (script.getAttribute("type") || "").trim().toLowerCase() === "module";
402
+ }
403
+
404
+ function isRunnableClassicScript(script) {
405
+ const type = (script.getAttribute("type") || "").trim().toLowerCase();
406
+ return !type || type === "text/javascript" || type === "application/javascript";
407
+ }
408
+
409
+ function runClassicScript(script) {
410
+ const next = document.createElement("script");
411
+ for (const attr of Array.from(script.attributes)) {
412
+ if (attr.name === "type") continue;
413
+ next.setAttribute(attr.name, attr.value);
414
+ }
415
+ if (script.src) {
416
+ next.src = script.src;
417
+ } else {
418
+ next.textContent = script.textContent || "";
419
+ }
420
+ document.body.appendChild(next);
421
+ next.remove();
422
+ }
423
+
424
+ function rootRelativeSpecifiersToAbsolute(code, appOrigin) {
425
+ return String(code).replace(/(["'])\\/(?!\\/)/g, "$1" + appOrigin + "/");
426
+ }
427
+
428
+ function moduleCodeToClassicAsync(code, appOrigin) {
429
+ return rootRelativeSpecifiersToAbsolute(code, appOrigin)
430
+ .replace(
431
+ /\\bimport\\s+\\*\\s+as\\s+([A-Za-z_$][\\w$]*)\\s+from\\s+(["'][^"']+["'])\\s*;?/g,
432
+ "const $1 = await import($2);"
433
+ )
434
+ .replace(/\\bimport\\s+(["'][^"']+["'])\\s*;?/g, "await import($1);")
435
+ .replace(/\\bimport\\((["'][^"']+["'])\\)\\s*;?/g, "await import($1);");
436
+ }
437
+
438
+ function runModuleScriptAsClassic(script, appOrigin) {
439
+ const code = moduleCodeToClassicAsync(script.textContent || "", appOrigin);
440
+ const runner = document.createElement("script");
441
+ runner.textContent =
442
+ "(async()=>{" +
443
+ code +
444
+ "})().catch((err)=>{console.error('[agent-native] transplanted app module failed',err);document.body.setAttribute('data-agent-native-hydration-error',String(err&&err.message||err));});";
445
+ document.body.appendChild(runner);
446
+ runner.remove();
447
+ }
448
+
449
+ function mountTransplantedHtml(html, appUrl) {
450
+ const config = embedConfigForAppUrl(appUrl);
451
+ installExternalEmbedRuntime(config);
452
+ const parsed = new DOMParser().parseFromString(
453
+ rewriteRootRelativeHtmlUrls(removeHtmlCspMeta(html), appUrl.origin),
454
+ "text/html"
455
+ );
456
+ const scripts = Array.from(parsed.querySelectorAll("script"));
457
+ copyDocumentElementAttributes(parsed.documentElement);
458
+ importChildren(parsed.head, document.head);
459
+ const base = document.createElement("base");
460
+ base.href = config.baseHref;
461
+ document.head.prepend(base);
462
+ importChildren(parsed.body, document.body);
463
+ for (const script of scripts) {
464
+ if (isRunnableClassicScript(script)) runClassicScript(script);
465
+ }
466
+ for (const script of scripts) {
467
+ if (isModuleScript(script)) runModuleScriptAsClassic(script, appUrl.origin);
468
+ }
469
+ }
470
+
471
+ async function transplantAppDocument(src) {
472
+ clearFrameReadyTimer();
473
+ clearFrameLoadTimer();
474
+ appFrame = null;
475
+ lastFrameSrc = src;
476
+ setMessage("Loading app");
477
+ const response = await fetch(src, {
478
+ credentials: "omit",
479
+ redirect: "follow",
480
+ headers: { Accept: "text/html" }
481
+ });
482
+ if (!response.ok) {
483
+ throw new Error("Embedded app returned HTTP " + response.status + ".");
484
+ }
485
+ const html = await response.text();
486
+ const appUrl = new URL(response.url || src);
487
+ try {
488
+ window.history.replaceState(window.history.state, "", localPathFromUrl(appUrl, false));
489
+ } catch {}
490
+ mountTransplantedHtml(html, appUrl);
491
+ notifyHostHeightRepeatedly();
492
+ }
493
+
190
494
  function wantsEmbed() {
191
495
  if (toolInput.embed === false || toolInput.embed === "false") return false;
192
496
  if (embedByDefault) return true;
@@ -285,11 +589,10 @@ export function embedApp(options = {}) {
285
589
  }
286
590
 
287
591
  async function openFallbackExternal() {
288
- let url = openUrl;
592
+ let url = withChatBridgeParam(openUrl);
289
593
  try {
290
- const embedUrl = withChatBridgeParam(openUrl);
291
594
  const result = await callEmbedSessionTool({
292
- url: embedUrl,
595
+ url,
293
596
  chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
294
597
  });
295
598
  const data = parseToolResult(result);
@@ -325,6 +628,53 @@ export function embedApp(options = {}) {
325
628
  }, 30000);
326
629
  }
327
630
 
631
+ function shouldSelfNavigateToApp() {
632
+ const mode = typeof toolInput.embedMode === "string"
633
+ ? toolInput.embedMode
634
+ : typeof toolInput.renderMode === "string"
635
+ ? toolInput.renderMode
636
+ : "";
637
+ if (mode === "iframe" || mode === "nested") return false;
638
+ if (toolInput.nested === true || toolInput.frame === "iframe") return false;
639
+ return true;
640
+ }
641
+
642
+ function isClaudeMcpContentHost() {
643
+ try {
644
+ return /(^|\\.)claudemcpcontent\\.com$/i.test(window.location.hostname || "");
645
+ } catch {
646
+ return false;
647
+ }
648
+ }
649
+
650
+ function isChatGptSandboxHost() {
651
+ try {
652
+ const host = window.location.hostname || "";
653
+ const appParam = new URL(window.location.href).searchParams.get("app");
654
+ return /(^|\\.)oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
655
+ } catch {
656
+ return false;
657
+ }
658
+ }
659
+
660
+ function shouldRenderControlledAppFrame() {
661
+ return !!openAiBridge || isChatGptSandboxHost();
662
+ }
663
+
664
+ function navigateToAppFrame(src) {
665
+ clearFrameReadyTimer();
666
+ clearFrameLoadTimer();
667
+ appFrame = null;
668
+ lastFrameSrc = src;
669
+ setMessage("Opening app");
670
+ try {
671
+ window.location.replace(src);
672
+ } catch (err) {
673
+ console.warn("[agent-native] MCP app self-navigation failed", err);
674
+ renderFrameFallback();
675
+ }
676
+ }
677
+
328
678
  async function updateHostModelContext(data) {
329
679
  const params = {};
330
680
  if (Array.isArray(data && data.content)) params.content = data.content;
@@ -357,11 +707,19 @@ export function embedApp(options = {}) {
357
707
  }
358
708
 
359
709
  function notifyHostHeight() {
710
+ const height = applyIntrinsicHeight(visibleIntrinsicHeight());
360
711
  if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== "function") {
712
+ if (app && typeof app.sendSizeChanged === "function") {
713
+ try {
714
+ app.sendSizeChanged({ height });
715
+ } catch (err) {
716
+ console.warn("[agent-native] MCP host rejected size update", err);
717
+ }
718
+ }
361
719
  return;
362
720
  }
363
721
  try {
364
- openAiBridge.notifyIntrinsicHeight({ height: intrinsicHeight });
722
+ openAiBridge.notifyIntrinsicHeight({ height });
365
723
  } catch (err) {
366
724
  console.warn("[agent-native] ChatGPT rejected intrinsic height update", err);
367
725
  }
@@ -457,6 +815,22 @@ export function embedApp(options = {}) {
457
815
  }
458
816
  });
459
817
 
818
+ function notifyHostHeightSoon() {
819
+ requestAnimationFrame(() => notifyHostHeight());
820
+ }
821
+
822
+ function notifyHostHeightRepeatedly() {
823
+ notifyHostHeight();
824
+ [0, 250, 1000, 2500].forEach((delay) => {
825
+ setTimeout(() => notifyHostHeight(), delay);
826
+ });
827
+ }
828
+
829
+ window.addEventListener("resize", notifyHostHeightSoon, { passive: true });
830
+ if (window.visualViewport) {
831
+ window.visualViewport.addEventListener("resize", notifyHostHeightSoon, { passive: true });
832
+ }
833
+
460
834
  async function launchEmbed() {
461
835
  if (!openUrl) {
462
836
  setMessage("Open link was not available.");
@@ -470,18 +844,43 @@ export function embedApp(options = {}) {
470
844
  startedFor = openUrl;
471
845
  setMessage("Loading app");
472
846
  try {
847
+ const selfNavigate = shouldSelfNavigateToApp();
473
848
  const embedUrl = withChatBridgeParam(openUrl);
849
+ if (selfNavigate && isEmbedStartUrl(embedUrl)) {
850
+ if (isClaudeMcpContentHost()) {
851
+ await transplantAppDocument(embedUrl);
852
+ } else if (shouldRenderControlledAppFrame()) {
853
+ renderFrame(embedUrl);
854
+ } else {
855
+ navigateToAppFrame(embedUrl);
856
+ }
857
+ return;
858
+ }
859
+ if (!selfNavigate && isEmbedStartUrl(embedUrl)) {
860
+ renderFrame(embedUrl);
861
+ return;
862
+ }
474
863
  const result = await callEmbedSessionTool({
475
864
  url: embedUrl,
476
865
  chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
477
866
  });
478
867
  const data = parseToolResult(result);
479
- if (!data.startUrl) {
868
+ if (typeof data.startUrl !== "string" || !data.startUrl) {
480
869
  startedFor = "";
481
870
  setMessage(data.error || "This app can be opened, but not embedded from this MCP server.");
482
871
  return;
483
872
  }
484
- renderFrame(data.startUrl);
873
+ if (selfNavigate) {
874
+ if (isClaudeMcpContentHost()) {
875
+ await transplantAppDocument(data.startUrl);
876
+ } else if (shouldRenderControlledAppFrame()) {
877
+ renderFrame(data.startUrl);
878
+ } else {
879
+ navigateToAppFrame(data.startUrl);
880
+ }
881
+ } else {
882
+ renderFrame(data.startUrl);
883
+ }
485
884
  } catch (err) {
486
885
  startedFor = "";
487
886
  setMessage(err && err.message ? err.message : "Could not launch embedded app.");
@@ -588,7 +987,11 @@ export function embedApp(options = {}) {
588
987
 
589
988
  async function startMcpAppsBridge() {
590
989
  const { App } = await import("${MCP_APP_IMPORT}");
591
- app = new App({ name: "Agent Native Embed", version: "1.0.0" }, {});
990
+ app = new App(
991
+ { name: "Agent Native Embed", version: "1.0.0" },
992
+ {},
993
+ { autoResize: false }
994
+ );
592
995
  app.ontoolinput = (params) => {
593
996
  toolInput = params.arguments || {};
594
997
  };
@@ -601,10 +1004,12 @@ export function embedApp(options = {}) {
601
1004
  };
602
1005
  app.onhostcontextchanged = () => {
603
1006
  updateDisplayButton();
1007
+ notifyHostHeight();
604
1008
  sendHostContext();
605
1009
  };
606
1010
  await app.connect();
607
1011
  updateDisplayButton();
1012
+ notifyHostHeight();
608
1013
  sendHostContext();
609
1014
  }
610
1015
 
@@ -616,16 +1021,14 @@ export function embedApp(options = {}) {
616
1021
  </body>
617
1022
  </html>`,
618
1023
  csp: {
619
- connectDomains: ["https://esm.sh"],
1024
+ connectDomains: ["https://esm.sh", MCP_APP_REQUEST_ORIGIN_CSP_SOURCE],
620
1025
  resourceDomains: [
621
1026
  "https://esm.sh",
622
1027
  MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
623
1028
  ...(options.frameDomains ?? []),
624
1029
  ],
625
- frameDomains: [
626
- MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
627
- ...(options.frameDomains ?? []),
628
- ],
1030
+ baseUriDomains: [MCP_APP_REQUEST_ORIGIN_CSP_SOURCE],
1031
+ frameDomains,
629
1032
  },
630
1033
  prefersBorder: false,
631
1034
  };