@frontmcp/uipack 1.8.1 → 1.8.3
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/README.md +12 -11
- package/adapters/index.js +72 -29
- package/adapters/template-renderer.d.ts +11 -1
- package/adapters/template-renderer.d.ts.map +1 -1
- package/esm/adapters/index.mjs +72 -29
- package/esm/index.mjs +75 -29
- package/esm/package.json +2 -2
- package/esm/shell/index.mjs +41 -13
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +78 -29
- package/package.json +2 -2
- package/shell/data-injector.d.ts +5 -0
- package/shell/data-injector.d.ts.map +1 -1
- package/shell/index.d.ts +1 -0
- package/shell/index.d.ts.map +1 -1
- package/shell/index.js +44 -13
- package/shell/trusted-html.d.ts +40 -0
- package/shell/trusted-html.d.ts.map +1 -0
- package/types/ui-config.d.ts +30 -2
- package/types/ui-config.d.ts.map +1 -1
package/README.md
CHANGED
|
@@ -125,17 +125,18 @@ const result = await fetcher.fetchBatch({
|
|
|
125
125
|
|
|
126
126
|
### Shell
|
|
127
127
|
|
|
128
|
-
| Export
|
|
129
|
-
|
|
|
130
|
-
| `buildShell(content, config)`
|
|
131
|
-
| `buildCSPMetaTag(config?)`
|
|
132
|
-
| `buildCSPDirectives(config?)`
|
|
133
|
-
| `buildDataInjectionScript(toolName, input?, output?)`
|
|
134
|
-
| `createTemplateHelpers(toolName)`
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
128
|
+
| Export | Type | Purpose |
|
|
129
|
+
| ------------------------------------------------------------ | -------- | ----------------------------------------------------------------------------------------------- |
|
|
130
|
+
| `buildShell(content, config)` | function | Main entry point -> `ShellResult` |
|
|
131
|
+
| `buildCSPMetaTag(config?)` | function | Generate `<meta>` CSP tag |
|
|
132
|
+
| `buildCSPDirectives(config?)` | function | Merge custom domains with defaults |
|
|
133
|
+
| `buildDataInjectionScript(toolName, input?, output?)` | function | `<script>` setting `window.__mcp*` globals |
|
|
134
|
+
| `createTemplateHelpers(toolName)` | function | `html`, `trustedHtml`, `escapeHtml`, `formatDate`, `formatCurrency`, `uniqueId`, `jsonEmbed` |
|
|
135
|
+
| `` html`…` ``, `trustedHtml(markup)`, `isTrustedHtml(value)` | function | Build / mark / detect `TrustedHtml` template markup (interpolated values in `html` are escaped) |
|
|
136
|
+
| `resolveShellTemplate(opts)` | function | Resolve custom shell from npm/URL/inline |
|
|
137
|
+
| `applyShellTemplate(template, values)` | function | Apply values to resolved template |
|
|
138
|
+
| `validateShellTemplate(template)` | function | Validate a custom shell template |
|
|
139
|
+
| `DEFAULT_CDN_DOMAINS` | constant | jsDelivr, Cloudflare, Google Fonts |
|
|
139
140
|
|
|
140
141
|
### Component
|
|
141
142
|
|
package/adapters/index.js
CHANGED
|
@@ -1857,6 +1857,29 @@ function validateShellTemplate(template) {
|
|
|
1857
1857
|
};
|
|
1858
1858
|
}
|
|
1859
1859
|
|
|
1860
|
+
// libs/uipack/src/shell/trusted-html.ts
|
|
1861
|
+
var TRUSTED_HTML = /* @__PURE__ */ Symbol.for("@frontmcp/uipack/trusted-html");
|
|
1862
|
+
function trustedHtml(markup) {
|
|
1863
|
+
const value = String(markup);
|
|
1864
|
+
return Object.freeze({ [TRUSTED_HTML]: value, toString: () => value });
|
|
1865
|
+
}
|
|
1866
|
+
function isTrustedHtml(value) {
|
|
1867
|
+
return typeof value === "object" && value !== null && typeof value[TRUSTED_HTML] === "string";
|
|
1868
|
+
}
|
|
1869
|
+
function interpolate(value) {
|
|
1870
|
+
if (isTrustedHtml(value)) return value[TRUSTED_HTML];
|
|
1871
|
+
if (Array.isArray(value)) return value.map(interpolate).join("");
|
|
1872
|
+
if (value === false) return "";
|
|
1873
|
+
return escapeHtml(value);
|
|
1874
|
+
}
|
|
1875
|
+
function html(strings, ...values) {
|
|
1876
|
+
let markup = strings[0];
|
|
1877
|
+
for (let index = 0; index < values.length; index++) {
|
|
1878
|
+
markup += interpolate(values[index]) + strings[index + 1];
|
|
1879
|
+
}
|
|
1880
|
+
return trustedHtml(markup);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1860
1883
|
// libs/uipack/src/shell/data-injector.ts
|
|
1861
1884
|
function hasSizing(sizing) {
|
|
1862
1885
|
if (!sizing) return false;
|
|
@@ -1910,7 +1933,9 @@ function createTemplateHelpers() {
|
|
|
1910
1933
|
uniqueId: (prefix = "mcp") => {
|
|
1911
1934
|
return `${prefix}-${++_uniqueIdCounter}`;
|
|
1912
1935
|
},
|
|
1913
|
-
jsonEmbed: (data) => safeJsonForScript(data)
|
|
1936
|
+
jsonEmbed: (data) => safeJsonForScript(data),
|
|
1937
|
+
html,
|
|
1938
|
+
trustedHtml
|
|
1914
1939
|
};
|
|
1915
1940
|
}
|
|
1916
1941
|
|
|
@@ -1988,12 +2013,12 @@ function buildShell(content, config) {
|
|
|
1988
2013
|
sizing
|
|
1989
2014
|
});
|
|
1990
2015
|
if (!withShell) {
|
|
1991
|
-
const
|
|
2016
|
+
const html3 = `${dataScript}
|
|
1992
2017
|
${content}`;
|
|
1993
2018
|
return {
|
|
1994
|
-
html:
|
|
1995
|
-
hash: simpleHash(
|
|
1996
|
-
size: Buffer.byteLength(
|
|
2019
|
+
html: html3,
|
|
2020
|
+
hash: simpleHash(html3),
|
|
2021
|
+
size: Buffer.byteLength(html3, "utf-8")
|
|
1997
2022
|
};
|
|
1998
2023
|
}
|
|
1999
2024
|
if (customShell) {
|
|
@@ -2026,7 +2051,7 @@ ${content}`;
|
|
|
2026
2051
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
2027
2052
|
headParts.push(`<script>${bridgeScript}</script>`);
|
|
2028
2053
|
}
|
|
2029
|
-
const
|
|
2054
|
+
const html2 = `<!DOCTYPE html>
|
|
2030
2055
|
<html lang="en">
|
|
2031
2056
|
<head>
|
|
2032
2057
|
${headParts.map((p) => ` ${p}`).join("\n")}
|
|
@@ -2036,9 +2061,9 @@ ${content}
|
|
|
2036
2061
|
</body>
|
|
2037
2062
|
</html>`;
|
|
2038
2063
|
return {
|
|
2039
|
-
html,
|
|
2040
|
-
hash: simpleHash(
|
|
2041
|
-
size: Buffer.byteLength(
|
|
2064
|
+
html: html2,
|
|
2065
|
+
hash: simpleHash(html2),
|
|
2066
|
+
size: Buffer.byteLength(html2, "utf-8")
|
|
2042
2067
|
};
|
|
2043
2068
|
}
|
|
2044
2069
|
function buildCustomShell(content, customShell, ctx) {
|
|
@@ -2071,7 +2096,7 @@ ${sizingStyle}` : dataScript;
|
|
|
2071
2096
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
2072
2097
|
bridgeHtml = `<script>${bridgeScript}</script>`;
|
|
2073
2098
|
}
|
|
2074
|
-
const
|
|
2099
|
+
const html2 = applyShellTemplate(template, {
|
|
2075
2100
|
csp: cspTag,
|
|
2076
2101
|
data: dataWithSizing,
|
|
2077
2102
|
bridge: bridgeHtml,
|
|
@@ -2079,9 +2104,9 @@ ${sizingStyle}` : dataScript;
|
|
|
2079
2104
|
title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
|
|
2080
2105
|
});
|
|
2081
2106
|
return {
|
|
2082
|
-
html,
|
|
2083
|
-
hash: simpleHash(
|
|
2084
|
-
size: Buffer.byteLength(
|
|
2107
|
+
html: html2,
|
|
2108
|
+
hash: simpleHash(html2),
|
|
2109
|
+
size: Buffer.byteLength(html2, "utf-8")
|
|
2085
2110
|
};
|
|
2086
2111
|
}
|
|
2087
2112
|
function simpleHash(str) {
|
|
@@ -2378,10 +2403,10 @@ function resolveFileSource(source, options = {}) {
|
|
|
2378
2403
|
};
|
|
2379
2404
|
}
|
|
2380
2405
|
function resolveFunctionSource(source, input, output) {
|
|
2381
|
-
const
|
|
2406
|
+
const html2 = source(input, output);
|
|
2382
2407
|
return {
|
|
2383
2408
|
mode: "inline",
|
|
2384
|
-
html,
|
|
2409
|
+
html: html2,
|
|
2385
2410
|
exportName: "default",
|
|
2386
2411
|
meta: { mcpAware: false, renderer: "html" },
|
|
2387
2412
|
peerDependencies: []
|
|
@@ -2758,11 +2783,29 @@ function buildCspConfig(resolver) {
|
|
|
2758
2783
|
}
|
|
2759
2784
|
return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
|
|
2760
2785
|
}
|
|
2761
|
-
|
|
2786
|
+
var STRING_RESULT_DOCS = "https://docs.agentfront.dev/frontmcp/guides/building-tool-ui#trusted-markup";
|
|
2787
|
+
var noticedStringResultTools = /* @__PURE__ */ new Set();
|
|
2788
|
+
function noticeStringResult(toolName, logger) {
|
|
2789
|
+
if (noticedStringResultTools.has(toolName)) return;
|
|
2790
|
+
noticedStringResultTools.add(toolName);
|
|
2791
|
+
logger.warn(
|
|
2792
|
+
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup, which is rendered as HTML. FrontMCP 1.9 will HTML-escape plain string results by default. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(), then set ui.escapeStringResults: true (or escapeStringResults: false to keep the current behaviour without this notice). See ${STRING_RESULT_DOCS}`
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2795
|
+
function renderUnwrappedResult(rawResult, options) {
|
|
2796
|
+
if (isTrustedHtml(rawResult)) {
|
|
2797
|
+
return String(rawResult);
|
|
2798
|
+
}
|
|
2762
2799
|
if (typeof rawResult !== "string") {
|
|
2763
2800
|
return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
|
|
2764
2801
|
}
|
|
2765
|
-
|
|
2802
|
+
if (detectContentType(rawResult) !== "html" || options.escapeStringResults === true) {
|
|
2803
|
+
return escapeHtml(rawResult);
|
|
2804
|
+
}
|
|
2805
|
+
if (options.escapeStringResults === void 0) {
|
|
2806
|
+
noticeStringResult(options.toolName, options.logger ?? console);
|
|
2807
|
+
}
|
|
2808
|
+
return rawResult;
|
|
2766
2809
|
}
|
|
2767
2810
|
function renderToolTemplate(options) {
|
|
2768
2811
|
const { toolName, input, output, template, resolver, platformType, sizing } = options;
|
|
@@ -2776,7 +2819,7 @@ function renderToolTemplate(options) {
|
|
|
2776
2819
|
resolver,
|
|
2777
2820
|
sizing
|
|
2778
2821
|
};
|
|
2779
|
-
let
|
|
2822
|
+
let html2;
|
|
2780
2823
|
let hash = "";
|
|
2781
2824
|
let size = 0;
|
|
2782
2825
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
@@ -2785,43 +2828,43 @@ function renderToolTemplate(options) {
|
|
|
2785
2828
|
{ source: template, inlineReact: resourceMode === "inline" },
|
|
2786
2829
|
{ ...shellConfig, csp: cspConfig }
|
|
2787
2830
|
);
|
|
2788
|
-
|
|
2831
|
+
html2 = result.html;
|
|
2789
2832
|
hash = result.hash;
|
|
2790
2833
|
size = result.size;
|
|
2791
2834
|
} else if (typeof template === "function") {
|
|
2792
2835
|
if (uiType === "react") {
|
|
2793
2836
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2794
|
-
|
|
2837
|
+
html2 = shellResult.html;
|
|
2795
2838
|
hash = shellResult.hash;
|
|
2796
2839
|
size = shellResult.size;
|
|
2797
2840
|
} else {
|
|
2798
2841
|
const helpers = createTemplateHelpers();
|
|
2799
2842
|
const ctx = { input, output, helpers };
|
|
2800
2843
|
const rawResult = template(ctx);
|
|
2801
|
-
const wrapped = wrapDetectedContent(rawResult);
|
|
2844
|
+
const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
|
|
2802
2845
|
if (wrapped) {
|
|
2803
|
-
|
|
2846
|
+
html2 = wrapped;
|
|
2804
2847
|
} else {
|
|
2805
|
-
const shellResult = buildShell(renderUnwrappedResult(rawResult), shellConfig);
|
|
2806
|
-
|
|
2848
|
+
const shellResult = buildShell(renderUnwrappedResult(rawResult, options), shellConfig);
|
|
2849
|
+
html2 = shellResult.html;
|
|
2807
2850
|
hash = shellResult.hash;
|
|
2808
2851
|
size = shellResult.size;
|
|
2809
2852
|
}
|
|
2810
2853
|
}
|
|
2811
2854
|
} else if (typeof template === "string") {
|
|
2812
2855
|
const shellResult = buildShell(template, shellConfig);
|
|
2813
|
-
|
|
2856
|
+
html2 = shellResult.html;
|
|
2814
2857
|
hash = shellResult.hash;
|
|
2815
2858
|
size = shellResult.size;
|
|
2816
2859
|
} else {
|
|
2817
2860
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2818
|
-
|
|
2861
|
+
html2 = shellResult.html;
|
|
2819
2862
|
hash = shellResult.hash;
|
|
2820
2863
|
size = shellResult.size;
|
|
2821
2864
|
}
|
|
2822
2865
|
const htmlKey = "ui/html";
|
|
2823
2866
|
const meta = {
|
|
2824
|
-
[htmlKey]:
|
|
2867
|
+
[htmlKey]: html2,
|
|
2825
2868
|
"ui/type": uiType,
|
|
2826
2869
|
"ui/mimeType": MCP_APPS_MIME_TYPE
|
|
2827
2870
|
};
|
|
@@ -2832,10 +2875,10 @@ function renderToolTemplate(options) {
|
|
|
2832
2875
|
if (sizing.aspectRatio !== void 0) meta["ui/aspectRatio"] = sizing.aspectRatio;
|
|
2833
2876
|
}
|
|
2834
2877
|
return {
|
|
2835
|
-
html,
|
|
2878
|
+
html: html2,
|
|
2836
2879
|
uiType,
|
|
2837
2880
|
hash,
|
|
2838
|
-
size: size || Buffer.byteLength(
|
|
2881
|
+
size: size || Buffer.byteLength(html2, "utf8"),
|
|
2839
2882
|
meta
|
|
2840
2883
|
};
|
|
2841
2884
|
}
|
|
@@ -36,6 +36,16 @@ export interface RenderToolTemplateOptions {
|
|
|
36
36
|
* shell, and is mirrored onto the returned `meta` as `ui/preferredHeight` etc.
|
|
37
37
|
*/
|
|
38
38
|
sizing?: WidgetSizing;
|
|
39
|
+
/**
|
|
40
|
+
* HTML-escape a plain string returned by a template function; results built with
|
|
41
|
+
* `html` / `trustedHtml` stay markup. When unset, strings that look like HTML render as
|
|
42
|
+
* markup and a one-time notice per tool is logged; `false` keeps that without the notice.
|
|
43
|
+
*/
|
|
44
|
+
escapeStringResults?: boolean;
|
|
45
|
+
/** Receives the one-time string-result notice. Defaults to `console`. */
|
|
46
|
+
logger?: {
|
|
47
|
+
warn: (message: string) => void;
|
|
48
|
+
};
|
|
39
49
|
}
|
|
40
50
|
/**
|
|
41
51
|
* Result of rendering a tool template.
|
|
@@ -57,7 +67,7 @@ export interface RenderToolTemplateResult {
|
|
|
57
67
|
*
|
|
58
68
|
* Supported template types:
|
|
59
69
|
* - FileSource object `{ file: './widget.tsx' }` — bundled with esbuild, React loaded from esm.sh
|
|
60
|
-
* - HTML template builder function `(ctx) => string`
|
|
70
|
+
* - HTML template builder function `(ctx) => string | TrustedHtml`
|
|
61
71
|
* - Static HTML/MDX string
|
|
62
72
|
*
|
|
63
73
|
* React function references (`template: MyComponent`) are NOT supported for bundling.
|
|
@@ -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;
|
|
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;AAOnD;;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;;;;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;AA6DD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAsG/F"}
|
package/esm/adapters/index.mjs
CHANGED
|
@@ -1825,6 +1825,29 @@ function validateShellTemplate(template) {
|
|
|
1825
1825
|
};
|
|
1826
1826
|
}
|
|
1827
1827
|
|
|
1828
|
+
// libs/uipack/src/shell/trusted-html.ts
|
|
1829
|
+
var TRUSTED_HTML = /* @__PURE__ */ Symbol.for("@frontmcp/uipack/trusted-html");
|
|
1830
|
+
function trustedHtml(markup) {
|
|
1831
|
+
const value = String(markup);
|
|
1832
|
+
return Object.freeze({ [TRUSTED_HTML]: value, toString: () => value });
|
|
1833
|
+
}
|
|
1834
|
+
function isTrustedHtml(value) {
|
|
1835
|
+
return typeof value === "object" && value !== null && typeof value[TRUSTED_HTML] === "string";
|
|
1836
|
+
}
|
|
1837
|
+
function interpolate(value) {
|
|
1838
|
+
if (isTrustedHtml(value)) return value[TRUSTED_HTML];
|
|
1839
|
+
if (Array.isArray(value)) return value.map(interpolate).join("");
|
|
1840
|
+
if (value === false) return "";
|
|
1841
|
+
return escapeHtml(value);
|
|
1842
|
+
}
|
|
1843
|
+
function html(strings, ...values) {
|
|
1844
|
+
let markup = strings[0];
|
|
1845
|
+
for (let index = 0; index < values.length; index++) {
|
|
1846
|
+
markup += interpolate(values[index]) + strings[index + 1];
|
|
1847
|
+
}
|
|
1848
|
+
return trustedHtml(markup);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1828
1851
|
// libs/uipack/src/shell/data-injector.ts
|
|
1829
1852
|
function hasSizing(sizing) {
|
|
1830
1853
|
if (!sizing) return false;
|
|
@@ -1878,7 +1901,9 @@ function createTemplateHelpers() {
|
|
|
1878
1901
|
uniqueId: (prefix = "mcp") => {
|
|
1879
1902
|
return `${prefix}-${++_uniqueIdCounter}`;
|
|
1880
1903
|
},
|
|
1881
|
-
jsonEmbed: (data) => safeJsonForScript(data)
|
|
1904
|
+
jsonEmbed: (data) => safeJsonForScript(data),
|
|
1905
|
+
html,
|
|
1906
|
+
trustedHtml
|
|
1882
1907
|
};
|
|
1883
1908
|
}
|
|
1884
1909
|
|
|
@@ -1956,12 +1981,12 @@ function buildShell(content, config) {
|
|
|
1956
1981
|
sizing
|
|
1957
1982
|
});
|
|
1958
1983
|
if (!withShell) {
|
|
1959
|
-
const
|
|
1984
|
+
const html3 = `${dataScript}
|
|
1960
1985
|
${content}`;
|
|
1961
1986
|
return {
|
|
1962
|
-
html:
|
|
1963
|
-
hash: simpleHash(
|
|
1964
|
-
size: Buffer.byteLength(
|
|
1987
|
+
html: html3,
|
|
1988
|
+
hash: simpleHash(html3),
|
|
1989
|
+
size: Buffer.byteLength(html3, "utf-8")
|
|
1965
1990
|
};
|
|
1966
1991
|
}
|
|
1967
1992
|
if (customShell) {
|
|
@@ -1994,7 +2019,7 @@ ${content}`;
|
|
|
1994
2019
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
1995
2020
|
headParts.push(`<script>${bridgeScript}</script>`);
|
|
1996
2021
|
}
|
|
1997
|
-
const
|
|
2022
|
+
const html2 = `<!DOCTYPE html>
|
|
1998
2023
|
<html lang="en">
|
|
1999
2024
|
<head>
|
|
2000
2025
|
${headParts.map((p) => ` ${p}`).join("\n")}
|
|
@@ -2004,9 +2029,9 @@ ${content}
|
|
|
2004
2029
|
</body>
|
|
2005
2030
|
</html>`;
|
|
2006
2031
|
return {
|
|
2007
|
-
html,
|
|
2008
|
-
hash: simpleHash(
|
|
2009
|
-
size: Buffer.byteLength(
|
|
2032
|
+
html: html2,
|
|
2033
|
+
hash: simpleHash(html2),
|
|
2034
|
+
size: Buffer.byteLength(html2, "utf-8")
|
|
2010
2035
|
};
|
|
2011
2036
|
}
|
|
2012
2037
|
function buildCustomShell(content, customShell, ctx) {
|
|
@@ -2039,7 +2064,7 @@ ${sizingStyle}` : dataScript;
|
|
|
2039
2064
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
2040
2065
|
bridgeHtml = `<script>${bridgeScript}</script>`;
|
|
2041
2066
|
}
|
|
2042
|
-
const
|
|
2067
|
+
const html2 = applyShellTemplate(template, {
|
|
2043
2068
|
csp: cspTag,
|
|
2044
2069
|
data: dataWithSizing,
|
|
2045
2070
|
bridge: bridgeHtml,
|
|
@@ -2047,9 +2072,9 @@ ${sizingStyle}` : dataScript;
|
|
|
2047
2072
|
title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
|
|
2048
2073
|
});
|
|
2049
2074
|
return {
|
|
2050
|
-
html,
|
|
2051
|
-
hash: simpleHash(
|
|
2052
|
-
size: Buffer.byteLength(
|
|
2075
|
+
html: html2,
|
|
2076
|
+
hash: simpleHash(html2),
|
|
2077
|
+
size: Buffer.byteLength(html2, "utf-8")
|
|
2053
2078
|
};
|
|
2054
2079
|
}
|
|
2055
2080
|
function simpleHash(str) {
|
|
@@ -2346,10 +2371,10 @@ function resolveFileSource(source, options = {}) {
|
|
|
2346
2371
|
};
|
|
2347
2372
|
}
|
|
2348
2373
|
function resolveFunctionSource(source, input, output) {
|
|
2349
|
-
const
|
|
2374
|
+
const html2 = source(input, output);
|
|
2350
2375
|
return {
|
|
2351
2376
|
mode: "inline",
|
|
2352
|
-
html,
|
|
2377
|
+
html: html2,
|
|
2353
2378
|
exportName: "default",
|
|
2354
2379
|
meta: { mcpAware: false, renderer: "html" },
|
|
2355
2380
|
peerDependencies: []
|
|
@@ -2726,11 +2751,29 @@ function buildCspConfig(resolver) {
|
|
|
2726
2751
|
}
|
|
2727
2752
|
return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
|
|
2728
2753
|
}
|
|
2729
|
-
|
|
2754
|
+
var STRING_RESULT_DOCS = "https://docs.agentfront.dev/frontmcp/guides/building-tool-ui#trusted-markup";
|
|
2755
|
+
var noticedStringResultTools = /* @__PURE__ */ new Set();
|
|
2756
|
+
function noticeStringResult(toolName, logger) {
|
|
2757
|
+
if (noticedStringResultTools.has(toolName)) return;
|
|
2758
|
+
noticedStringResultTools.add(toolName);
|
|
2759
|
+
logger.warn(
|
|
2760
|
+
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup, which is rendered as HTML. FrontMCP 1.9 will HTML-escape plain string results by default. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(), then set ui.escapeStringResults: true (or escapeStringResults: false to keep the current behaviour without this notice). See ${STRING_RESULT_DOCS}`
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
function renderUnwrappedResult(rawResult, options) {
|
|
2764
|
+
if (isTrustedHtml(rawResult)) {
|
|
2765
|
+
return String(rawResult);
|
|
2766
|
+
}
|
|
2730
2767
|
if (typeof rawResult !== "string") {
|
|
2731
2768
|
return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
|
|
2732
2769
|
}
|
|
2733
|
-
|
|
2770
|
+
if (detectContentType(rawResult) !== "html" || options.escapeStringResults === true) {
|
|
2771
|
+
return escapeHtml(rawResult);
|
|
2772
|
+
}
|
|
2773
|
+
if (options.escapeStringResults === void 0) {
|
|
2774
|
+
noticeStringResult(options.toolName, options.logger ?? console);
|
|
2775
|
+
}
|
|
2776
|
+
return rawResult;
|
|
2734
2777
|
}
|
|
2735
2778
|
function renderToolTemplate(options) {
|
|
2736
2779
|
const { toolName, input, output, template, resolver, platformType, sizing } = options;
|
|
@@ -2744,7 +2787,7 @@ function renderToolTemplate(options) {
|
|
|
2744
2787
|
resolver,
|
|
2745
2788
|
sizing
|
|
2746
2789
|
};
|
|
2747
|
-
let
|
|
2790
|
+
let html2;
|
|
2748
2791
|
let hash = "";
|
|
2749
2792
|
let size = 0;
|
|
2750
2793
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
@@ -2753,43 +2796,43 @@ function renderToolTemplate(options) {
|
|
|
2753
2796
|
{ source: template, inlineReact: resourceMode === "inline" },
|
|
2754
2797
|
{ ...shellConfig, csp: cspConfig }
|
|
2755
2798
|
);
|
|
2756
|
-
|
|
2799
|
+
html2 = result.html;
|
|
2757
2800
|
hash = result.hash;
|
|
2758
2801
|
size = result.size;
|
|
2759
2802
|
} else if (typeof template === "function") {
|
|
2760
2803
|
if (uiType === "react") {
|
|
2761
2804
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2762
|
-
|
|
2805
|
+
html2 = shellResult.html;
|
|
2763
2806
|
hash = shellResult.hash;
|
|
2764
2807
|
size = shellResult.size;
|
|
2765
2808
|
} else {
|
|
2766
2809
|
const helpers = createTemplateHelpers();
|
|
2767
2810
|
const ctx = { input, output, helpers };
|
|
2768
2811
|
const rawResult = template(ctx);
|
|
2769
|
-
const wrapped = wrapDetectedContent(rawResult);
|
|
2812
|
+
const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
|
|
2770
2813
|
if (wrapped) {
|
|
2771
|
-
|
|
2814
|
+
html2 = wrapped;
|
|
2772
2815
|
} else {
|
|
2773
|
-
const shellResult = buildShell(renderUnwrappedResult(rawResult), shellConfig);
|
|
2774
|
-
|
|
2816
|
+
const shellResult = buildShell(renderUnwrappedResult(rawResult, options), shellConfig);
|
|
2817
|
+
html2 = shellResult.html;
|
|
2775
2818
|
hash = shellResult.hash;
|
|
2776
2819
|
size = shellResult.size;
|
|
2777
2820
|
}
|
|
2778
2821
|
}
|
|
2779
2822
|
} else if (typeof template === "string") {
|
|
2780
2823
|
const shellResult = buildShell(template, shellConfig);
|
|
2781
|
-
|
|
2824
|
+
html2 = shellResult.html;
|
|
2782
2825
|
hash = shellResult.hash;
|
|
2783
2826
|
size = shellResult.size;
|
|
2784
2827
|
} else {
|
|
2785
2828
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
2786
|
-
|
|
2829
|
+
html2 = shellResult.html;
|
|
2787
2830
|
hash = shellResult.hash;
|
|
2788
2831
|
size = shellResult.size;
|
|
2789
2832
|
}
|
|
2790
2833
|
const htmlKey = "ui/html";
|
|
2791
2834
|
const meta = {
|
|
2792
|
-
[htmlKey]:
|
|
2835
|
+
[htmlKey]: html2,
|
|
2793
2836
|
"ui/type": uiType,
|
|
2794
2837
|
"ui/mimeType": MCP_APPS_MIME_TYPE
|
|
2795
2838
|
};
|
|
@@ -2800,10 +2843,10 @@ function renderToolTemplate(options) {
|
|
|
2800
2843
|
if (sizing.aspectRatio !== void 0) meta["ui/aspectRatio"] = sizing.aspectRatio;
|
|
2801
2844
|
}
|
|
2802
2845
|
return {
|
|
2803
|
-
html,
|
|
2846
|
+
html: html2,
|
|
2804
2847
|
uiType,
|
|
2805
2848
|
hash,
|
|
2806
|
-
size: size || Buffer.byteLength(
|
|
2849
|
+
size: size || Buffer.byteLength(html2, "utf8"),
|
|
2807
2850
|
meta
|
|
2808
2851
|
};
|
|
2809
2852
|
}
|