@agent-native/core 0.22.17 → 0.22.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/client/agent-chat.d.ts.map +1 -1
  2. package/dist/client/agent-chat.js +19 -6
  3. package/dist/client/agent-chat.js.map +1 -1
  4. package/dist/client/embed-auth.d.ts +5 -0
  5. package/dist/client/embed-auth.d.ts.map +1 -1
  6. package/dist/client/embed-auth.js +100 -13
  7. package/dist/client/embed-auth.js.map +1 -1
  8. package/dist/client/frame.d.ts.map +1 -1
  9. package/dist/client/frame.js +1 -0
  10. package/dist/client/frame.js.map +1 -1
  11. package/dist/client/index.d.ts +1 -1
  12. package/dist/client/index.d.ts.map +1 -1
  13. package/dist/client/index.js +1 -1
  14. package/dist/client/index.js.map +1 -1
  15. package/dist/client/mcp-app-host.d.ts +5 -0
  16. package/dist/client/mcp-app-host.d.ts.map +1 -1
  17. package/dist/client/mcp-app-host.js +267 -2
  18. package/dist/client/mcp-app-host.js.map +1 -1
  19. package/dist/client/theme.js +1 -1
  20. package/dist/client/theme.js.map +1 -1
  21. package/dist/client/vite-dev-recovery-script.d.ts.map +1 -1
  22. package/dist/client/vite-dev-recovery-script.js +9 -0
  23. package/dist/client/vite-dev-recovery-script.js.map +1 -1
  24. package/dist/index.browser.d.ts +1 -1
  25. package/dist/index.browser.d.ts.map +1 -1
  26. package/dist/index.browser.js +1 -1
  27. package/dist/index.browser.js.map +1 -1
  28. package/dist/mcp/build-server.d.ts.map +1 -1
  29. package/dist/mcp/build-server.js +63 -9
  30. package/dist/mcp/build-server.js.map +1 -1
  31. package/dist/mcp/embed-app.d.ts.map +1 -1
  32. package/dist/mcp/embed-app.js +332 -34
  33. package/dist/mcp/embed-app.js.map +1 -1
  34. package/dist/server/open-route.d.ts.map +1 -1
  35. package/dist/server/open-route.js +7 -2
  36. package/dist/server/open-route.js.map +1 -1
  37. package/docs/content/client.md +6 -3
  38. package/docs/content/external-agents.md +34 -16
  39. package/docs/content/mcp-protocol.md +17 -7
  40. package/package.json +1 -1
@@ -40,6 +40,11 @@ export function embedApp(options = {}) {
40
40
  .stage { position: relative; min-height: ${viewportHeight}px; }
41
41
  iframe { display: block; width: 100%; height: ${viewportHeight}px; border: 0; background: Canvas; }
42
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
+ .fallback-title { max-width: 440px; font-size: 14px; font-weight: 700; }
45
+ .fallback-copy { max-width: 520px; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.45; }
46
+ .fallback-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }
47
+ .fallback-url { max-width: min(560px, 100%); overflow-wrap: anywhere; color: color-mix(in srgb, CanvasText 76%, Canvas); font-size: 12px; }
43
48
  </style>
44
49
  </head>
45
50
  <body
@@ -62,9 +67,6 @@ export function embedApp(options = {}) {
62
67
  </section>
63
68
  </main>
64
69
  <script type="module">
65
- import { App } from "${MCP_APP_IMPORT}";
66
-
67
- const app = new App({ name: "Agent Native Embed", version: "1.0.0" }, {});
68
70
  const body = document.body;
69
71
  const stage = document.querySelector("[data-stage]");
70
72
  const titleEl = document.querySelector("[data-title-label]");
@@ -73,10 +75,17 @@ export function embedApp(options = {}) {
73
75
  const startTool = body.dataset.startTool || "create_embed_session";
74
76
  const embedByDefault = body.dataset.embedDefault !== "0";
75
77
  const chatBridgeParam = ${JSON.stringify(MCP_APP_CHAT_BRIDGE_QUERY_PARAM)};
78
+ const intrinsicHeight = ${height};
79
+ let app = null;
80
+ let openAiBridge = null;
76
81
  let toolInput = {};
77
82
  let openUrl = "";
78
83
  let startedFor = "";
79
84
  let appFrame = null;
85
+ let appFrameReady = false;
86
+ let appFrameReadyTimer = null;
87
+ let appFrameLoadTimer = null;
88
+ let lastFrameSrc = "";
80
89
 
81
90
  function esc(value) {
82
91
  return String(value ?? "")
@@ -92,8 +101,20 @@ export function embedApp(options = {}) {
92
101
  try { return JSON.parse(value); } catch { return fallback; }
93
102
  }
94
103
 
104
+ function objectValue(value) {
105
+ return value && typeof value === "object" && !Array.isArray(value)
106
+ ? value
107
+ : {};
108
+ }
109
+
95
110
  function parseToolResult(params) {
96
111
  if (!params) return {};
112
+ if (params.result && typeof params.result === "object") {
113
+ return parseToolResult(params.result);
114
+ }
115
+ if (params.toolResult && typeof params.toolResult === "object") {
116
+ return parseToolResult(params.toolResult);
117
+ }
97
118
  if (params.structuredContent && typeof params.structuredContent === "object") {
98
119
  return params.structuredContent;
99
120
  }
@@ -103,17 +124,34 @@ export function embedApp(options = {}) {
103
124
  }
104
125
 
105
126
  function openLinkFrom(params, data) {
106
- const metaUrl = params && params._meta && params._meta["agent-native/openLink"]
107
- ? params._meta["agent-native/openLink"].webUrl
127
+ const openLink = params && params._meta && params._meta["agent-native/openLink"];
128
+ const metaUrl = openLink && typeof openLink === "object" && typeof openLink.webUrl === "string"
129
+ ? openLink.webUrl
108
130
  : "";
109
131
  return metaUrl || data.url || data.deepLink || data.openUrl || "";
110
132
  }
111
133
 
112
134
  function hostState() {
135
+ if (openAiBridge) {
136
+ return {
137
+ context: {
138
+ displayMode: openAiBridge.displayMode,
139
+ availableDisplayModes: typeof openAiBridge.requestDisplayMode === "function"
140
+ ? ["inline", "fullscreen", "pip"]
141
+ : [],
142
+ maxHeight: openAiBridge.maxHeight,
143
+ locale: openAiBridge.locale,
144
+ theme: openAiBridge.theme,
145
+ view: openAiBridge.view
146
+ },
147
+ capabilities: { openai: true },
148
+ version: openAiBridge.userAgent
149
+ };
150
+ }
113
151
  return {
114
- context: app.getHostContext ? app.getHostContext() : undefined,
115
- capabilities: app.getHostCapabilities ? app.getHostCapabilities() : undefined,
116
- version: app.getHostVersion ? app.getHostVersion() : undefined
152
+ context: app && app.getHostContext ? app.getHostContext() : undefined,
153
+ capabilities: app && app.getHostCapabilities ? app.getHostCapabilities() : undefined,
154
+ version: app && app.getHostVersion ? app.getHostVersion() : undefined
117
155
  };
118
156
  }
119
157
 
@@ -157,12 +195,23 @@ export function embedApp(options = {}) {
157
195
  }
158
196
 
159
197
  function supportedDisplayMode(mode) {
198
+ if (openAiBridge && typeof openAiBridge.requestDisplayMode === "function") {
199
+ return mode === "inline" || mode === "fullscreen" || mode === "pip";
200
+ }
160
201
  const modes = hostState().context && hostState().context.availableDisplayModes;
161
202
  return Array.isArray(modes) && modes.includes(mode);
162
203
  }
163
204
 
164
205
  async function requestHostDisplayMode(mode) {
165
- const result = await app.requestDisplayMode({ mode });
206
+ let result;
207
+ if (openAiBridge && typeof openAiBridge.requestDisplayMode === "function") {
208
+ result = await openAiBridge.requestDisplayMode({ mode });
209
+ } else {
210
+ if (!app || typeof app.requestDisplayMode !== "function") {
211
+ throw new Error("Display mode changes are not available in this host.");
212
+ }
213
+ result = await app.requestDisplayMode({ mode });
214
+ }
166
215
  updateDisplayButton();
167
216
  sendHostContext();
168
217
  return result;
@@ -187,14 +236,118 @@ export function embedApp(options = {}) {
187
236
  stage.innerHTML = '<div class="message">' + esc(message) + '</div>';
188
237
  }
189
238
 
239
+ function clearFrameReadyTimer() {
240
+ if (!appFrameReadyTimer) return;
241
+ clearTimeout(appFrameReadyTimer);
242
+ appFrameReadyTimer = null;
243
+ }
244
+
245
+ function clearFrameLoadTimer() {
246
+ if (!appFrameLoadTimer) return;
247
+ clearTimeout(appFrameLoadTimer);
248
+ appFrameLoadTimer = null;
249
+ }
250
+
251
+ function startFrameReadyTimer(frame) {
252
+ clearFrameReadyTimer();
253
+ appFrameReadyTimer = setTimeout(() => {
254
+ if (!appFrameReady && appFrame === frame) renderFrameFallback();
255
+ }, 7000);
256
+ }
257
+
258
+ function renderFrameFallback() {
259
+ clearFrameReadyTimer();
260
+ clearFrameLoadTimer();
261
+ appFrame = null;
262
+ stage.innerHTML =
263
+ '<div class="fallback">' +
264
+ '<div class="fallback-title">Open this app in its own tab</div>' +
265
+ '<div class="fallback-copy">This chat host did not allow the embedded app frame to load inline. You can still open the same app route through the host or use the URL below.</div>' +
266
+ '<div class="fallback-actions">' +
267
+ '<button type="button" data-fallback-open>Open app</button>' +
268
+ '<button type="button" data-fallback-retry>Try inline again</button>' +
269
+ '</div>' +
270
+ (openUrl ? '<a class="fallback-url" href="' + esc(openUrl) + '" target="_blank" rel="noreferrer">' + esc(openUrl) + '</a>' : '') +
271
+ '</div>';
272
+ const fallbackOpen = stage.querySelector("[data-fallback-open]");
273
+ const fallbackRetry = stage.querySelector("[data-fallback-retry]");
274
+ if (fallbackOpen) {
275
+ fallbackOpen.disabled = !openUrl;
276
+ fallbackOpen.onclick = () => {
277
+ if (openUrl) void openFallbackExternal();
278
+ };
279
+ }
280
+ if (fallbackRetry) {
281
+ fallbackRetry.disabled = !lastFrameSrc;
282
+ fallbackRetry.onclick = () => {
283
+ if (lastFrameSrc) renderFrame(lastFrameSrc);
284
+ };
285
+ }
286
+ }
287
+
288
+ async function openFallbackExternal() {
289
+ let url = withChatBridgeParam(openUrl);
290
+ try {
291
+ const result = await callEmbedSessionTool({
292
+ url,
293
+ chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
294
+ });
295
+ const data = parseToolResult(result);
296
+ if (typeof data.startUrl === "string" && data.startUrl) {
297
+ url = data.startUrl;
298
+ }
299
+ } catch (err) {
300
+ console.warn("[agent-native] MCP fallback could not mint a fresh app session", err);
301
+ }
302
+ await openHostLink({ url });
303
+ }
304
+
190
305
  function renderFrame(src) {
306
+ clearFrameReadyTimer();
307
+ clearFrameLoadTimer();
191
308
  const frame = document.createElement("iframe");
192
309
  frame.title = body.dataset.iframeTitle || "Agent Native app";
193
310
  frame.src = src;
194
311
  frame.allow = "clipboard-read; clipboard-write";
195
312
  appFrame = frame;
196
- frame.addEventListener("load", () => sendFrameReadyMessages(frame));
313
+ appFrameReady = false;
314
+ lastFrameSrc = src;
315
+ frame.addEventListener("load", () => {
316
+ if (appFrame !== frame) return;
317
+ clearFrameLoadTimer();
318
+ sendFrameReadyMessages(frame);
319
+ startFrameReadyTimer(frame);
320
+ });
197
321
  stage.replaceChildren(frame);
322
+ notifyHostHeight();
323
+ appFrameLoadTimer = setTimeout(() => {
324
+ if (!appFrameReady && appFrame === frame) renderFrameFallback();
325
+ }, 30000);
326
+ }
327
+
328
+ function shouldSelfNavigateToApp() {
329
+ const mode = typeof toolInput.embedMode === "string"
330
+ ? toolInput.embedMode
331
+ : typeof toolInput.renderMode === "string"
332
+ ? toolInput.renderMode
333
+ : "";
334
+ if (mode === "iframe" || mode === "nested") return false;
335
+ if (toolInput.nested === true || toolInput.frame === "iframe") return false;
336
+ return true;
337
+ }
338
+
339
+ function navigateToAppFrame(src) {
340
+ clearFrameReadyTimer();
341
+ clearFrameLoadTimer();
342
+ appFrame = null;
343
+ lastFrameSrc = src;
344
+ setMessage("Opening app");
345
+ try {
346
+ window.location.replace(src);
347
+ } catch (err) {
348
+ console.warn("[agent-native] MCP app self-navigation failed", err);
349
+ renderFrameFallback();
350
+ }
198
351
  }
199
352
 
200
353
  async function updateHostModelContext(data) {
@@ -203,13 +356,40 @@ export function embedApp(options = {}) {
203
356
  if (data && data.structuredContent && typeof data.structuredContent === "object") {
204
357
  params.structuredContent = data.structuredContent;
205
358
  }
359
+ if (openAiBridge && typeof openAiBridge.setWidgetState === "function") {
360
+ openAiBridge.setWidgetState({
361
+ ...objectValue(openAiBridge.widgetState),
362
+ agentNativeModelContext: params
363
+ });
364
+ return { ok: true };
365
+ }
366
+ if (!app || typeof app.updateModelContext !== "function") return { ok: false };
206
367
  await app.updateModelContext(params);
368
+ return { ok: true };
207
369
  }
208
370
 
209
371
  async function openHostLink(data) {
210
372
  const url = typeof (data && data.url) === "string" ? data.url : "";
211
373
  if (!url) return { isError: true };
212
- return await app.openLink({ url });
374
+ if (openAiBridge && typeof openAiBridge.openExternal === "function") {
375
+ return await openAiBridge.openExternal({ href: url, redirectUrl: false });
376
+ }
377
+ if (app && typeof app.openLink === "function") {
378
+ return await app.openLink({ url });
379
+ }
380
+ window.open(url, "_blank", "noopener,noreferrer");
381
+ return { ok: true };
382
+ }
383
+
384
+ function notifyHostHeight() {
385
+ if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== "function") {
386
+ return;
387
+ }
388
+ try {
389
+ openAiBridge.notifyIntrinsicHeight({ height: intrinsicHeight });
390
+ } catch (err) {
391
+ console.warn("[agent-native] ChatGPT rejected intrinsic height update", err);
392
+ }
213
393
  }
214
394
 
215
395
  function respondToAppFrame(requestId, work) {
@@ -240,14 +420,29 @@ export function embedApp(options = {}) {
240
420
  const context = typeof chat.context === "string" ? chat.context : "";
241
421
  if (context.trim()) {
242
422
  try {
243
- await app.updateModelContext({
244
- content: [{ type: "text", text: context }]
245
- });
423
+ if (openAiBridge && typeof openAiBridge.setWidgetState === "function") {
424
+ openAiBridge.setWidgetState({
425
+ ...objectValue(openAiBridge.widgetState),
426
+ agentNativeChatContext: context
427
+ });
428
+ } else if (app && typeof app.updateModelContext === "function") {
429
+ await app.updateModelContext({
430
+ content: [{ type: "text", text: context }]
431
+ });
432
+ }
246
433
  } catch (err) {
247
434
  console.warn("[agent-native] MCP host rejected model context update", err);
248
435
  }
249
436
  }
250
437
  try {
438
+ if (openAiBridge && typeof openAiBridge.sendFollowUpMessage === "function") {
439
+ await openAiBridge.sendFollowUpMessage({
440
+ prompt: context.trim() ? context.trim() + "\\n\\n" + message : message,
441
+ scrollToBottom: true
442
+ });
443
+ return;
444
+ }
445
+ if (!app || typeof app.sendMessage !== "function") return;
251
446
  const result = await app.sendMessage({
252
447
  role: "user",
253
448
  content: [{ type: "text", text: message }]
@@ -264,6 +459,12 @@ export function embedApp(options = {}) {
264
459
  if (!appFrame || event.source !== appFrame.contentWindow) return;
265
460
  if (!event.data) return;
266
461
  const data = event.data.data || {};
462
+ if (event.data.type === "agentNative.embeddedAppReady") {
463
+ appFrameReady = true;
464
+ clearFrameLoadTimer();
465
+ clearFrameReadyTimer();
466
+ return;
467
+ }
267
468
  if (event.data.type === "agentNative.submitChat") {
268
469
  void sendHostChat(data);
269
470
  return;
@@ -294,32 +495,56 @@ export function embedApp(options = {}) {
294
495
  startedFor = openUrl;
295
496
  setMessage("Loading app");
296
497
  try {
498
+ const selfNavigate = shouldSelfNavigateToApp();
297
499
  const embedUrl = withChatBridgeParam(openUrl);
298
- const result = await app.callServerTool({
299
- name: startTool,
300
- arguments: {
301
- url: embedUrl,
302
- chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
303
- }
500
+ const result = await callEmbedSessionTool({
501
+ url: embedUrl,
502
+ chrome: typeof toolInput.chrome === "string" ? toolInput.chrome : "full"
304
503
  });
305
504
  const data = parseToolResult(result);
306
- if (!data.startUrl) {
505
+ if (typeof data.startUrl !== "string" || !data.startUrl) {
307
506
  startedFor = "";
308
507
  setMessage(data.error || "This app can be opened, but not embedded from this MCP server.");
309
508
  return;
310
509
  }
311
- renderFrame(data.startUrl);
510
+ if (selfNavigate) {
511
+ navigateToAppFrame(data.startUrl);
512
+ } else {
513
+ renderFrame(data.startUrl);
514
+ }
312
515
  } catch (err) {
313
516
  startedFor = "";
314
517
  setMessage(err && err.message ? err.message : "Could not launch embedded app.");
315
518
  }
316
519
  }
317
520
 
521
+ async function callEmbedSessionTool(args) {
522
+ if (openAiBridge && typeof openAiBridge.callTool === "function") {
523
+ return await openAiBridge.callTool(startTool, args);
524
+ }
525
+ if (!app || typeof app.callServerTool !== "function") {
526
+ throw new Error("Host tool calls are not available.");
527
+ }
528
+ return await app.callServerTool({ name: startTool, arguments: args });
529
+ }
530
+
531
+ function updateHostOpenInAppUrl() {
532
+ if (!openAiBridge || !openUrl || typeof openAiBridge.setOpenInAppUrl !== "function") {
533
+ return;
534
+ }
535
+ try {
536
+ openAiBridge.setOpenInAppUrl({ href: openUrl });
537
+ } catch (err) {
538
+ console.warn("[agent-native] ChatGPT rejected open-in-app URL", err);
539
+ }
540
+ }
541
+
318
542
  function updateOpenButton() {
319
543
  openButton.disabled = !openUrl;
320
544
  openButton.onclick = () => {
321
- if (openUrl) void app.openLink({ url: openUrl });
545
+ if (openUrl) void openHostLink({ url: openUrl });
322
546
  };
547
+ updateHostOpenInAppUrl();
323
548
  }
324
549
 
325
550
  function updateTitle(data) {
@@ -327,23 +552,96 @@ export function embedApp(options = {}) {
327
552
  titleEl.textContent = String(label);
328
553
  }
329
554
 
330
- app.ontoolinput = (params) => {
331
- toolInput = params.arguments || {};
332
- };
333
- app.ontoolresult = (params) => {
555
+ function readOpenAiBridge() {
556
+ return window.openai && typeof window.openai === "object"
557
+ ? window.openai
558
+ : null;
559
+ }
560
+
561
+ function openAiToolResultParams(bridge) {
562
+ const params = {};
563
+ if (bridge && bridge.toolOutput !== undefined) {
564
+ if (bridge.toolOutput && typeof bridge.toolOutput === "object") {
565
+ params.structuredContent = bridge.toolOutput;
566
+ } else {
567
+ params.content = [{ type: "text", text: String(bridge.toolOutput) }];
568
+ }
569
+ }
570
+ if (bridge && bridge.toolResponseMetadata && typeof bridge.toolResponseMetadata === "object") {
571
+ params._meta = bridge.toolResponseMetadata;
572
+ }
573
+ return params;
574
+ }
575
+
576
+ function syncOpenAiBridge(bridge) {
577
+ if (!bridge) return false;
578
+ openAiBridge = bridge;
579
+ toolInput = objectValue(bridge.toolInput);
580
+ const params = openAiToolResultParams(bridge);
334
581
  const data = parseToolResult(params);
335
582
  openUrl = openLinkFrom(params, data);
336
583
  updateTitle(data);
337
584
  updateOpenButton();
338
- void launchEmbed();
339
- };
340
- app.onhostcontextchanged = () => {
341
585
  updateDisplayButton();
586
+ notifyHostHeight();
342
587
  sendHostContext();
343
- };
344
- await app.connect();
345
- updateDisplayButton();
346
- sendHostContext();
588
+ if (openUrl) {
589
+ void launchEmbed();
590
+ } else if (!appFrame) {
591
+ setMessage("Waiting for app result");
592
+ }
593
+ return true;
594
+ }
595
+
596
+ function waitForOpenAiBridge() {
597
+ const existing = readOpenAiBridge();
598
+ if (existing) return Promise.resolve(existing);
599
+ return new Promise((resolve) => {
600
+ let settled = false;
601
+ const finish = (bridge) => {
602
+ if (settled) return;
603
+ settled = true;
604
+ window.removeEventListener("openai:set_globals", onGlobals);
605
+ clearTimeout(timer);
606
+ resolve(bridge || readOpenAiBridge());
607
+ };
608
+ const onGlobals = () => finish(readOpenAiBridge());
609
+ const timer = setTimeout(() => finish(null), 200);
610
+ window.addEventListener("openai:set_globals", onGlobals, { passive: true });
611
+ });
612
+ }
613
+
614
+ window.addEventListener("openai:set_globals", () => {
615
+ const bridge = readOpenAiBridge();
616
+ if (bridge && (!appFrame || openAiBridge)) syncOpenAiBridge(bridge);
617
+ }, { passive: true });
618
+
619
+ async function startMcpAppsBridge() {
620
+ const { App } = await import("${MCP_APP_IMPORT}");
621
+ app = new App({ name: "Agent Native Embed", version: "1.0.0" }, {});
622
+ app.ontoolinput = (params) => {
623
+ toolInput = params.arguments || {};
624
+ };
625
+ app.ontoolresult = (params) => {
626
+ const data = parseToolResult(params);
627
+ openUrl = openLinkFrom(params, data);
628
+ updateTitle(data);
629
+ updateOpenButton();
630
+ void launchEmbed();
631
+ };
632
+ app.onhostcontextchanged = () => {
633
+ updateDisplayButton();
634
+ sendHostContext();
635
+ };
636
+ await app.connect();
637
+ updateDisplayButton();
638
+ sendHostContext();
639
+ }
640
+
641
+ const initialOpenAiBridge = await waitForOpenAiBridge();
642
+ if (!syncOpenAiBridge(initialOpenAiBridge)) {
643
+ await startMcpAppsBridge();
644
+ }
347
645
  </script>
348
646
  </body>
349
647
  </html>`,
@@ -1 +1 @@
1
- {"version":3,"file":"embed-app.js","sourceRoot":"","sources":["../../src/mcp/embed-app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,MAAM,cAAc,GAClB,mEAAmE,CAAC;AAEtE,MAAM,CAAC,MAAM,iCAAiC,GAAG,gBAAgB,CAAC;AAClE,MAAM,6BAA6B,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,CAAC;AACnD,MAAM,CAAC,MAAM,4BAA4B,GACvC,+BAA+B,GAAG,6BAA6B,CAAC;AAalE,SAAS,IAAI,CAAC,KAAyB;IACrC,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,UAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC;IAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IACrD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACtE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,KAAK,KAAK,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CACrB,GAAG,EACH,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,IAAI,4BAA4B,CAAC,CAC9D,CAAC;IACF,MAAM,cAAc,GAAG,MAAM,GAAG,6BAA6B,CAAC;IAE9D,OAAO;QACL,KAAK;QACL,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,IAAI,EAAE,GAAG,EAAE,CAAC;;;;;;;;;oDASoC,MAAM;;;;;;+CAMX,cAAc;oDACT,cAAc;iEACD,cAAc;;;;oBAI3D,IAAI,CAAC,KAAK,CAAC;uBACR,IAAI,CAAC,WAAW,CAAC;qBACnB,IAAI,CAAC,SAAS,CAAC;qBACf,IAAI,CAAC,aAAa,CAAC;wBAChB,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;;;;4CAIN,IAAI,CAAC,KAAK,CAAC;;;mDAGJ,IAAI,CAAC,SAAS,CAAC;;;;;;;;2BAQvC,cAAc;;;;;;;;;;8BAUX,IAAI,CAAC,SAAS,CAAC,+BAA+B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAkRrE;QACJ,GAAG,EAAE;YACH,cAAc,EAAE,CAAC,gBAAgB,CAAC;YAClC,eAAe,EAAE;gBACf,gBAAgB;gBAChB,iCAAiC;gBACjC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;aAChC;YACD,YAAY,EAAE;gBACZ,iCAAiC;gBACjC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;aAChC;SACF;QACD,aAAa,EAAE,KAAK;KACrB,CAAC;AACJ,CAAC","sourcesContent":["import type { ActionMcpAppResourceConfig } from \"../action.js\";\nimport { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from \"../shared/embed-auth.js\";\n\nconst MCP_APP_IMPORT =\n \"https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps\";\n\nexport const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = \"$requestOrigin\";\nconst MCP_APP_WRAPPER_CHROME_HEIGHT = 44;\nexport const DEFAULT_MCP_APP_VIEWPORT_HEIGHT = 720;\nexport const DEFAULT_MCP_APP_SHELL_HEIGHT =\n DEFAULT_MCP_APP_VIEWPORT_HEIGHT + MCP_APP_WRAPPER_CHROME_HEIGHT;\n\nexport interface EmbedAppOptions {\n title?: string;\n description?: string;\n iframeTitle?: string;\n openLabel?: string;\n embedByDefault?: boolean;\n startToolName?: string;\n frameDomains?: string[];\n height?: number;\n}\n\nfunction attr(value: string | undefined): string {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nexport function embedApp(\n options: EmbedAppOptions = {},\n): ActionMcpAppResourceConfig {\n const title = options.title ?? \"Open app\";\n const iframeTitle = options.iframeTitle ?? \"Agent Native app\";\n const openLabel = options.openLabel ?? \"Open in app\";\n const startToolName = options.startToolName ?? \"create_embed_session\";\n const embedByDefault = options.embedByDefault !== false;\n const height = Math.max(\n 320,\n Math.min(900, options.height ?? DEFAULT_MCP_APP_SHELL_HEIGHT),\n );\n const viewportHeight = height - MCP_APP_WRAPPER_CHROME_HEIGHT;\n\n return {\n title,\n ...(options.description ? { description: options.description } : {}),\n html: () => `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style>\n :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: Canvas; color: CanvasText; }\n * { box-sizing: border-box; }\n body { margin: 0; }\n .shell { display: grid; gap: 8px; min-height: ${height}px; padding: 0; }\n .bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 6px 8px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, Canvas); }\n .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }\n .actions { display: flex; align-items: center; gap: 6px; }\n button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }\n button:disabled { opacity: .55; cursor: default; }\n .stage { position: relative; min-height: ${viewportHeight}px; }\n iframe { display: block; width: 100%; height: ${viewportHeight}px; border: 0; background: Canvas; }\n .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; }\n </style>\n</head>\n<body\n data-app-title=\"${attr(title)}\"\n data-iframe-title=\"${attr(iframeTitle)}\"\n data-open-label=\"${attr(openLabel)}\"\n data-start-tool=\"${attr(startToolName)}\"\n data-embed-default=\"${embedByDefault ? \"1\" : \"0\"}\"\n>\n <main class=\"shell\">\n <div class=\"bar\">\n <div class=\"title\" data-title-label>${attr(title)}</div>\n <div class=\"actions\">\n <button type=\"button\" data-display hidden disabled>Fullscreen</button>\n <button type=\"button\" data-open disabled>${attr(openLabel)}</button>\n </div>\n </div>\n <section class=\"stage\" data-stage>\n <div class=\"message\">Preparing app</div>\n </section>\n </main>\n <script type=\"module\">\n import { App } from \"${MCP_APP_IMPORT}\";\n\n const app = new App({ name: \"Agent Native Embed\", version: \"1.0.0\" }, {});\n const body = document.body;\n const stage = document.querySelector(\"[data-stage]\");\n const titleEl = document.querySelector(\"[data-title-label]\");\n const openButton = document.querySelector(\"[data-open]\");\n const displayButton = document.querySelector(\"[data-display]\");\n const startTool = body.dataset.startTool || \"create_embed_session\";\n const embedByDefault = body.dataset.embedDefault !== \"0\";\n const chatBridgeParam = ${JSON.stringify(MCP_APP_CHAT_BRIDGE_QUERY_PARAM)};\n let toolInput = {};\n let openUrl = \"\";\n let startedFor = \"\";\n let appFrame = null;\n\n function esc(value) {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n }\n\n function parseJson(value, fallback) {\n if (value && typeof value === \"object\") return value;\n if (typeof value !== \"string\" || !value.trim()) return fallback;\n try { return JSON.parse(value); } catch { return fallback; }\n }\n\n function parseToolResult(params) {\n if (!params) return {};\n if (params.structuredContent && typeof params.structuredContent === \"object\") {\n return params.structuredContent;\n }\n const parts = Array.isArray(params.content) ? params.content : [];\n const textPart = parts.find((part) => part && part.type === \"text\" && typeof part.text === \"string\");\n return parseJson(textPart ? textPart.text : \"\", {});\n }\n\n function openLinkFrom(params, data) {\n const metaUrl = params && params._meta && params._meta[\"agent-native/openLink\"]\n ? params._meta[\"agent-native/openLink\"].webUrl\n : \"\";\n return metaUrl || data.url || data.deepLink || data.openUrl || \"\";\n }\n\n function hostState() {\n return {\n context: app.getHostContext ? app.getHostContext() : undefined,\n capabilities: app.getHostCapabilities ? app.getHostCapabilities() : undefined,\n version: app.getHostVersion ? app.getHostVersion() : undefined\n };\n }\n\n function sendToAppFrame(message) {\n if (!appFrame || !appFrame.contentWindow) return;\n try { appFrame.contentWindow.postMessage(message, \"*\"); } catch {}\n }\n\n function sendHostContext() {\n sendToAppFrame({ type: \"agentNative.mcpHostContext\", data: hostState() });\n }\n\n function sendFrameReadyMessages(frame) {\n const originPayload = { type: \"agentNative.frameOrigin\", origin: window.location.origin };\n [0, 200, 500, 1500].forEach((delay) => {\n setTimeout(() => {\n try { frame.contentWindow && frame.contentWindow.postMessage(originPayload, \"*\"); } catch {}\n sendHostContext();\n }, delay);\n });\n }\n\n function withChatBridgeParam(value) {\n if (typeof value !== \"string\" || !value) return value;\n try {\n const base = \"http://agent-native.invalid\";\n const url = value.startsWith(\"/\") ? new URL(value, base) : new URL(value);\n url.searchParams.set(chatBridgeParam, \"1\");\n return value.startsWith(\"/\")\n ? url.pathname + url.search + url.hash\n : url.toString();\n } catch {\n return value;\n }\n }\n\n function wantsEmbed() {\n if (toolInput.embed === false || toolInput.embed === \"false\") return false;\n if (embedByDefault) return true;\n return toolInput.embed === true || toolInput.embed === \"true\";\n }\n\n function supportedDisplayMode(mode) {\n const modes = hostState().context && hostState().context.availableDisplayModes;\n return Array.isArray(modes) && modes.includes(mode);\n }\n\n async function requestHostDisplayMode(mode) {\n const result = await app.requestDisplayMode({ mode });\n updateDisplayButton();\n sendHostContext();\n return result;\n }\n\n function updateDisplayButton() {\n const context = hostState().context || {};\n const nextMode = context.displayMode === \"fullscreen\" ? \"inline\" : \"fullscreen\";\n const supported = supportedDisplayMode(nextMode);\n displayButton.hidden = !supported;\n displayButton.disabled = !supported;\n displayButton.textContent = nextMode === \"fullscreen\" ? \"Fullscreen\" : \"Inline\";\n displayButton.onclick = () => {\n if (!supportedDisplayMode(nextMode)) return;\n void requestHostDisplayMode(nextMode).catch((err) => {\n console.warn(\"[agent-native] MCP host rejected display mode request\", err);\n });\n };\n }\n\n function setMessage(message) {\n stage.innerHTML = '<div class=\"message\">' + esc(message) + '</div>';\n }\n\n function renderFrame(src) {\n const frame = document.createElement(\"iframe\");\n frame.title = body.dataset.iframeTitle || \"Agent Native app\";\n frame.src = src;\n frame.allow = \"clipboard-read; clipboard-write\";\n appFrame = frame;\n frame.addEventListener(\"load\", () => sendFrameReadyMessages(frame));\n stage.replaceChildren(frame);\n }\n\n async function updateHostModelContext(data) {\n const params = {};\n if (Array.isArray(data && data.content)) params.content = data.content;\n if (data && data.structuredContent && typeof data.structuredContent === \"object\") {\n params.structuredContent = data.structuredContent;\n }\n await app.updateModelContext(params);\n }\n\n async function openHostLink(data) {\n const url = typeof (data && data.url) === \"string\" ? data.url : \"\";\n if (!url) return { isError: true };\n return await app.openLink({ url });\n }\n\n function respondToAppFrame(requestId, work) {\n if (!requestId) return;\n Promise.resolve(work)\n .then((result) => {\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: { requestId, ok: true, result }\n });\n })\n .catch((err) => {\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: {\n requestId,\n ok: false,\n error: err && err.message ? err.message : String(err)\n }\n });\n });\n }\n\n async function sendHostChat(chat) {\n if (!chat || chat.submit === false) return;\n const message = typeof chat.message === \"string\" ? chat.message : \"\";\n if (!message.trim()) return;\n const context = typeof chat.context === \"string\" ? chat.context : \"\";\n if (context.trim()) {\n try {\n await app.updateModelContext({\n content: [{ type: \"text\", text: context }]\n });\n } catch (err) {\n console.warn(\"[agent-native] MCP host rejected model context update\", err);\n }\n }\n try {\n const result = await app.sendMessage({\n role: \"user\",\n content: [{ type: \"text\", text: message }]\n });\n if (result && result.isError) {\n console.warn(\"[agent-native] MCP host rejected chat message\", result);\n }\n } catch (err) {\n console.warn(\"[agent-native] MCP host chat bridge failed\", err);\n }\n }\n\n window.addEventListener(\"message\", (event) => {\n if (!appFrame || event.source !== appFrame.contentWindow) return;\n if (!event.data) return;\n const data = event.data.data || {};\n if (event.data.type === \"agentNative.submitChat\") {\n void sendHostChat(data);\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.updateModelContext\") {\n respondToAppFrame(data.requestId, updateHostModelContext(data));\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.openLink\") {\n respondToAppFrame(data.requestId, openHostLink(data));\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.requestDisplayMode\") {\n respondToAppFrame(data.requestId, requestHostDisplayMode(data.mode));\n }\n });\n\n async function launchEmbed() {\n if (!openUrl) {\n setMessage(\"Open link was not available.\");\n return;\n }\n if (!wantsEmbed()) {\n setMessage(\"Ready to open.\");\n return;\n }\n if (startedFor === openUrl) return;\n startedFor = openUrl;\n setMessage(\"Loading app\");\n try {\n const embedUrl = withChatBridgeParam(openUrl);\n const result = await app.callServerTool({\n name: startTool,\n arguments: {\n url: embedUrl,\n chrome: typeof toolInput.chrome === \"string\" ? toolInput.chrome : \"full\"\n }\n });\n const data = parseToolResult(result);\n if (!data.startUrl) {\n startedFor = \"\";\n setMessage(data.error || \"This app can be opened, but not embedded from this MCP server.\");\n return;\n }\n renderFrame(data.startUrl);\n } catch (err) {\n startedFor = \"\";\n setMessage(err && err.message ? err.message : \"Could not launch embedded app.\");\n }\n }\n\n function updateOpenButton() {\n openButton.disabled = !openUrl;\n openButton.onclick = () => {\n if (openUrl) void app.openLink({ url: openUrl });\n };\n }\n\n function updateTitle(data) {\n const label = data.label || data.app || data.view || body.dataset.appTitle || \"App\";\n titleEl.textContent = String(label);\n }\n\n app.ontoolinput = (params) => {\n toolInput = params.arguments || {};\n };\n app.ontoolresult = (params) => {\n const data = parseToolResult(params);\n openUrl = openLinkFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n void launchEmbed();\n };\n app.onhostcontextchanged = () => {\n updateDisplayButton();\n sendHostContext();\n };\n await app.connect();\n updateDisplayButton();\n sendHostContext();\n </script>\n</body>\n</html>`,\n csp: {\n connectDomains: [\"https://esm.sh\"],\n resourceDomains: [\n \"https://esm.sh\",\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.frameDomains ?? []),\n ],\n frameDomains: [\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.frameDomains ?? []),\n ],\n },\n prefersBorder: false,\n };\n}\n"]}
1
+ {"version":3,"file":"embed-app.js","sourceRoot":"","sources":["../../src/mcp/embed-app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,MAAM,cAAc,GAClB,mEAAmE,CAAC;AAEtE,MAAM,CAAC,MAAM,iCAAiC,GAAG,gBAAgB,CAAC;AAClE,MAAM,6BAA6B,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,+BAA+B,GAAG,GAAG,CAAC;AACnD,MAAM,CAAC,MAAM,4BAA4B,GACvC,+BAA+B,GAAG,6BAA6B,CAAC;AAalE,SAAS,IAAI,CAAC,KAAyB;IACrC,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,UAA2B,EAAE;IAE7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC;IAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,aAAa,CAAC;IACrD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,sBAAsB,CAAC;IACtE,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,KAAK,KAAK,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CACrB,GAAG,EACH,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,IAAI,4BAA4B,CAAC,CAC9D,CAAC;IACF,MAAM,cAAc,GAAG,MAAM,GAAG,6BAA6B,CAAC;IAE9D,OAAO;QACL,KAAK;QACL,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,IAAI,EAAE,GAAG,EAAE,CAAC;;;;;;;;;oDASoC,MAAM;;;;;;+CAMX,cAAc;oDACT,cAAc;iEACD,cAAc;sGACuB,cAAc;;;;;;;;oBAQhG,IAAI,CAAC,KAAK,CAAC;uBACR,IAAI,CAAC,WAAW,CAAC;qBACnB,IAAI,CAAC,SAAS,CAAC;qBACf,IAAI,CAAC,aAAa,CAAC;wBAChB,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;;;;4CAIN,IAAI,CAAC,KAAK,CAAC;;;mDAGJ,IAAI,CAAC,SAAS,CAAC;;;;;;;;;;;;;;;8BAepC,IAAI,CAAC,SAAS,CAAC,+BAA+B,CAAC;8BAC/C,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sCA8hBE,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;QA2B5C;QACJ,GAAG,EAAE;YACH,cAAc,EAAE,CAAC,gBAAgB,CAAC;YAClC,eAAe,EAAE;gBACf,gBAAgB;gBAChB,iCAAiC;gBACjC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;aAChC;YACD,YAAY,EAAE;gBACZ,iCAAiC;gBACjC,GAAG,CAAC,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;aAChC;SACF;QACD,aAAa,EAAE,KAAK;KACrB,CAAC;AACJ,CAAC","sourcesContent":["import type { ActionMcpAppResourceConfig } from \"../action.js\";\nimport { MCP_APP_CHAT_BRIDGE_QUERY_PARAM } from \"../shared/embed-auth.js\";\n\nconst MCP_APP_IMPORT =\n \"https://esm.sh/@modelcontextprotocol/ext-apps@1.7.2/app-with-deps\";\n\nexport const MCP_APP_REQUEST_ORIGIN_CSP_SOURCE = \"$requestOrigin\";\nconst MCP_APP_WRAPPER_CHROME_HEIGHT = 44;\nexport const DEFAULT_MCP_APP_VIEWPORT_HEIGHT = 720;\nexport const DEFAULT_MCP_APP_SHELL_HEIGHT =\n DEFAULT_MCP_APP_VIEWPORT_HEIGHT + MCP_APP_WRAPPER_CHROME_HEIGHT;\n\nexport interface EmbedAppOptions {\n title?: string;\n description?: string;\n iframeTitle?: string;\n openLabel?: string;\n embedByDefault?: boolean;\n startToolName?: string;\n frameDomains?: string[];\n height?: number;\n}\n\nfunction attr(value: string | undefined): string {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nexport function embedApp(\n options: EmbedAppOptions = {},\n): ActionMcpAppResourceConfig {\n const title = options.title ?? \"Open app\";\n const iframeTitle = options.iframeTitle ?? \"Agent Native app\";\n const openLabel = options.openLabel ?? \"Open in app\";\n const startToolName = options.startToolName ?? \"create_embed_session\";\n const embedByDefault = options.embedByDefault !== false;\n const height = Math.max(\n 320,\n Math.min(900, options.height ?? DEFAULT_MCP_APP_SHELL_HEIGHT),\n );\n const viewportHeight = height - MCP_APP_WRAPPER_CHROME_HEIGHT;\n\n return {\n title,\n ...(options.description ? { description: options.description } : {}),\n html: () => `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <style>\n :root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: Canvas; color: CanvasText; }\n * { box-sizing: border-box; }\n body { margin: 0; }\n .shell { display: grid; gap: 8px; min-height: ${height}px; padding: 0; }\n .bar { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 36px; padding: 6px 8px; border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, Canvas); }\n .title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 700; color: color-mix(in srgb, CanvasText 72%, Canvas); }\n .actions { display: flex; align-items: center; gap: 6px; }\n button { min-height: 28px; border: 1px solid color-mix(in srgb, CanvasText 14%, Canvas); border-radius: 7px; background: Canvas; color: CanvasText; cursor: pointer; font: inherit; font-size: 12px; font-weight: 700; padding: 0 9px; }\n button:disabled { opacity: .55; cursor: default; }\n .stage { position: relative; min-height: ${viewportHeight}px; }\n iframe { display: block; width: 100%; height: ${viewportHeight}px; border: 0; background: Canvas; }\n .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; }\n .fallback { display: grid; align-content: center; justify-items: center; gap: 12px; min-height: ${viewportHeight}px; padding: 24px; background: Canvas; color: CanvasText; text-align: center; }\n .fallback-title { max-width: 440px; font-size: 14px; font-weight: 700; }\n .fallback-copy { max-width: 520px; color: color-mix(in srgb, CanvasText 64%, Canvas); font-size: 13px; line-height: 1.45; }\n .fallback-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; }\n .fallback-url { max-width: min(560px, 100%); overflow-wrap: anywhere; color: color-mix(in srgb, CanvasText 76%, Canvas); font-size: 12px; }\n </style>\n</head>\n<body\n data-app-title=\"${attr(title)}\"\n data-iframe-title=\"${attr(iframeTitle)}\"\n data-open-label=\"${attr(openLabel)}\"\n data-start-tool=\"${attr(startToolName)}\"\n data-embed-default=\"${embedByDefault ? \"1\" : \"0\"}\"\n>\n <main class=\"shell\">\n <div class=\"bar\">\n <div class=\"title\" data-title-label>${attr(title)}</div>\n <div class=\"actions\">\n <button type=\"button\" data-display hidden disabled>Fullscreen</button>\n <button type=\"button\" data-open disabled>${attr(openLabel)}</button>\n </div>\n </div>\n <section class=\"stage\" data-stage>\n <div class=\"message\">Preparing app</div>\n </section>\n </main>\n <script type=\"module\">\n const body = document.body;\n const stage = document.querySelector(\"[data-stage]\");\n const titleEl = document.querySelector(\"[data-title-label]\");\n const openButton = document.querySelector(\"[data-open]\");\n const displayButton = document.querySelector(\"[data-display]\");\n const startTool = body.dataset.startTool || \"create_embed_session\";\n const embedByDefault = body.dataset.embedDefault !== \"0\";\n const chatBridgeParam = ${JSON.stringify(MCP_APP_CHAT_BRIDGE_QUERY_PARAM)};\n const intrinsicHeight = ${height};\n let app = null;\n let openAiBridge = null;\n let toolInput = {};\n let openUrl = \"\";\n let startedFor = \"\";\n let appFrame = null;\n let appFrameReady = false;\n let appFrameReadyTimer = null;\n let appFrameLoadTimer = null;\n let lastFrameSrc = \"\";\n\n function esc(value) {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n }\n\n function parseJson(value, fallback) {\n if (value && typeof value === \"object\") return value;\n if (typeof value !== \"string\" || !value.trim()) return fallback;\n try { return JSON.parse(value); } catch { return fallback; }\n }\n\n function objectValue(value) {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value\n : {};\n }\n\n function parseToolResult(params) {\n if (!params) return {};\n if (params.result && typeof params.result === \"object\") {\n return parseToolResult(params.result);\n }\n if (params.toolResult && typeof params.toolResult === \"object\") {\n return parseToolResult(params.toolResult);\n }\n if (params.structuredContent && typeof params.structuredContent === \"object\") {\n return params.structuredContent;\n }\n const parts = Array.isArray(params.content) ? params.content : [];\n const textPart = parts.find((part) => part && part.type === \"text\" && typeof part.text === \"string\");\n return parseJson(textPart ? textPart.text : \"\", {});\n }\n\n function openLinkFrom(params, data) {\n const openLink = params && params._meta && params._meta[\"agent-native/openLink\"];\n const metaUrl = openLink && typeof openLink === \"object\" && typeof openLink.webUrl === \"string\"\n ? openLink.webUrl\n : \"\";\n return metaUrl || data.url || data.deepLink || data.openUrl || \"\";\n }\n\n function hostState() {\n if (openAiBridge) {\n return {\n context: {\n displayMode: openAiBridge.displayMode,\n availableDisplayModes: typeof openAiBridge.requestDisplayMode === \"function\"\n ? [\"inline\", \"fullscreen\", \"pip\"]\n : [],\n maxHeight: openAiBridge.maxHeight,\n locale: openAiBridge.locale,\n theme: openAiBridge.theme,\n view: openAiBridge.view\n },\n capabilities: { openai: true },\n version: openAiBridge.userAgent\n };\n }\n return {\n context: app && app.getHostContext ? app.getHostContext() : undefined,\n capabilities: app && app.getHostCapabilities ? app.getHostCapabilities() : undefined,\n version: app && app.getHostVersion ? app.getHostVersion() : undefined\n };\n }\n\n function sendToAppFrame(message) {\n if (!appFrame || !appFrame.contentWindow) return;\n try { appFrame.contentWindow.postMessage(message, \"*\"); } catch {}\n }\n\n function sendHostContext() {\n sendToAppFrame({ type: \"agentNative.mcpHostContext\", data: hostState() });\n }\n\n function sendFrameReadyMessages(frame) {\n const originPayload = { type: \"agentNative.frameOrigin\", origin: window.location.origin };\n [0, 200, 500, 1500].forEach((delay) => {\n setTimeout(() => {\n try { frame.contentWindow && frame.contentWindow.postMessage(originPayload, \"*\"); } catch {}\n sendHostContext();\n }, delay);\n });\n }\n\n function withChatBridgeParam(value) {\n if (typeof value !== \"string\" || !value) return value;\n try {\n const base = \"http://agent-native.invalid\";\n const url = value.startsWith(\"/\") ? new URL(value, base) : new URL(value);\n url.searchParams.set(chatBridgeParam, \"1\");\n return value.startsWith(\"/\")\n ? url.pathname + url.search + url.hash\n : url.toString();\n } catch {\n return value;\n }\n }\n\n function wantsEmbed() {\n if (toolInput.embed === false || toolInput.embed === \"false\") return false;\n if (embedByDefault) return true;\n return toolInput.embed === true || toolInput.embed === \"true\";\n }\n\n function supportedDisplayMode(mode) {\n if (openAiBridge && typeof openAiBridge.requestDisplayMode === \"function\") {\n return mode === \"inline\" || mode === \"fullscreen\" || mode === \"pip\";\n }\n const modes = hostState().context && hostState().context.availableDisplayModes;\n return Array.isArray(modes) && modes.includes(mode);\n }\n\n async function requestHostDisplayMode(mode) {\n let result;\n if (openAiBridge && typeof openAiBridge.requestDisplayMode === \"function\") {\n result = await openAiBridge.requestDisplayMode({ mode });\n } else {\n if (!app || typeof app.requestDisplayMode !== \"function\") {\n throw new Error(\"Display mode changes are not available in this host.\");\n }\n result = await app.requestDisplayMode({ mode });\n }\n updateDisplayButton();\n sendHostContext();\n return result;\n }\n\n function updateDisplayButton() {\n const context = hostState().context || {};\n const nextMode = context.displayMode === \"fullscreen\" ? \"inline\" : \"fullscreen\";\n const supported = supportedDisplayMode(nextMode);\n displayButton.hidden = !supported;\n displayButton.disabled = !supported;\n displayButton.textContent = nextMode === \"fullscreen\" ? \"Fullscreen\" : \"Inline\";\n displayButton.onclick = () => {\n if (!supportedDisplayMode(nextMode)) return;\n void requestHostDisplayMode(nextMode).catch((err) => {\n console.warn(\"[agent-native] MCP host rejected display mode request\", err);\n });\n };\n }\n\n function setMessage(message) {\n stage.innerHTML = '<div class=\"message\">' + esc(message) + '</div>';\n }\n\n function clearFrameReadyTimer() {\n if (!appFrameReadyTimer) return;\n clearTimeout(appFrameReadyTimer);\n appFrameReadyTimer = null;\n }\n\n function clearFrameLoadTimer() {\n if (!appFrameLoadTimer) return;\n clearTimeout(appFrameLoadTimer);\n appFrameLoadTimer = null;\n }\n\n function startFrameReadyTimer(frame) {\n clearFrameReadyTimer();\n appFrameReadyTimer = setTimeout(() => {\n if (!appFrameReady && appFrame === frame) renderFrameFallback();\n }, 7000);\n }\n\n function renderFrameFallback() {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n stage.innerHTML =\n '<div class=\"fallback\">' +\n '<div class=\"fallback-title\">Open this app in its own tab</div>' +\n '<div class=\"fallback-copy\">This chat host did not allow the embedded app frame to load inline. You can still open the same app route through the host or use the URL below.</div>' +\n '<div class=\"fallback-actions\">' +\n '<button type=\"button\" data-fallback-open>Open app</button>' +\n '<button type=\"button\" data-fallback-retry>Try inline again</button>' +\n '</div>' +\n (openUrl ? '<a class=\"fallback-url\" href=\"' + esc(openUrl) + '\" target=\"_blank\" rel=\"noreferrer\">' + esc(openUrl) + '</a>' : '') +\n '</div>';\n const fallbackOpen = stage.querySelector(\"[data-fallback-open]\");\n const fallbackRetry = stage.querySelector(\"[data-fallback-retry]\");\n if (fallbackOpen) {\n fallbackOpen.disabled = !openUrl;\n fallbackOpen.onclick = () => {\n if (openUrl) void openFallbackExternal();\n };\n }\n if (fallbackRetry) {\n fallbackRetry.disabled = !lastFrameSrc;\n fallbackRetry.onclick = () => {\n if (lastFrameSrc) renderFrame(lastFrameSrc);\n };\n }\n }\n\n async function openFallbackExternal() {\n let url = withChatBridgeParam(openUrl);\n try {\n const result = await callEmbedSessionTool({\n url,\n chrome: typeof toolInput.chrome === \"string\" ? toolInput.chrome : \"full\"\n });\n const data = parseToolResult(result);\n if (typeof data.startUrl === \"string\" && data.startUrl) {\n url = data.startUrl;\n }\n } catch (err) {\n console.warn(\"[agent-native] MCP fallback could not mint a fresh app session\", err);\n }\n await openHostLink({ url });\n }\n\n function renderFrame(src) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n const frame = document.createElement(\"iframe\");\n frame.title = body.dataset.iframeTitle || \"Agent Native app\";\n frame.src = src;\n frame.allow = \"clipboard-read; clipboard-write\";\n appFrame = frame;\n appFrameReady = false;\n lastFrameSrc = src;\n frame.addEventListener(\"load\", () => {\n if (appFrame !== frame) return;\n clearFrameLoadTimer();\n sendFrameReadyMessages(frame);\n startFrameReadyTimer(frame);\n });\n stage.replaceChildren(frame);\n notifyHostHeight();\n appFrameLoadTimer = setTimeout(() => {\n if (!appFrameReady && appFrame === frame) renderFrameFallback();\n }, 30000);\n }\n\n function shouldSelfNavigateToApp() {\n const mode = typeof toolInput.embedMode === \"string\"\n ? toolInput.embedMode\n : typeof toolInput.renderMode === \"string\"\n ? toolInput.renderMode\n : \"\";\n if (mode === \"iframe\" || mode === \"nested\") return false;\n if (toolInput.nested === true || toolInput.frame === \"iframe\") return false;\n return true;\n }\n\n function navigateToAppFrame(src) {\n clearFrameReadyTimer();\n clearFrameLoadTimer();\n appFrame = null;\n lastFrameSrc = src;\n setMessage(\"Opening app\");\n try {\n window.location.replace(src);\n } catch (err) {\n console.warn(\"[agent-native] MCP app self-navigation failed\", err);\n renderFrameFallback();\n }\n }\n\n async function updateHostModelContext(data) {\n const params = {};\n if (Array.isArray(data && data.content)) params.content = data.content;\n if (data && data.structuredContent && typeof data.structuredContent === \"object\") {\n params.structuredContent = data.structuredContent;\n }\n if (openAiBridge && typeof openAiBridge.setWidgetState === \"function\") {\n openAiBridge.setWidgetState({\n ...objectValue(openAiBridge.widgetState),\n agentNativeModelContext: params\n });\n return { ok: true };\n }\n if (!app || typeof app.updateModelContext !== \"function\") return { ok: false };\n await app.updateModelContext(params);\n return { ok: true };\n }\n\n async function openHostLink(data) {\n const url = typeof (data && data.url) === \"string\" ? data.url : \"\";\n if (!url) return { isError: true };\n if (openAiBridge && typeof openAiBridge.openExternal === \"function\") {\n return await openAiBridge.openExternal({ href: url, redirectUrl: false });\n }\n if (app && typeof app.openLink === \"function\") {\n return await app.openLink({ url });\n }\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n return { ok: true };\n }\n\n function notifyHostHeight() {\n if (!openAiBridge || typeof openAiBridge.notifyIntrinsicHeight !== \"function\") {\n return;\n }\n try {\n openAiBridge.notifyIntrinsicHeight({ height: intrinsicHeight });\n } catch (err) {\n console.warn(\"[agent-native] ChatGPT rejected intrinsic height update\", err);\n }\n }\n\n function respondToAppFrame(requestId, work) {\n if (!requestId) return;\n Promise.resolve(work)\n .then((result) => {\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: { requestId, ok: true, result }\n });\n })\n .catch((err) => {\n sendToAppFrame({\n type: \"agentNative.mcpHost.response\",\n data: {\n requestId,\n ok: false,\n error: err && err.message ? err.message : String(err)\n }\n });\n });\n }\n\n async function sendHostChat(chat) {\n if (!chat || chat.submit === false) return;\n const message = typeof chat.message === \"string\" ? chat.message : \"\";\n if (!message.trim()) return;\n const context = typeof chat.context === \"string\" ? chat.context : \"\";\n if (context.trim()) {\n try {\n if (openAiBridge && typeof openAiBridge.setWidgetState === \"function\") {\n openAiBridge.setWidgetState({\n ...objectValue(openAiBridge.widgetState),\n agentNativeChatContext: context\n });\n } else if (app && typeof app.updateModelContext === \"function\") {\n await app.updateModelContext({\n content: [{ type: \"text\", text: context }]\n });\n }\n } catch (err) {\n console.warn(\"[agent-native] MCP host rejected model context update\", err);\n }\n }\n try {\n if (openAiBridge && typeof openAiBridge.sendFollowUpMessage === \"function\") {\n await openAiBridge.sendFollowUpMessage({\n prompt: context.trim() ? context.trim() + \"\\\\n\\\\n\" + message : message,\n scrollToBottom: true\n });\n return;\n }\n if (!app || typeof app.sendMessage !== \"function\") return;\n const result = await app.sendMessage({\n role: \"user\",\n content: [{ type: \"text\", text: message }]\n });\n if (result && result.isError) {\n console.warn(\"[agent-native] MCP host rejected chat message\", result);\n }\n } catch (err) {\n console.warn(\"[agent-native] MCP host chat bridge failed\", err);\n }\n }\n\n window.addEventListener(\"message\", (event) => {\n if (!appFrame || event.source !== appFrame.contentWindow) return;\n if (!event.data) return;\n const data = event.data.data || {};\n if (event.data.type === \"agentNative.embeddedAppReady\") {\n appFrameReady = true;\n clearFrameLoadTimer();\n clearFrameReadyTimer();\n return;\n }\n if (event.data.type === \"agentNative.submitChat\") {\n void sendHostChat(data);\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.updateModelContext\") {\n respondToAppFrame(data.requestId, updateHostModelContext(data));\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.openLink\") {\n respondToAppFrame(data.requestId, openHostLink(data));\n return;\n }\n if (event.data.type === \"agentNative.mcpHost.requestDisplayMode\") {\n respondToAppFrame(data.requestId, requestHostDisplayMode(data.mode));\n }\n });\n\n async function launchEmbed() {\n if (!openUrl) {\n setMessage(\"Open link was not available.\");\n return;\n }\n if (!wantsEmbed()) {\n setMessage(\"Ready to open.\");\n return;\n }\n if (startedFor === openUrl) return;\n startedFor = openUrl;\n setMessage(\"Loading app\");\n try {\n const selfNavigate = shouldSelfNavigateToApp();\n const embedUrl = withChatBridgeParam(openUrl);\n const result = await callEmbedSessionTool({\n url: embedUrl,\n chrome: typeof toolInput.chrome === \"string\" ? toolInput.chrome : \"full\"\n });\n const data = parseToolResult(result);\n if (typeof data.startUrl !== \"string\" || !data.startUrl) {\n startedFor = \"\";\n setMessage(data.error || \"This app can be opened, but not embedded from this MCP server.\");\n return;\n }\n if (selfNavigate) {\n navigateToAppFrame(data.startUrl);\n } else {\n renderFrame(data.startUrl);\n }\n } catch (err) {\n startedFor = \"\";\n setMessage(err && err.message ? err.message : \"Could not launch embedded app.\");\n }\n }\n\n async function callEmbedSessionTool(args) {\n if (openAiBridge && typeof openAiBridge.callTool === \"function\") {\n return await openAiBridge.callTool(startTool, args);\n }\n if (!app || typeof app.callServerTool !== \"function\") {\n throw new Error(\"Host tool calls are not available.\");\n }\n return await app.callServerTool({ name: startTool, arguments: args });\n }\n\n function updateHostOpenInAppUrl() {\n if (!openAiBridge || !openUrl || typeof openAiBridge.setOpenInAppUrl !== \"function\") {\n return;\n }\n try {\n openAiBridge.setOpenInAppUrl({ href: openUrl });\n } catch (err) {\n console.warn(\"[agent-native] ChatGPT rejected open-in-app URL\", err);\n }\n }\n\n function updateOpenButton() {\n openButton.disabled = !openUrl;\n openButton.onclick = () => {\n if (openUrl) void openHostLink({ url: openUrl });\n };\n updateHostOpenInAppUrl();\n }\n\n function updateTitle(data) {\n const label = data.label || data.app || data.view || body.dataset.appTitle || \"App\";\n titleEl.textContent = String(label);\n }\n\n function readOpenAiBridge() {\n return window.openai && typeof window.openai === \"object\"\n ? window.openai\n : null;\n }\n\n function openAiToolResultParams(bridge) {\n const params = {};\n if (bridge && bridge.toolOutput !== undefined) {\n if (bridge.toolOutput && typeof bridge.toolOutput === \"object\") {\n params.structuredContent = bridge.toolOutput;\n } else {\n params.content = [{ type: \"text\", text: String(bridge.toolOutput) }];\n }\n }\n if (bridge && bridge.toolResponseMetadata && typeof bridge.toolResponseMetadata === \"object\") {\n params._meta = bridge.toolResponseMetadata;\n }\n return params;\n }\n\n function syncOpenAiBridge(bridge) {\n if (!bridge) return false;\n openAiBridge = bridge;\n toolInput = objectValue(bridge.toolInput);\n const params = openAiToolResultParams(bridge);\n const data = parseToolResult(params);\n openUrl = openLinkFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n updateDisplayButton();\n notifyHostHeight();\n sendHostContext();\n if (openUrl) {\n void launchEmbed();\n } else if (!appFrame) {\n setMessage(\"Waiting for app result\");\n }\n return true;\n }\n\n function waitForOpenAiBridge() {\n const existing = readOpenAiBridge();\n if (existing) return Promise.resolve(existing);\n return new Promise((resolve) => {\n let settled = false;\n const finish = (bridge) => {\n if (settled) return;\n settled = true;\n window.removeEventListener(\"openai:set_globals\", onGlobals);\n clearTimeout(timer);\n resolve(bridge || readOpenAiBridge());\n };\n const onGlobals = () => finish(readOpenAiBridge());\n const timer = setTimeout(() => finish(null), 200);\n window.addEventListener(\"openai:set_globals\", onGlobals, { passive: true });\n });\n }\n\n window.addEventListener(\"openai:set_globals\", () => {\n const bridge = readOpenAiBridge();\n if (bridge && (!appFrame || openAiBridge)) syncOpenAiBridge(bridge);\n }, { passive: true });\n\n async function startMcpAppsBridge() {\n const { App } = await import(\"${MCP_APP_IMPORT}\");\n app = new App({ name: \"Agent Native Embed\", version: \"1.0.0\" }, {});\n app.ontoolinput = (params) => {\n toolInput = params.arguments || {};\n };\n app.ontoolresult = (params) => {\n const data = parseToolResult(params);\n openUrl = openLinkFrom(params, data);\n updateTitle(data);\n updateOpenButton();\n void launchEmbed();\n };\n app.onhostcontextchanged = () => {\n updateDisplayButton();\n sendHostContext();\n };\n await app.connect();\n updateDisplayButton();\n sendHostContext();\n }\n\n const initialOpenAiBridge = await waitForOpenAiBridge();\n if (!syncOpenAiBridge(initialOpenAiBridge)) {\n await startMcpAppsBridge();\n }\n </script>\n</body>\n</html>`,\n csp: {\n connectDomains: [\"https://esm.sh\"],\n resourceDomains: [\n \"https://esm.sh\",\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.frameDomains ?? []),\n ],\n frameDomains: [\n MCP_APP_REQUEST_ORIGIN_CSP_SOURCE,\n ...(options.frameDomains ?? []),\n ],\n },\n prefersBorder: false,\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"open-route.d.ts","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"AA0DA,MAAM,WAAW,gBAAgB;IAC/B;;yEAEqE;IACrE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE;QACzB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACjC;AA6DD,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,gBAAqB,2FAgIpE"}
1
+ {"version":3,"file":"open-route.d.ts","sourceRoot":"","sources":["../../src/server/open-route.ts"],"names":[],"mappings":"AA4DA,MAAM,WAAW,gBAAgB;IAC/B;;yEAEqE;IACrE,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE;QACzB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACjC;AA6DD,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,gBAAqB,2FAoIpE"}
@@ -2,7 +2,7 @@ import { defineEventHandler, getMethod } from "h3";
2
2
  import { getSession, getConfiguredLoginHtml } from "./auth.js";
3
3
  import { appStatePut, appStateGet } from "../application-state/store.js";
4
4
  import { AGENT_SIDEBAR_QUERY_PARAM, withCollapsedAgentSidebarParam, } from "../shared/agent-sidebar-url.js";
5
- import { EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, } from "../shared/embed-auth.js";
5
+ import { EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
6
6
  import { getConfiguredAppBasePath } from "./app-base-path.js";
7
7
  /** Query keys that are route control, not navigation payload. */
8
8
  const RESERVED = new Set([
@@ -12,6 +12,7 @@ const RESERVED = new Set([
12
12
  "compose",
13
13
  EMBED_MODE_QUERY_PARAM,
14
14
  EMBED_TOKEN_QUERY_PARAM,
15
+ MCP_APP_CHAT_BRIDGE_QUERY_PARAM,
15
16
  AGENT_SIDEBAR_QUERY_PARAM,
16
17
  ]);
17
18
  // Control-char guard (NUL..US + DEL). Defined via codepoints so the source
@@ -196,7 +197,11 @@ export function createOpenRouteHandler(options = {}) {
196
197
  }
197
198
  target = appendSearchParams(target, filters);
198
199
  const embedParams = new URLSearchParams();
199
- for (const key of [EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM]) {
200
+ for (const key of [
201
+ EMBED_MODE_QUERY_PARAM,
202
+ EMBED_TOKEN_QUERY_PARAM,
203
+ MCP_APP_CHAT_BRIDGE_QUERY_PARAM,
204
+ ]) {
200
205
  const value = search.get(key);
201
206
  if (value)
202
207
  embedParams.set(key, value);