@frontmcp/uipack 1.8.6 → 1.9.0
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 +224 -38
- package/adapters/markdown.d.ts +9 -0
- package/adapters/markdown.d.ts.map +1 -0
- package/adapters/template-renderer.d.ts +8 -0
- package/adapters/template-renderer.d.ts.map +1 -1
- package/bridge-runtime/iife-generator.d.ts.map +1 -1
- package/bridge-runtime/index.js +81 -15
- package/component/index.js +100 -29
- package/esm/adapters/index.mjs +227 -38
- package/esm/bridge-runtime/index.mjs +84 -15
- package/esm/component/index.mjs +103 -29
- package/esm/index.mjs +227 -38
- package/esm/package.json +2 -2
- package/esm/resolver/index.mjs +3 -0
- package/esm/shell/index.mjs +100 -26
- package/esm/types/index.mjs +3 -0
- package/esm/typings/index.mjs +3 -0
- package/esm/utils/index.mjs +3 -0
- package/index.js +224 -38
- package/package.json +2 -2
- package/shell/csp.d.ts +6 -1
- package/shell/csp.d.ts.map +1 -1
- package/shell/index.js +97 -26
- package/types/ui-config.d.ts +1 -1
package/esm/component/index.mjs
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
|
|
2
|
+
const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
|
|
3
|
+
// __frontmcpCreateRequire
|
|
1
4
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
5
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
6
|
}) : x)(function(x) {
|
|
@@ -535,8 +538,8 @@ if (__root) {
|
|
|
535
538
|
configurable: true, enumerable: true
|
|
536
539
|
});
|
|
537
540
|
}
|
|
538
|
-
// 2. Try injected data globals
|
|
539
|
-
if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
|
|
541
|
+
// 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
|
|
542
|
+
if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
|
|
540
543
|
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
541
544
|
var __bridge = window.FrontMcpBridge;
|
|
542
545
|
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
@@ -788,7 +791,7 @@ createRoot(document.getElementById('root'))
|
|
|
788
791
|
return `${componentImport}
|
|
789
792
|
import React from 'react';
|
|
790
793
|
import { createRoot } from 'react-dom/client';
|
|
791
|
-
const output = window.__mcpToolOutput;
|
|
794
|
+
const output = window.__mcpToolOutput || {};
|
|
792
795
|
const props = ${propsCode};
|
|
793
796
|
createRoot(document.getElementById('root'))
|
|
794
797
|
.render(React.createElement(Component, props));`;
|
|
@@ -1092,12 +1095,38 @@ function __applyHostTheme(theme) {
|
|
|
1092
1095
|
} catch (e) {}
|
|
1093
1096
|
}
|
|
1094
1097
|
|
|
1098
|
+
function extractToolOutput(params) {
|
|
1099
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
1100
|
+
if (!params) return undefined;
|
|
1101
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
1102
|
+
var content = params.content;
|
|
1103
|
+
if (Array.isArray(content)) {
|
|
1104
|
+
for (var i = 0; i < content.length; i++) {
|
|
1105
|
+
var item = content[i];
|
|
1106
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
1107
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return content;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1095
1114
|
function readInjectedData() {
|
|
1096
1115
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
1097
1116
|
if (typeof window !== 'undefined') {
|
|
1098
1117
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
1099
1118
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
1100
1119
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
1120
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
1121
|
+
// (toolOutput is the tool's structuredContent).
|
|
1122
|
+
var oa = window.openai;
|
|
1123
|
+
if (oa && typeof oa === 'object') {
|
|
1124
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
1125
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
1126
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
1127
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1101
1130
|
}
|
|
1102
1131
|
return data;
|
|
1103
1132
|
}
|
|
@@ -1146,7 +1175,24 @@ var OpenAIAdapter = {
|
|
|
1146
1175
|
if (sdk.displayMode) {
|
|
1147
1176
|
context.hostContext.displayMode = sdk.displayMode;
|
|
1148
1177
|
}
|
|
1149
|
-
//
|
|
1178
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
1179
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
1180
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
1181
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
1182
|
+
context.toolInput = globals.toolInput;
|
|
1183
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
1184
|
+
}
|
|
1185
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
1186
|
+
context.structuredContent = globals.toolOutput;
|
|
1187
|
+
context.notifyToolResult(globals.toolOutput);
|
|
1188
|
+
}
|
|
1189
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
1190
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
1191
|
+
}
|
|
1192
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
1193
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1150
1196
|
return Promise.resolve();
|
|
1151
1197
|
},
|
|
1152
1198
|
callTool: function(context, name, args) {
|
|
@@ -1293,9 +1339,10 @@ var ExtAppsAdapter = {
|
|
|
1293
1339
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
1294
1340
|
break;
|
|
1295
1341
|
case 'ui/notifications/tool-result':
|
|
1296
|
-
|
|
1342
|
+
var resultOutput = extractToolOutput(params);
|
|
1343
|
+
context.toolOutput = resultOutput;
|
|
1297
1344
|
context.structuredContent = params.structuredContent;
|
|
1298
|
-
context.notifyToolResult(
|
|
1345
|
+
context.notifyToolResult(resultOutput);
|
|
1299
1346
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
1300
1347
|
break;
|
|
1301
1348
|
case 'ui/notifications/host-context-changed':
|
|
@@ -1349,6 +1396,15 @@ var ExtAppsAdapter = {
|
|
|
1349
1396
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
1350
1397
|
});
|
|
1351
1398
|
},
|
|
1399
|
+
sendNotification: function(method, params) {
|
|
1400
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
1401
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1402
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
1403
|
+
}
|
|
1404
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
1405
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
1406
|
+
return Promise.resolve();
|
|
1407
|
+
},
|
|
1352
1408
|
sendHandshake: function(context) {
|
|
1353
1409
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
1354
1410
|
// been established yet. The response from the host will establish
|
|
@@ -1414,7 +1470,7 @@ var ExtAppsAdapter = {
|
|
|
1414
1470
|
var pending = this.pendingSize;
|
|
1415
1471
|
if (!pending) return;
|
|
1416
1472
|
this.pendingSize = null;
|
|
1417
|
-
this.
|
|
1473
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
1418
1474
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
1419
1475
|
}, function(err) {
|
|
1420
1476
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -1441,13 +1497,11 @@ var ExtAppsAdapter = {
|
|
|
1441
1497
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
1442
1498
|
},
|
|
1443
1499
|
setSize: function(context, size) {
|
|
1444
|
-
//
|
|
1445
|
-
//
|
|
1446
|
-
var params = {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
aspectRatio: size && size.aspectRatio
|
|
1450
|
-
};
|
|
1500
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
1501
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
1502
|
+
var params = {};
|
|
1503
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
1504
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
1451
1505
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1452
1506
|
// No host origin yet: keep only the latest size and send it when the
|
|
1453
1507
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -1458,7 +1512,7 @@ var ExtAppsAdapter = {
|
|
|
1458
1512
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
1459
1513
|
});
|
|
1460
1514
|
}
|
|
1461
|
-
return this.
|
|
1515
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1462
1516
|
},
|
|
1463
1517
|
requestClose: function(context) {
|
|
1464
1518
|
return this.sendRequest('ui/close', {});
|
|
@@ -1881,7 +1935,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1881
1935
|
|
|
1882
1936
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1883
1937
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1884
|
-
// ext-apps sends ui/
|
|
1938
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1885
1939
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1886
1940
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1887
1941
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1983,7 +2037,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1983
2037
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1984
2038
|
|
|
1985
2039
|
// Add spinner for buttons
|
|
1986
|
-
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
|
2040
|
+
var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" style="width:1em;height:1em;vertical-align:-0.125em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
|
|
1987
2041
|
if (target.tagName === 'BUTTON') {
|
|
1988
2042
|
target.innerHTML = spinner + 'Loading...';
|
|
1989
2043
|
}
|
|
@@ -2064,7 +2118,22 @@ function minifyJS(code) {
|
|
|
2064
2118
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
2065
2119
|
return code;
|
|
2066
2120
|
}
|
|
2067
|
-
|
|
2121
|
+
const out = [];
|
|
2122
|
+
let inBlockComment = false;
|
|
2123
|
+
for (const raw of code.split("\n")) {
|
|
2124
|
+
const line = raw.trim();
|
|
2125
|
+
if (inBlockComment) {
|
|
2126
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
2130
|
+
if (line.startsWith("/*")) {
|
|
2131
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
2132
|
+
continue;
|
|
2133
|
+
}
|
|
2134
|
+
out.push(line);
|
|
2135
|
+
}
|
|
2136
|
+
return out.join("\n");
|
|
2068
2137
|
}
|
|
2069
2138
|
function generatePlatformBundle(platform, options = {}) {
|
|
2070
2139
|
const platformAdapters = {
|
|
@@ -2119,11 +2188,8 @@ function buildCSPDirectives(csp) {
|
|
|
2119
2188
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
2120
2189
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
2121
2190
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
} else {
|
|
2125
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
2126
|
-
}
|
|
2191
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
2192
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
2127
2193
|
directives.push("object-src 'self' data:");
|
|
2128
2194
|
return directives;
|
|
2129
2195
|
}
|
|
@@ -2132,26 +2198,34 @@ function buildCSPMetaTag(csp) {
|
|
|
2132
2198
|
const content = directives.join("; ");
|
|
2133
2199
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
2134
2200
|
}
|
|
2201
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
2135
2202
|
function validateCSPDomain(domain) {
|
|
2136
|
-
if (domain
|
|
2137
|
-
|
|
2138
|
-
|
|
2203
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
2204
|
+
if (/[?#]/.test(domain)) return false;
|
|
2205
|
+
if (domain.includes("*")) {
|
|
2206
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
2207
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
2139
2208
|
}
|
|
2140
2209
|
try {
|
|
2141
2210
|
const url = new URL(domain);
|
|
2142
|
-
|
|
2211
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
2212
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
2143
2213
|
} catch {
|
|
2144
2214
|
return false;
|
|
2145
2215
|
}
|
|
2146
2216
|
}
|
|
2217
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
2147
2218
|
function sanitizeCSPDomains(domains) {
|
|
2148
2219
|
if (!domains) return [];
|
|
2149
2220
|
const valid = [];
|
|
2150
2221
|
for (const domain of domains) {
|
|
2151
2222
|
if (validateCSPDomain(domain)) {
|
|
2152
2223
|
valid.push(domain);
|
|
2153
|
-
} else {
|
|
2154
|
-
|
|
2224
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
2225
|
+
reportedInvalidDomains.add(domain);
|
|
2226
|
+
console.warn(
|
|
2227
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
2228
|
+
);
|
|
2155
2229
|
}
|
|
2156
2230
|
}
|
|
2157
2231
|
return valid;
|