@frontmcp/uipack 1.9.2 → 1.9.3

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.
@@ -829,6 +829,8 @@ ${json}
829
829
  }
830
830
 
831
831
  // libs/uipack/src/bridge-runtime/iife-generator.ts
832
+ var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
833
+ var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
832
834
  function generateBridgeIIFE(options = {}) {
833
835
  const { debug = false, trustedOrigins = [], minify = false } = options;
834
836
  const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
@@ -876,6 +878,7 @@ function generateBridgeIIFE(options = {}) {
876
878
  parts.push("bridge.initialize().then(function() {");
877
879
  parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
878
880
  parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
881
+ parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
879
882
  parts.push("}).catch(function(err) {");
880
883
  parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
881
884
  parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
@@ -1209,6 +1212,9 @@ var OpenAIAdapter = {
1209
1212
  return Promise.resolve();
1210
1213
  },
1211
1214
  requestDisplayMode: function(context, mode) {
1215
+ if (window.openai && typeof window.openai.requestDisplayMode === 'function') {
1216
+ return Promise.resolve(window.openai.requestDisplayMode({ mode: mode }));
1217
+ }
1212
1218
  return Promise.resolve();
1213
1219
  },
1214
1220
  setSize: function(context, size) {
@@ -1246,6 +1252,7 @@ var ExtAppsAdapter = {
1246
1252
  hostCapabilities: {},
1247
1253
  handshakeSettled: false,
1248
1254
  pendingSize: null,
1255
+ modelContext: null,
1249
1256
  canHandle: function() {
1250
1257
  if (typeof window === 'undefined') return false;
1251
1258
  if (window.parent === window) return false;
@@ -1324,8 +1331,19 @@ var ExtAppsAdapter = {
1324
1331
 
1325
1332
  if ('method' in data && !('id' in data)) {
1326
1333
  this.handleNotification(context, data);
1334
+ return;
1335
+ }
1336
+
1337
+ if ('method' in data && 'id' in data) {
1338
+ this.handleRequest(context, data, event.origin);
1327
1339
  }
1328
1340
  },
1341
+ handleRequest: function(context, request, origin) {
1342
+ // ui/resource-teardown: the host is about to unmount the view; reply once listeners have cleaned up.
1343
+ if (request.method !== 'ui/resource-teardown') return;
1344
+ window.dispatchEvent(new CustomEvent('bridge:teardown', { detail: {} }));
1345
+ window.parent.postMessage({ jsonrpc: '2.0', id: request.id, result: {} }, origin);
1346
+ },
1329
1347
  handleNotification: function(context, notification) {
1330
1348
  var params = notification.params || {};
1331
1349
  switch (notification.method) {
@@ -1349,6 +1367,7 @@ var ExtAppsAdapter = {
1349
1367
  Object.assign(context.hostContext, params);
1350
1368
  context.notifyContextChange(params);
1351
1369
  break;
1370
+ case 'ui/notifications/tool-cancelled':
1352
1371
  case 'ui/notifications/cancelled':
1353
1372
  window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
1354
1373
  break;
@@ -1413,7 +1432,7 @@ var ExtAppsAdapter = {
1413
1432
  var id = ++this.requestId;
1414
1433
  var params = {
1415
1434
  appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
1416
- appCapabilities: { tools: { listChanged: false } },
1435
+ appCapabilities: { tools: { listChanged: false }, availableDisplayModes: ${JSON.stringify(MCP_APPS_DISPLAY_MODES)} },
1417
1436
  protocolVersion: '2024-11-05'
1418
1437
  };
1419
1438
 
@@ -1481,20 +1500,25 @@ var ExtAppsAdapter = {
1481
1500
  return Promise.reject(new Error('Server tool proxy not supported'));
1482
1501
  }
1483
1502
  // Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
1484
- return this.sendRequest('tools/call', { name: name, arguments: args || {} });
1503
+ return this.sendRequest('tools/call', { name: name, arguments: args || {}, _meta: { '${WIDGET_CALL_META_KEY}': true } });
1485
1504
  },
1486
1505
  sendMessage: function(context, content) {
1487
1506
  return this.sendRequest('ui/message', { content: content });
1488
1507
  },
1489
1508
  openLink: function(context, url) {
1490
- if (!this.hostCapabilities.openLink) {
1509
+ if (!this.hostCapabilities.openLinks && !this.hostCapabilities.openLink) {
1491
1510
  window.open(url, '_blank', 'noopener,noreferrer');
1492
1511
  return Promise.resolve();
1493
1512
  }
1494
- return this.sendRequest('ui/openLink', { url: url });
1513
+ return this.sendRequest('ui/open-link', { url: url });
1495
1514
  },
1496
1515
  requestDisplayMode: function(context, mode) {
1497
- return this.sendRequest('ui/setDisplayMode', { mode: mode });
1516
+ // Ask only for a mode the host offers; it answers with the mode it actually set.
1517
+ var offered = context.hostContext.availableDisplayModes;
1518
+ if (!Array.isArray(offered) || offered.indexOf(mode) === -1) {
1519
+ return Promise.reject(new Error('Display mode "' + mode + '" is not available on this host'));
1520
+ }
1521
+ return this.sendRequest('ui/request-display-mode', { mode: mode });
1498
1522
  },
1499
1523
  setSize: function(context, size) {
1500
1524
  // Standard ext-apps sizing: the view tells the host its dimensions with the
@@ -1515,14 +1539,22 @@ var ExtAppsAdapter = {
1515
1539
  return this.sendNotification('ui/notifications/size-changed', params);
1516
1540
  },
1517
1541
  requestClose: function(context) {
1518
- return this.sendRequest('ui/close', {});
1542
+ // The host decides whether to tear the view down; it answers with ui/resource-teardown if it does.
1543
+ return this.sendNotification('ui/notifications/request-teardown', {});
1519
1544
  },
1520
1545
  // Extended ext-apps methods (full specification)
1521
1546
  updateModelContext: function(context, data, merge) {
1522
- if (!this.hostCapabilities.modelContextUpdate) {
1547
+ if (!this.hostCapabilities.updateModelContext && !this.hostCapabilities.modelContextUpdate) {
1523
1548
  return Promise.reject(new Error('Model context update not supported'));
1524
1549
  }
1525
- return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
1550
+ // The host keeps only the latest update, so an object merges into the last one here.
1551
+ var isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
1552
+ var modelContext = isObject && merge !== false ? Object.assign({}, this.modelContext, data) : data;
1553
+ this.modelContext = isObject ? modelContext : null;
1554
+ var text = typeof modelContext === 'string' ? modelContext : JSON.stringify(modelContext);
1555
+ var params = { content: [{ type: 'text', text: text }] };
1556
+ if (isObject) params.structuredContent = modelContext;
1557
+ return this.sendRequest('ui/update-model-context', params);
1526
1558
  },
1527
1559
  log: function(context, level, message, data) {
1528
1560
  if (!this.hostCapabilities.logging) {
@@ -1531,7 +1563,10 @@ var ExtAppsAdapter = {
1531
1563
  logFn('[Widget] ' + message, data);
1532
1564
  return Promise.resolve();
1533
1565
  }
1534
- return this.sendRequest('ui/log', { level: level, message: message, data: data });
1566
+ return this.sendNotification('notifications/message', {
1567
+ level: level === 'warn' ? 'warning' : level,
1568
+ data: data === undefined ? message : { message: message, data: data }
1569
+ });
1535
1570
  },
1536
1571
  registerTool: function(context, name, description, inputSchema) {
1537
1572
  if (!this.hostCapabilities.widgetTools) {
@@ -1928,8 +1963,10 @@ FrontMcpBridge.prototype.openLink = function(url) {
1928
1963
  FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1929
1964
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1930
1965
  var self = this;
1931
- return this._adapter.requestDisplayMode(this._context, mode).then(function() {
1932
- self._context.hostContext.displayMode = mode;
1966
+ return this._adapter.requestDisplayMode(this._context, mode).then(function(result) {
1967
+ // The host may set another mode than the one asked for, e.g. when it refuses the change.
1968
+ var setMode = result && result.mode;
1969
+ if (${JSON.stringify(MCP_APPS_DISPLAY_MODES)}.indexOf(setMode) !== -1) self._context.hostContext.displayMode = setMode;
1933
1970
  });
1934
1971
  };
1935
1972
 
@@ -2281,7 +2318,7 @@ function hasSizing(sizing) {
2281
2318
  return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
2282
2319
  }
2283
2320
  function buildDataInjectionScript(options) {
2284
- const { toolName, input, output, structuredContent, sizing } = options;
2321
+ const { toolName, input, output, structuredContent, sizing, displayMode } = options;
2285
2322
  const lines = [
2286
2323
  `window.__mcpAppsEnabled = true;`,
2287
2324
  `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
@@ -2292,6 +2329,9 @@ function buildDataInjectionScript(options) {
2292
2329
  if (hasSizing(sizing)) {
2293
2330
  lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
2294
2331
  }
2332
+ if (displayMode && displayMode !== "inline") {
2333
+ lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`);
2334
+ }
2295
2335
  return `<script>
2296
2336
  ${lines.join("\n")}
2297
2337
  </script>`;
@@ -2357,7 +2397,8 @@ function resolveDataInjectionScript(args) {
2357
2397
  input: args.input,
2358
2398
  output: args.output,
2359
2399
  structuredContent: args.structuredContent,
2360
- sizing: args.sizing
2400
+ sizing: args.sizing,
2401
+ displayMode: args.displayMode
2361
2402
  });
2362
2403
  }
2363
2404
  function buildShell(content, config) {
@@ -2370,7 +2411,8 @@ function buildShell(content, config) {
2370
2411
  structuredContent,
2371
2412
  includeBridge = true,
2372
2413
  title,
2373
- sizing
2414
+ sizing,
2415
+ displayMode
2374
2416
  } = config;
2375
2417
  const { customShell, dataInjection } = config;
2376
2418
  const dataScript = resolveDataInjectionScript({
@@ -2379,7 +2421,8 @@ function buildShell(content, config) {
2379
2421
  input,
2380
2422
  output,
2381
2423
  structuredContent,
2382
- sizing
2424
+ sizing,
2425
+ displayMode
2383
2426
  });
2384
2427
  if (!withShell) {
2385
2428
  const html2 = `${dataScript}
@@ -2400,6 +2443,7 @@ ${content}`;
2400
2443
  includeBridge,
2401
2444
  title,
2402
2445
  sizing,
2446
+ displayMode,
2403
2447
  dataInjection
2404
2448
  });
2405
2449
  }
@@ -2455,7 +2499,8 @@ function buildCustomShell(content, customShell, ctx) {
2455
2499
  input: ctx.input,
2456
2500
  output: ctx.output,
2457
2501
  structuredContent: ctx.structuredContent,
2458
- sizing: ctx.sizing
2502
+ sizing: ctx.sizing,
2503
+ displayMode: ctx.displayMode
2459
2504
  });
2460
2505
  const sizingStyle = buildSizingStyleTag(ctx.sizing);
2461
2506
  const dataWithSizing = sizingStyle ? `${dataScript}
package/esm/index.mjs CHANGED
@@ -135,6 +135,8 @@ function safeJsonForScript(value) {
135
135
  }
136
136
 
137
137
  // libs/uipack/src/bridge-runtime/iife-generator.ts
138
+ var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
139
+ var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
138
140
  function generateBridgeIIFE(options = {}) {
139
141
  const { debug = false, trustedOrigins = [], minify = false } = options;
140
142
  const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
@@ -182,6 +184,7 @@ function generateBridgeIIFE(options = {}) {
182
184
  parts.push("bridge.initialize().then(function() {");
183
185
  parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
184
186
  parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
187
+ parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
185
188
  parts.push("}).catch(function(err) {");
186
189
  parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
187
190
  parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
@@ -515,6 +518,9 @@ var OpenAIAdapter = {
515
518
  return Promise.resolve();
516
519
  },
517
520
  requestDisplayMode: function(context, mode) {
521
+ if (window.openai && typeof window.openai.requestDisplayMode === 'function') {
522
+ return Promise.resolve(window.openai.requestDisplayMode({ mode: mode }));
523
+ }
518
524
  return Promise.resolve();
519
525
  },
520
526
  setSize: function(context, size) {
@@ -552,6 +558,7 @@ var ExtAppsAdapter = {
552
558
  hostCapabilities: {},
553
559
  handshakeSettled: false,
554
560
  pendingSize: null,
561
+ modelContext: null,
555
562
  canHandle: function() {
556
563
  if (typeof window === 'undefined') return false;
557
564
  if (window.parent === window) return false;
@@ -630,7 +637,18 @@ var ExtAppsAdapter = {
630
637
 
631
638
  if ('method' in data && !('id' in data)) {
632
639
  this.handleNotification(context, data);
640
+ return;
633
641
  }
642
+
643
+ if ('method' in data && 'id' in data) {
644
+ this.handleRequest(context, data, event.origin);
645
+ }
646
+ },
647
+ handleRequest: function(context, request, origin) {
648
+ // ui/resource-teardown: the host is about to unmount the view; reply once listeners have cleaned up.
649
+ if (request.method !== 'ui/resource-teardown') return;
650
+ window.dispatchEvent(new CustomEvent('bridge:teardown', { detail: {} }));
651
+ window.parent.postMessage({ jsonrpc: '2.0', id: request.id, result: {} }, origin);
634
652
  },
635
653
  handleNotification: function(context, notification) {
636
654
  var params = notification.params || {};
@@ -655,6 +673,7 @@ var ExtAppsAdapter = {
655
673
  Object.assign(context.hostContext, params);
656
674
  context.notifyContextChange(params);
657
675
  break;
676
+ case 'ui/notifications/tool-cancelled':
658
677
  case 'ui/notifications/cancelled':
659
678
  window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
660
679
  break;
@@ -719,7 +738,7 @@ var ExtAppsAdapter = {
719
738
  var id = ++this.requestId;
720
739
  var params = {
721
740
  appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
722
- appCapabilities: { tools: { listChanged: false } },
741
+ appCapabilities: { tools: { listChanged: false }, availableDisplayModes: ${JSON.stringify(MCP_APPS_DISPLAY_MODES)} },
723
742
  protocolVersion: '2024-11-05'
724
743
  };
725
744
 
@@ -787,20 +806,25 @@ var ExtAppsAdapter = {
787
806
  return Promise.reject(new Error('Server tool proxy not supported'));
788
807
  }
789
808
  // Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
790
- return this.sendRequest('tools/call', { name: name, arguments: args || {} });
809
+ return this.sendRequest('tools/call', { name: name, arguments: args || {}, _meta: { '${WIDGET_CALL_META_KEY}': true } });
791
810
  },
792
811
  sendMessage: function(context, content) {
793
812
  return this.sendRequest('ui/message', { content: content });
794
813
  },
795
814
  openLink: function(context, url) {
796
- if (!this.hostCapabilities.openLink) {
815
+ if (!this.hostCapabilities.openLinks && !this.hostCapabilities.openLink) {
797
816
  window.open(url, '_blank', 'noopener,noreferrer');
798
817
  return Promise.resolve();
799
818
  }
800
- return this.sendRequest('ui/openLink', { url: url });
819
+ return this.sendRequest('ui/open-link', { url: url });
801
820
  },
802
821
  requestDisplayMode: function(context, mode) {
803
- return this.sendRequest('ui/setDisplayMode', { mode: mode });
822
+ // Ask only for a mode the host offers; it answers with the mode it actually set.
823
+ var offered = context.hostContext.availableDisplayModes;
824
+ if (!Array.isArray(offered) || offered.indexOf(mode) === -1) {
825
+ return Promise.reject(new Error('Display mode "' + mode + '" is not available on this host'));
826
+ }
827
+ return this.sendRequest('ui/request-display-mode', { mode: mode });
804
828
  },
805
829
  setSize: function(context, size) {
806
830
  // Standard ext-apps sizing: the view tells the host its dimensions with the
@@ -821,14 +845,22 @@ var ExtAppsAdapter = {
821
845
  return this.sendNotification('ui/notifications/size-changed', params);
822
846
  },
823
847
  requestClose: function(context) {
824
- return this.sendRequest('ui/close', {});
848
+ // The host decides whether to tear the view down; it answers with ui/resource-teardown if it does.
849
+ return this.sendNotification('ui/notifications/request-teardown', {});
825
850
  },
826
851
  // Extended ext-apps methods (full specification)
827
852
  updateModelContext: function(context, data, merge) {
828
- if (!this.hostCapabilities.modelContextUpdate) {
853
+ if (!this.hostCapabilities.updateModelContext && !this.hostCapabilities.modelContextUpdate) {
829
854
  return Promise.reject(new Error('Model context update not supported'));
830
855
  }
831
- return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
856
+ // The host keeps only the latest update, so an object merges into the last one here.
857
+ var isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
858
+ var modelContext = isObject && merge !== false ? Object.assign({}, this.modelContext, data) : data;
859
+ this.modelContext = isObject ? modelContext : null;
860
+ var text = typeof modelContext === 'string' ? modelContext : JSON.stringify(modelContext);
861
+ var params = { content: [{ type: 'text', text: text }] };
862
+ if (isObject) params.structuredContent = modelContext;
863
+ return this.sendRequest('ui/update-model-context', params);
832
864
  },
833
865
  log: function(context, level, message, data) {
834
866
  if (!this.hostCapabilities.logging) {
@@ -837,7 +869,10 @@ var ExtAppsAdapter = {
837
869
  logFn('[Widget] ' + message, data);
838
870
  return Promise.resolve();
839
871
  }
840
- return this.sendRequest('ui/log', { level: level, message: message, data: data });
872
+ return this.sendNotification('notifications/message', {
873
+ level: level === 'warn' ? 'warning' : level,
874
+ data: data === undefined ? message : { message: message, data: data }
875
+ });
841
876
  },
842
877
  registerTool: function(context, name, description, inputSchema) {
843
878
  if (!this.hostCapabilities.widgetTools) {
@@ -1234,8 +1269,10 @@ FrontMcpBridge.prototype.openLink = function(url) {
1234
1269
  FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1235
1270
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1236
1271
  var self = this;
1237
- return this._adapter.requestDisplayMode(this._context, mode).then(function() {
1238
- self._context.hostContext.displayMode = mode;
1272
+ return this._adapter.requestDisplayMode(this._context, mode).then(function(result) {
1273
+ // The host may set another mode than the one asked for, e.g. when it refuses the change.
1274
+ var setMode = result && result.mode;
1275
+ if (${JSON.stringify(MCP_APPS_DISPLAY_MODES)}.indexOf(setMode) !== -1) self._context.hostContext.displayMode = setMode;
1239
1276
  });
1240
1277
  };
1241
1278
 
@@ -3101,7 +3138,7 @@ function hasSizing(sizing) {
3101
3138
  return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
3102
3139
  }
3103
3140
  function buildDataInjectionScript(options) {
3104
- const { toolName, input, output, structuredContent, sizing } = options;
3141
+ const { toolName, input, output, structuredContent, sizing, displayMode } = options;
3105
3142
  const lines = [
3106
3143
  `window.__mcpAppsEnabled = true;`,
3107
3144
  `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
@@ -3112,6 +3149,9 @@ function buildDataInjectionScript(options) {
3112
3149
  if (hasSizing(sizing)) {
3113
3150
  lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
3114
3151
  }
3152
+ if (displayMode && displayMode !== "inline") {
3153
+ lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`);
3154
+ }
3115
3155
  return `<script>
3116
3156
  ${lines.join("\n")}
3117
3157
  </script>`;
@@ -3203,7 +3243,8 @@ function resolveDataInjectionScript(args) {
3203
3243
  input: args.input,
3204
3244
  output: args.output,
3205
3245
  structuredContent: args.structuredContent,
3206
- sizing: args.sizing
3246
+ sizing: args.sizing,
3247
+ displayMode: args.displayMode
3207
3248
  });
3208
3249
  }
3209
3250
  function buildShell(content, config) {
@@ -3216,7 +3257,8 @@ function buildShell(content, config) {
3216
3257
  structuredContent,
3217
3258
  includeBridge = true,
3218
3259
  title,
3219
- sizing
3260
+ sizing,
3261
+ displayMode
3220
3262
  } = config;
3221
3263
  const { customShell, dataInjection } = config;
3222
3264
  const dataScript = resolveDataInjectionScript({
@@ -3225,7 +3267,8 @@ function buildShell(content, config) {
3225
3267
  input,
3226
3268
  output,
3227
3269
  structuredContent,
3228
- sizing
3270
+ sizing,
3271
+ displayMode
3229
3272
  });
3230
3273
  if (!withShell) {
3231
3274
  const html3 = `${dataScript}
@@ -3246,6 +3289,7 @@ ${content}`;
3246
3289
  includeBridge,
3247
3290
  title,
3248
3291
  sizing,
3292
+ displayMode,
3249
3293
  dataInjection
3250
3294
  });
3251
3295
  }
@@ -3301,7 +3345,8 @@ function buildCustomShell(content, customShell, ctx) {
3301
3345
  input: ctx.input,
3302
3346
  output: ctx.output,
3303
3347
  structuredContent: ctx.structuredContent,
3304
- sizing: ctx.sizing
3348
+ sizing: ctx.sizing,
3349
+ displayMode: ctx.displayMode
3305
3350
  });
3306
3351
  const sizingStyle = buildSizingStyleTag(ctx.sizing);
3307
3352
  const dataWithSizing = sizingStyle ? `${dataScript}
@@ -4319,7 +4364,7 @@ function noticeStringResult(toolName, logger) {
4319
4364
  if (noticedStringResultTools.has(toolName)) return;
4320
4365
  noticedStringResultTools.add(toolName);
4321
4366
  logger.warn(
4322
- `[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}`
4367
+ `[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}`
4323
4368
  );
4324
4369
  }
4325
4370
  function renderUnwrappedResult(rawResult, options) {
@@ -4378,6 +4423,7 @@ function renderToolTemplate(options) {
4378
4423
  includeBridge: true,
4379
4424
  resolver,
4380
4425
  sizing,
4426
+ displayMode: options.displayMode,
4381
4427
  csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
4382
4428
  };
4383
4429
  let html2;
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
4
4
  "description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -61,7 +61,7 @@
61
61
  "typescript": "^5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@frontmcp/lazy-zod": "1.9.2",
64
+ "@frontmcp/lazy-zod": "1.9.3",
65
65
  "esbuild": ">=0.27.0 <1"
66
66
  },
67
67
  "peerDependenciesMeta": {