@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/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) {
|
|
@@ -398,12 +401,38 @@ function __applyHostTheme(theme) {
|
|
|
398
401
|
} catch (e) {}
|
|
399
402
|
}
|
|
400
403
|
|
|
404
|
+
function extractToolOutput(params) {
|
|
405
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
406
|
+
if (!params) return undefined;
|
|
407
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
408
|
+
var content = params.content;
|
|
409
|
+
if (Array.isArray(content)) {
|
|
410
|
+
for (var i = 0; i < content.length; i++) {
|
|
411
|
+
var item = content[i];
|
|
412
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
413
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return content;
|
|
418
|
+
}
|
|
419
|
+
|
|
401
420
|
function readInjectedData() {
|
|
402
421
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
403
422
|
if (typeof window !== 'undefined') {
|
|
404
423
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
405
424
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
406
425
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
426
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
427
|
+
// (toolOutput is the tool's structuredContent).
|
|
428
|
+
var oa = window.openai;
|
|
429
|
+
if (oa && typeof oa === 'object') {
|
|
430
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
431
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
432
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
433
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
407
436
|
}
|
|
408
437
|
return data;
|
|
409
438
|
}
|
|
@@ -452,7 +481,24 @@ var OpenAIAdapter = {
|
|
|
452
481
|
if (sdk.displayMode) {
|
|
453
482
|
context.hostContext.displayMode = sdk.displayMode;
|
|
454
483
|
}
|
|
455
|
-
//
|
|
484
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
485
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
486
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
487
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
488
|
+
context.toolInput = globals.toolInput;
|
|
489
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
490
|
+
}
|
|
491
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
492
|
+
context.structuredContent = globals.toolOutput;
|
|
493
|
+
context.notifyToolResult(globals.toolOutput);
|
|
494
|
+
}
|
|
495
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
496
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
497
|
+
}
|
|
498
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
499
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
500
|
+
}
|
|
501
|
+
});
|
|
456
502
|
return Promise.resolve();
|
|
457
503
|
},
|
|
458
504
|
callTool: function(context, name, args) {
|
|
@@ -599,9 +645,10 @@ var ExtAppsAdapter = {
|
|
|
599
645
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
600
646
|
break;
|
|
601
647
|
case 'ui/notifications/tool-result':
|
|
602
|
-
|
|
648
|
+
var resultOutput = extractToolOutput(params);
|
|
649
|
+
context.toolOutput = resultOutput;
|
|
603
650
|
context.structuredContent = params.structuredContent;
|
|
604
|
-
context.notifyToolResult(
|
|
651
|
+
context.notifyToolResult(resultOutput);
|
|
605
652
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
606
653
|
break;
|
|
607
654
|
case 'ui/notifications/host-context-changed':
|
|
@@ -655,6 +702,15 @@ var ExtAppsAdapter = {
|
|
|
655
702
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
656
703
|
});
|
|
657
704
|
},
|
|
705
|
+
sendNotification: function(method, params) {
|
|
706
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
707
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
708
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
709
|
+
}
|
|
710
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
711
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
712
|
+
return Promise.resolve();
|
|
713
|
+
},
|
|
658
714
|
sendHandshake: function(context) {
|
|
659
715
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
660
716
|
// been established yet. The response from the host will establish
|
|
@@ -720,7 +776,7 @@ var ExtAppsAdapter = {
|
|
|
720
776
|
var pending = this.pendingSize;
|
|
721
777
|
if (!pending) return;
|
|
722
778
|
this.pendingSize = null;
|
|
723
|
-
this.
|
|
779
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
724
780
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
725
781
|
}, function(err) {
|
|
726
782
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -747,13 +803,11 @@ var ExtAppsAdapter = {
|
|
|
747
803
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
748
804
|
},
|
|
749
805
|
setSize: function(context, size) {
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
var params = {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
aspectRatio: size && size.aspectRatio
|
|
756
|
-
};
|
|
806
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
807
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
808
|
+
var params = {};
|
|
809
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
810
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
757
811
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
758
812
|
// No host origin yet: keep only the latest size and send it when the
|
|
759
813
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -764,7 +818,7 @@ var ExtAppsAdapter = {
|
|
|
764
818
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
765
819
|
});
|
|
766
820
|
}
|
|
767
|
-
return this.
|
|
821
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
768
822
|
},
|
|
769
823
|
requestClose: function(context) {
|
|
770
824
|
return this.sendRequest('ui/close', {});
|
|
@@ -1187,7 +1241,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1187
1241
|
|
|
1188
1242
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1189
1243
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1190
|
-
// ext-apps sends ui/
|
|
1244
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1191
1245
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1192
1246
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1193
1247
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1289,7 +1343,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1289
1343
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1290
1344
|
|
|
1291
1345
|
// Add spinner for buttons
|
|
1292
|
-
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>';
|
|
1346
|
+
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>';
|
|
1293
1347
|
if (target.tagName === 'BUTTON') {
|
|
1294
1348
|
target.innerHTML = spinner + 'Loading...';
|
|
1295
1349
|
}
|
|
@@ -1370,7 +1424,22 @@ function minifyJS(code) {
|
|
|
1370
1424
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1371
1425
|
return code;
|
|
1372
1426
|
}
|
|
1373
|
-
|
|
1427
|
+
const out = [];
|
|
1428
|
+
let inBlockComment = false;
|
|
1429
|
+
for (const raw of code.split("\n")) {
|
|
1430
|
+
const line = raw.trim();
|
|
1431
|
+
if (inBlockComment) {
|
|
1432
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1436
|
+
if (line.startsWith("/*")) {
|
|
1437
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
out.push(line);
|
|
1441
|
+
}
|
|
1442
|
+
return out.join("\n");
|
|
1374
1443
|
}
|
|
1375
1444
|
function generatePlatformBundle(platform, options = {}) {
|
|
1376
1445
|
const platformAdapters = {
|
|
@@ -2907,11 +2976,8 @@ function buildCSPDirectives(csp) {
|
|
|
2907
2976
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
2908
2977
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
2909
2978
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
} else {
|
|
2913
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
2914
|
-
}
|
|
2979
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
2980
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
2915
2981
|
directives.push("object-src 'self' data:");
|
|
2916
2982
|
return directives;
|
|
2917
2983
|
}
|
|
@@ -2920,26 +2986,34 @@ function buildCSPMetaTag(csp) {
|
|
|
2920
2986
|
const content = directives.join("; ");
|
|
2921
2987
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
2922
2988
|
}
|
|
2989
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
2923
2990
|
function validateCSPDomain(domain) {
|
|
2924
|
-
if (domain
|
|
2925
|
-
|
|
2926
|
-
|
|
2991
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
2992
|
+
if (/[?#]/.test(domain)) return false;
|
|
2993
|
+
if (domain.includes("*")) {
|
|
2994
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
2995
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
2927
2996
|
}
|
|
2928
2997
|
try {
|
|
2929
2998
|
const url = new URL(domain);
|
|
2930
|
-
|
|
2999
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
3000
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
2931
3001
|
} catch {
|
|
2932
3002
|
return false;
|
|
2933
3003
|
}
|
|
2934
3004
|
}
|
|
3005
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
2935
3006
|
function sanitizeCSPDomains(domains) {
|
|
2936
3007
|
if (!domains) return [];
|
|
2937
3008
|
const valid = [];
|
|
2938
3009
|
for (const domain of domains) {
|
|
2939
3010
|
if (validateCSPDomain(domain)) {
|
|
2940
3011
|
valid.push(domain);
|
|
2941
|
-
} else {
|
|
2942
|
-
|
|
3012
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
3013
|
+
reportedInvalidDomains.add(domain);
|
|
3014
|
+
console.warn(
|
|
3015
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
3016
|
+
);
|
|
2943
3017
|
}
|
|
2944
3018
|
}
|
|
2945
3019
|
return valid;
|
|
@@ -3476,8 +3550,8 @@ if (__root) {
|
|
|
3476
3550
|
configurable: true, enumerable: true
|
|
3477
3551
|
});
|
|
3478
3552
|
}
|
|
3479
|
-
// 2. Try injected data globals
|
|
3480
|
-
if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
|
|
3553
|
+
// 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
|
|
3554
|
+
if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
|
|
3481
3555
|
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
3482
3556
|
var __bridge = window.FrontMcpBridge;
|
|
3483
3557
|
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
@@ -3729,7 +3803,7 @@ createRoot(document.getElementById('root'))
|
|
|
3729
3803
|
return `${componentImport}
|
|
3730
3804
|
import React from 'react';
|
|
3731
3805
|
import { createRoot } from 'react-dom/client';
|
|
3732
|
-
const output = window.__mcpToolOutput;
|
|
3806
|
+
const output = window.__mcpToolOutput || {};
|
|
3733
3807
|
const props = ${propsCode};
|
|
3734
3808
|
createRoot(document.getElementById('root'))
|
|
3735
3809
|
.render(React.createElement(Component, props));`;
|
|
@@ -4102,6 +4176,86 @@ function wrapDetectedContent(value) {
|
|
|
4102
4176
|
}
|
|
4103
4177
|
}
|
|
4104
4178
|
|
|
4179
|
+
// libs/uipack/src/adapters/markdown.ts
|
|
4180
|
+
var SAFE_HREF = /^(https?:|mailto:|\/|#)/i;
|
|
4181
|
+
function emphasis(text) {
|
|
4182
|
+
return text.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
4183
|
+
}
|
|
4184
|
+
function inline(text) {
|
|
4185
|
+
const stash = [];
|
|
4186
|
+
const hold = (html2) => `\uE000${stash.push(html2) - 1}\uE000`;
|
|
4187
|
+
let out = escapeHtml(text.replace(/\uE000/g, "")).replace(
|
|
4188
|
+
/`([^`]+)`/g,
|
|
4189
|
+
(_m, code) => hold(`<code>${code}</code>`)
|
|
4190
|
+
);
|
|
4191
|
+
out = out.replace(
|
|
4192
|
+
/\[([^\]]+)\]\(([^)\s]+)\)/g,
|
|
4193
|
+
(_m, label, href) => SAFE_HREF.test(href) ? hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`) : label
|
|
4194
|
+
);
|
|
4195
|
+
out = emphasis(out);
|
|
4196
|
+
for (let i = 0; i < 2; i++) out = out.replace(/\uE000(\d+)\uE000/g, (_m, n) => stash[Number(n)]);
|
|
4197
|
+
return out;
|
|
4198
|
+
}
|
|
4199
|
+
function markdownToHtml(source) {
|
|
4200
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
4201
|
+
const blocks = [];
|
|
4202
|
+
let paragraph = [];
|
|
4203
|
+
let list;
|
|
4204
|
+
const flushParagraph = () => {
|
|
4205
|
+
if (paragraph.length > 0) blocks.push(`<p>${inline(paragraph.join(" "))}</p>`);
|
|
4206
|
+
paragraph = [];
|
|
4207
|
+
};
|
|
4208
|
+
const flushList = () => {
|
|
4209
|
+
if (list)
|
|
4210
|
+
blocks.push(`<${list.tag}>${list.items.map((item) => `<li>${inline(item)}</li>`).join("")}</${list.tag}>`);
|
|
4211
|
+
list = void 0;
|
|
4212
|
+
};
|
|
4213
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4214
|
+
const line = lines[i];
|
|
4215
|
+
const fence = /^\s*(`{3,})/.exec(line);
|
|
4216
|
+
if (fence) {
|
|
4217
|
+
flushParagraph();
|
|
4218
|
+
flushList();
|
|
4219
|
+
const fenceLength = fence[1].length;
|
|
4220
|
+
const isClosing = (l) => {
|
|
4221
|
+
const m = /^\s*(`{3,})\s*$/.exec(l);
|
|
4222
|
+
return m !== null && m[1].length >= fenceLength;
|
|
4223
|
+
};
|
|
4224
|
+
const code = [];
|
|
4225
|
+
for (i++; i < lines.length && !isClosing(lines[i]); i++) code.push(lines[i]);
|
|
4226
|
+
blocks.push(`<pre><code>${escapeHtml(code.join("\n"))}
|
|
4227
|
+
</code></pre>`);
|
|
4228
|
+
continue;
|
|
4229
|
+
}
|
|
4230
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
4231
|
+
if (heading) {
|
|
4232
|
+
flushParagraph();
|
|
4233
|
+
flushList();
|
|
4234
|
+
blocks.push(`<h${heading[1].length}>${inline(heading[2].trim())}</h${heading[1].length}>`);
|
|
4235
|
+
continue;
|
|
4236
|
+
}
|
|
4237
|
+
const item = /^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
4238
|
+
if (item) {
|
|
4239
|
+
flushParagraph();
|
|
4240
|
+
const tag = /\d/.test(item[1]) ? "ol" : "ul";
|
|
4241
|
+
if (list && list.tag !== tag) flushList();
|
|
4242
|
+
list ??= { tag, items: [] };
|
|
4243
|
+
list.items.push(item[2]);
|
|
4244
|
+
continue;
|
|
4245
|
+
}
|
|
4246
|
+
if (line.trim() === "") {
|
|
4247
|
+
flushParagraph();
|
|
4248
|
+
flushList();
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4251
|
+
flushList();
|
|
4252
|
+
paragraph.push(line.trim());
|
|
4253
|
+
}
|
|
4254
|
+
flushParagraph();
|
|
4255
|
+
flushList();
|
|
4256
|
+
return blocks.join("\n");
|
|
4257
|
+
}
|
|
4258
|
+
|
|
4105
4259
|
// libs/uipack/src/adapters/type-detector.ts
|
|
4106
4260
|
function detectUIType(template) {
|
|
4107
4261
|
if (template === null || template === void 0) {
|
|
@@ -4135,9 +4289,15 @@ function detectUIType(template) {
|
|
|
4135
4289
|
}
|
|
4136
4290
|
|
|
4137
4291
|
// libs/uipack/src/adapters/template-renderer.ts
|
|
4138
|
-
function buildCspConfig(resolver) {
|
|
4292
|
+
function buildCspConfig(resolver, extra) {
|
|
4139
4293
|
const cspResourceDomains = ["https://esm.sh"];
|
|
4140
4294
|
const cspConnectDomains = ["https://esm.sh"];
|
|
4295
|
+
for (const domain of extra?.resourceDomains ?? []) {
|
|
4296
|
+
if (!cspResourceDomains.includes(domain)) cspResourceDomains.push(domain);
|
|
4297
|
+
}
|
|
4298
|
+
for (const domain of extra?.connectDomains ?? []) {
|
|
4299
|
+
if (!cspConnectDomains.includes(domain)) cspConnectDomains.push(domain);
|
|
4300
|
+
}
|
|
4141
4301
|
if (resolver && "overrides" in resolver) {
|
|
4142
4302
|
const overrides = resolver.overrides;
|
|
4143
4303
|
if (overrides) {
|
|
@@ -4177,6 +4337,24 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
4177
4337
|
}
|
|
4178
4338
|
return rawResult;
|
|
4179
4339
|
}
|
|
4340
|
+
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
4341
|
+
function warnComponentReference(template, toolName, logger) {
|
|
4342
|
+
const warned = warnedComponentReferences.get(template) ?? /* @__PURE__ */ new Set();
|
|
4343
|
+
if (warned.has(toolName)) return;
|
|
4344
|
+
warned.add(toolName);
|
|
4345
|
+
warnedComponentReferences.set(template, warned);
|
|
4346
|
+
logger.warn(
|
|
4347
|
+
`[FrontMCP] Tool "${toolName}": \`ui.template\` is a React component reference, which cannot be bundled (there is no source file to compile), so the widget would render an empty page. Use \`template: { file: './widget.tsx' }\` instead.`
|
|
4348
|
+
);
|
|
4349
|
+
}
|
|
4350
|
+
function isReactElement(value) {
|
|
4351
|
+
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
4352
|
+
}
|
|
4353
|
+
function isDefinitelyReactComponent(template) {
|
|
4354
|
+
if (typeof template !== "function") return false;
|
|
4355
|
+
const proto = template.prototype;
|
|
4356
|
+
return typeof proto?.render === "function" || template["$$typeof"] !== void 0;
|
|
4357
|
+
}
|
|
4180
4358
|
function renderToolTemplate(options) {
|
|
4181
4359
|
const { toolName, input, output, template, resolver, platformType, sizing } = options;
|
|
4182
4360
|
const uiType = detectUIType(template);
|
|
@@ -4187,30 +4365,41 @@ function renderToolTemplate(options) {
|
|
|
4187
4365
|
output,
|
|
4188
4366
|
includeBridge: true,
|
|
4189
4367
|
resolver,
|
|
4190
|
-
sizing
|
|
4368
|
+
sizing,
|
|
4369
|
+
csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
|
|
4191
4370
|
};
|
|
4192
4371
|
let html2;
|
|
4193
4372
|
let hash = "";
|
|
4194
4373
|
let size = 0;
|
|
4195
4374
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
4196
|
-
const cspConfig = buildCspConfig(resolver);
|
|
4197
4375
|
const result = renderComponent(
|
|
4198
4376
|
{ source: template, inlineReact: resourceMode === "inline" },
|
|
4199
|
-
{ ...shellConfig, csp:
|
|
4377
|
+
{ ...shellConfig, csp: buildCspConfig(resolver, options.csp) }
|
|
4200
4378
|
);
|
|
4201
4379
|
html2 = result.html;
|
|
4202
4380
|
hash = result.hash;
|
|
4203
4381
|
size = result.size;
|
|
4204
4382
|
} else if (typeof template === "function") {
|
|
4205
|
-
|
|
4383
|
+
const helpers = createTemplateHelpers();
|
|
4384
|
+
const ctx = { input: input ?? {}, output: output ?? {}, helpers };
|
|
4385
|
+
let rawResult;
|
|
4386
|
+
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
4387
|
+
if (isHtmlBuilder) {
|
|
4388
|
+
try {
|
|
4389
|
+
rawResult = template(ctx);
|
|
4390
|
+
isHtmlBuilder = !isReactElement(rawResult);
|
|
4391
|
+
} catch (error) {
|
|
4392
|
+
if (uiType !== "react") throw error;
|
|
4393
|
+
isHtmlBuilder = false;
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
if (!isHtmlBuilder) {
|
|
4397
|
+
warnComponentReference(template, toolName, options.logger ?? console);
|
|
4206
4398
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
4207
4399
|
html2 = shellResult.html;
|
|
4208
4400
|
hash = shellResult.hash;
|
|
4209
4401
|
size = shellResult.size;
|
|
4210
4402
|
} else {
|
|
4211
|
-
const helpers = createTemplateHelpers();
|
|
4212
|
-
const ctx = { input, output, helpers };
|
|
4213
|
-
const rawResult = template(ctx);
|
|
4214
4403
|
const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
|
|
4215
4404
|
if (wrapped) {
|
|
4216
4405
|
html2 = wrapped;
|
|
@@ -4222,7 +4411,7 @@ function renderToolTemplate(options) {
|
|
|
4222
4411
|
}
|
|
4223
4412
|
}
|
|
4224
4413
|
} else if (typeof template === "string") {
|
|
4225
|
-
const shellResult = buildShell(template, shellConfig);
|
|
4414
|
+
const shellResult = buildShell(uiType === "markdown" ? markdownToHtml(template) : template, shellConfig);
|
|
4226
4415
|
html2 = shellResult.html;
|
|
4227
4416
|
hash = shellResult.hash;
|
|
4228
4417
|
size = shellResult.size;
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"typescript": "^5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@frontmcp/lazy-zod": "1.
|
|
64
|
+
"@frontmcp/lazy-zod": "1.9.0",
|
|
65
65
|
"esbuild": ">=0.27.0 <1"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
package/esm/resolver/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/resolver/cdn-registry.ts
|
|
2
5
|
var DEFAULT_CDN_REGISTRY = {
|
|
3
6
|
react: {
|