@agent-native/core 0.22.19 → 0.22.21

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 (47) hide show
  1. package/dist/client/embed-auth.d.ts.map +1 -1
  2. package/dist/client/embed-auth.js +85 -3
  3. package/dist/client/embed-auth.js.map +1 -1
  4. package/dist/client/mcp-apps/McpAppRenderer.d.ts +3 -0
  5. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  6. package/dist/client/mcp-apps/McpAppRenderer.js +86 -9
  7. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  8. package/dist/deploy/build.d.ts.map +1 -1
  9. package/dist/deploy/build.js +73 -5
  10. package/dist/deploy/build.js.map +1 -1
  11. package/dist/mcp/build-server.d.ts.map +1 -1
  12. package/dist/mcp/build-server.js +40 -3
  13. package/dist/mcp/build-server.js.map +1 -1
  14. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  15. package/dist/mcp/builtin-tools.js +6 -3
  16. package/dist/mcp/builtin-tools.js.map +1 -1
  17. package/dist/mcp/embed-app.d.ts +2 -2
  18. package/dist/mcp/embed-app.d.ts.map +1 -1
  19. package/dist/mcp/embed-app.js +420 -29
  20. package/dist/mcp/embed-app.js.map +1 -1
  21. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  22. package/dist/server/core-routes-plugin.js +37 -10
  23. package/dist/server/core-routes-plugin.js.map +1 -1
  24. package/dist/server/create-server.d.ts.map +1 -1
  25. package/dist/server/create-server.js +21 -7
  26. package/dist/server/create-server.js.map +1 -1
  27. package/dist/server/embed-route.d.ts.map +1 -1
  28. package/dist/server/embed-route.js +62 -21
  29. package/dist/server/embed-route.js.map +1 -1
  30. package/dist/server/security-headers.d.ts.map +1 -1
  31. package/dist/server/security-headers.js +9 -1
  32. package/dist/server/security-headers.js.map +1 -1
  33. package/dist/server/ssr-handler.d.ts +2 -0
  34. package/dist/server/ssr-handler.d.ts.map +1 -1
  35. package/dist/server/ssr-handler.js +66 -11
  36. package/dist/server/ssr-handler.js.map +1 -1
  37. package/dist/shared/mcp-embed-headers.d.ts +12 -0
  38. package/dist/shared/mcp-embed-headers.d.ts.map +1 -0
  39. package/dist/shared/mcp-embed-headers.js +51 -0
  40. package/dist/shared/mcp-embed-headers.js.map +1 -0
  41. package/dist/vite/client.d.ts.map +1 -1
  42. package/dist/vite/client.js +23 -0
  43. package/dist/vite/client.js.map +1 -1
  44. package/docs/content/actions.md +16 -5
  45. package/docs/content/external-agents.md +61 -26
  46. package/docs/content/mcp-protocol.md +32 -4
  47. 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,11 @@ 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};
84
+ const frameReadyMessageDelays = [0, 200, 500, 1500, 3000, 7000, 15000, 30000];
85
+ const frameReadyTimeoutMs = 45000;
86
+ const frameLoadTimeoutMs = 45000;
79
87
  let app = null;
80
88
  let openAiBridge = null;
81
89
  let toolInput = {};
@@ -107,6 +115,40 @@ export function embedApp(options = {}) {
107
115
  : {};
108
116
  }
109
117
 
118
+ function finiteNumber(value) {
119
+ return typeof value === "number" && Number.isFinite(value) && value > 0
120
+ ? value
121
+ : null;
122
+ }
123
+
124
+ function contextMaxHeight(context) {
125
+ if (!context || typeof context !== "object") return null;
126
+ return finiteNumber(context.maxHeight) ||
127
+ finiteNumber(context.containerDimensions && context.containerDimensions.maxHeight);
128
+ }
129
+
130
+ function visibleIntrinsicHeight() {
131
+ const context = hostState().context || {};
132
+ const hostMaxHeight = contextMaxHeight(context);
133
+ if (hostMaxHeight) return Math.floor(hostMaxHeight);
134
+ const viewportHeight = finiteNumber(window.visualViewport && window.visualViewport.height) ||
135
+ finiteNumber(window.innerHeight);
136
+ return Math.floor(viewportHeight || defaultIntrinsicHeight);
137
+ }
138
+
139
+ function applyIntrinsicHeight(nextHeight) {
140
+ const boundedHeight = Math.min(
141
+ defaultIntrinsicHeight,
142
+ Math.floor(nextHeight || defaultIntrinsicHeight)
143
+ );
144
+ const height = Math.max(320, boundedHeight);
145
+ const viewportHeight = Math.max(0, height - chromeHeight);
146
+ document.documentElement.style.setProperty("--agent-native-shell-height", height + "px");
147
+ document.documentElement.style.setProperty("--agent-native-viewport-height", viewportHeight + "px");
148
+ if (appFrame) appFrame.style.height = viewportHeight + "px";
149
+ return height;
150
+ }
151
+
110
152
  function parseToolResult(params) {
111
153
  if (!params) return {};
112
154
  if (params.result && typeof params.result === "object") {
@@ -128,7 +170,8 @@ export function embedApp(options = {}) {
128
170
  const metaUrl = openLink && typeof openLink === "object" && typeof openLink.webUrl === "string"
129
171
  ? openLink.webUrl
130
172
  : "";
131
- return metaUrl || data.url || data.deepLink || data.openUrl || "";
173
+ const record = data && typeof data === "object" ? data : {};
174
+ return metaUrl || record.url || record.deepLink || record.openUrl || "";
132
175
  }
133
176
 
134
177
  function hostState() {
@@ -166,7 +209,7 @@ export function embedApp(options = {}) {
166
209
 
167
210
  function sendFrameReadyMessages(frame) {
168
211
  const originPayload = { type: "agentNative.frameOrigin", origin: window.location.origin };
169
- [0, 200, 500, 1500].forEach((delay) => {
212
+ frameReadyMessageDelays.forEach((delay) => {
170
213
  setTimeout(() => {
171
214
  try { frame.contentWindow && frame.contentWindow.postMessage(originPayload, "*"); } catch {}
172
215
  sendHostContext();
@@ -188,6 +231,277 @@ export function embedApp(options = {}) {
188
231
  }
189
232
  }
190
233
 
234
+ function embedSessionArgsFor(value) {
235
+ const chrome = typeof toolInput.chrome === "string" ? toolInput.chrome : "full";
236
+ return typeof value === "string" && value.startsWith("/")
237
+ ? { path: value, chrome }
238
+ : { url: value, chrome };
239
+ }
240
+
241
+ function isEmbedStartUrl(value) {
242
+ if (typeof value !== "string" || !value) return false;
243
+ try {
244
+ const url = new URL(value, window.location.href);
245
+ return /\\/_agent-native\\/embed\\/start$/.test(url.pathname);
246
+ } catch {
247
+ return false;
248
+ }
249
+ }
250
+
251
+ function localPathFromUrl(url, includeToken) {
252
+ const next = new URL(url.href);
253
+ if (!includeToken) next.searchParams.delete("__an_embed_token");
254
+ return next.pathname + next.search + next.hash;
255
+ }
256
+
257
+ function rewriteRootRelativeHtmlUrls(html, appOrigin) {
258
+ return String(html).replace(
259
+ /\\b(src|href|poster|action)\\s*=\\s*(["'])\\/(?!\\/)/gi,
260
+ (_match, name, quote) => String(name) + "=" + quote + appOrigin + "/"
261
+ );
262
+ }
263
+
264
+ function removeHtmlCspMeta(html) {
265
+ return String(html).replace(
266
+ /<meta\\s+[^>]*http-equiv\\s*=\\s*(["'])?content-security-policy\\1?[^>]*>/gi,
267
+ ""
268
+ );
269
+ }
270
+
271
+ function embedConfigForAppUrl(appUrl) {
272
+ const sanitizedTarget = localPathFromUrl(appUrl, false);
273
+ return {
274
+ origin: appUrl.origin,
275
+ href: appUrl.href,
276
+ baseHref: appUrl.origin + appUrl.pathname,
277
+ target: sanitizedTarget,
278
+ token: appUrl.searchParams.get("__an_embed_token") || "",
279
+ chatBridgeActive: appUrl.searchParams.get(chatBridgeParam) === "1",
280
+ chatBridgeParam,
281
+ embedTokenParam: "__an_embed_token",
282
+ embedTargetHeader: "x-agent-native-embed-target"
283
+ };
284
+ }
285
+
286
+ function installExternalEmbedRuntime(config) {
287
+ window.__AGENT_NATIVE_EXTERNAL_EMBED = config;
288
+ try {
289
+ if (config.target) {
290
+ window.history.replaceState(window.history.state, "", config.target);
291
+ }
292
+ } catch (_err) {}
293
+ try {
294
+ if (config.token) {
295
+ sessionStorage.setItem("agent-native:embed-auth-token", config.token);
296
+ }
297
+ if (config.chatBridgeActive && config.token) {
298
+ sessionStorage.setItem("agent-native:mcp-chat-bridge", config.token);
299
+ }
300
+ } catch (_err) {}
301
+ if (window.__agentNativeExternalEmbedRuntimeInstalled) return;
302
+ window.__agentNativeExternalEmbedRuntimeInstalled = true;
303
+ function appOrigin() {
304
+ try {
305
+ return new URL(config.origin).origin;
306
+ } catch (_err) {
307
+ return "";
308
+ }
309
+ }
310
+ function targetPath() {
311
+ return config.target || location.pathname + location.search;
312
+ }
313
+ function rewrittenUrl(value, appendToken) {
314
+ const origin = appOrigin();
315
+ if (!origin) return null;
316
+ let url;
317
+ try {
318
+ url = new URL(value, location.href);
319
+ } catch (_err) {
320
+ return null;
321
+ }
322
+ if (url.origin !== location.origin && url.origin !== origin) return null;
323
+ if (url.origin !== origin) {
324
+ const app = new URL(origin);
325
+ url.protocol = app.protocol;
326
+ url.host = app.host;
327
+ }
328
+ if (appendToken && config.token && url.pathname === "/_agent-native/events") {
329
+ url.searchParams.set(config.embedTokenParam, config.token);
330
+ }
331
+ return url.toString();
332
+ }
333
+ function authHeaders(input, init) {
334
+ const headers = new Headers(
335
+ init && init.headers ? init.headers : input instanceof Request ? input.headers : undefined
336
+ );
337
+ if (config.token && !headers.has("Authorization")) {
338
+ headers.set("Authorization", "Bearer " + config.token);
339
+ }
340
+ if (!headers.has(config.embedTargetHeader)) {
341
+ headers.set(config.embedTargetHeader, targetPath());
342
+ }
343
+ return headers;
344
+ }
345
+ if (typeof fetch === "function") {
346
+ const originalFetch = fetch.bind(window);
347
+ window.fetch = function(input, init) {
348
+ const raw = input instanceof Request ? input.url : String(input);
349
+ const url = rewrittenUrl(raw, false);
350
+ if (!url) return originalFetch(input, init);
351
+ const nextInit = Object.assign({}, init || {}, {
352
+ headers: authHeaders(input, init),
353
+ credentials: "omit"
354
+ });
355
+ if (input instanceof Request) {
356
+ return originalFetch(new Request(url, input), nextInit);
357
+ }
358
+ return originalFetch(url, nextInit);
359
+ };
360
+ }
361
+ if (typeof XMLHttpRequest !== "undefined") {
362
+ const originalOpen = XMLHttpRequest.prototype.open;
363
+ const originalSend = XMLHttpRequest.prototype.send;
364
+ XMLHttpRequest.prototype.open = function(method, url) {
365
+ const rewritten = rewrittenUrl(url, false);
366
+ this.__agentNativeExternalEmbed = !!rewritten;
367
+ return originalOpen.call(
368
+ this,
369
+ method,
370
+ rewritten || url,
371
+ arguments.length > 2 ? arguments[2] : true,
372
+ arguments[3],
373
+ arguments[4]
374
+ );
375
+ };
376
+ XMLHttpRequest.prototype.send = function(body) {
377
+ if (this.__agentNativeExternalEmbed) {
378
+ try {
379
+ if (config.token) this.setRequestHeader("Authorization", "Bearer " + config.token);
380
+ this.setRequestHeader(config.embedTargetHeader, targetPath());
381
+ } catch (_err) {}
382
+ }
383
+ return originalSend.call(this, body);
384
+ };
385
+ }
386
+ if (typeof EventSource !== "undefined") {
387
+ const OriginalEventSource = EventSource;
388
+ window.EventSource = function(url, options) {
389
+ return new OriginalEventSource(rewrittenUrl(url, true) || url, options);
390
+ };
391
+ window.EventSource.prototype = OriginalEventSource.prototype;
392
+ }
393
+ }
394
+
395
+ function copyDocumentElementAttributes(source) {
396
+ const target = document.documentElement;
397
+ for (const attr of Array.from(target.attributes)) {
398
+ target.removeAttribute(attr.name);
399
+ }
400
+ for (const attr of Array.from(source.attributes)) {
401
+ target.setAttribute(attr.name, attr.value);
402
+ }
403
+ }
404
+
405
+ function importChildren(source, target) {
406
+ target.replaceChildren(
407
+ ...Array.from(source.childNodes).map((node) => document.importNode(node, true))
408
+ );
409
+ }
410
+
411
+ function isModuleScript(script) {
412
+ return (script.getAttribute("type") || "").trim().toLowerCase() === "module";
413
+ }
414
+
415
+ function isRunnableClassicScript(script) {
416
+ const type = (script.getAttribute("type") || "").trim().toLowerCase();
417
+ return !type || type === "text/javascript" || type === "application/javascript";
418
+ }
419
+
420
+ function runClassicScript(script) {
421
+ const next = document.createElement("script");
422
+ for (const attr of Array.from(script.attributes)) {
423
+ if (attr.name === "type") continue;
424
+ next.setAttribute(attr.name, attr.value);
425
+ }
426
+ if (script.src) {
427
+ next.src = script.src;
428
+ } else {
429
+ next.textContent = script.textContent || "";
430
+ }
431
+ document.body.appendChild(next);
432
+ next.remove();
433
+ }
434
+
435
+ function rootRelativeSpecifiersToAbsolute(code, appOrigin) {
436
+ return String(code).replace(/(["'])\\/(?!\\/)/g, "$1" + appOrigin + "/");
437
+ }
438
+
439
+ function moduleCodeToClassicAsync(code, appOrigin) {
440
+ return rootRelativeSpecifiersToAbsolute(code, appOrigin)
441
+ .replace(
442
+ /\\bimport\\s+\\*\\s+as\\s+([A-Za-z_$][\\w$]*)\\s+from\\s+(["'][^"']+["'])\\s*;?/g,
443
+ "const $1 = await import($2);"
444
+ )
445
+ .replace(/\\bimport\\s+(["'][^"']+["'])\\s*;?/g, "await import($1);")
446
+ .replace(/\\bimport\\((["'][^"']+["'])\\)\\s*;?/g, "await import($1);");
447
+ }
448
+
449
+ function runModuleScriptAsClassic(script, appOrigin) {
450
+ const code = moduleCodeToClassicAsync(script.textContent || "", appOrigin);
451
+ const runner = document.createElement("script");
452
+ runner.textContent =
453
+ "(async()=>{" +
454
+ code +
455
+ "})().catch((err)=>{console.error('[agent-native] transplanted app module failed',err);document.body.setAttribute('data-agent-native-hydration-error',String(err&&err.message||err));});";
456
+ document.body.appendChild(runner);
457
+ runner.remove();
458
+ }
459
+
460
+ function mountTransplantedHtml(html, appUrl) {
461
+ const config = embedConfigForAppUrl(appUrl);
462
+ installExternalEmbedRuntime(config);
463
+ const parsed = new DOMParser().parseFromString(
464
+ rewriteRootRelativeHtmlUrls(removeHtmlCspMeta(html), appUrl.origin),
465
+ "text/html"
466
+ );
467
+ const scripts = Array.from(parsed.querySelectorAll("script"));
468
+ copyDocumentElementAttributes(parsed.documentElement);
469
+ importChildren(parsed.head, document.head);
470
+ const base = document.createElement("base");
471
+ base.href = config.baseHref;
472
+ document.head.prepend(base);
473
+ importChildren(parsed.body, document.body);
474
+ for (const script of scripts) {
475
+ if (isRunnableClassicScript(script)) runClassicScript(script);
476
+ }
477
+ for (const script of scripts) {
478
+ if (isModuleScript(script)) runModuleScriptAsClassic(script, appUrl.origin);
479
+ }
480
+ }
481
+
482
+ async function transplantAppDocument(src) {
483
+ clearFrameReadyTimer();
484
+ clearFrameLoadTimer();
485
+ appFrame = null;
486
+ lastFrameSrc = src;
487
+ setMessage("Loading app");
488
+ const response = await fetch(src, {
489
+ credentials: "omit",
490
+ redirect: "follow",
491
+ headers: { Accept: "text/html" }
492
+ });
493
+ if (!response.ok) {
494
+ throw new Error("Embedded app returned HTTP " + response.status + ".");
495
+ }
496
+ const html = await response.text();
497
+ const appUrl = new URL(response.url || src);
498
+ try {
499
+ window.history.replaceState(window.history.state, "", localPathFromUrl(appUrl, false));
500
+ } catch {}
501
+ mountTransplantedHtml(html, appUrl);
502
+ notifyHostHeightRepeatedly();
503
+ }
504
+
191
505
  function wantsEmbed() {
192
506
  if (toolInput.embed === false || toolInput.embed === "false") return false;
193
507
  if (embedByDefault) return true;
@@ -252,7 +566,7 @@ export function embedApp(options = {}) {
252
566
  clearFrameReadyTimer();
253
567
  appFrameReadyTimer = setTimeout(() => {
254
568
  if (!appFrameReady && appFrame === frame) renderFrameFallback();
255
- }, 7000);
569
+ }, frameReadyTimeoutMs);
256
570
  }
257
571
 
258
572
  function renderFrameFallback() {
@@ -288,10 +602,7 @@ export function embedApp(options = {}) {
288
602
  async function openFallbackExternal() {
289
603
  let url = withChatBridgeParam(openUrl);
290
604
  try {
291
- const result = await callEmbedSessionTool({
292
- url,
293
- chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
294
- });
605
+ const result = await callEmbedSessionTool(embedSessionArgsFor(url));
295
606
  const data = parseToolResult(result);
296
607
  if (typeof data.startUrl === "string" && data.startUrl) {
297
608
  url = data.startUrl;
@@ -322,7 +633,7 @@ export function embedApp(options = {}) {
322
633
  notifyHostHeight();
323
634
  appFrameLoadTimer = setTimeout(() => {
324
635
  if (!appFrameReady && appFrame === frame) renderFrameFallback();
325
- }, 30000);
636
+ }, frameLoadTimeoutMs);
326
637
  }
327
638
 
328
639
  function shouldSelfNavigateToApp() {
@@ -336,6 +647,41 @@ export function embedApp(options = {}) {
336
647
  return true;
337
648
  }
338
649
 
650
+ function shouldTransplantAppDocument() {
651
+ const mode = typeof toolInput.embedMode === "string"
652
+ ? toolInput.embedMode
653
+ : typeof toolInput.renderMode === "string"
654
+ ? toolInput.renderMode
655
+ : "";
656
+ return (
657
+ mode === "transplant" ||
658
+ toolInput.frame === "transplant" ||
659
+ isClaudeMcpContentHost()
660
+ );
661
+ }
662
+
663
+ function isClaudeMcpContentHost() {
664
+ try {
665
+ return /(^|\\.)claudemcpcontent\\.com$/i.test(window.location.hostname || "");
666
+ } catch {
667
+ return false;
668
+ }
669
+ }
670
+
671
+ function isChatGptSandboxHost() {
672
+ try {
673
+ const host = window.location.hostname || "";
674
+ const appParam = new URL(window.location.href).searchParams.get("app");
675
+ return /(^|\\.)oaiusercontent\\.com$/i.test(host) || appParam === "chatgpt";
676
+ } catch {
677
+ return false;
678
+ }
679
+ }
680
+
681
+ function shouldRenderControlledAppFrame() {
682
+ return !!openAiBridge || isChatGptSandboxHost();
683
+ }
684
+
339
685
  function navigateToAppFrame(src) {
340
686
  clearFrameReadyTimer();
341
687
  clearFrameLoadTimer();
@@ -382,11 +728,19 @@ export function embedApp(options = {}) {
382
728
  }
383
729
 
384
730
  function notifyHostHeight() {
731
+ const height = applyIntrinsicHeight(visibleIntrinsicHeight());
385
732
  if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== "function") {
733
+ if (app && typeof app.sendSizeChanged === "function") {
734
+ try {
735
+ app.sendSizeChanged({ height });
736
+ } catch (err) {
737
+ console.warn("[agent-native] MCP host rejected size update", err);
738
+ }
739
+ }
386
740
  return;
387
741
  }
388
742
  try {
389
- openAiBridge.notifyIntrinsicHeight({ height: intrinsicHeight });
743
+ openAiBridge.notifyIntrinsicHeight({ height });
390
744
  } catch (err) {
391
745
  console.warn("[agent-native] ChatGPT rejected intrinsic height update", err);
392
746
  }
@@ -482,6 +836,22 @@ export function embedApp(options = {}) {
482
836
  }
483
837
  });
484
838
 
839
+ function notifyHostHeightSoon() {
840
+ requestAnimationFrame(() => notifyHostHeight());
841
+ }
842
+
843
+ function notifyHostHeightRepeatedly() {
844
+ notifyHostHeight();
845
+ [0, 250, 1000, 2500].forEach((delay) => {
846
+ setTimeout(() => notifyHostHeight(), delay);
847
+ });
848
+ }
849
+
850
+ window.addEventListener("resize", notifyHostHeightSoon, { passive: true });
851
+ if (window.visualViewport) {
852
+ window.visualViewport.addEventListener("resize", notifyHostHeightSoon, { passive: true });
853
+ }
854
+
485
855
  async function launchEmbed() {
486
856
  if (!openUrl) {
487
857
  setMessage("Open link was not available.");
@@ -497,10 +867,21 @@ export function embedApp(options = {}) {
497
867
  try {
498
868
  const selfNavigate = shouldSelfNavigateToApp();
499
869
  const embedUrl = withChatBridgeParam(openUrl);
500
- const result = await callEmbedSessionTool({
501
- url: embedUrl,
502
- chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
503
- });
870
+ if (selfNavigate && isEmbedStartUrl(embedUrl)) {
871
+ if (isClaudeMcpContentHost() && shouldTransplantAppDocument()) {
872
+ await transplantAppDocument(embedUrl);
873
+ } else if (shouldRenderControlledAppFrame()) {
874
+ renderFrame(embedUrl);
875
+ } else {
876
+ navigateToAppFrame(embedUrl);
877
+ }
878
+ return;
879
+ }
880
+ if (!selfNavigate && isEmbedStartUrl(embedUrl)) {
881
+ renderFrame(embedUrl);
882
+ return;
883
+ }
884
+ const result = await callEmbedSessionTool(embedSessionArgsFor(embedUrl));
504
885
  const data = parseToolResult(result);
505
886
  if (typeof data.startUrl !== "string" || !data.startUrl) {
506
887
  startedFor = "";
@@ -508,7 +889,13 @@ export function embedApp(options = {}) {
508
889
  return;
509
890
  }
510
891
  if (selfNavigate) {
511
- navigateToAppFrame(data.startUrl);
892
+ if (isClaudeMcpContentHost() && shouldTransplantAppDocument()) {
893
+ await transplantAppDocument(data.startUrl);
894
+ } else if (shouldRenderControlledAppFrame()) {
895
+ renderFrame(data.startUrl);
896
+ } else {
897
+ navigateToAppFrame(data.startUrl);
898
+ }
512
899
  } else {
513
900
  renderFrame(data.startUrl);
514
901
  }
@@ -618,7 +1005,11 @@ export function embedApp(options = {}) {
618
1005
 
619
1006
  async function startMcpAppsBridge() {
620
1007
  const { App } = await import("${MCP_APP_IMPORT}");
621
- app = new App({ name: "Agent Native Embed", version: "1.0.0" }, {});
1008
+ app = new App(
1009
+ { name: "Agent Native Embed", version: "1.0.0" },
1010
+ {},
1011
+ { autoResize: false }
1012
+ );
622
1013
  app.ontoolinput = (params) => {
623
1014
  toolInput = params.arguments || {};
624
1015
  };
@@ -631,10 +1022,12 @@ export function embedApp(options = {}) {
631
1022
  };
632
1023
  app.onhostcontextchanged = () => {
633
1024
  updateDisplayButton();
1025
+ notifyHostHeight();
634
1026
  sendHostContext();
635
1027
  };
636
1028
  await app.connect();
637
1029
  updateDisplayButton();
1030
+ notifyHostHeight();
638
1031
  sendHostContext();
639
1032
  }
640
1033
 
@@ -646,16 +1039,14 @@ export function embedApp(options = {}) {
646
1039
  </body>
647
1040
  </html>`,
648
1041
  csp: {
649
- connectDomains: ["https://esm.sh"],
1042
+ connectDomains: ["https://esm.sh", MCP_APP_REQUEST_ORIGIN_CSP_SOURCE],
650
1043
  resourceDomains: [
651
1044
  "https://esm.sh",
652
1045
  MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
653
1046
  ...(options.frameDomains ?? []),
654
1047
  ],
655
- frameDomains: [
656
- MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,
657
- ...(options.frameDomains ?? []),
658
- ],
1048
+ baseUriDomains: [MCP_APP_REQUEST_ORIGIN_CSP_SOURCE],
1049
+ frameDomains,
659
1050
  },
660
1051
  prefersBorder: false,
661
1052
  };