@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 +63 -17
- package/adapters/template-renderer.d.ts +4 -2
- 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 +63 -17
- package/esm/bridge-runtime/index.mjs +50 -11
- package/esm/component/index.mjs +61 -16
- package/esm/index.mjs +63 -17
- package/esm/package.json +2 -2
- package/esm/shell/index.mjs +61 -16
- package/index.js +63 -17
- 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/types.d.ts +2 -0
- package/shell/types.d.ts.map +1 -1
- package/types/ui-config.d.ts +21 -15
- package/types/ui-config.d.ts.map +1 -1
- package/types/ui-runtime.d.ts +1 -1
package/component/index.js
CHANGED
|
@@ -855,6 +855,8 @@ ${json}
|
|
|
855
855
|
}
|
|
856
856
|
|
|
857
857
|
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
858
|
+
var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
|
|
859
|
+
var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
|
|
858
860
|
function generateBridgeIIFE(options = {}) {
|
|
859
861
|
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
860
862
|
const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
|
|
@@ -902,6 +904,7 @@ function generateBridgeIIFE(options = {}) {
|
|
|
902
904
|
parts.push("bridge.initialize().then(function() {");
|
|
903
905
|
parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
|
|
904
906
|
parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
|
|
907
|
+
parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
|
|
905
908
|
parts.push("}).catch(function(err) {");
|
|
906
909
|
parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
|
|
907
910
|
parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
|
|
@@ -1235,6 +1238,9 @@ var OpenAIAdapter = {
|
|
|
1235
1238
|
return Promise.resolve();
|
|
1236
1239
|
},
|
|
1237
1240
|
requestDisplayMode: function(context, mode) {
|
|
1241
|
+
if (window.openai && typeof window.openai.requestDisplayMode === 'function') {
|
|
1242
|
+
return Promise.resolve(window.openai.requestDisplayMode({ mode: mode }));
|
|
1243
|
+
}
|
|
1238
1244
|
return Promise.resolve();
|
|
1239
1245
|
},
|
|
1240
1246
|
setSize: function(context, size) {
|
|
@@ -1272,6 +1278,7 @@ var ExtAppsAdapter = {
|
|
|
1272
1278
|
hostCapabilities: {},
|
|
1273
1279
|
handshakeSettled: false,
|
|
1274
1280
|
pendingSize: null,
|
|
1281
|
+
modelContext: null,
|
|
1275
1282
|
canHandle: function() {
|
|
1276
1283
|
if (typeof window === 'undefined') return false;
|
|
1277
1284
|
if (window.parent === window) return false;
|
|
@@ -1350,8 +1357,19 @@ var ExtAppsAdapter = {
|
|
|
1350
1357
|
|
|
1351
1358
|
if ('method' in data && !('id' in data)) {
|
|
1352
1359
|
this.handleNotification(context, data);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
if ('method' in data && 'id' in data) {
|
|
1364
|
+
this.handleRequest(context, data, event.origin);
|
|
1353
1365
|
}
|
|
1354
1366
|
},
|
|
1367
|
+
handleRequest: function(context, request, origin) {
|
|
1368
|
+
// ui/resource-teardown: the host is about to unmount the view; reply once listeners have cleaned up.
|
|
1369
|
+
if (request.method !== 'ui/resource-teardown') return;
|
|
1370
|
+
window.dispatchEvent(new CustomEvent('bridge:teardown', { detail: {} }));
|
|
1371
|
+
window.parent.postMessage({ jsonrpc: '2.0', id: request.id, result: {} }, origin);
|
|
1372
|
+
},
|
|
1355
1373
|
handleNotification: function(context, notification) {
|
|
1356
1374
|
var params = notification.params || {};
|
|
1357
1375
|
switch (notification.method) {
|
|
@@ -1375,6 +1393,7 @@ var ExtAppsAdapter = {
|
|
|
1375
1393
|
Object.assign(context.hostContext, params);
|
|
1376
1394
|
context.notifyContextChange(params);
|
|
1377
1395
|
break;
|
|
1396
|
+
case 'ui/notifications/tool-cancelled':
|
|
1378
1397
|
case 'ui/notifications/cancelled':
|
|
1379
1398
|
window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
|
|
1380
1399
|
break;
|
|
@@ -1439,7 +1458,7 @@ var ExtAppsAdapter = {
|
|
|
1439
1458
|
var id = ++this.requestId;
|
|
1440
1459
|
var params = {
|
|
1441
1460
|
appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
|
|
1442
|
-
appCapabilities: { tools: { listChanged: false } },
|
|
1461
|
+
appCapabilities: { tools: { listChanged: false }, availableDisplayModes: ${JSON.stringify(MCP_APPS_DISPLAY_MODES)} },
|
|
1443
1462
|
protocolVersion: '2024-11-05'
|
|
1444
1463
|
};
|
|
1445
1464
|
|
|
@@ -1507,20 +1526,25 @@ var ExtAppsAdapter = {
|
|
|
1507
1526
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
1508
1527
|
}
|
|
1509
1528
|
// Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
|
|
1510
|
-
return this.sendRequest('tools/call', { name: name, arguments: args || {} });
|
|
1529
|
+
return this.sendRequest('tools/call', { name: name, arguments: args || {}, _meta: { '${WIDGET_CALL_META_KEY}': true } });
|
|
1511
1530
|
},
|
|
1512
1531
|
sendMessage: function(context, content) {
|
|
1513
1532
|
return this.sendRequest('ui/message', { content: content });
|
|
1514
1533
|
},
|
|
1515
1534
|
openLink: function(context, url) {
|
|
1516
|
-
if (!this.hostCapabilities.openLink) {
|
|
1535
|
+
if (!this.hostCapabilities.openLinks && !this.hostCapabilities.openLink) {
|
|
1517
1536
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
1518
1537
|
return Promise.resolve();
|
|
1519
1538
|
}
|
|
1520
|
-
return this.sendRequest('ui/
|
|
1539
|
+
return this.sendRequest('ui/open-link', { url: url });
|
|
1521
1540
|
},
|
|
1522
1541
|
requestDisplayMode: function(context, mode) {
|
|
1523
|
-
|
|
1542
|
+
// Ask only for a mode the host offers; it answers with the mode it actually set.
|
|
1543
|
+
var offered = context.hostContext.availableDisplayModes;
|
|
1544
|
+
if (!Array.isArray(offered) || offered.indexOf(mode) === -1) {
|
|
1545
|
+
return Promise.reject(new Error('Display mode "' + mode + '" is not available on this host'));
|
|
1546
|
+
}
|
|
1547
|
+
return this.sendRequest('ui/request-display-mode', { mode: mode });
|
|
1524
1548
|
},
|
|
1525
1549
|
setSize: function(context, size) {
|
|
1526
1550
|
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
@@ -1541,14 +1565,22 @@ var ExtAppsAdapter = {
|
|
|
1541
1565
|
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1542
1566
|
},
|
|
1543
1567
|
requestClose: function(context) {
|
|
1544
|
-
|
|
1568
|
+
// The host decides whether to tear the view down; it answers with ui/resource-teardown if it does.
|
|
1569
|
+
return this.sendNotification('ui/notifications/request-teardown', {});
|
|
1545
1570
|
},
|
|
1546
1571
|
// Extended ext-apps methods (full specification)
|
|
1547
1572
|
updateModelContext: function(context, data, merge) {
|
|
1548
|
-
if (!this.hostCapabilities.modelContextUpdate) {
|
|
1573
|
+
if (!this.hostCapabilities.updateModelContext && !this.hostCapabilities.modelContextUpdate) {
|
|
1549
1574
|
return Promise.reject(new Error('Model context update not supported'));
|
|
1550
1575
|
}
|
|
1551
|
-
|
|
1576
|
+
// The host keeps only the latest update, so an object merges into the last one here.
|
|
1577
|
+
var isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
|
|
1578
|
+
var modelContext = isObject && merge !== false ? Object.assign({}, this.modelContext, data) : data;
|
|
1579
|
+
this.modelContext = isObject ? modelContext : null;
|
|
1580
|
+
var text = typeof modelContext === 'string' ? modelContext : JSON.stringify(modelContext);
|
|
1581
|
+
var params = { content: [{ type: 'text', text: text }] };
|
|
1582
|
+
if (isObject) params.structuredContent = modelContext;
|
|
1583
|
+
return this.sendRequest('ui/update-model-context', params);
|
|
1552
1584
|
},
|
|
1553
1585
|
log: function(context, level, message, data) {
|
|
1554
1586
|
if (!this.hostCapabilities.logging) {
|
|
@@ -1557,7 +1589,10 @@ var ExtAppsAdapter = {
|
|
|
1557
1589
|
logFn('[Widget] ' + message, data);
|
|
1558
1590
|
return Promise.resolve();
|
|
1559
1591
|
}
|
|
1560
|
-
return this.
|
|
1592
|
+
return this.sendNotification('notifications/message', {
|
|
1593
|
+
level: level === 'warn' ? 'warning' : level,
|
|
1594
|
+
data: data === undefined ? message : { message: message, data: data }
|
|
1595
|
+
});
|
|
1561
1596
|
},
|
|
1562
1597
|
registerTool: function(context, name, description, inputSchema) {
|
|
1563
1598
|
if (!this.hostCapabilities.widgetTools) {
|
|
@@ -1954,8 +1989,10 @@ FrontMcpBridge.prototype.openLink = function(url) {
|
|
|
1954
1989
|
FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
1955
1990
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1956
1991
|
var self = this;
|
|
1957
|
-
return this._adapter.requestDisplayMode(this._context, mode).then(function() {
|
|
1958
|
-
|
|
1992
|
+
return this._adapter.requestDisplayMode(this._context, mode).then(function(result) {
|
|
1993
|
+
// The host may set another mode than the one asked for, e.g. when it refuses the change.
|
|
1994
|
+
var setMode = result && result.mode;
|
|
1995
|
+
if (${JSON.stringify(MCP_APPS_DISPLAY_MODES)}.indexOf(setMode) !== -1) self._context.hostContext.displayMode = setMode;
|
|
1959
1996
|
});
|
|
1960
1997
|
};
|
|
1961
1998
|
|
|
@@ -2307,7 +2344,7 @@ function hasSizing(sizing) {
|
|
|
2307
2344
|
return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
|
|
2308
2345
|
}
|
|
2309
2346
|
function buildDataInjectionScript(options) {
|
|
2310
|
-
const { toolName, input, output, structuredContent, sizing } = options;
|
|
2347
|
+
const { toolName, input, output, structuredContent, sizing, displayMode } = options;
|
|
2311
2348
|
const lines = [
|
|
2312
2349
|
`window.__mcpAppsEnabled = true;`,
|
|
2313
2350
|
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
@@ -2318,6 +2355,9 @@ function buildDataInjectionScript(options) {
|
|
|
2318
2355
|
if (hasSizing(sizing)) {
|
|
2319
2356
|
lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
|
|
2320
2357
|
}
|
|
2358
|
+
if (displayMode && displayMode !== "inline") {
|
|
2359
|
+
lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`);
|
|
2360
|
+
}
|
|
2321
2361
|
return `<script>
|
|
2322
2362
|
${lines.join("\n")}
|
|
2323
2363
|
</script>`;
|
|
@@ -2383,7 +2423,8 @@ function resolveDataInjectionScript(args) {
|
|
|
2383
2423
|
input: args.input,
|
|
2384
2424
|
output: args.output,
|
|
2385
2425
|
structuredContent: args.structuredContent,
|
|
2386
|
-
sizing: args.sizing
|
|
2426
|
+
sizing: args.sizing,
|
|
2427
|
+
displayMode: args.displayMode
|
|
2387
2428
|
});
|
|
2388
2429
|
}
|
|
2389
2430
|
function buildShell(content, config) {
|
|
@@ -2396,7 +2437,8 @@ function buildShell(content, config) {
|
|
|
2396
2437
|
structuredContent,
|
|
2397
2438
|
includeBridge = true,
|
|
2398
2439
|
title,
|
|
2399
|
-
sizing
|
|
2440
|
+
sizing,
|
|
2441
|
+
displayMode
|
|
2400
2442
|
} = config;
|
|
2401
2443
|
const { customShell, dataInjection } = config;
|
|
2402
2444
|
const dataScript = resolveDataInjectionScript({
|
|
@@ -2405,7 +2447,8 @@ function buildShell(content, config) {
|
|
|
2405
2447
|
input,
|
|
2406
2448
|
output,
|
|
2407
2449
|
structuredContent,
|
|
2408
|
-
sizing
|
|
2450
|
+
sizing,
|
|
2451
|
+
displayMode
|
|
2409
2452
|
});
|
|
2410
2453
|
if (!withShell) {
|
|
2411
2454
|
const html2 = `${dataScript}
|
|
@@ -2426,6 +2469,7 @@ ${content}`;
|
|
|
2426
2469
|
includeBridge,
|
|
2427
2470
|
title,
|
|
2428
2471
|
sizing,
|
|
2472
|
+
displayMode,
|
|
2429
2473
|
dataInjection
|
|
2430
2474
|
});
|
|
2431
2475
|
}
|
|
@@ -2481,7 +2525,8 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
2481
2525
|
input: ctx.input,
|
|
2482
2526
|
output: ctx.output,
|
|
2483
2527
|
structuredContent: ctx.structuredContent,
|
|
2484
|
-
sizing: ctx.sizing
|
|
2528
|
+
sizing: ctx.sizing,
|
|
2529
|
+
displayMode: ctx.displayMode
|
|
2485
2530
|
});
|
|
2486
2531
|
const sizingStyle = buildSizingStyleTag(ctx.sizing);
|
|
2487
2532
|
const dataWithSizing = sizingStyle ? `${dataScript}
|
package/esm/adapters/index.mjs
CHANGED
|
@@ -534,6 +534,8 @@ ${json}
|
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
537
|
+
var WIDGET_CALL_META_KEY = "frontmcp/widgetCall";
|
|
538
|
+
var MCP_APPS_DISPLAY_MODES = ["inline", "fullscreen", "pip"];
|
|
537
539
|
function generateBridgeIIFE(options = {}) {
|
|
538
540
|
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
539
541
|
const adapters = options.adapters || ["openai", "ext-apps", "claude", "gemini", "generic"];
|
|
@@ -581,6 +583,7 @@ function generateBridgeIIFE(options = {}) {
|
|
|
581
583
|
parts.push("bridge.initialize().then(function() {");
|
|
582
584
|
parts.push(' log("Bridge initialized with adapter: " + bridge.adapterId);');
|
|
583
585
|
parts.push(' window.dispatchEvent(new CustomEvent("bridge:ready", { detail: { adapter: bridge.adapterId } }));');
|
|
586
|
+
parts.push(" if (window.__mcpDisplayMode) bridge.requestDisplayMode(window.__mcpDisplayMode).catch(function() {});");
|
|
584
587
|
parts.push("}).catch(function(err) {");
|
|
585
588
|
parts.push(' console.error("[FrontMcpBridge] Init failed:", err);');
|
|
586
589
|
parts.push(' window.dispatchEvent(new CustomEvent("bridge:error", { detail: { error: err } }));');
|
|
@@ -914,6 +917,9 @@ var OpenAIAdapter = {
|
|
|
914
917
|
return Promise.resolve();
|
|
915
918
|
},
|
|
916
919
|
requestDisplayMode: function(context, mode) {
|
|
920
|
+
if (window.openai && typeof window.openai.requestDisplayMode === 'function') {
|
|
921
|
+
return Promise.resolve(window.openai.requestDisplayMode({ mode: mode }));
|
|
922
|
+
}
|
|
917
923
|
return Promise.resolve();
|
|
918
924
|
},
|
|
919
925
|
setSize: function(context, size) {
|
|
@@ -951,6 +957,7 @@ var ExtAppsAdapter = {
|
|
|
951
957
|
hostCapabilities: {},
|
|
952
958
|
handshakeSettled: false,
|
|
953
959
|
pendingSize: null,
|
|
960
|
+
modelContext: null,
|
|
954
961
|
canHandle: function() {
|
|
955
962
|
if (typeof window === 'undefined') return false;
|
|
956
963
|
if (window.parent === window) return false;
|
|
@@ -1029,7 +1036,18 @@ var ExtAppsAdapter = {
|
|
|
1029
1036
|
|
|
1030
1037
|
if ('method' in data && !('id' in data)) {
|
|
1031
1038
|
this.handleNotification(context, data);
|
|
1039
|
+
return;
|
|
1032
1040
|
}
|
|
1041
|
+
|
|
1042
|
+
if ('method' in data && 'id' in data) {
|
|
1043
|
+
this.handleRequest(context, data, event.origin);
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
handleRequest: function(context, request, origin) {
|
|
1047
|
+
// ui/resource-teardown: the host is about to unmount the view; reply once listeners have cleaned up.
|
|
1048
|
+
if (request.method !== 'ui/resource-teardown') return;
|
|
1049
|
+
window.dispatchEvent(new CustomEvent('bridge:teardown', { detail: {} }));
|
|
1050
|
+
window.parent.postMessage({ jsonrpc: '2.0', id: request.id, result: {} }, origin);
|
|
1033
1051
|
},
|
|
1034
1052
|
handleNotification: function(context, notification) {
|
|
1035
1053
|
var params = notification.params || {};
|
|
@@ -1054,6 +1072,7 @@ var ExtAppsAdapter = {
|
|
|
1054
1072
|
Object.assign(context.hostContext, params);
|
|
1055
1073
|
context.notifyContextChange(params);
|
|
1056
1074
|
break;
|
|
1075
|
+
case 'ui/notifications/tool-cancelled':
|
|
1057
1076
|
case 'ui/notifications/cancelled':
|
|
1058
1077
|
window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
|
|
1059
1078
|
break;
|
|
@@ -1118,7 +1137,7 @@ var ExtAppsAdapter = {
|
|
|
1118
1137
|
var id = ++this.requestId;
|
|
1119
1138
|
var params = {
|
|
1120
1139
|
appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
|
|
1121
|
-
appCapabilities: { tools: { listChanged: false } },
|
|
1140
|
+
appCapabilities: { tools: { listChanged: false }, availableDisplayModes: ${JSON.stringify(MCP_APPS_DISPLAY_MODES)} },
|
|
1122
1141
|
protocolVersion: '2024-11-05'
|
|
1123
1142
|
};
|
|
1124
1143
|
|
|
@@ -1186,20 +1205,25 @@ var ExtAppsAdapter = {
|
|
|
1186
1205
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
1187
1206
|
}
|
|
1188
1207
|
// Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
|
|
1189
|
-
return this.sendRequest('tools/call', { name: name, arguments: args || {} });
|
|
1208
|
+
return this.sendRequest('tools/call', { name: name, arguments: args || {}, _meta: { '${WIDGET_CALL_META_KEY}': true } });
|
|
1190
1209
|
},
|
|
1191
1210
|
sendMessage: function(context, content) {
|
|
1192
1211
|
return this.sendRequest('ui/message', { content: content });
|
|
1193
1212
|
},
|
|
1194
1213
|
openLink: function(context, url) {
|
|
1195
|
-
if (!this.hostCapabilities.openLink) {
|
|
1214
|
+
if (!this.hostCapabilities.openLinks && !this.hostCapabilities.openLink) {
|
|
1196
1215
|
window.open(url, '_blank', 'noopener,noreferrer');
|
|
1197
1216
|
return Promise.resolve();
|
|
1198
1217
|
}
|
|
1199
|
-
return this.sendRequest('ui/
|
|
1218
|
+
return this.sendRequest('ui/open-link', { url: url });
|
|
1200
1219
|
},
|
|
1201
1220
|
requestDisplayMode: function(context, mode) {
|
|
1202
|
-
|
|
1221
|
+
// Ask only for a mode the host offers; it answers with the mode it actually set.
|
|
1222
|
+
var offered = context.hostContext.availableDisplayModes;
|
|
1223
|
+
if (!Array.isArray(offered) || offered.indexOf(mode) === -1) {
|
|
1224
|
+
return Promise.reject(new Error('Display mode "' + mode + '" is not available on this host'));
|
|
1225
|
+
}
|
|
1226
|
+
return this.sendRequest('ui/request-display-mode', { mode: mode });
|
|
1203
1227
|
},
|
|
1204
1228
|
setSize: function(context, size) {
|
|
1205
1229
|
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
@@ -1220,14 +1244,22 @@ var ExtAppsAdapter = {
|
|
|
1220
1244
|
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1221
1245
|
},
|
|
1222
1246
|
requestClose: function(context) {
|
|
1223
|
-
|
|
1247
|
+
// The host decides whether to tear the view down; it answers with ui/resource-teardown if it does.
|
|
1248
|
+
return this.sendNotification('ui/notifications/request-teardown', {});
|
|
1224
1249
|
},
|
|
1225
1250
|
// Extended ext-apps methods (full specification)
|
|
1226
1251
|
updateModelContext: function(context, data, merge) {
|
|
1227
|
-
if (!this.hostCapabilities.modelContextUpdate) {
|
|
1252
|
+
if (!this.hostCapabilities.updateModelContext && !this.hostCapabilities.modelContextUpdate) {
|
|
1228
1253
|
return Promise.reject(new Error('Model context update not supported'));
|
|
1229
1254
|
}
|
|
1230
|
-
|
|
1255
|
+
// The host keeps only the latest update, so an object merges into the last one here.
|
|
1256
|
+
var isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
|
|
1257
|
+
var modelContext = isObject && merge !== false ? Object.assign({}, this.modelContext, data) : data;
|
|
1258
|
+
this.modelContext = isObject ? modelContext : null;
|
|
1259
|
+
var text = typeof modelContext === 'string' ? modelContext : JSON.stringify(modelContext);
|
|
1260
|
+
var params = { content: [{ type: 'text', text: text }] };
|
|
1261
|
+
if (isObject) params.structuredContent = modelContext;
|
|
1262
|
+
return this.sendRequest('ui/update-model-context', params);
|
|
1231
1263
|
},
|
|
1232
1264
|
log: function(context, level, message, data) {
|
|
1233
1265
|
if (!this.hostCapabilities.logging) {
|
|
@@ -1236,7 +1268,10 @@ var ExtAppsAdapter = {
|
|
|
1236
1268
|
logFn('[Widget] ' + message, data);
|
|
1237
1269
|
return Promise.resolve();
|
|
1238
1270
|
}
|
|
1239
|
-
return this.
|
|
1271
|
+
return this.sendNotification('notifications/message', {
|
|
1272
|
+
level: level === 'warn' ? 'warning' : level,
|
|
1273
|
+
data: data === undefined ? message : { message: message, data: data }
|
|
1274
|
+
});
|
|
1240
1275
|
},
|
|
1241
1276
|
registerTool: function(context, name, description, inputSchema) {
|
|
1242
1277
|
if (!this.hostCapabilities.widgetTools) {
|
|
@@ -1633,8 +1668,10 @@ FrontMcpBridge.prototype.openLink = function(url) {
|
|
|
1633
1668
|
FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
1634
1669
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1635
1670
|
var self = this;
|
|
1636
|
-
return this._adapter.requestDisplayMode(this._context, mode).then(function() {
|
|
1637
|
-
|
|
1671
|
+
return this._adapter.requestDisplayMode(this._context, mode).then(function(result) {
|
|
1672
|
+
// The host may set another mode than the one asked for, e.g. when it refuses the change.
|
|
1673
|
+
var setMode = result && result.mode;
|
|
1674
|
+
if (${JSON.stringify(MCP_APPS_DISPLAY_MODES)}.indexOf(setMode) !== -1) self._context.hostContext.displayMode = setMode;
|
|
1638
1675
|
});
|
|
1639
1676
|
};
|
|
1640
1677
|
|
|
@@ -2009,7 +2046,7 @@ function hasSizing(sizing) {
|
|
|
2009
2046
|
return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
|
|
2010
2047
|
}
|
|
2011
2048
|
function buildDataInjectionScript(options) {
|
|
2012
|
-
const { toolName, input, output, structuredContent, sizing } = options;
|
|
2049
|
+
const { toolName, input, output, structuredContent, sizing, displayMode } = options;
|
|
2013
2050
|
const lines = [
|
|
2014
2051
|
`window.__mcpAppsEnabled = true;`,
|
|
2015
2052
|
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
@@ -2020,6 +2057,9 @@ function buildDataInjectionScript(options) {
|
|
|
2020
2057
|
if (hasSizing(sizing)) {
|
|
2021
2058
|
lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
|
|
2022
2059
|
}
|
|
2060
|
+
if (displayMode && displayMode !== "inline") {
|
|
2061
|
+
lines.push(`window.__mcpDisplayMode = ${safeJsonForScript(displayMode)};`);
|
|
2062
|
+
}
|
|
2023
2063
|
return `<script>
|
|
2024
2064
|
${lines.join("\n")}
|
|
2025
2065
|
</script>`;
|
|
@@ -2111,7 +2151,8 @@ function resolveDataInjectionScript(args) {
|
|
|
2111
2151
|
input: args.input,
|
|
2112
2152
|
output: args.output,
|
|
2113
2153
|
structuredContent: args.structuredContent,
|
|
2114
|
-
sizing: args.sizing
|
|
2154
|
+
sizing: args.sizing,
|
|
2155
|
+
displayMode: args.displayMode
|
|
2115
2156
|
});
|
|
2116
2157
|
}
|
|
2117
2158
|
function buildShell(content, config) {
|
|
@@ -2124,7 +2165,8 @@ function buildShell(content, config) {
|
|
|
2124
2165
|
structuredContent,
|
|
2125
2166
|
includeBridge = true,
|
|
2126
2167
|
title,
|
|
2127
|
-
sizing
|
|
2168
|
+
sizing,
|
|
2169
|
+
displayMode
|
|
2128
2170
|
} = config;
|
|
2129
2171
|
const { customShell, dataInjection } = config;
|
|
2130
2172
|
const dataScript = resolveDataInjectionScript({
|
|
@@ -2133,7 +2175,8 @@ function buildShell(content, config) {
|
|
|
2133
2175
|
input,
|
|
2134
2176
|
output,
|
|
2135
2177
|
structuredContent,
|
|
2136
|
-
sizing
|
|
2178
|
+
sizing,
|
|
2179
|
+
displayMode
|
|
2137
2180
|
});
|
|
2138
2181
|
if (!withShell) {
|
|
2139
2182
|
const html3 = `${dataScript}
|
|
@@ -2154,6 +2197,7 @@ ${content}`;
|
|
|
2154
2197
|
includeBridge,
|
|
2155
2198
|
title,
|
|
2156
2199
|
sizing,
|
|
2200
|
+
displayMode,
|
|
2157
2201
|
dataInjection
|
|
2158
2202
|
});
|
|
2159
2203
|
}
|
|
@@ -2209,7 +2253,8 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
2209
2253
|
input: ctx.input,
|
|
2210
2254
|
output: ctx.output,
|
|
2211
2255
|
structuredContent: ctx.structuredContent,
|
|
2212
|
-
sizing: ctx.sizing
|
|
2256
|
+
sizing: ctx.sizing,
|
|
2257
|
+
displayMode: ctx.displayMode
|
|
2213
2258
|
});
|
|
2214
2259
|
const sizingStyle = buildSizingStyleTag(ctx.sizing);
|
|
2215
2260
|
const dataWithSizing = sizingStyle ? `${dataScript}
|
|
@@ -3048,7 +3093,7 @@ function noticeStringResult(toolName, logger) {
|
|
|
3048
3093
|
if (noticedStringResultTools.has(toolName)) return;
|
|
3049
3094
|
noticedStringResultTools.add(toolName);
|
|
3050
3095
|
logger.warn(
|
|
3051
|
-
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup.
|
|
3096
|
+
`[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}`
|
|
3052
3097
|
);
|
|
3053
3098
|
}
|
|
3054
3099
|
function renderUnwrappedResult(rawResult, options) {
|
|
@@ -3107,6 +3152,7 @@ function renderToolTemplate(options) {
|
|
|
3107
3152
|
includeBridge: true,
|
|
3108
3153
|
resolver,
|
|
3109
3154
|
sizing,
|
|
3155
|
+
displayMode: options.displayMode,
|
|
3110
3156
|
csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
|
|
3111
3157
|
};
|
|
3112
3158
|
let html2;
|
|
@@ -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
|
|
|
@@ -1329,7 +1366,9 @@ var BRIDGE_SCRIPT_TAGS = {
|
|
|
1329
1366
|
};
|
|
1330
1367
|
export {
|
|
1331
1368
|
BRIDGE_SCRIPT_TAGS,
|
|
1369
|
+
MCP_APPS_DISPLAY_MODES,
|
|
1332
1370
|
UNIVERSAL_BRIDGE_SCRIPT,
|
|
1371
|
+
WIDGET_CALL_META_KEY,
|
|
1333
1372
|
generateBridgeIIFE,
|
|
1334
1373
|
generatePlatformBundle
|
|
1335
1374
|
};
|