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