@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/component/index.js
CHANGED
|
@@ -564,8 +564,8 @@ if (__root) {
|
|
|
564
564
|
configurable: true, enumerable: true
|
|
565
565
|
});
|
|
566
566
|
}
|
|
567
|
-
// 2. Try injected data globals
|
|
568
|
-
if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
|
|
567
|
+
// 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
|
|
568
|
+
if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
|
|
569
569
|
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
570
570
|
var __bridge = window.FrontMcpBridge;
|
|
571
571
|
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
@@ -817,7 +817,7 @@ createRoot(document.getElementById('root'))
|
|
|
817
817
|
return `${componentImport}
|
|
818
818
|
import React from 'react';
|
|
819
819
|
import { createRoot } from 'react-dom/client';
|
|
820
|
-
const output = window.__mcpToolOutput;
|
|
820
|
+
const output = window.__mcpToolOutput || {};
|
|
821
821
|
const props = ${propsCode};
|
|
822
822
|
createRoot(document.getElementById('root'))
|
|
823
823
|
.render(React.createElement(Component, props));`;
|
|
@@ -1121,12 +1121,38 @@ function __applyHostTheme(theme) {
|
|
|
1121
1121
|
} catch (e) {}
|
|
1122
1122
|
}
|
|
1123
1123
|
|
|
1124
|
+
function extractToolOutput(params) {
|
|
1125
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
1126
|
+
if (!params) return undefined;
|
|
1127
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
1128
|
+
var content = params.content;
|
|
1129
|
+
if (Array.isArray(content)) {
|
|
1130
|
+
for (var i = 0; i < content.length; i++) {
|
|
1131
|
+
var item = content[i];
|
|
1132
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
1133
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return content;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1124
1140
|
function readInjectedData() {
|
|
1125
1141
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
1126
1142
|
if (typeof window !== 'undefined') {
|
|
1127
1143
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
1128
1144
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
1129
1145
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
1146
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
1147
|
+
// (toolOutput is the tool's structuredContent).
|
|
1148
|
+
var oa = window.openai;
|
|
1149
|
+
if (oa && typeof oa === 'object') {
|
|
1150
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
1151
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
1152
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
1153
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1130
1156
|
}
|
|
1131
1157
|
return data;
|
|
1132
1158
|
}
|
|
@@ -1175,7 +1201,24 @@ var OpenAIAdapter = {
|
|
|
1175
1201
|
if (sdk.displayMode) {
|
|
1176
1202
|
context.hostContext.displayMode = sdk.displayMode;
|
|
1177
1203
|
}
|
|
1178
|
-
//
|
|
1204
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
1205
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
1206
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
1207
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
1208
|
+
context.toolInput = globals.toolInput;
|
|
1209
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
1210
|
+
}
|
|
1211
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
1212
|
+
context.structuredContent = globals.toolOutput;
|
|
1213
|
+
context.notifyToolResult(globals.toolOutput);
|
|
1214
|
+
}
|
|
1215
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
1216
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
1217
|
+
}
|
|
1218
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
1219
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1179
1222
|
return Promise.resolve();
|
|
1180
1223
|
},
|
|
1181
1224
|
callTool: function(context, name, args) {
|
|
@@ -1322,9 +1365,10 @@ var ExtAppsAdapter = {
|
|
|
1322
1365
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
1323
1366
|
break;
|
|
1324
1367
|
case 'ui/notifications/tool-result':
|
|
1325
|
-
|
|
1368
|
+
var resultOutput = extractToolOutput(params);
|
|
1369
|
+
context.toolOutput = resultOutput;
|
|
1326
1370
|
context.structuredContent = params.structuredContent;
|
|
1327
|
-
context.notifyToolResult(
|
|
1371
|
+
context.notifyToolResult(resultOutput);
|
|
1328
1372
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
1329
1373
|
break;
|
|
1330
1374
|
case 'ui/notifications/host-context-changed':
|
|
@@ -1378,6 +1422,15 @@ var ExtAppsAdapter = {
|
|
|
1378
1422
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
1379
1423
|
});
|
|
1380
1424
|
},
|
|
1425
|
+
sendNotification: function(method, params) {
|
|
1426
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
1427
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1428
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
1429
|
+
}
|
|
1430
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
1431
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
1432
|
+
return Promise.resolve();
|
|
1433
|
+
},
|
|
1381
1434
|
sendHandshake: function(context) {
|
|
1382
1435
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
1383
1436
|
// been established yet. The response from the host will establish
|
|
@@ -1443,7 +1496,7 @@ var ExtAppsAdapter = {
|
|
|
1443
1496
|
var pending = this.pendingSize;
|
|
1444
1497
|
if (!pending) return;
|
|
1445
1498
|
this.pendingSize = null;
|
|
1446
|
-
this.
|
|
1499
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
1447
1500
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
1448
1501
|
}, function(err) {
|
|
1449
1502
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -1470,13 +1523,11 @@ var ExtAppsAdapter = {
|
|
|
1470
1523
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
1471
1524
|
},
|
|
1472
1525
|
setSize: function(context, size) {
|
|
1473
|
-
//
|
|
1474
|
-
//
|
|
1475
|
-
var params = {
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
aspectRatio: size && size.aspectRatio
|
|
1479
|
-
};
|
|
1526
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
1527
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
1528
|
+
var params = {};
|
|
1529
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
1530
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
1480
1531
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
1481
1532
|
// No host origin yet: keep only the latest size and send it when the
|
|
1482
1533
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -1487,7 +1538,7 @@ var ExtAppsAdapter = {
|
|
|
1487
1538
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
1488
1539
|
});
|
|
1489
1540
|
}
|
|
1490
|
-
return this.
|
|
1541
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
1491
1542
|
},
|
|
1492
1543
|
requestClose: function(context) {
|
|
1493
1544
|
return this.sendRequest('ui/close', {});
|
|
@@ -1910,7 +1961,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1910
1961
|
|
|
1911
1962
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1912
1963
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1913
|
-
// ext-apps sends ui/
|
|
1964
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1914
1965
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1915
1966
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1916
1967
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -2012,7 +2063,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
2012
2063
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
2013
2064
|
|
|
2014
2065
|
// Add spinner for buttons
|
|
2015
|
-
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>';
|
|
2066
|
+
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>';
|
|
2016
2067
|
if (target.tagName === 'BUTTON') {
|
|
2017
2068
|
target.innerHTML = spinner + 'Loading...';
|
|
2018
2069
|
}
|
|
@@ -2093,7 +2144,22 @@ function minifyJS(code) {
|
|
|
2093
2144
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
2094
2145
|
return code;
|
|
2095
2146
|
}
|
|
2096
|
-
|
|
2147
|
+
const out = [];
|
|
2148
|
+
let inBlockComment = false;
|
|
2149
|
+
for (const raw of code.split("\n")) {
|
|
2150
|
+
const line = raw.trim();
|
|
2151
|
+
if (inBlockComment) {
|
|
2152
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
2156
|
+
if (line.startsWith("/*")) {
|
|
2157
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
out.push(line);
|
|
2161
|
+
}
|
|
2162
|
+
return out.join("\n");
|
|
2097
2163
|
}
|
|
2098
2164
|
function generatePlatformBundle(platform, options = {}) {
|
|
2099
2165
|
const platformAdapters = {
|
|
@@ -2148,11 +2214,8 @@ function buildCSPDirectives(csp) {
|
|
|
2148
2214
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
2149
2215
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
2150
2216
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
} else {
|
|
2154
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
2155
|
-
}
|
|
2217
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
2218
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
2156
2219
|
directives.push("object-src 'self' data:");
|
|
2157
2220
|
return directives;
|
|
2158
2221
|
}
|
|
@@ -2161,26 +2224,34 @@ function buildCSPMetaTag(csp) {
|
|
|
2161
2224
|
const content = directives.join("; ");
|
|
2162
2225
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
2163
2226
|
}
|
|
2227
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
2164
2228
|
function validateCSPDomain(domain) {
|
|
2165
|
-
if (domain
|
|
2166
|
-
|
|
2167
|
-
|
|
2229
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
2230
|
+
if (/[?#]/.test(domain)) return false;
|
|
2231
|
+
if (domain.includes("*")) {
|
|
2232
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
2233
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
2168
2234
|
}
|
|
2169
2235
|
try {
|
|
2170
2236
|
const url = new URL(domain);
|
|
2171
|
-
|
|
2237
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
2238
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
2172
2239
|
} catch {
|
|
2173
2240
|
return false;
|
|
2174
2241
|
}
|
|
2175
2242
|
}
|
|
2243
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
2176
2244
|
function sanitizeCSPDomains(domains) {
|
|
2177
2245
|
if (!domains) return [];
|
|
2178
2246
|
const valid = [];
|
|
2179
2247
|
for (const domain of domains) {
|
|
2180
2248
|
if (validateCSPDomain(domain)) {
|
|
2181
2249
|
valid.push(domain);
|
|
2182
|
-
} else {
|
|
2183
|
-
|
|
2250
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
2251
|
+
reportedInvalidDomains.add(domain);
|
|
2252
|
+
console.warn(
|
|
2253
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
2254
|
+
);
|
|
2184
2255
|
}
|
|
2185
2256
|
}
|
|
2186
2257
|
return valid;
|