@frontmcp/uipack 1.9.2 → 1.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/adapters/index.js CHANGED
@@ -564,6 +564,8 @@ ${json}
564
564
  }
565
565
 
566
566
  // libs/uipack/src/bridge-runtime/iife-generator.ts
567
+ var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
568
+ var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
567
569
  function generateBridgeIIFE(options = {}) {
568
570
  const { debug = false, trustedOrigins = [], minify = false } = options;
569
571
  const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
@@ -611,6 +613,7 @@ function generateBridgeIIFE(options = {}) {
611
613
  parts.push("bridge.initialize().then(function() {");
612
614
  parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
613
615
  parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
616
+ parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
614
617
  parts.push("}).catch(function(err) {");
615
618
  parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
616
619
  parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
@@ -944,6 +947,9 @@ var OpenAIAdapter = {
944
947
  return Promise.resolve();
945
948
  },
946
949
  requestDisplayMode: function(context, mode) {
950
+ if (window.openai && typeof window.openai.requestDisplayMode === 'function') {
951
+ return Promise.resolve(window.openai.requestDisplayMode({ mode: mode }));
952
+ }
947
953
  return Promise.resolve();
948
954
  },
949
955
  setSize: function(context, size) {
@@ -981,6 +987,7 @@ var ExtAppsAdapter = {
981
987
  hostCapabilities: {},
982
988
  handshakeSettled: false,
983
989
  pendingSize: null,
990
+ modelContext: null,
984
991
  canHandle: function() {
985
992
  if (typeof window === 'undefined') return false;
986
993
  if (window.parent === window) return false;
@@ -1059,7 +1066,18 @@ var ExtAppsAdapter = {
1059
1066
 
1060
1067
  if ('method' in data && !('id' in data)) {
1061
1068
  this.handleNotification(context, data);
1069
+ return;
1062
1070
  }
1071
+
1072
+ if ('method' in data && 'id' in data) {
1073
+ this.handleRequest(context, data, event.origin);
1074
+ }
1075
+ },
1076
+ handleRequest: function(context, request, origin) {
1077
+ // ui/resource-teardown: the host is about to unmount the view; reply once listeners have cleaned up.
1078
+ if (request.method !== 'ui/resource-teardown') return;
1079
+ window.dispatchEvent(new CustomEvent('bridge:teardown', { detail: {} }));
1080
+ window.parent.postMessage({ jsonrpc: '2.0', id: request.id, result: {} }, origin);
1063
1081
  },
1064
1082
  handleNotification: function(context, notification) {
1065
1083
  var params = notification.params || {};
@@ -1084,6 +1102,7 @@ var ExtAppsAdapter = {
1084
1102
  Object.assign(context.hostContext, params);
1085
1103
  context.notifyContextChange(params);
1086
1104
  break;
1105
+ case 'ui/notifications/tool-cancelled':
1087
1106
  case 'ui/notifications/cancelled':
1088
1107
  window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
1089
1108
  break;
@@ -1148,7 +1167,7 @@ var ExtAppsAdapter = {
1148
1167
  var id = ++this.requestId;
1149
1168
  var params = {
1150
1169
  appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
1151
- appCapabilities: { tools: { listChanged: false } },
1170
+ appCapabilities: { tools: { listChanged: false }, availableDisplayModes: ${JSON.stringify(MCP_APPS_DISPLAY_MODES)} },
1152
1171
  protocolVersion: '2024-11-05'
1153
1172
  };
1154
1173
 
@@ -1216,20 +1235,25 @@ var ExtAppsAdapter = {
1216
1235
  return Promise.reject(new Error('Server tool proxy not supported'));
1217
1236
  }
1218
1237
  // Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
1219
- return this.sendRequest('tools/call', { name: name, arguments: args || {} });
1238
+ return this.sendRequest('tools/call', { name: name, arguments: args || {}, _meta: { '${WIDGET_CALL_META_KEY}': true } });
1220
1239
  },
1221
1240
  sendMessage: function(context, content) {
1222
1241
  return this.sendRequest('ui/message', { content: content });
1223
1242
  },
1224
1243
  openLink: function(context, url) {
1225
- if (!this.hostCapabilities.openLink) {
1244
+ if (!this.hostCapabilities.openLinks && !this.hostCapabilities.openLink) {
1226
1245
  window.open(url, '_blank', 'noopener,noreferrer');
1227
1246
  return Promise.resolve();
1228
1247
  }
1229
- return this.sendRequest('ui/openLink', { url: url });
1248
+ return this.sendRequest('ui/open-link', { url: url });
1230
1249
  },
1231
1250
  requestDisplayMode: function(context, mode) {
1232
- return this.sendRequest('ui/setDisplayMode', { mode: mode });
1251
+ // Ask only for a mode the host offers; it answers with the mode it actually set.
1252
+ var offered = context.hostContext.availableDisplayModes;
1253
+ if (!Array.isArray(offered) || offered.indexOf(mode) === -1) {
1254
+ return Promise.reject(new Error('Display mode "' + mode + '" is not available on this host'));
1255
+ }
1256
+ return this.sendRequest('ui/request-display-mode', { mode: mode });
1233
1257
  },
1234
1258
  setSize: function(context, size) {
1235
1259
  // Standard ext-apps sizing: the view tells the host its dimensions with the
@@ -1250,14 +1274,22 @@ var ExtAppsAdapter = {
1250
1274
  return this.sendNotification('ui/notifications/size-changed', params);
1251
1275
  },
1252
1276
  requestClose: function(context) {
1253
- return this.sendRequest('ui/close', {});
1277
+ // The host decides whether to tear the view down; it answers with ui/resource-teardown if it does.
1278
+ return this.sendNotification('ui/notifications/request-teardown', {});
1254
1279
  },
1255
1280
  // Extended ext-apps methods (full specification)
1256
1281
  updateModelContext: function(context, data, merge) {
1257
- if (!this.hostCapabilities.modelContextUpdate) {
1282
+ if (!this.hostCapabilities.updateModelContext && !this.hostCapabilities.modelContextUpdate) {
1258
1283
  return Promise.reject(new Error('Model context update not supported'));
1259
1284
  }
1260
- return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
1285
+ // The host keeps only the latest update, so an object merges into the last one here.
1286
+ var isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
1287
+ var modelContext = isObject && merge !== false ? Object.assign({}, this.modelContext, data) : data;
1288
+ this.modelContext = isObject ? modelContext : null;
1289
+ var text = typeof modelContext === 'string' ? modelContext : JSON.stringify(modelContext);
1290
+ var params = { content: [{ type: 'text', text: text }] };
1291
+ if (isObject) params.structuredContent = modelContext;
1292
+ return this.sendRequest('ui/update-model-context', params);
1261
1293
  },
1262
1294
  log: function(context, level, message, data) {
1263
1295
  if (!this.hostCapabilities.logging) {
@@ -1266,7 +1298,10 @@ var ExtAppsAdapter = {
1266
1298
  logFn('[Widget] ' + message, data);
1267
1299
  return Promise.resolve();
1268
1300
  }
1269
- return this.sendRequest('ui/log', { level: level, message: message, data: data });
1301
+ return this.sendNotification('notifications/message', {
1302
+ level: level === 'warn' ? 'warning' : level,
1303
+ data: data === undefined ? message : { message: message, data: data }
1304
+ });
1270
1305
  },
1271
1306
  registerTool: function(context, name, description, inputSchema) {
1272
1307
  if (!this.hostCapabilities.widgetTools) {
@@ -1663,8 +1698,10 @@ FrontMcpBridge.prototype.openLink = function(url) {
1663
1698
  FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1664
1699
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1665
1700
  var self = this;
1666
- return this._adapter.requestDisplayMode(this._context, mode).then(function() {
1667
- self._context.hostContext.displayMode = mode;
1701
+ return this._adapter.requestDisplayMode(this._context, mode).then(function(result) {
1702
+ // The host may set another mode than the one asked for, e.g. when it refuses the change.
1703
+ var setMode = result && result.mode;
1704
+ if (${JSON.stringify(MCP_APPS_DISPLAY_MODES)}.indexOf(setMode) !== -1) self._context.hostContext.displayMode = setMode;
1668
1705
  });
1669
1706
  };
1670
1707
 
@@ -2039,7 +2076,7 @@ function hasSizing(sizing) {
2039
2076
  return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
2040
2077
  }
2041
2078
  function buildDataInjectionScript(options) {
2042
- const { toolName, input, output, structuredContent, sizing } = options;
2079
+ const { toolName, input, output, structuredContent, sizing, displayMode } = options;
2043
2080
  const lines = [
2044
2081
  `window.__mcpAppsEnabled = true;`,
2045
2082
  `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
@@ -2050,6 +2087,9 @@ function buildDataInjectionScript(options) {
2050
2087
  if (hasSizing(sizing)) {
2051
2088
  lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
2052
2089
  }
2090
+ if (displayMode && displayMode !== "inline") {
2091
+ lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`);
2092
+ }
2053
2093
  return `<script>
2054
2094
  ${lines.join("\n")}
2055
2095
  </script>`;
@@ -2141,7 +2181,8 @@ function resolveDataInjectionScript(args) {
2141
2181
  input: args.input,
2142
2182
  output: args.output,
2143
2183
  structuredContent: args.structuredContent,
2144
- sizing: args.sizing
2184
+ sizing: args.sizing,
2185
+ displayMode: args.displayMode
2145
2186
  });
2146
2187
  }
2147
2188
  function buildShell(content, config) {
@@ -2154,7 +2195,8 @@ function buildShell(content, config) {
2154
2195
  structuredContent,
2155
2196
  includeBridge = true,
2156
2197
  title,
2157
- sizing
2198
+ sizing,
2199
+ displayMode
2158
2200
  } = config;
2159
2201
  const { customShell, dataInjection } = config;
2160
2202
  const dataScript = resolveDataInjectionScript({
@@ -2163,7 +2205,8 @@ function buildShell(content, config) {
2163
2205
  input,
2164
2206
  output,
2165
2207
  structuredContent,
2166
- sizing
2208
+ sizing,
2209
+ displayMode
2167
2210
  });
2168
2211
  if (!withShell) {
2169
2212
  const html3 = `${dataScript}
@@ -2184,6 +2227,7 @@ ${content}`;
2184
2227
  includeBridge,
2185
2228
  title,
2186
2229
  sizing,
2230
+ displayMode,
2187
2231
  dataInjection
2188
2232
  });
2189
2233
  }
@@ -2239,7 +2283,8 @@ function buildCustomShell(content, customShell, ctx) {
2239
2283
  input: ctx.input,
2240
2284
  output: ctx.output,
2241
2285
  structuredContent: ctx.structuredContent,
2242
- sizing: ctx.sizing
2286
+ sizing: ctx.sizing,
2287
+ displayMode: ctx.displayMode
2243
2288
  });
2244
2289
  const sizingStyle = buildSizingStyleTag(ctx.sizing);
2245
2290
  const dataWithSizing = sizingStyle ? `${dataScript}
@@ -3078,7 +3123,7 @@ function noticeStringResult(toolName, logger) {
3078
3123
  if (noticedStringResultTools.has(toolName)) return;
3079
3124
  noticedStringResultTools.add(toolName);
3080
3125
  logger.warn(
3081
- `[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup. FrontMCP 1.9 HTML-escapes plain string results by default, so it is shown as text. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(). Set ui.escapeStringResults: false to render plain strings as markup, or true to silence this notice. See ${STRING_RESULT_DOCS}`
3126
+ `[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup. Since 1.9.2, FrontMCP HTML-escapes plain string results by default, so it is shown as text. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(). Set ui.escapeStringResults: false to render plain strings as markup, or true to silence this notice. See ${STRING_RESULT_DOCS}`
3082
3127
  );
3083
3128
  }
3084
3129
  function renderUnwrappedResult(rawResult, options) {
@@ -3137,6 +3182,7 @@ function renderToolTemplate(options) {
3137
3182
  includeBridge: true,
3138
3183
  resolver,
3139
3184
  sizing,
3185
+ displayMode: options.displayMode,
3140
3186
  csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
3141
3187
  };
3142
3188
  let html2;
@@ -6,7 +6,7 @@
6
6
  * @packageDocumentation
7
7
  */
8
8
  import type { ImportResolver } from '../resolver/types';
9
- import type { WidgetSizing } from '../shell/types';
9
+ import type { ShellConfig, WidgetSizing } from '../shell/types';
10
10
  /**
11
11
  * Options for rendering a tool template.
12
12
  */
@@ -36,6 +36,8 @@ export interface RenderToolTemplateOptions {
36
36
  * shell, and is mirrored onto the returned `meta` as `ui/preferredHeight` etc.
37
37
  */
38
38
  sizing?: WidgetSizing;
39
+ /** `ui.displayMode`: the display mode the page asks the host for once its bridge connects. */
40
+ displayMode?: ShellConfig['displayMode'];
39
41
  /**
40
42
  * Extra origins the widget may load from (`resourceDomains`) or call (`connectDomains`), from the
41
43
  * tool's `ui.csp`. Merged into the page's own Content-Security-Policy on every template path.
@@ -46,7 +48,7 @@ export interface RenderToolTemplateOptions {
46
48
  };
47
49
  /**
48
50
  * HTML-escape a plain string returned by a template function; results built with
49
- * `html` / `trustedHtml` stay markup. Unset is the 1.9 default: strings are escaped, and a
51
+ * `html` / `trustedHtml` stay markup. Unset is the default since 1.9.2: strings are escaped, and a
50
52
  * one-time notice per tool is logged when one looked like markup. `true` escapes without the
51
53
  * notice; `false` renders strings that look like HTML as markup.
52
54
  */
@@ -1 +1 @@
1
- {"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/adapters/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQnD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,GAAG,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,yEAAyE;IACzE,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAiGD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,EACjB,MAAM,GAAE;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAY,GACpD,OAAO,CAIT;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAqH/F"}
1
+ {"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/adapters/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAIxD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAQhE;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;IACzC;;;OAGG;IACH,GAAG,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,yEAAyE;IACzE,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;CAC9C;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAiGD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,EACjB,MAAM,GAAE;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAY,GACpD,OAAO,CAIT;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAsH/F"}
@@ -6,6 +6,13 @@
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
+ /**
10
+ * The `_meta` key the bridge sets on a widget's own `tools/call`, so the server answers with the
11
+ * data and does not render the tool's page again into a widget that is already on screen.
12
+ */
13
+ export declare const WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
14
+ /** The display modes a widget supports, declared to an MCP Apps host in `ui/initialize`. */
15
+ export declare const MCP_APPS_DISPLAY_MODES: readonly ["inline", "fullscreen", "pip"];
9
16
  /**
10
17
  * Options for generating the bridge IIFE.
11
18
  */
@@ -1 +1 @@
1
- {"version":3,"file":"iife-generator.d.ts","sourceRoot":"","sources":["../../src/bridge-runtime/iife-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC;IACvE,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,CA8G7E;AA8xCD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,EACvD,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAM,GACnD,MAAM,CAYR;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAuB,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;CAK9B,CAAC"}
1
+ {"version":3,"file":"iife-generator.d.ts","sourceRoot":"","sources":["../../src/bridge-runtime/iife-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,wBAAwB,CAAC;AAE1D,4FAA4F;AAC5F,eAAO,MAAM,sBAAsB,0CAA2C,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,QAAQ,CAAC,EAAE,CAAC,QAAQ,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC;IACvE,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,oBAAyB,GAAG,MAAM,CAgH7E;AAg0CD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,EACvD,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAM,GACnD,MAAM,CAYR;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAuB,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;CAK9B,CAAC"}
@@ -6,5 +6,5 @@
6
6
  *
7
7
  * @packageDocumentation
8
8
  */
9
- export { generateBridgeIIFE, generatePlatformBundle, UNIVERSAL_BRIDGE_SCRIPT, BRIDGE_SCRIPT_TAGS, type IIFEGeneratorOptions, } from './iife-generator';
9
+ export { generateBridgeIIFE, generatePlatformBundle, UNIVERSAL_BRIDGE_SCRIPT, BRIDGE_SCRIPT_TAGS, WIDGET_CALL_META_KEY, MCP_APPS_DISPLAY_MODES, type IIFEGeneratorOptions, } from './iife-generator';
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge-runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bridge-runtime/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC"}
@@ -21,13 +21,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var bridge_runtime_exports = {};
22
22
  __export(bridge_runtime_exports, {
23
23
  BRIDGE_SCRIPT_TAGS: () => BRIDGE_SCRIPT_TAGS,
24
+ MCP_APPS_DISPLAY_MODES: () => MCP_APPS_DISPLAY_MODES,
24
25
  UNIVERSAL_BRIDGE_SCRIPT: () => UNIVERSAL_BRIDGE_SCRIPT,
26
+ WIDGET_CALL_META_KEY: () => WIDGET_CALL_META_KEY,
25
27
  generateBridgeIIFE: () => generateBridgeIIFE,
26
28
  generatePlatformBundle: () => generatePlatformBundle
27
29
  });
28
30
  module.exports = __toCommonJS(bridge_runtime_exports);
29
31
 
30
32
  // libs/uipack/src/bridge-runtime/iife-generator.ts
33
+ var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
34
+ var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
31
35
  function generateBridgeIIFE(options = {}) {
32
36
  const { debug = false, trustedOrigins = [], minify = false } = options;
33
37
  const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
@@ -75,6 +79,7 @@ function generateBridgeIIFE(options = {}) {
75
79
  parts.push("bridge.initialize().then(function() {");
76
80
  parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
77
81
  parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
82
+ parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
78
83
  parts.push("}).catch(function(err) {");
79
84
  parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
80
85
  parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
@@ -408,6 +413,9 @@ var OpenAIAdapter = {
408
413
  return Promise.resolve();
409
414
  },
410
415
  requestDisplayMode: function(context, mode) {
416
+ if (window.openai && typeof window.openai.requestDisplayMode === 'function') {
417
+ return Promise.resolve(window.openai.requestDisplayMode({ mode: mode }));
418
+ }
411
419
  return Promise.resolve();
412
420
  },
413
421
  setSize: function(context, size) {
@@ -445,6 +453,7 @@ var ExtAppsAdapter = {
445
453
  hostCapabilities: {},
446
454
  handshakeSettled: false,
447
455
  pendingSize: null,
456
+ modelContext: null,
448
457
  canHandle: function() {
449
458
  if (typeof window === 'undefined') return false;
450
459
  if (window.parent === window) return false;
@@ -523,8 +532,19 @@ var ExtAppsAdapter = {
523
532
 
524
533
  if ('method' in data && !('id' in data)) {
525
534
  this.handleNotification(context, data);
535
+ return;
536
+ }
537
+
538
+ if ('method' in data && 'id' in data) {
539
+ this.handleRequest(context, data, event.origin);
526
540
  }
527
541
  },
542
+ handleRequest: function(context, request, origin) {
543
+ // ui/resource-teardown: the host is about to unmount the view; reply once listeners have cleaned up.
544
+ if (request.method !== 'ui/resource-teardown') return;
545
+ window.dispatchEvent(new CustomEvent('bridge:teardown', { detail: {} }));
546
+ window.parent.postMessage({ jsonrpc: '2.0', id: request.id, result: {} }, origin);
547
+ },
528
548
  handleNotification: function(context, notification) {
529
549
  var params = notification.params || {};
530
550
  switch (notification.method) {
@@ -548,6 +568,7 @@ var ExtAppsAdapter = {
548
568
  Object.assign(context.hostContext, params);
549
569
  context.notifyContextChange(params);
550
570
  break;
571
+ case 'ui/notifications/tool-cancelled':
551
572
  case 'ui/notifications/cancelled':
552
573
  window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
553
574
  break;
@@ -612,7 +633,7 @@ var ExtAppsAdapter = {
612
633
  var id = ++this.requestId;
613
634
  var params = {
614
635
  appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
615
- appCapabilities: { tools: { listChanged: false } },
636
+ appCapabilities: { tools: { listChanged: false }, availableDisplayModes: ${JSON.stringify(MCP_APPS_DISPLAY_MODES)} },
616
637
  protocolVersion: '2024-11-05'
617
638
  };
618
639
 
@@ -680,20 +701,25 @@ var ExtAppsAdapter = {
680
701
  return Promise.reject(new Error('Server tool proxy not supported'));
681
702
  }
682
703
  // Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
683
- return this.sendRequest('tools/call', { name: name, arguments: args || {} });
704
+ return this.sendRequest('tools/call', { name: name, arguments: args || {}, _meta: { '${WIDGET_CALL_META_KEY}': true } });
684
705
  },
685
706
  sendMessage: function(context, content) {
686
707
  return this.sendRequest('ui/message', { content: content });
687
708
  },
688
709
  openLink: function(context, url) {
689
- if (!this.hostCapabilities.openLink) {
710
+ if (!this.hostCapabilities.openLinks && !this.hostCapabilities.openLink) {
690
711
  window.open(url, '_blank', 'noopener,noreferrer');
691
712
  return Promise.resolve();
692
713
  }
693
- return this.sendRequest('ui/openLink', { url: url });
714
+ return this.sendRequest('ui/open-link', { url: url });
694
715
  },
695
716
  requestDisplayMode: function(context, mode) {
696
- return this.sendRequest('ui/setDisplayMode', { mode: mode });
717
+ // Ask only for a mode the host offers; it answers with the mode it actually set.
718
+ var offered = context.hostContext.availableDisplayModes;
719
+ if (!Array.isArray(offered) || offered.indexOf(mode) === -1) {
720
+ return Promise.reject(new Error('Display mode "' + mode + '" is not available on this host'));
721
+ }
722
+ return this.sendRequest('ui/request-display-mode', { mode: mode });
697
723
  },
698
724
  setSize: function(context, size) {
699
725
  // Standard ext-apps sizing: the view tells the host its dimensions with the
@@ -714,14 +740,22 @@ var ExtAppsAdapter = {
714
740
  return this.sendNotification('ui/notifications/size-changed', params);
715
741
  },
716
742
  requestClose: function(context) {
717
- return this.sendRequest('ui/close', {});
743
+ // The host decides whether to tear the view down; it answers with ui/resource-teardown if it does.
744
+ return this.sendNotification('ui/notifications/request-teardown', {});
718
745
  },
719
746
  // Extended ext-apps methods (full specification)
720
747
  updateModelContext: function(context, data, merge) {
721
- if (!this.hostCapabilities.modelContextUpdate) {
748
+ if (!this.hostCapabilities.updateModelContext && !this.hostCapabilities.modelContextUpdate) {
722
749
  return Promise.reject(new Error('Model context update not supported'));
723
750
  }
724
- return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
751
+ // The host keeps only the latest update, so an object merges into the last one here.
752
+ var isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
753
+ var modelContext = isObject && merge !== false ? Object.assign({}, this.modelContext, data) : data;
754
+ this.modelContext = isObject ? modelContext : null;
755
+ var text = typeof modelContext === 'string' ? modelContext : JSON.stringify(modelContext);
756
+ var params = { content: [{ type: 'text', text: text }] };
757
+ if (isObject) params.structuredContent = modelContext;
758
+ return this.sendRequest('ui/update-model-context', params);
725
759
  },
726
760
  log: function(context, level, message, data) {
727
761
  if (!this.hostCapabilities.logging) {
@@ -730,7 +764,10 @@ var ExtAppsAdapter = {
730
764
  logFn('[Widget] ' + message, data);
731
765
  return Promise.resolve();
732
766
  }
733
- return this.sendRequest('ui/log', { level: level, message: message, data: data });
767
+ return this.sendNotification('notifications/message', {
768
+ level: level === 'warn' ? 'warning' : level,
769
+ data: data === undefined ? message : { message: message, data: data }
770
+ });
734
771
  },
735
772
  registerTool: function(context, name, description, inputSchema) {
736
773
  if (!this.hostCapabilities.widgetTools) {
@@ -1127,8 +1164,10 @@ FrontMcpBridge.prototype.openLink = function(url) {
1127
1164
  FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1128
1165
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1129
1166
  var self = this;
1130
- return this._adapter.requestDisplayMode(this._context, mode).then(function() {
1131
- self._context.hostContext.displayMode = mode;
1167
+ return this._adapter.requestDisplayMode(this._context, mode).then(function(result) {
1168
+ // The host may set another mode than the one asked for, e.g. when it refuses the change.
1169
+ var setMode = result && result.mode;
1170
+ if (${JSON.stringify(MCP_APPS_DISPLAY_MODES)}.indexOf(setMode) !== -1) self._context.hostContext.displayMode = setMode;
1132
1171
  });
1133
1172
  };
1134
1173
 
@@ -1356,7 +1395,9 @@ var BRIDGE_SCRIPT_TAGS = {
1356
1395
  // Annotate the CommonJS export names for ESM import in node:
1357
1396
  0 && (module.exports = {
1358
1397
  BRIDGE_SCRIPT_TAGS,
1398
+ MCP_APPS_DISPLAY_MODES,
1359
1399
  UNIVERSAL_BRIDGE_SCRIPT,
1400
+ WIDGET_CALL_META_KEY,
1360
1401
  generateBridgeIIFE,
1361
1402
  generatePlatformBundle
1362
1403
  });