@frontmcp/uipack 1.9.1 → 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.
- package/adapters/index.d.ts +1 -1
- package/adapters/index.d.ts.map +1 -1
- package/adapters/index.js +84 -23
- package/adapters/template-renderer.d.ts +15 -3
- package/adapters/template-renderer.d.ts.map +1 -1
- package/bridge-runtime/iife-generator.d.ts +7 -0
- package/bridge-runtime/iife-generator.d.ts.map +1 -1
- package/bridge-runtime/index.d.ts +1 -1
- package/bridge-runtime/index.d.ts.map +1 -1
- package/bridge-runtime/index.js +52 -11
- package/component/index.js +61 -16
- package/esm/adapters/index.mjs +83 -23
- package/esm/bridge-runtime/index.mjs +50 -11
- package/esm/component/index.mjs +61 -16
- package/esm/index.mjs +83 -23
- package/esm/package.json +2 -2
- package/esm/shell/index.mjs +61 -16
- package/index.js +84 -23
- package/package.json +2 -2
- package/shell/builder.d.ts.map +1 -1
- package/shell/data-injector.d.ts +3 -1
- package/shell/data-injector.d.ts.map +1 -1
- package/shell/index.js +61 -16
- package/shell/trusted-html.d.ts +2 -2
- package/shell/types.d.ts +2 -0
- package/shell/types.d.ts.map +1 -1
- package/types/ui-config.d.ts +23 -20
- package/types/ui-config.d.ts.map +1 -1
- package/types/ui-runtime.d.ts +1 -1
package/esm/shell/index.mjs
CHANGED
|
@@ -2,6 +2,8 @@ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuilt
|
|
|
2
2
|
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
3
|
// __frontmcpCreateRequire
|
|
4
4
|
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
5
|
+
var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
|
|
6
|
+
var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
|
|
5
7
|
function generateBridgeIIFE(options = {}) {
|
|
6
8
|
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
7
9
|
const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
|
|
@@ -49,6 +51,7 @@ function generateBridgeIIFE(options = {}) {
|
|
|
49
51
|
parts.push("bridge.initialize().then(function() {");
|
|
50
52
|
parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
|
|
51
53
|
parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
|
|
54
|
+
parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
|
|
52
55
|
parts.push("}).catch(function(err) {");
|
|
53
56
|
parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
|
|
54
57
|
parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
|
|
@@ -382,6 +385,9 @@ var OpenAIAdapter = {
|
|
|
382
385
|
return Promise.resolve();
|
|
383
386
|
},
|
|
384
387
|
requestDisplayMode: function(context, mode) {
|
|
388
|
+
if (window.openai && typeof window.openai.requestDisplayMode === 'function') {
|
|
389
|
+
return Promise.resolve(window.openai.requestDisplayMode({ mode: mode }));
|
|
390
|
+
}
|
|
385
391
|
return Promise.resolve();
|
|
386
392
|
},
|
|
387
393
|
setSize: function(context, size) {
|
|
@@ -419,6 +425,7 @@ var ExtAppsAdapter = {
|
|
|
419
425
|
hostCapabilities: {},
|
|
420
426
|
handshakeSettled: false,
|
|
421
427
|
pendingSize: null,
|
|
428
|
+
modelContext: null,
|
|
422
429
|
canHandle: function() {
|
|
423
430
|
if (typeof window === 'undefined') return false;
|
|
424
431
|
if (window.parent === window) return false;
|
|
@@ -497,8 +504,19 @@ var ExtAppsAdapter = {
|
|
|
497
504
|
|
|
498
505
|
if ('method' in data && !('id' in data)) {
|
|
499
506
|
this.handleNotification(context, data);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if ('method' in data && 'id' in data) {
|
|
511
|
+
this.handleRequest(context, data, event.origin);
|
|
500
512
|
}
|
|
501
513
|
},
|
|
514
|
+
handleRequest: function(context, request, origin) {
|
|
515
|
+
// ui/resource-teardown: the host is about to unmount the view; reply once listeners have cleaned up.
|
|
516
|
+
if (request.method !== 'ui/resource-teardown') return;
|
|
517
|
+
window.dispatchEvent(new CustomEvent('bridge:teardown', { detail: {} }));
|
|
518
|
+
window.parent.postMessage({ jsonrpc: '2.0', id: request.id, result: {} }, origin);
|
|
519
|
+
},
|
|
502
520
|
handleNotification: function(context, notification) {
|
|
503
521
|
var params = notification.params || {};
|
|
504
522
|
switch (notification.method) {
|
|
@@ -522,6 +540,7 @@ var ExtAppsAdapter = {
|
|
|
522
540
|
Object.assign(context.hostContext, params);
|
|
523
541
|
context.notifyContextChange(params);
|
|
524
542
|
break;
|
|
543
|
+
case 'ui/notifications/tool-cancelled':
|
|
525
544
|
case 'ui/notifications/cancelled':
|
|
526
545
|
window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
|
|
527
546
|
break;
|
|
@@ -586,7 +605,7 @@ var ExtAppsAdapter = {
|
|
|
586
605
|
var id = ++this.requestId;
|
|
587
606
|
var params = {
|
|
588
607
|
appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
|
|
589
|
-
appCapabilities: { tools: { listChanged: false } },
|
|
608
|
+
appCapabilities: { tools: { listChanged: false }, availableDisplayModes: ${JSON.stringify(MCP_APPS_DISPLAY_MODES)} },
|
|
590
609
|
protocolVersion: '2024-11-05'
|
|
591
610
|
};
|
|
592
611
|
|
|
@@ -654,20 +673,25 @@ var ExtAppsAdapter = {
|
|
|
654
673
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
655
674
|
}
|
|
656
675
|
// Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
|
|
657
|
-
return this.sendRequest('tools/call', { name: name, arguments: args || {} });
|
|
676
|
+
return this.sendRequest('tools/call', { name: name, arguments: args || {}, _meta: { '${WIDGET_CALL_META_KEY}': true } });
|
|
658
677
|
},
|
|
659
678
|
sendMessage: function(context, content) {
|
|
660
679
|
return this.sendRequest('ui/message', { content: content });
|
|
661
680
|
},
|
|
662
681
|
openLink: function(context, url) {
|
|
663
|
-
if (!this.hostCapabilities.openLink) {
|
|
682
|
+
if (!this.hostCapabilities.openLinks && !this.hostCapabilities.openLink) {
|
|
664
683
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
665
684
|
return Promise.resolve();
|
|
666
685
|
}
|
|
667
|
-
return this.sendRequest('ui/
|
|
686
|
+
return this.sendRequest('ui/open-link', { url: url });
|
|
668
687
|
},
|
|
669
688
|
requestDisplayMode: function(context, mode) {
|
|
670
|
-
|
|
689
|
+
// Ask only for a mode the host offers; it answers with the mode it actually set.
|
|
690
|
+
var offered = context.hostContext.availableDisplayModes;
|
|
691
|
+
if (!Array.isArray(offered) || offered.indexOf(mode) === -1) {
|
|
692
|
+
return Promise.reject(new Error('Display mode "' + mode + '" is not available on this host'));
|
|
693
|
+
}
|
|
694
|
+
return this.sendRequest('ui/request-display-mode', { mode: mode });
|
|
671
695
|
},
|
|
672
696
|
setSize: function(context, size) {
|
|
673
697
|
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
@@ -688,14 +712,22 @@ var ExtAppsAdapter = {
|
|
|
688
712
|
return this.sendNotification('ui/notifications/size-changed', params);
|
|
689
713
|
},
|
|
690
714
|
requestClose: function(context) {
|
|
691
|
-
|
|
715
|
+
// The host decides whether to tear the view down; it answers with ui/resource-teardown if it does.
|
|
716
|
+
return this.sendNotification('ui/notifications/request-teardown', {});
|
|
692
717
|
},
|
|
693
718
|
// Extended ext-apps methods (full specification)
|
|
694
719
|
updateModelContext: function(context, data, merge) {
|
|
695
|
-
if (!this.hostCapabilities.modelContextUpdate) {
|
|
720
|
+
if (!this.hostCapabilities.updateModelContext && !this.hostCapabilities.modelContextUpdate) {
|
|
696
721
|
return Promise.reject(new Error('Model context update not supported'));
|
|
697
722
|
}
|
|
698
|
-
|
|
723
|
+
// The host keeps only the latest update, so an object merges into the last one here.
|
|
724
|
+
var isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
|
|
725
|
+
var modelContext = isObject && merge !== false ? Object.assign({}, this.modelContext, data) : data;
|
|
726
|
+
this.modelContext = isObject ? modelContext : null;
|
|
727
|
+
var text = typeof modelContext === 'string' ? modelContext : JSON.stringify(modelContext);
|
|
728
|
+
var params = { content: [{ type: 'text', text: text }] };
|
|
729
|
+
if (isObject) params.structuredContent = modelContext;
|
|
730
|
+
return this.sendRequest('ui/update-model-context', params);
|
|
699
731
|
},
|
|
700
732
|
log: function(context, level, message, data) {
|
|
701
733
|
if (!this.hostCapabilities.logging) {
|
|
@@ -704,7 +736,10 @@ var ExtAppsAdapter = {
|
|
|
704
736
|
logFn('[Widget] ' + message, data);
|
|
705
737
|
return Promise.resolve();
|
|
706
738
|
}
|
|
707
|
-
return this.
|
|
739
|
+
return this.sendNotification('notifications/message', {
|
|
740
|
+
level: level === 'warn' ? 'warning' : level,
|
|
741
|
+
data: data === undefined ? message : { message: message, data: data }
|
|
742
|
+
});
|
|
708
743
|
},
|
|
709
744
|
registerTool: function(context, name, description, inputSchema) {
|
|
710
745
|
if (!this.hostCapabilities.widgetTools) {
|
|
@@ -1101,8 +1136,10 @@ FrontMcpBridge.prototype.openLink = function(url) {
|
|
|
1101
1136
|
FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
1102
1137
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1103
1138
|
var self = this;
|
|
1104
|
-
return this._adapter.requestDisplayMode(this._context, mode).then(function() {
|
|
1105
|
-
|
|
1139
|
+
return this._adapter.requestDisplayMode(this._context, mode).then(function(result) {
|
|
1140
|
+
// The host may set another mode than the one asked for, e.g. when it refuses the change.
|
|
1141
|
+
var setMode = result && result.mode;
|
|
1142
|
+
if (${JSON.stringify(MCP_APPS_DISPLAY_MODES)}.indexOf(setMode) !== -1) self._context.hostContext.displayMode = setMode;
|
|
1106
1143
|
});
|
|
1107
1144
|
};
|
|
1108
1145
|
|
|
@@ -1527,7 +1564,7 @@ function hasSizing(sizing) {
|
|
|
1527
1564
|
return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
|
|
1528
1565
|
}
|
|
1529
1566
|
function buildDataInjectionScript(options) {
|
|
1530
|
-
const { toolName, input, output, structuredContent, sizing } = options;
|
|
1567
|
+
const { toolName, input, output, structuredContent, sizing, displayMode } = options;
|
|
1531
1568
|
const lines = [
|
|
1532
1569
|
`window.__mcpAppsEnabled = true;`,
|
|
1533
1570
|
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
@@ -1538,6 +1575,9 @@ function buildDataInjectionScript(options) {
|
|
|
1538
1575
|
if (hasSizing(sizing)) {
|
|
1539
1576
|
lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
|
|
1540
1577
|
}
|
|
1578
|
+
if (displayMode && displayMode !== "inline") {
|
|
1579
|
+
lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`);
|
|
1580
|
+
}
|
|
1541
1581
|
return `<script>
|
|
1542
1582
|
${lines.join("\n")}
|
|
1543
1583
|
</script>`;
|
|
@@ -1629,7 +1669,8 @@ function resolveDataInjectionScript(args) {
|
|
|
1629
1669
|
input: args.input,
|
|
1630
1670
|
output: args.output,
|
|
1631
1671
|
structuredContent: args.structuredContent,
|
|
1632
|
-
sizing: args.sizing
|
|
1672
|
+
sizing: args.sizing,
|
|
1673
|
+
displayMode: args.displayMode
|
|
1633
1674
|
});
|
|
1634
1675
|
}
|
|
1635
1676
|
function buildShell(content, config) {
|
|
@@ -1642,7 +1683,8 @@ function buildShell(content, config) {
|
|
|
1642
1683
|
structuredContent,
|
|
1643
1684
|
includeBridge = true,
|
|
1644
1685
|
title,
|
|
1645
|
-
sizing
|
|
1686
|
+
sizing,
|
|
1687
|
+
displayMode
|
|
1646
1688
|
} = config;
|
|
1647
1689
|
const { customShell, dataInjection } = config;
|
|
1648
1690
|
const dataScript = resolveDataInjectionScript({
|
|
@@ -1651,7 +1693,8 @@ function buildShell(content, config) {
|
|
|
1651
1693
|
input,
|
|
1652
1694
|
output,
|
|
1653
1695
|
structuredContent,
|
|
1654
|
-
sizing
|
|
1696
|
+
sizing,
|
|
1697
|
+
displayMode
|
|
1655
1698
|
});
|
|
1656
1699
|
if (!withShell) {
|
|
1657
1700
|
const html3 = `${dataScript}
|
|
@@ -1672,6 +1715,7 @@ ${content}`;
|
|
|
1672
1715
|
includeBridge,
|
|
1673
1716
|
title,
|
|
1674
1717
|
sizing,
|
|
1718
|
+
displayMode,
|
|
1675
1719
|
dataInjection
|
|
1676
1720
|
});
|
|
1677
1721
|
}
|
|
@@ -1727,7 +1771,8 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
1727
1771
|
input: ctx.input,
|
|
1728
1772
|
output: ctx.output,
|
|
1729
1773
|
structuredContent: ctx.structuredContent,
|
|
1730
|
-
sizing: ctx.sizing
|
|
1774
|
+
sizing: ctx.sizing,
|
|
1775
|
+
displayMode: ctx.displayMode
|
|
1731
1776
|
});
|
|
1732
1777
|
const sizingStyle = buildSizingStyleTag(ctx.sizing);
|
|
1733
1778
|
const dataWithSizing = sizingStyle ? `${dataScript}
|
package/index.js
CHANGED
|
@@ -121,6 +121,7 @@ __export(index_exports, {
|
|
|
121
121
|
validateCSPDomain: () => validateCSPDomain,
|
|
122
122
|
validateImportMap: () => validateImportMap,
|
|
123
123
|
validateShellTemplate: () => validateShellTemplate,
|
|
124
|
+
warnIfComponentReference: () => warnIfComponentReference,
|
|
124
125
|
wrapDetectedContent: () => wrapDetectedContent
|
|
125
126
|
});
|
|
126
127
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -252,6 +253,8 @@ function safeJsonForScript(value) {
|
|
|
252
253
|
}
|
|
253
254
|
|
|
254
255
|
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
256
|
+
var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
|
|
257
|
+
var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
|
|
255
258
|
function generateBridgeIIFE(options = {}) {
|
|
256
259
|
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
257
260
|
const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
|
|
@@ -299,6 +302,7 @@ function generateBridgeIIFE(options = {}) {
|
|
|
299
302
|
parts.push("bridge.initialize().then(function() {");
|
|
300
303
|
parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
|
|
301
304
|
parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
|
|
305
|
+
parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
|
|
302
306
|
parts.push("}).catch(function(err) {");
|
|
303
307
|
parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
|
|
304
308
|
parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
|
|
@@ -632,6 +636,9 @@ var OpenAIAdapter = {
|
|
|
632
636
|
return Promise.resolve();
|
|
633
637
|
},
|
|
634
638
|
requestDisplayMode: function(context, mode) {
|
|
639
|
+
if (window.openai && typeof window.openai.requestDisplayMode === 'function') {
|
|
640
|
+
return Promise.resolve(window.openai.requestDisplayMode({ mode: mode }));
|
|
641
|
+
}
|
|
635
642
|
return Promise.resolve();
|
|
636
643
|
},
|
|
637
644
|
setSize: function(context, size) {
|
|
@@ -669,6 +676,7 @@ var ExtAppsAdapter = {
|
|
|
669
676
|
hostCapabilities: {},
|
|
670
677
|
handshakeSettled: false,
|
|
671
678
|
pendingSize: null,
|
|
679
|
+
modelContext: null,
|
|
672
680
|
canHandle: function() {
|
|
673
681
|
if (typeof window === 'undefined') return false;
|
|
674
682
|
if (window.parent === window) return false;
|
|
@@ -747,8 +755,19 @@ var ExtAppsAdapter = {
|
|
|
747
755
|
|
|
748
756
|
if ('method' in data && !('id' in data)) {
|
|
749
757
|
this.handleNotification(context, data);
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if ('method' in data && 'id' in data) {
|
|
762
|
+
this.handleRequest(context, data, event.origin);
|
|
750
763
|
}
|
|
751
764
|
},
|
|
765
|
+
handleRequest: function(context, request, origin) {
|
|
766
|
+
// ui/resource-teardown: the host is about to unmount the view; reply once listeners have cleaned up.
|
|
767
|
+
if (request.method !== 'ui/resource-teardown') return;
|
|
768
|
+
window.dispatchEvent(new CustomEvent('bridge:teardown', { detail: {} }));
|
|
769
|
+
window.parent.postMessage({ jsonrpc: '2.0', id: request.id, result: {} }, origin);
|
|
770
|
+
},
|
|
752
771
|
handleNotification: function(context, notification) {
|
|
753
772
|
var params = notification.params || {};
|
|
754
773
|
switch (notification.method) {
|
|
@@ -772,6 +791,7 @@ var ExtAppsAdapter = {
|
|
|
772
791
|
Object.assign(context.hostContext, params);
|
|
773
792
|
context.notifyContextChange(params);
|
|
774
793
|
break;
|
|
794
|
+
case 'ui/notifications/tool-cancelled':
|
|
775
795
|
case 'ui/notifications/cancelled':
|
|
776
796
|
window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
|
|
777
797
|
break;
|
|
@@ -836,7 +856,7 @@ var ExtAppsAdapter = {
|
|
|
836
856
|
var id = ++this.requestId;
|
|
837
857
|
var params = {
|
|
838
858
|
appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
|
|
839
|
-
appCapabilities: { tools: { listChanged: false } },
|
|
859
|
+
appCapabilities: { tools: { listChanged: false }, availableDisplayModes: ${JSON.stringify(MCP_APPS_DISPLAY_MODES)} },
|
|
840
860
|
protocolVersion: '2024-11-05'
|
|
841
861
|
};
|
|
842
862
|
|
|
@@ -904,20 +924,25 @@ var ExtAppsAdapter = {
|
|
|
904
924
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
905
925
|
}
|
|
906
926
|
// Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
|
|
907
|
-
return this.sendRequest('tools/call', { name: name, arguments: args || {} });
|
|
927
|
+
return this.sendRequest('tools/call', { name: name, arguments: args || {}, _meta: { '${WIDGET_CALL_META_KEY}': true } });
|
|
908
928
|
},
|
|
909
929
|
sendMessage: function(context, content) {
|
|
910
930
|
return this.sendRequest('ui/message', { content: content });
|
|
911
931
|
},
|
|
912
932
|
openLink: function(context, url) {
|
|
913
|
-
if (!this.hostCapabilities.openLink) {
|
|
933
|
+
if (!this.hostCapabilities.openLinks && !this.hostCapabilities.openLink) {
|
|
914
934
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
915
935
|
return Promise.resolve();
|
|
916
936
|
}
|
|
917
|
-
return this.sendRequest('ui/
|
|
937
|
+
return this.sendRequest('ui/open-link', { url: url });
|
|
918
938
|
},
|
|
919
939
|
requestDisplayMode: function(context, mode) {
|
|
920
|
-
|
|
940
|
+
// Ask only for a mode the host offers; it answers with the mode it actually set.
|
|
941
|
+
var offered = context.hostContext.availableDisplayModes;
|
|
942
|
+
if (!Array.isArray(offered) || offered.indexOf(mode) === -1) {
|
|
943
|
+
return Promise.reject(new Error('Display mode "' + mode + '" is not available on this host'));
|
|
944
|
+
}
|
|
945
|
+
return this.sendRequest('ui/request-display-mode', { mode: mode });
|
|
921
946
|
},
|
|
922
947
|
setSize: function(context, size) {
|
|
923
948
|
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
@@ -938,14 +963,22 @@ var ExtAppsAdapter = {
|
|
|
938
963
|
return this.sendNotification('ui/notifications/size-changed', params);
|
|
939
964
|
},
|
|
940
965
|
requestClose: function(context) {
|
|
941
|
-
|
|
966
|
+
// The host decides whether to tear the view down; it answers with ui/resource-teardown if it does.
|
|
967
|
+
return this.sendNotification('ui/notifications/request-teardown', {});
|
|
942
968
|
},
|
|
943
969
|
// Extended ext-apps methods (full specification)
|
|
944
970
|
updateModelContext: function(context, data, merge) {
|
|
945
|
-
if (!this.hostCapabilities.modelContextUpdate) {
|
|
971
|
+
if (!this.hostCapabilities.updateModelContext && !this.hostCapabilities.modelContextUpdate) {
|
|
946
972
|
return Promise.reject(new Error('Model context update not supported'));
|
|
947
973
|
}
|
|
948
|
-
|
|
974
|
+
// The host keeps only the latest update, so an object merges into the last one here.
|
|
975
|
+
var isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
|
|
976
|
+
var modelContext = isObject && merge !== false ? Object.assign({}, this.modelContext, data) : data;
|
|
977
|
+
this.modelContext = isObject ? modelContext : null;
|
|
978
|
+
var text = typeof modelContext === 'string' ? modelContext : JSON.stringify(modelContext);
|
|
979
|
+
var params = { content: [{ type: 'text', text: text }] };
|
|
980
|
+
if (isObject) params.structuredContent = modelContext;
|
|
981
|
+
return this.sendRequest('ui/update-model-context', params);
|
|
949
982
|
},
|
|
950
983
|
log: function(context, level, message, data) {
|
|
951
984
|
if (!this.hostCapabilities.logging) {
|
|
@@ -954,7 +987,10 @@ var ExtAppsAdapter = {
|
|
|
954
987
|
logFn('[Widget] ' + message, data);
|
|
955
988
|
return Promise.resolve();
|
|
956
989
|
}
|
|
957
|
-
return this.
|
|
990
|
+
return this.sendNotification('notifications/message', {
|
|
991
|
+
level: level === 'warn' ? 'warning' : level,
|
|
992
|
+
data: data === undefined ? message : { message: message, data: data }
|
|
993
|
+
});
|
|
958
994
|
},
|
|
959
995
|
registerTool: function(context, name, description, inputSchema) {
|
|
960
996
|
if (!this.hostCapabilities.widgetTools) {
|
|
@@ -1351,8 +1387,10 @@ FrontMcpBridge.prototype.openLink = function(url) {
|
|
|
1351
1387
|
FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
1352
1388
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1353
1389
|
var self = this;
|
|
1354
|
-
return this._adapter.requestDisplayMode(this._context, mode).then(function() {
|
|
1355
|
-
|
|
1390
|
+
return this._adapter.requestDisplayMode(this._context, mode).then(function(result) {
|
|
1391
|
+
// The host may set another mode than the one asked for, e.g. when it refuses the change.
|
|
1392
|
+
var setMode = result && result.mode;
|
|
1393
|
+
if (${JSON.stringify(MCP_APPS_DISPLAY_MODES)}.indexOf(setMode) !== -1) self._context.hostContext.displayMode = setMode;
|
|
1356
1394
|
});
|
|
1357
1395
|
};
|
|
1358
1396
|
|
|
@@ -3218,7 +3256,7 @@ function hasSizing(sizing) {
|
|
|
3218
3256
|
return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
|
|
3219
3257
|
}
|
|
3220
3258
|
function buildDataInjectionScript(options) {
|
|
3221
|
-
const { toolName, input, output, structuredContent, sizing } = options;
|
|
3259
|
+
const { toolName, input, output, structuredContent, sizing, displayMode } = options;
|
|
3222
3260
|
const lines = [
|
|
3223
3261
|
`window.__mcpAppsEnabled = true;`,
|
|
3224
3262
|
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
@@ -3229,6 +3267,9 @@ function buildDataInjectionScript(options) {
|
|
|
3229
3267
|
if (hasSizing(sizing)) {
|
|
3230
3268
|
lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
|
|
3231
3269
|
}
|
|
3270
|
+
if (displayMode && displayMode !== "inline") {
|
|
3271
|
+
lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`);
|
|
3272
|
+
}
|
|
3232
3273
|
return `<script>
|
|
3233
3274
|
${lines.join("\n")}
|
|
3234
3275
|
</script>`;
|
|
@@ -3320,7 +3361,8 @@ function resolveDataInjectionScript(args) {
|
|
|
3320
3361
|
input: args.input,
|
|
3321
3362
|
output: args.output,
|
|
3322
3363
|
structuredContent: args.structuredContent,
|
|
3323
|
-
sizing: args.sizing
|
|
3364
|
+
sizing: args.sizing,
|
|
3365
|
+
displayMode: args.displayMode
|
|
3324
3366
|
});
|
|
3325
3367
|
}
|
|
3326
3368
|
function buildShell(content, config) {
|
|
@@ -3333,7 +3375,8 @@ function buildShell(content, config) {
|
|
|
3333
3375
|
structuredContent,
|
|
3334
3376
|
includeBridge = true,
|
|
3335
3377
|
title,
|
|
3336
|
-
sizing
|
|
3378
|
+
sizing,
|
|
3379
|
+
displayMode
|
|
3337
3380
|
} = config;
|
|
3338
3381
|
const { customShell, dataInjection } = config;
|
|
3339
3382
|
const dataScript = resolveDataInjectionScript({
|
|
@@ -3342,7 +3385,8 @@ function buildShell(content, config) {
|
|
|
3342
3385
|
input,
|
|
3343
3386
|
output,
|
|
3344
3387
|
structuredContent,
|
|
3345
|
-
sizing
|
|
3388
|
+
sizing,
|
|
3389
|
+
displayMode
|
|
3346
3390
|
});
|
|
3347
3391
|
if (!withShell) {
|
|
3348
3392
|
const html3 = `${dataScript}
|
|
@@ -3363,6 +3407,7 @@ ${content}`;
|
|
|
3363
3407
|
includeBridge,
|
|
3364
3408
|
title,
|
|
3365
3409
|
sizing,
|
|
3410
|
+
displayMode,
|
|
3366
3411
|
dataInjection
|
|
3367
3412
|
});
|
|
3368
3413
|
}
|
|
@@ -3418,7 +3463,8 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
3418
3463
|
input: ctx.input,
|
|
3419
3464
|
output: ctx.output,
|
|
3420
3465
|
structuredContent: ctx.structuredContent,
|
|
3421
|
-
sizing: ctx.sizing
|
|
3466
|
+
sizing: ctx.sizing,
|
|
3467
|
+
displayMode: ctx.displayMode
|
|
3422
3468
|
});
|
|
3423
3469
|
const sizingStyle = buildSizingStyleTag(ctx.sizing);
|
|
3424
3470
|
const dataWithSizing = sizingStyle ? `${dataScript}
|
|
@@ -4436,7 +4482,7 @@ function noticeStringResult(toolName, logger) {
|
|
|
4436
4482
|
if (noticedStringResultTools.has(toolName)) return;
|
|
4437
4483
|
noticedStringResultTools.add(toolName);
|
|
4438
4484
|
logger.warn(
|
|
4439
|
-
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup
|
|
4485
|
+
`[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}`
|
|
4440
4486
|
);
|
|
4441
4487
|
}
|
|
4442
4488
|
function renderUnwrappedResult(rawResult, options) {
|
|
@@ -4446,13 +4492,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
4446
4492
|
if (typeof rawResult !== "string") {
|
|
4447
4493
|
return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
|
|
4448
4494
|
}
|
|
4449
|
-
|
|
4450
|
-
|
|
4495
|
+
const looksLikeMarkup = detectContentType(rawResult) === "html";
|
|
4496
|
+
if (looksLikeMarkup && options.escapeStringResults === false) {
|
|
4497
|
+
return rawResult;
|
|
4451
4498
|
}
|
|
4452
|
-
if (options.escapeStringResults === void 0) {
|
|
4499
|
+
if (looksLikeMarkup && options.escapeStringResults === void 0) {
|
|
4453
4500
|
noticeStringResult(options.toolName, options.logger ?? console);
|
|
4454
4501
|
}
|
|
4455
|
-
return rawResult;
|
|
4502
|
+
return escapeHtml(rawResult);
|
|
4456
4503
|
}
|
|
4457
4504
|
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
4458
4505
|
function warnComponentReference(template, toolName, logger) {
|
|
@@ -4464,7 +4511,18 @@ function warnComponentReference(template, toolName, logger) {
|
|
|
4464
4511
|
`[FrontMCP] Tool "${toolName}": \`ui.template\` is a React component reference, which cannot be bundled (there is no source file to compile), so the widget would render an empty page. Use \`template: { file: './widget.tsx' }\` instead.`
|
|
4465
4512
|
);
|
|
4466
4513
|
}
|
|
4467
|
-
|
|
4514
|
+
var COMPILED_JSX_PATTERN = /\b(?:React\.createElement|_?jsx(?:s|DEV)?)\)?\s*\(/;
|
|
4515
|
+
function isComponentReference(template) {
|
|
4516
|
+
if (isReactTypedObject(template)) return true;
|
|
4517
|
+
if (typeof template !== "function" || detectUIType(template) !== "react") return false;
|
|
4518
|
+
return isDefinitelyReactComponent(template) || COMPILED_JSX_PATTERN.test(Function.prototype.toString.call(template));
|
|
4519
|
+
}
|
|
4520
|
+
function warnIfComponentReference(toolName, template, logger = console) {
|
|
4521
|
+
if (!isComponentReference(template)) return false;
|
|
4522
|
+
warnComponentReference(template, toolName, logger);
|
|
4523
|
+
return true;
|
|
4524
|
+
}
|
|
4525
|
+
function isReactTypedObject(value) {
|
|
4468
4526
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
4469
4527
|
}
|
|
4470
4528
|
function isDefinitelyReactComponent(template) {
|
|
@@ -4483,6 +4541,7 @@ function renderToolTemplate(options) {
|
|
|
4483
4541
|
includeBridge: true,
|
|
4484
4542
|
resolver,
|
|
4485
4543
|
sizing,
|
|
4544
|
+
displayMode: options.displayMode,
|
|
4486
4545
|
csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
|
|
4487
4546
|
};
|
|
4488
4547
|
let html2;
|
|
@@ -4504,7 +4563,7 @@ function renderToolTemplate(options) {
|
|
|
4504
4563
|
if (isHtmlBuilder) {
|
|
4505
4564
|
try {
|
|
4506
4565
|
rawResult = template(ctx);
|
|
4507
|
-
isHtmlBuilder = !
|
|
4566
|
+
isHtmlBuilder = !isReactTypedObject(rawResult);
|
|
4508
4567
|
} catch (error) {
|
|
4509
4568
|
if (uiType !== "react") throw error;
|
|
4510
4569
|
isHtmlBuilder = false;
|
|
@@ -4533,6 +4592,7 @@ function renderToolTemplate(options) {
|
|
|
4533
4592
|
hash = shellResult.hash;
|
|
4534
4593
|
size = shellResult.size;
|
|
4535
4594
|
} else {
|
|
4595
|
+
if (isReactTypedObject(template)) warnComponentReference(template, toolName, options.logger ?? console);
|
|
4536
4596
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
4537
4597
|
html2 = shellResult.html;
|
|
4538
4598
|
hash = shellResult.hash;
|
|
@@ -4735,5 +4795,6 @@ function buildCDNInfoForUIType(uiType) {
|
|
|
4735
4795
|
validateCSPDomain,
|
|
4736
4796
|
validateImportMap,
|
|
4737
4797
|
validateShellTemplate,
|
|
4798
|
+
warnIfComponentReference,
|
|
4738
4799
|
wrapDetectedContent
|
|
4739
4800
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "1.9.
|
|
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.
|
|
64
|
+
"@frontmcp/lazy-zod": "1.9.3",
|
|
65
65
|
"esbuild": ">=0.27.0 <1"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
package/shell/builder.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/shell/builder.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,KAAK,EAAE,WAAW,EAAgC,WAAW,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../src/shell/builder.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,KAAK,EAAE,WAAW,EAAgC,WAAW,EAAE,MAAM,SAAS,CAAC;AAgCtF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW,CA+F5E"}
|
package/shell/data-injector.d.ts
CHANGED
|
@@ -21,7 +21,8 @@ export declare function hasSizing(sizing?: WidgetSizing): sizing is WidgetSizing
|
|
|
21
21
|
* Sets `window.__mcpToolName`, `window.__mcpToolInput`,
|
|
22
22
|
* `window.__mcpToolOutput`, and `window.__mcpStructuredContent`. When `sizing`
|
|
23
23
|
* is provided it also sets `window.__mcpWidgetSizing` so the bridge runtime can
|
|
24
|
-
* apply CSS and drive auto-resize
|
|
24
|
+
* apply CSS and drive auto-resize, and a `displayMode` other than `'inline'` sets
|
|
25
|
+
* `window.__mcpDisplayMode`, which the bridge asks the host for once it connects.
|
|
25
26
|
*/
|
|
26
27
|
export declare function buildDataInjectionScript(options: {
|
|
27
28
|
toolName: string;
|
|
@@ -29,6 +30,7 @@ export declare function buildDataInjectionScript(options: {
|
|
|
29
30
|
output?: unknown;
|
|
30
31
|
structuredContent?: unknown;
|
|
31
32
|
sizing?: WidgetSizing;
|
|
33
|
+
displayMode?: string;
|
|
32
34
|
}): string;
|
|
33
35
|
/**
|
|
34
36
|
* Generic data-injection descriptor: override the injected window global.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-injector.d.ts","sourceRoot":"","sources":["../../src/shell/data-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,IAAI,YAAY,CASvE;AAED
|
|
1
|
+
{"version":3,"file":"data-injector.d.ts","sourceRoot":"","sources":["../../src/shell/data-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,IAAI,YAAY,CASvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAoBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAeT;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACrC,gCAAgC;IAChC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7D,kCAAkC;IAClC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9D,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,qCAAqC;IACrC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;IACrC,8FAA8F;IAC9F,IAAI,EAAE,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK,WAAW,CAAC;IAC3E,gEAAgE;IAChE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAW,CAAC;CAC9C;AAID;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CA+BvD"}
|