@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/shell/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
|
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
2
5
|
function generateBridgeIIFE(options = {}) {
|
|
3
6
|
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
@@ -265,12 +268,38 @@ function __applyHostTheme(theme) {
|
|
|
265
268
|
} catch (e) {}
|
|
266
269
|
}
|
|
267
270
|
|
|
271
|
+
function extractToolOutput(params) {
|
|
272
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
273
|
+
if (!params) return undefined;
|
|
274
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
275
|
+
var content = params.content;
|
|
276
|
+
if (Array.isArray(content)) {
|
|
277
|
+
for (var i = 0; i < content.length; i++) {
|
|
278
|
+
var item = content[i];
|
|
279
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
280
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return content;
|
|
285
|
+
}
|
|
286
|
+
|
|
268
287
|
function readInjectedData() {
|
|
269
288
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
270
289
|
if (typeof window !== 'undefined') {
|
|
271
290
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
272
291
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
273
292
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
293
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
294
|
+
// (toolOutput is the tool's structuredContent).
|
|
295
|
+
var oa = window.openai;
|
|
296
|
+
if (oa && typeof oa === 'object') {
|
|
297
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
298
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
299
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
300
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
274
303
|
}
|
|
275
304
|
return data;
|
|
276
305
|
}
|
|
@@ -319,7 +348,24 @@ var OpenAIAdapter = {
|
|
|
319
348
|
if (sdk.displayMode) {
|
|
320
349
|
context.hostContext.displayMode = sdk.displayMode;
|
|
321
350
|
}
|
|
322
|
-
//
|
|
351
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
352
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
353
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
354
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
355
|
+
context.toolInput = globals.toolInput;
|
|
356
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
357
|
+
}
|
|
358
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
359
|
+
context.structuredContent = globals.toolOutput;
|
|
360
|
+
context.notifyToolResult(globals.toolOutput);
|
|
361
|
+
}
|
|
362
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
363
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
364
|
+
}
|
|
365
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
366
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
323
369
|
return Promise.resolve();
|
|
324
370
|
},
|
|
325
371
|
callTool: function(context, name, args) {
|
|
@@ -466,9 +512,10 @@ var ExtAppsAdapter = {
|
|
|
466
512
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
467
513
|
break;
|
|
468
514
|
case 'ui/notifications/tool-result':
|
|
469
|
-
|
|
515
|
+
var resultOutput = extractToolOutput(params);
|
|
516
|
+
context.toolOutput = resultOutput;
|
|
470
517
|
context.structuredContent = params.structuredContent;
|
|
471
|
-
context.notifyToolResult(
|
|
518
|
+
context.notifyToolResult(resultOutput);
|
|
472
519
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
473
520
|
break;
|
|
474
521
|
case 'ui/notifications/host-context-changed':
|
|
@@ -522,6 +569,15 @@ var ExtAppsAdapter = {
|
|
|
522
569
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
523
570
|
});
|
|
524
571
|
},
|
|
572
|
+
sendNotification: function(method, params) {
|
|
573
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
574
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
575
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
576
|
+
}
|
|
577
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
578
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
579
|
+
return Promise.resolve();
|
|
580
|
+
},
|
|
525
581
|
sendHandshake: function(context) {
|
|
526
582
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
527
583
|
// been established yet. The response from the host will establish
|
|
@@ -587,7 +643,7 @@ var ExtAppsAdapter = {
|
|
|
587
643
|
var pending = this.pendingSize;
|
|
588
644
|
if (!pending) return;
|
|
589
645
|
this.pendingSize = null;
|
|
590
|
-
this.
|
|
646
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
591
647
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
592
648
|
}, function(err) {
|
|
593
649
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -614,13 +670,11 @@ var ExtAppsAdapter = {
|
|
|
614
670
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
615
671
|
},
|
|
616
672
|
setSize: function(context, size) {
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
var params = {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
aspectRatio: size && size.aspectRatio
|
|
623
|
-
};
|
|
673
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
674
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
675
|
+
var params = {};
|
|
676
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
677
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
624
678
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
625
679
|
// No host origin yet: keep only the latest size and send it when the
|
|
626
680
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -631,7 +685,7 @@ var ExtAppsAdapter = {
|
|
|
631
685
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
632
686
|
});
|
|
633
687
|
}
|
|
634
|
-
return this.
|
|
688
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
635
689
|
},
|
|
636
690
|
requestClose: function(context) {
|
|
637
691
|
return this.sendRequest('ui/close', {});
|
|
@@ -1054,7 +1108,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1054
1108
|
|
|
1055
1109
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1056
1110
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1057
|
-
// ext-apps sends ui/
|
|
1111
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1058
1112
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1059
1113
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1060
1114
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1156,7 +1210,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1156
1210
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1157
1211
|
|
|
1158
1212
|
// Add spinner for buttons
|
|
1159
|
-
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>';
|
|
1213
|
+
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>';
|
|
1160
1214
|
if (target.tagName === 'BUTTON') {
|
|
1161
1215
|
target.innerHTML = spinner + 'Loading...';
|
|
1162
1216
|
}
|
|
@@ -1237,7 +1291,22 @@ function minifyJS(code) {
|
|
|
1237
1291
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1238
1292
|
return code;
|
|
1239
1293
|
}
|
|
1240
|
-
|
|
1294
|
+
const out = [];
|
|
1295
|
+
let inBlockComment = false;
|
|
1296
|
+
for (const raw of code.split("\n")) {
|
|
1297
|
+
const line = raw.trim();
|
|
1298
|
+
if (inBlockComment) {
|
|
1299
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1303
|
+
if (line.startsWith("/*")) {
|
|
1304
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
out.push(line);
|
|
1308
|
+
}
|
|
1309
|
+
return out.join("\n");
|
|
1241
1310
|
}
|
|
1242
1311
|
function generatePlatformBundle(platform, options = {}) {
|
|
1243
1312
|
const platformAdapters = {
|
|
@@ -1301,11 +1370,8 @@ function buildCSPDirectives(csp) {
|
|
|
1301
1370
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
1302
1371
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
1303
1372
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
} else {
|
|
1307
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
1308
|
-
}
|
|
1373
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
1374
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
1309
1375
|
directives.push("object-src 'self' data:");
|
|
1310
1376
|
return directives;
|
|
1311
1377
|
}
|
|
@@ -1314,26 +1380,34 @@ function buildCSPMetaTag(csp) {
|
|
|
1314
1380
|
const content = directives.join("; ");
|
|
1315
1381
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
1316
1382
|
}
|
|
1383
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1317
1384
|
function validateCSPDomain(domain) {
|
|
1318
|
-
if (domain
|
|
1319
|
-
|
|
1320
|
-
|
|
1385
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
1386
|
+
if (/[?#]/.test(domain)) return false;
|
|
1387
|
+
if (domain.includes("*")) {
|
|
1388
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
1389
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
1321
1390
|
}
|
|
1322
1391
|
try {
|
|
1323
1392
|
const url = new URL(domain);
|
|
1324
|
-
|
|
1393
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
1394
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
1325
1395
|
} catch {
|
|
1326
1396
|
return false;
|
|
1327
1397
|
}
|
|
1328
1398
|
}
|
|
1399
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
1329
1400
|
function sanitizeCSPDomains(domains) {
|
|
1330
1401
|
if (!domains) return [];
|
|
1331
1402
|
const valid = [];
|
|
1332
1403
|
for (const domain of domains) {
|
|
1333
1404
|
if (validateCSPDomain(domain)) {
|
|
1334
1405
|
valid.push(domain);
|
|
1335
|
-
} else {
|
|
1336
|
-
|
|
1406
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
1407
|
+
reportedInvalidDomains.add(domain);
|
|
1408
|
+
console.warn(
|
|
1409
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
1410
|
+
);
|
|
1337
1411
|
}
|
|
1338
1412
|
}
|
|
1339
1413
|
return valid;
|
package/esm/types/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
|
// libs/uipack/src/types/ui-runtime.ts
|
|
2
5
|
function isUIType(value) {
|
|
3
6
|
return typeof value === "string" && ["html", "react", "mdx", "markdown", "auto"].includes(value);
|
package/esm/typings/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
|
// libs/uipack/src/typings/types.ts
|
|
2
5
|
var DEFAULT_ALLOWED_PACKAGES = ["react", "react-dom", "react/jsx-runtime", "zod", "@frontmcp/*"];
|
|
3
6
|
var DEFAULT_TYPE_FETCHER_OPTIONS = {
|
package/esm/utils/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
|
// libs/uipack/src/utils/index.ts
|
|
2
5
|
function safeStringify(value, space) {
|
|
3
6
|
const seen = /* @__PURE__ */ new WeakSet();
|