@frontmcp/uipack 1.8.7 → 1.9.1-rc.1
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 +31 -18
- package/adapters/template-renderer.d.ts.map +1 -1
- package/component/index.js +19 -14
- package/esm/adapters/index.mjs +34 -18
- package/esm/bridge-runtime/index.mjs +3 -0
- package/esm/component/index.mjs +22 -14
- package/esm/index.mjs +34 -18
- package/esm/package.json +2 -2
- package/esm/resolver/index.mjs +3 -0
- package/esm/shell/index.mjs +19 -11
- package/esm/types/index.mjs +3 -0
- package/esm/typings/index.mjs +3 -0
- package/esm/utils/index.mjs +3 -0
- package/index.js +31 -18
- package/package.json +2 -2
- package/shell/csp.d.ts +6 -1
- package/shell/csp.d.ts.map +1 -1
- package/shell/index.js +16 -11
package/adapters/index.js
CHANGED
|
@@ -1922,11 +1922,8 @@ function buildCSPDirectives(csp) {
|
|
|
1922
1922
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
1923
1923
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
1924
1924
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
} else {
|
|
1928
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
1929
|
-
}
|
|
1925
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
1926
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
1930
1927
|
directives.push("object-src 'self' data:");
|
|
1931
1928
|
return directives;
|
|
1932
1929
|
}
|
|
@@ -1935,26 +1932,34 @@ function buildCSPMetaTag(csp) {
|
|
|
1935
1932
|
const content = directives.join("; ");
|
|
1936
1933
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
1937
1934
|
}
|
|
1935
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1938
1936
|
function validateCSPDomain(domain) {
|
|
1939
|
-
if (domain
|
|
1940
|
-
|
|
1941
|
-
|
|
1937
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
1938
|
+
if (/[?#]/.test(domain)) return false;
|
|
1939
|
+
if (domain.includes("*")) {
|
|
1940
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
1941
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
1942
1942
|
}
|
|
1943
1943
|
try {
|
|
1944
1944
|
const url = new URL(domain);
|
|
1945
|
-
|
|
1945
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
1946
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
1946
1947
|
} catch {
|
|
1947
1948
|
return false;
|
|
1948
1949
|
}
|
|
1949
1950
|
}
|
|
1951
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
1950
1952
|
function sanitizeCSPDomains(domains) {
|
|
1951
1953
|
if (!domains) return [];
|
|
1952
1954
|
const valid = [];
|
|
1953
1955
|
for (const domain of domains) {
|
|
1954
1956
|
if (validateCSPDomain(domain)) {
|
|
1955
1957
|
valid.push(domain);
|
|
1956
|
-
} else {
|
|
1957
|
-
|
|
1958
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
1959
|
+
reportedInvalidDomains.add(domain);
|
|
1960
|
+
console.warn(
|
|
1961
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
1962
|
+
);
|
|
1958
1963
|
}
|
|
1959
1964
|
}
|
|
1960
1965
|
return valid;
|
|
@@ -2358,8 +2363,8 @@ if (__root) {
|
|
|
2358
2363
|
configurable: true, enumerable: true
|
|
2359
2364
|
});
|
|
2360
2365
|
}
|
|
2361
|
-
// 2. Try injected data globals
|
|
2362
|
-
if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
|
|
2366
|
+
// 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
|
|
2367
|
+
if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
|
|
2363
2368
|
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
2364
2369
|
var __bridge = window.FrontMcpBridge;
|
|
2365
2370
|
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
@@ -2625,7 +2630,7 @@ createRoot(document.getElementById('root'))
|
|
|
2625
2630
|
return `${componentImport}
|
|
2626
2631
|
import React from 'react';
|
|
2627
2632
|
import { createRoot } from 'react-dom/client';
|
|
2628
|
-
const output = window.__mcpToolOutput;
|
|
2633
|
+
const output = window.__mcpToolOutput || {};
|
|
2629
2634
|
const props = ${propsCode};
|
|
2630
2635
|
createRoot(document.getElementById('root'))
|
|
2631
2636
|
.render(React.createElement(Component, props));`;
|
|
@@ -3090,6 +3095,16 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
3090
3095
|
}
|
|
3091
3096
|
return rawResult;
|
|
3092
3097
|
}
|
|
3098
|
+
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
3099
|
+
function warnComponentReference(template, toolName, logger) {
|
|
3100
|
+
const warned = warnedComponentReferences.get(template) ?? /* @__PURE__ */ new Set();
|
|
3101
|
+
if (warned.has(toolName)) return;
|
|
3102
|
+
warned.add(toolName);
|
|
3103
|
+
warnedComponentReferences.set(template, warned);
|
|
3104
|
+
logger.warn(
|
|
3105
|
+
`[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.`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3093
3108
|
function isReactElement(value) {
|
|
3094
3109
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
3095
3110
|
}
|
|
@@ -3124,7 +3139,7 @@ function renderToolTemplate(options) {
|
|
|
3124
3139
|
size = result.size;
|
|
3125
3140
|
} else if (typeof template === "function") {
|
|
3126
3141
|
const helpers = createTemplateHelpers();
|
|
3127
|
-
const ctx = { input, output, helpers };
|
|
3142
|
+
const ctx = { input: input ?? {}, output: output ?? {}, helpers };
|
|
3128
3143
|
let rawResult;
|
|
3129
3144
|
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
3130
3145
|
if (isHtmlBuilder) {
|
|
@@ -3137,9 +3152,7 @@ function renderToolTemplate(options) {
|
|
|
3137
3152
|
}
|
|
3138
3153
|
}
|
|
3139
3154
|
if (!isHtmlBuilder) {
|
|
3140
|
-
(options.logger ?? console)
|
|
3141
|
-
`[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.`
|
|
3142
|
-
);
|
|
3155
|
+
warnComponentReference(template, toolName, options.logger ?? console);
|
|
3143
3156
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
3144
3157
|
html2 = shellResult.html;
|
|
3145
3158
|
hash = shellResult.hash;
|
|
@@ -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;AAQnD;;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;;;OAGG;IACH,GAAG,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE;;;;OAIG;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;
|
|
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;AAQnD;;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;;;OAGG;IACH,GAAG,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAChE;;;;OAIG;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;AA8FD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAoH/F"}
|
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));`;
|
|
@@ -2214,11 +2214,8 @@ function buildCSPDirectives(csp) {
|
|
|
2214
2214
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
2215
2215
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
2216
2216
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
} else {
|
|
2220
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
2221
|
-
}
|
|
2217
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
2218
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
2222
2219
|
directives.push("object-src 'self' data:");
|
|
2223
2220
|
return directives;
|
|
2224
2221
|
}
|
|
@@ -2227,26 +2224,34 @@ function buildCSPMetaTag(csp) {
|
|
|
2227
2224
|
const content = directives.join("; ");
|
|
2228
2225
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
2229
2226
|
}
|
|
2227
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
2230
2228
|
function validateCSPDomain(domain) {
|
|
2231
|
-
if (domain
|
|
2232
|
-
|
|
2233
|
-
|
|
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]);
|
|
2234
2234
|
}
|
|
2235
2235
|
try {
|
|
2236
2236
|
const url = new URL(domain);
|
|
2237
|
-
|
|
2237
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
2238
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
2238
2239
|
} catch {
|
|
2239
2240
|
return false;
|
|
2240
2241
|
}
|
|
2241
2242
|
}
|
|
2243
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
2242
2244
|
function sanitizeCSPDomains(domains) {
|
|
2243
2245
|
if (!domains) return [];
|
|
2244
2246
|
const valid = [];
|
|
2245
2247
|
for (const domain of domains) {
|
|
2246
2248
|
if (validateCSPDomain(domain)) {
|
|
2247
2249
|
valid.push(domain);
|
|
2248
|
-
} else {
|
|
2249
|
-
|
|
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
|
+
);
|
|
2250
2255
|
}
|
|
2251
2256
|
}
|
|
2252
2257
|
return valid;
|
package/esm/adapters/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) {
|
|
@@ -1890,11 +1893,8 @@ function buildCSPDirectives(csp) {
|
|
|
1890
1893
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
1891
1894
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
1892
1895
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
} else {
|
|
1896
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
1897
|
-
}
|
|
1896
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
1897
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
1898
1898
|
directives.push("object-src 'self' data:");
|
|
1899
1899
|
return directives;
|
|
1900
1900
|
}
|
|
@@ -1903,26 +1903,34 @@ function buildCSPMetaTag(csp) {
|
|
|
1903
1903
|
const content = directives.join("; ");
|
|
1904
1904
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
1905
1905
|
}
|
|
1906
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1906
1907
|
function validateCSPDomain(domain) {
|
|
1907
|
-
if (domain
|
|
1908
|
-
|
|
1909
|
-
|
|
1908
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
1909
|
+
if (/[?#]/.test(domain)) return false;
|
|
1910
|
+
if (domain.includes("*")) {
|
|
1911
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
1912
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
1910
1913
|
}
|
|
1911
1914
|
try {
|
|
1912
1915
|
const url = new URL(domain);
|
|
1913
|
-
|
|
1916
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
1917
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
1914
1918
|
} catch {
|
|
1915
1919
|
return false;
|
|
1916
1920
|
}
|
|
1917
1921
|
}
|
|
1922
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
1918
1923
|
function sanitizeCSPDomains(domains) {
|
|
1919
1924
|
if (!domains) return [];
|
|
1920
1925
|
const valid = [];
|
|
1921
1926
|
for (const domain of domains) {
|
|
1922
1927
|
if (validateCSPDomain(domain)) {
|
|
1923
1928
|
valid.push(domain);
|
|
1924
|
-
} else {
|
|
1925
|
-
|
|
1929
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
1930
|
+
reportedInvalidDomains.add(domain);
|
|
1931
|
+
console.warn(
|
|
1932
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
1933
|
+
);
|
|
1926
1934
|
}
|
|
1927
1935
|
}
|
|
1928
1936
|
return valid;
|
|
@@ -2326,8 +2334,8 @@ if (__root) {
|
|
|
2326
2334
|
configurable: true, enumerable: true
|
|
2327
2335
|
});
|
|
2328
2336
|
}
|
|
2329
|
-
// 2. Try injected data globals
|
|
2330
|
-
if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
|
|
2337
|
+
// 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
|
|
2338
|
+
if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
|
|
2331
2339
|
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
2332
2340
|
var __bridge = window.FrontMcpBridge;
|
|
2333
2341
|
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
@@ -2593,7 +2601,7 @@ createRoot(document.getElementById('root'))
|
|
|
2593
2601
|
return `${componentImport}
|
|
2594
2602
|
import React from 'react';
|
|
2595
2603
|
import { createRoot } from 'react-dom/client';
|
|
2596
|
-
const output = window.__mcpToolOutput;
|
|
2604
|
+
const output = window.__mcpToolOutput || {};
|
|
2597
2605
|
const props = ${propsCode};
|
|
2598
2606
|
createRoot(document.getElementById('root'))
|
|
2599
2607
|
.render(React.createElement(Component, props));`;
|
|
@@ -3058,6 +3066,16 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
3058
3066
|
}
|
|
3059
3067
|
return rawResult;
|
|
3060
3068
|
}
|
|
3069
|
+
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
3070
|
+
function warnComponentReference(template, toolName, logger) {
|
|
3071
|
+
const warned = warnedComponentReferences.get(template) ?? /* @__PURE__ */ new Set();
|
|
3072
|
+
if (warned.has(toolName)) return;
|
|
3073
|
+
warned.add(toolName);
|
|
3074
|
+
warnedComponentReferences.set(template, warned);
|
|
3075
|
+
logger.warn(
|
|
3076
|
+
`[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.`
|
|
3077
|
+
);
|
|
3078
|
+
}
|
|
3061
3079
|
function isReactElement(value) {
|
|
3062
3080
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
3063
3081
|
}
|
|
@@ -3092,7 +3110,7 @@ function renderToolTemplate(options) {
|
|
|
3092
3110
|
size = result.size;
|
|
3093
3111
|
} else if (typeof template === "function") {
|
|
3094
3112
|
const helpers = createTemplateHelpers();
|
|
3095
|
-
const ctx = { input, output, helpers };
|
|
3113
|
+
const ctx = { input: input ?? {}, output: output ?? {}, helpers };
|
|
3096
3114
|
let rawResult;
|
|
3097
3115
|
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
3098
3116
|
if (isHtmlBuilder) {
|
|
@@ -3105,9 +3123,7 @@ function renderToolTemplate(options) {
|
|
|
3105
3123
|
}
|
|
3106
3124
|
}
|
|
3107
3125
|
if (!isHtmlBuilder) {
|
|
3108
|
-
(options.logger ?? console)
|
|
3109
|
-
`[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.`
|
|
3110
|
-
);
|
|
3126
|
+
warnComponentReference(template, toolName, options.logger ?? console);
|
|
3111
3127
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
3112
3128
|
html2 = shellResult.html;
|
|
3113
3129
|
hash = shellResult.hash;
|
|
@@ -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;
|
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));`;
|
|
@@ -2185,11 +2188,8 @@ function buildCSPDirectives(csp) {
|
|
|
2185
2188
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
2186
2189
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
2187
2190
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
} else {
|
|
2191
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
2192
|
-
}
|
|
2191
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
2192
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
2193
2193
|
directives.push("object-src 'self' data:");
|
|
2194
2194
|
return directives;
|
|
2195
2195
|
}
|
|
@@ -2198,26 +2198,34 @@ function buildCSPMetaTag(csp) {
|
|
|
2198
2198
|
const content = directives.join("; ");
|
|
2199
2199
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
2200
2200
|
}
|
|
2201
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
2201
2202
|
function validateCSPDomain(domain) {
|
|
2202
|
-
if (domain
|
|
2203
|
-
|
|
2204
|
-
|
|
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]);
|
|
2205
2208
|
}
|
|
2206
2209
|
try {
|
|
2207
2210
|
const url = new URL(domain);
|
|
2208
|
-
|
|
2211
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
2212
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
2209
2213
|
} catch {
|
|
2210
2214
|
return false;
|
|
2211
2215
|
}
|
|
2212
2216
|
}
|
|
2217
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
2213
2218
|
function sanitizeCSPDomains(domains) {
|
|
2214
2219
|
if (!domains) return [];
|
|
2215
2220
|
const valid = [];
|
|
2216
2221
|
for (const domain of domains) {
|
|
2217
2222
|
if (validateCSPDomain(domain)) {
|
|
2218
2223
|
valid.push(domain);
|
|
2219
|
-
} else {
|
|
2220
|
-
|
|
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
|
+
);
|
|
2221
2229
|
}
|
|
2222
2230
|
}
|
|
2223
2231
|
return valid;
|
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) {
|
|
@@ -2973,11 +2976,8 @@ function buildCSPDirectives(csp) {
|
|
|
2973
2976
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
2974
2977
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
2975
2978
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
} else {
|
|
2979
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
2980
|
-
}
|
|
2979
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
2980
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
2981
2981
|
directives.push("object-src 'self' data:");
|
|
2982
2982
|
return directives;
|
|
2983
2983
|
}
|
|
@@ -2986,26 +2986,34 @@ function buildCSPMetaTag(csp) {
|
|
|
2986
2986
|
const content = directives.join("; ");
|
|
2987
2987
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
2988
2988
|
}
|
|
2989
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
2989
2990
|
function validateCSPDomain(domain) {
|
|
2990
|
-
if (domain
|
|
2991
|
-
|
|
2992
|
-
|
|
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]);
|
|
2993
2996
|
}
|
|
2994
2997
|
try {
|
|
2995
2998
|
const url = new URL(domain);
|
|
2996
|
-
|
|
2999
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
3000
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
2997
3001
|
} catch {
|
|
2998
3002
|
return false;
|
|
2999
3003
|
}
|
|
3000
3004
|
}
|
|
3005
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
3001
3006
|
function sanitizeCSPDomains(domains) {
|
|
3002
3007
|
if (!domains) return [];
|
|
3003
3008
|
const valid = [];
|
|
3004
3009
|
for (const domain of domains) {
|
|
3005
3010
|
if (validateCSPDomain(domain)) {
|
|
3006
3011
|
valid.push(domain);
|
|
3007
|
-
} else {
|
|
3008
|
-
|
|
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
|
+
);
|
|
3009
3017
|
}
|
|
3010
3018
|
}
|
|
3011
3019
|
return valid;
|
|
@@ -3542,8 +3550,8 @@ if (__root) {
|
|
|
3542
3550
|
configurable: true, enumerable: true
|
|
3543
3551
|
});
|
|
3544
3552
|
}
|
|
3545
|
-
// 2. Try injected data globals
|
|
3546
|
-
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); }
|
|
3547
3555
|
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
3548
3556
|
var __bridge = window.FrontMcpBridge;
|
|
3549
3557
|
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
@@ -3795,7 +3803,7 @@ createRoot(document.getElementById('root'))
|
|
|
3795
3803
|
return `${componentImport}
|
|
3796
3804
|
import React from 'react';
|
|
3797
3805
|
import { createRoot } from 'react-dom/client';
|
|
3798
|
-
const output = window.__mcpToolOutput;
|
|
3806
|
+
const output = window.__mcpToolOutput || {};
|
|
3799
3807
|
const props = ${propsCode};
|
|
3800
3808
|
createRoot(document.getElementById('root'))
|
|
3801
3809
|
.render(React.createElement(Component, props));`;
|
|
@@ -4329,6 +4337,16 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
4329
4337
|
}
|
|
4330
4338
|
return rawResult;
|
|
4331
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
|
+
}
|
|
4332
4350
|
function isReactElement(value) {
|
|
4333
4351
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
4334
4352
|
}
|
|
@@ -4363,7 +4381,7 @@ function renderToolTemplate(options) {
|
|
|
4363
4381
|
size = result.size;
|
|
4364
4382
|
} else if (typeof template === "function") {
|
|
4365
4383
|
const helpers = createTemplateHelpers();
|
|
4366
|
-
const ctx = { input, output, helpers };
|
|
4384
|
+
const ctx = { input: input ?? {}, output: output ?? {}, helpers };
|
|
4367
4385
|
let rawResult;
|
|
4368
4386
|
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
4369
4387
|
if (isHtmlBuilder) {
|
|
@@ -4376,9 +4394,7 @@ function renderToolTemplate(options) {
|
|
|
4376
4394
|
}
|
|
4377
4395
|
}
|
|
4378
4396
|
if (!isHtmlBuilder) {
|
|
4379
|
-
(options.logger ?? console)
|
|
4380
|
-
`[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.`
|
|
4381
|
-
);
|
|
4397
|
+
warnComponentReference(template, toolName, options.logger ?? console);
|
|
4382
4398
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
4383
4399
|
html2 = shellResult.html;
|
|
4384
4400
|
hash = shellResult.hash;
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1-rc.1",
|
|
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.1-rc.1",
|
|
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: {
|
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;
|
|
@@ -1367,11 +1370,8 @@ function buildCSPDirectives(csp) {
|
|
|
1367
1370
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
1368
1371
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
1369
1372
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
} else {
|
|
1373
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
1374
|
-
}
|
|
1373
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
1374
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
1375
1375
|
directives.push("object-src 'self' data:");
|
|
1376
1376
|
return directives;
|
|
1377
1377
|
}
|
|
@@ -1380,26 +1380,34 @@ function buildCSPMetaTag(csp) {
|
|
|
1380
1380
|
const content = directives.join("; ");
|
|
1381
1381
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
1382
1382
|
}
|
|
1383
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1383
1384
|
function validateCSPDomain(domain) {
|
|
1384
|
-
if (domain
|
|
1385
|
-
|
|
1386
|
-
|
|
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]);
|
|
1387
1390
|
}
|
|
1388
1391
|
try {
|
|
1389
1392
|
const url = new URL(domain);
|
|
1390
|
-
|
|
1393
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
1394
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
1391
1395
|
} catch {
|
|
1392
1396
|
return false;
|
|
1393
1397
|
}
|
|
1394
1398
|
}
|
|
1399
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
1395
1400
|
function sanitizeCSPDomains(domains) {
|
|
1396
1401
|
if (!domains) return [];
|
|
1397
1402
|
const valid = [];
|
|
1398
1403
|
for (const domain of domains) {
|
|
1399
1404
|
if (validateCSPDomain(domain)) {
|
|
1400
1405
|
valid.push(domain);
|
|
1401
|
-
} else {
|
|
1402
|
-
|
|
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
|
+
);
|
|
1403
1411
|
}
|
|
1404
1412
|
}
|
|
1405
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();
|
package/index.js
CHANGED
|
@@ -3093,11 +3093,8 @@ function buildCSPDirectives(csp) {
|
|
|
3093
3093
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
3094
3094
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
3095
3095
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
} else {
|
|
3099
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
3100
|
-
}
|
|
3096
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
3097
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
3101
3098
|
directives.push("object-src 'self' data:");
|
|
3102
3099
|
return directives;
|
|
3103
3100
|
}
|
|
@@ -3106,26 +3103,34 @@ function buildCSPMetaTag(csp) {
|
|
|
3106
3103
|
const content = directives.join("; ");
|
|
3107
3104
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
3108
3105
|
}
|
|
3106
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
3109
3107
|
function validateCSPDomain(domain) {
|
|
3110
|
-
if (domain
|
|
3111
|
-
|
|
3112
|
-
|
|
3108
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
3109
|
+
if (/[?#]/.test(domain)) return false;
|
|
3110
|
+
if (domain.includes("*")) {
|
|
3111
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
3112
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
3113
3113
|
}
|
|
3114
3114
|
try {
|
|
3115
3115
|
const url = new URL(domain);
|
|
3116
|
-
|
|
3116
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
3117
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
3117
3118
|
} catch {
|
|
3118
3119
|
return false;
|
|
3119
3120
|
}
|
|
3120
3121
|
}
|
|
3122
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
3121
3123
|
function sanitizeCSPDomains(domains) {
|
|
3122
3124
|
if (!domains) return [];
|
|
3123
3125
|
const valid = [];
|
|
3124
3126
|
for (const domain of domains) {
|
|
3125
3127
|
if (validateCSPDomain(domain)) {
|
|
3126
3128
|
valid.push(domain);
|
|
3127
|
-
} else {
|
|
3128
|
-
|
|
3129
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
3130
|
+
reportedInvalidDomains.add(domain);
|
|
3131
|
+
console.warn(
|
|
3132
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
3133
|
+
);
|
|
3129
3134
|
}
|
|
3130
3135
|
}
|
|
3131
3136
|
return valid;
|
|
@@ -3662,8 +3667,8 @@ if (__root) {
|
|
|
3662
3667
|
configurable: true, enumerable: true
|
|
3663
3668
|
});
|
|
3664
3669
|
}
|
|
3665
|
-
// 2. Try injected data globals
|
|
3666
|
-
if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
|
|
3670
|
+
// 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
|
|
3671
|
+
if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
|
|
3667
3672
|
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
3668
3673
|
var __bridge = window.FrontMcpBridge;
|
|
3669
3674
|
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
@@ -3915,7 +3920,7 @@ createRoot(document.getElementById('root'))
|
|
|
3915
3920
|
return `${componentImport}
|
|
3916
3921
|
import React from 'react';
|
|
3917
3922
|
import { createRoot } from 'react-dom/client';
|
|
3918
|
-
const output = window.__mcpToolOutput;
|
|
3923
|
+
const output = window.__mcpToolOutput || {};
|
|
3919
3924
|
const props = ${propsCode};
|
|
3920
3925
|
createRoot(document.getElementById('root'))
|
|
3921
3926
|
.render(React.createElement(Component, props));`;
|
|
@@ -4449,6 +4454,16 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
4449
4454
|
}
|
|
4450
4455
|
return rawResult;
|
|
4451
4456
|
}
|
|
4457
|
+
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
4458
|
+
function warnComponentReference(template, toolName, logger) {
|
|
4459
|
+
const warned = warnedComponentReferences.get(template) ?? /* @__PURE__ */ new Set();
|
|
4460
|
+
if (warned.has(toolName)) return;
|
|
4461
|
+
warned.add(toolName);
|
|
4462
|
+
warnedComponentReferences.set(template, warned);
|
|
4463
|
+
logger.warn(
|
|
4464
|
+
`[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.`
|
|
4465
|
+
);
|
|
4466
|
+
}
|
|
4452
4467
|
function isReactElement(value) {
|
|
4453
4468
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
4454
4469
|
}
|
|
@@ -4483,7 +4498,7 @@ function renderToolTemplate(options) {
|
|
|
4483
4498
|
size = result.size;
|
|
4484
4499
|
} else if (typeof template === "function") {
|
|
4485
4500
|
const helpers = createTemplateHelpers();
|
|
4486
|
-
const ctx = { input, output, helpers };
|
|
4501
|
+
const ctx = { input: input ?? {}, output: output ?? {}, helpers };
|
|
4487
4502
|
let rawResult;
|
|
4488
4503
|
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
4489
4504
|
if (isHtmlBuilder) {
|
|
@@ -4496,9 +4511,7 @@ function renderToolTemplate(options) {
|
|
|
4496
4511
|
}
|
|
4497
4512
|
}
|
|
4498
4513
|
if (!isHtmlBuilder) {
|
|
4499
|
-
(options.logger ?? console)
|
|
4500
|
-
`[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.`
|
|
4501
|
-
);
|
|
4514
|
+
warnComponentReference(template, toolName, options.logger ?? console);
|
|
4502
4515
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
4503
4516
|
html2 = shellResult.html;
|
|
4504
4517
|
hash = shellResult.hash;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1-rc.1",
|
|
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.1-rc.1",
|
|
65
65
|
"esbuild": ">=0.27.0 <1"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
package/shell/csp.d.ts
CHANGED
|
@@ -28,10 +28,15 @@ export declare function buildCSPDirectives(csp?: CSPConfig): string[];
|
|
|
28
28
|
export declare function buildCSPMetaTag(csp?: CSPConfig): string;
|
|
29
29
|
/**
|
|
30
30
|
* Validate CSP domain format.
|
|
31
|
+
*
|
|
32
|
+
* Accepts `https://` and `wss://` origins (a WebSocket API needs `wss://` in `connect-src`),
|
|
33
|
+
* their `https://*.` / `wss://*.` wildcard forms, and `http://` / `ws://` origins on a loopback
|
|
34
|
+
* host (`localhost`, `127.0.0.1`, `[::1]`) for local development. A value with a query or a
|
|
35
|
+
* fragment is rejected.
|
|
31
36
|
*/
|
|
32
37
|
export declare function validateCSPDomain(domain: string): boolean;
|
|
33
38
|
/**
|
|
34
|
-
* Filter
|
|
39
|
+
* Filter out invalid CSP domains, warning once per domain.
|
|
35
40
|
*/
|
|
36
41
|
export declare function sanitizeCSPDomains(domains: string[] | undefined): string[];
|
|
37
42
|
//# sourceMappingURL=csp.d.ts.map
|
package/shell/csp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../src/shell/csp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;GAEG;AACH,eAAO,MAAM,mBAAmB,sJAMtB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,sBAAsB,uPAQzB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,0BAA0B,4MAQ7B,CAAC;AAEX;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../src/shell/csp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;GAEG;AACH,eAAO,MAAM,mBAAmB,sJAMtB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,sBAAsB,uPAQzB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,0BAA0B,4MAQ7B,CAAC;AAEX;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,CA8B5D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,MAAM,CAIvD;AAKD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAkBzD;AAKD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAgB1E"}
|
package/shell/index.js
CHANGED
|
@@ -1419,11 +1419,8 @@ function buildCSPDirectives(csp) {
|
|
|
1419
1419
|
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
1420
1420
|
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
1421
1421
|
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
} else {
|
|
1425
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
1426
|
-
}
|
|
1422
|
+
const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
|
|
1423
|
+
directives.push(`connect-src ${connectSources.join(" ")}`);
|
|
1427
1424
|
directives.push("object-src 'self' data:");
|
|
1428
1425
|
return directives;
|
|
1429
1426
|
}
|
|
@@ -1432,26 +1429,34 @@ function buildCSPMetaTag(csp) {
|
|
|
1432
1429
|
const content = directives.join("; ");
|
|
1433
1430
|
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
1434
1431
|
}
|
|
1432
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
1435
1433
|
function validateCSPDomain(domain) {
|
|
1436
|
-
if (domain
|
|
1437
|
-
|
|
1438
|
-
|
|
1434
|
+
if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
|
|
1435
|
+
if (/[?#]/.test(domain)) return false;
|
|
1436
|
+
if (domain.includes("*")) {
|
|
1437
|
+
const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
|
|
1438
|
+
return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
|
|
1439
1439
|
}
|
|
1440
1440
|
try {
|
|
1441
1441
|
const url = new URL(domain);
|
|
1442
|
-
|
|
1442
|
+
if (url.protocol === "https:" || url.protocol === "wss:") return true;
|
|
1443
|
+
return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
|
|
1443
1444
|
} catch {
|
|
1444
1445
|
return false;
|
|
1445
1446
|
}
|
|
1446
1447
|
}
|
|
1448
|
+
var reportedInvalidDomains = /* @__PURE__ */ new Set();
|
|
1447
1449
|
function sanitizeCSPDomains(domains) {
|
|
1448
1450
|
if (!domains) return [];
|
|
1449
1451
|
const valid = [];
|
|
1450
1452
|
for (const domain of domains) {
|
|
1451
1453
|
if (validateCSPDomain(domain)) {
|
|
1452
1454
|
valid.push(domain);
|
|
1453
|
-
} else {
|
|
1454
|
-
|
|
1455
|
+
} else if (!reportedInvalidDomains.has(domain)) {
|
|
1456
|
+
reportedInvalidDomains.add(domain);
|
|
1457
|
+
console.warn(
|
|
1458
|
+
`Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
|
|
1459
|
+
);
|
|
1455
1460
|
}
|
|
1456
1461
|
}
|
|
1457
1462
|
return valid;
|