@frontmcp/uipack 1.9.1-rc.3 → 1.9.2
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.d.ts +1 -1
- package/adapters/index.d.ts.map +1 -1
- package/adapters/index.js +22 -7
- package/adapters/template-renderer.d.ts +12 -2
- package/adapters/template-renderer.d.ts.map +1 -1
- package/esm/adapters/index.mjs +21 -7
- package/esm/index.mjs +21 -7
- package/esm/package.json +2 -2
- package/index.js +22 -7
- package/package.json +2 -2
- package/shell/trusted-html.d.ts +2 -2
- package/types/ui-config.d.ts +7 -7
package/adapters/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { MCP_APPS_MIME_TYPE, MCP_APPS_EXTENSION_ID } from './constants';
|
|
|
11
11
|
export { resolveServingMode, type ResolveServingModeOptions, type ServingModeResult, type AdapterPlatformType, } from './serving-mode';
|
|
12
12
|
export { buildToolResponseContent, type ToolResponseContent, type BuildToolResponseContentOptions, } from './response-builder';
|
|
13
13
|
export { isUIRenderFailure, type UIRenderFailure } from './render-failure';
|
|
14
|
-
export { renderToolTemplate, type RenderToolTemplateOptions, type RenderToolTemplateResult } from './template-renderer';
|
|
14
|
+
export { renderToolTemplate, warnIfComponentReference, type RenderToolTemplateOptions, type RenderToolTemplateResult, } from './template-renderer';
|
|
15
15
|
export { detectContentType, type DetectedContentType } from './content-detector';
|
|
16
16
|
export { buildChartHtml, buildMermaidHtml, buildPdfHtml, wrapDetectedContent } from './content-renderers';
|
|
17
17
|
export { createDefaultBaseTemplate, type DefaultBaseTemplateOptions } from './base-template';
|
package/adapters/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKxE,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,+BAA+B,GACrC,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAK3E,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKxE,OAAO,EACL,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,gBAAgB,CAAC;AAKxB,OAAO,EACL,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,+BAA+B,GACrC,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAK3E,OAAO,EACL,kBAAkB,EAClB,wBAAwB,EACxB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,GAC9B,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EAAE,iBAAiB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAK1G,OAAO,EAAE,yBAAyB,EAAE,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAK7F,OAAO,EAAE,qBAAqB,EAAE,KAAK,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAKzF,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
package/adapters/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(adapters_exports, {
|
|
|
33
33
|
isUIRenderFailure: () => isUIRenderFailure,
|
|
34
34
|
renderToolTemplate: () => renderToolTemplate,
|
|
35
35
|
resolveServingMode: () => resolveServingMode,
|
|
36
|
+
warnIfComponentReference: () => warnIfComponentReference,
|
|
36
37
|
wrapDetectedContent: () => wrapDetectedContent
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(adapters_exports);
|
|
@@ -3077,7 +3078,7 @@ function noticeStringResult(toolName, logger) {
|
|
|
3077
3078
|
if (noticedStringResultTools.has(toolName)) return;
|
|
3078
3079
|
noticedStringResultTools.add(toolName);
|
|
3079
3080
|
logger.warn(
|
|
3080
|
-
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup
|
|
3081
|
+
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup. FrontMCP 1.9 HTML-escapes plain string results by default, so it is shown as text. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(). Set ui.escapeStringResults: false to render plain strings as markup, or true to silence this notice. See ${STRING_RESULT_DOCS}`
|
|
3081
3082
|
);
|
|
3082
3083
|
}
|
|
3083
3084
|
function renderUnwrappedResult(rawResult, options) {
|
|
@@ -3087,13 +3088,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
3087
3088
|
if (typeof rawResult !== "string") {
|
|
3088
3089
|
return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
|
|
3089
3090
|
}
|
|
3090
|
-
|
|
3091
|
-
|
|
3091
|
+
const looksLikeMarkup = detectContentType(rawResult) === "html";
|
|
3092
|
+
if (looksLikeMarkup && options.escapeStringResults === false) {
|
|
3093
|
+
return rawResult;
|
|
3092
3094
|
}
|
|
3093
|
-
if (options.escapeStringResults === void 0) {
|
|
3095
|
+
if (looksLikeMarkup && options.escapeStringResults === void 0) {
|
|
3094
3096
|
noticeStringResult(options.toolName, options.logger ?? console);
|
|
3095
3097
|
}
|
|
3096
|
-
return rawResult;
|
|
3098
|
+
return escapeHtml(rawResult);
|
|
3097
3099
|
}
|
|
3098
3100
|
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
3099
3101
|
function warnComponentReference(template, toolName, logger) {
|
|
@@ -3105,7 +3107,18 @@ function warnComponentReference(template, toolName, logger) {
|
|
|
3105
3107
|
`[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
3108
|
);
|
|
3107
3109
|
}
|
|
3108
|
-
|
|
3110
|
+
var COMPILED_JSX_PATTERN = /\b(?:React\.createElement|_?jsx(?:s|DEV)?)\)?\s*\(/;
|
|
3111
|
+
function isComponentReference(template) {
|
|
3112
|
+
if (isReactTypedObject(template)) return true;
|
|
3113
|
+
if (typeof template !== "function" || detectUIType(template) !== "react") return false;
|
|
3114
|
+
return isDefinitelyReactComponent(template) || COMPILED_JSX_PATTERN.test(Function.prototype.toString.call(template));
|
|
3115
|
+
}
|
|
3116
|
+
function warnIfComponentReference(toolName, template, logger = console) {
|
|
3117
|
+
if (!isComponentReference(template)) return false;
|
|
3118
|
+
warnComponentReference(template, toolName, logger);
|
|
3119
|
+
return true;
|
|
3120
|
+
}
|
|
3121
|
+
function isReactTypedObject(value) {
|
|
3109
3122
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
3110
3123
|
}
|
|
3111
3124
|
function isDefinitelyReactComponent(template) {
|
|
@@ -3145,7 +3158,7 @@ function renderToolTemplate(options) {
|
|
|
3145
3158
|
if (isHtmlBuilder) {
|
|
3146
3159
|
try {
|
|
3147
3160
|
rawResult = template(ctx);
|
|
3148
|
-
isHtmlBuilder = !
|
|
3161
|
+
isHtmlBuilder = !isReactTypedObject(rawResult);
|
|
3149
3162
|
} catch (error) {
|
|
3150
3163
|
if (uiType !== "react") throw error;
|
|
3151
3164
|
isHtmlBuilder = false;
|
|
@@ -3174,6 +3187,7 @@ function renderToolTemplate(options) {
|
|
|
3174
3187
|
hash = shellResult.hash;
|
|
3175
3188
|
size = shellResult.size;
|
|
3176
3189
|
} else {
|
|
3190
|
+
if (isReactTypedObject(template)) warnComponentReference(template, toolName, options.logger ?? console);
|
|
3177
3191
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
3178
3192
|
html2 = shellResult.html;
|
|
3179
3193
|
hash = shellResult.hash;
|
|
@@ -3288,5 +3302,6 @@ function buildCDNInfoForUIType(uiType) {
|
|
|
3288
3302
|
isUIRenderFailure,
|
|
3289
3303
|
renderToolTemplate,
|
|
3290
3304
|
resolveServingMode,
|
|
3305
|
+
warnIfComponentReference,
|
|
3291
3306
|
wrapDetectedContent
|
|
3292
3307
|
});
|
|
@@ -46,8 +46,9 @@ export interface RenderToolTemplateOptions {
|
|
|
46
46
|
};
|
|
47
47
|
/**
|
|
48
48
|
* HTML-escape a plain string returned by a template function; results built with
|
|
49
|
-
* `html` / `trustedHtml` stay markup.
|
|
50
|
-
*
|
|
49
|
+
* `html` / `trustedHtml` stay markup. Unset is the 1.9 default: strings are escaped, and a
|
|
50
|
+
* one-time notice per tool is logged when one looked like markup. `true` escapes without the
|
|
51
|
+
* notice; `false` renders strings that look like HTML as markup.
|
|
51
52
|
*/
|
|
52
53
|
escapeStringResults?: boolean;
|
|
53
54
|
/** Receives the one-time string-result notice. Defaults to `console`. */
|
|
@@ -70,6 +71,15 @@ export interface RenderToolTemplateResult {
|
|
|
70
71
|
/** Platform-specific metadata */
|
|
71
72
|
meta: Record<string, unknown>;
|
|
72
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Warn, once per tool, that `template` is a React component reference the widget cannot bundle.
|
|
76
|
+
* The server calls this while it registers UI tools, so the warning appears at startup (#769).
|
|
77
|
+
*
|
|
78
|
+
* @returns Whether the template is a component reference
|
|
79
|
+
*/
|
|
80
|
+
export declare function warnIfComponentReference(toolName: string, template: unknown, logger?: {
|
|
81
|
+
warn: (message: string) => void;
|
|
82
|
+
}): boolean;
|
|
73
83
|
/**
|
|
74
84
|
* Render a tool template into HTML.
|
|
75
85
|
*
|
|
@@ -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
|
|
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;;;;;OAKG;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;AAiGD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,EACjB,MAAM,GAAE;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAAY,GACpD,OAAO,CAIT;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAqH/F"}
|
package/esm/adapters/index.mjs
CHANGED
|
@@ -3048,7 +3048,7 @@ function noticeStringResult(toolName, logger) {
|
|
|
3048
3048
|
if (noticedStringResultTools.has(toolName)) return;
|
|
3049
3049
|
noticedStringResultTools.add(toolName);
|
|
3050
3050
|
logger.warn(
|
|
3051
|
-
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup
|
|
3051
|
+
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup. FrontMCP 1.9 HTML-escapes plain string results by default, so it is shown as text. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(). Set ui.escapeStringResults: false to render plain strings as markup, or true to silence this notice. See ${STRING_RESULT_DOCS}`
|
|
3052
3052
|
);
|
|
3053
3053
|
}
|
|
3054
3054
|
function renderUnwrappedResult(rawResult, options) {
|
|
@@ -3058,13 +3058,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
3058
3058
|
if (typeof rawResult !== "string") {
|
|
3059
3059
|
return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
|
|
3060
3060
|
}
|
|
3061
|
-
|
|
3062
|
-
|
|
3061
|
+
const looksLikeMarkup = detectContentType(rawResult) === "html";
|
|
3062
|
+
if (looksLikeMarkup && options.escapeStringResults === false) {
|
|
3063
|
+
return rawResult;
|
|
3063
3064
|
}
|
|
3064
|
-
if (options.escapeStringResults === void 0) {
|
|
3065
|
+
if (looksLikeMarkup && options.escapeStringResults === void 0) {
|
|
3065
3066
|
noticeStringResult(options.toolName, options.logger ?? console);
|
|
3066
3067
|
}
|
|
3067
|
-
return rawResult;
|
|
3068
|
+
return escapeHtml(rawResult);
|
|
3068
3069
|
}
|
|
3069
3070
|
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
3070
3071
|
function warnComponentReference(template, toolName, logger) {
|
|
@@ -3076,7 +3077,18 @@ function warnComponentReference(template, toolName, logger) {
|
|
|
3076
3077
|
`[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
|
);
|
|
3078
3079
|
}
|
|
3079
|
-
|
|
3080
|
+
var COMPILED_JSX_PATTERN = /\b(?:React\.createElement|_?jsx(?:s|DEV)?)\)?\s*\(/;
|
|
3081
|
+
function isComponentReference(template) {
|
|
3082
|
+
if (isReactTypedObject(template)) return true;
|
|
3083
|
+
if (typeof template !== "function" || detectUIType(template) !== "react") return false;
|
|
3084
|
+
return isDefinitelyReactComponent(template) || COMPILED_JSX_PATTERN.test(Function.prototype.toString.call(template));
|
|
3085
|
+
}
|
|
3086
|
+
function warnIfComponentReference(toolName, template, logger = console) {
|
|
3087
|
+
if (!isComponentReference(template)) return false;
|
|
3088
|
+
warnComponentReference(template, toolName, logger);
|
|
3089
|
+
return true;
|
|
3090
|
+
}
|
|
3091
|
+
function isReactTypedObject(value) {
|
|
3080
3092
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
3081
3093
|
}
|
|
3082
3094
|
function isDefinitelyReactComponent(template) {
|
|
@@ -3116,7 +3128,7 @@ function renderToolTemplate(options) {
|
|
|
3116
3128
|
if (isHtmlBuilder) {
|
|
3117
3129
|
try {
|
|
3118
3130
|
rawResult = template(ctx);
|
|
3119
|
-
isHtmlBuilder = !
|
|
3131
|
+
isHtmlBuilder = !isReactTypedObject(rawResult);
|
|
3120
3132
|
} catch (error) {
|
|
3121
3133
|
if (uiType !== "react") throw error;
|
|
3122
3134
|
isHtmlBuilder = false;
|
|
@@ -3145,6 +3157,7 @@ function renderToolTemplate(options) {
|
|
|
3145
3157
|
hash = shellResult.hash;
|
|
3146
3158
|
size = shellResult.size;
|
|
3147
3159
|
} else {
|
|
3160
|
+
if (isReactTypedObject(template)) warnComponentReference(template, toolName, options.logger ?? console);
|
|
3148
3161
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
3149
3162
|
html2 = shellResult.html;
|
|
3150
3163
|
hash = shellResult.hash;
|
|
@@ -3258,5 +3271,6 @@ export {
|
|
|
3258
3271
|
isUIRenderFailure,
|
|
3259
3272
|
renderToolTemplate,
|
|
3260
3273
|
resolveServingMode,
|
|
3274
|
+
warnIfComponentReference,
|
|
3261
3275
|
wrapDetectedContent
|
|
3262
3276
|
};
|
package/esm/index.mjs
CHANGED
|
@@ -4319,7 +4319,7 @@ function noticeStringResult(toolName, logger) {
|
|
|
4319
4319
|
if (noticedStringResultTools.has(toolName)) return;
|
|
4320
4320
|
noticedStringResultTools.add(toolName);
|
|
4321
4321
|
logger.warn(
|
|
4322
|
-
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup
|
|
4322
|
+
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup. FrontMCP 1.9 HTML-escapes plain string results by default, so it is shown as text. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(). Set ui.escapeStringResults: false to render plain strings as markup, or true to silence this notice. See ${STRING_RESULT_DOCS}`
|
|
4323
4323
|
);
|
|
4324
4324
|
}
|
|
4325
4325
|
function renderUnwrappedResult(rawResult, options) {
|
|
@@ -4329,13 +4329,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
4329
4329
|
if (typeof rawResult !== "string") {
|
|
4330
4330
|
return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
|
|
4331
4331
|
}
|
|
4332
|
-
|
|
4333
|
-
|
|
4332
|
+
const looksLikeMarkup = detectContentType(rawResult) === "html";
|
|
4333
|
+
if (looksLikeMarkup && options.escapeStringResults === false) {
|
|
4334
|
+
return rawResult;
|
|
4334
4335
|
}
|
|
4335
|
-
if (options.escapeStringResults === void 0) {
|
|
4336
|
+
if (looksLikeMarkup && options.escapeStringResults === void 0) {
|
|
4336
4337
|
noticeStringResult(options.toolName, options.logger ?? console);
|
|
4337
4338
|
}
|
|
4338
|
-
return rawResult;
|
|
4339
|
+
return escapeHtml(rawResult);
|
|
4339
4340
|
}
|
|
4340
4341
|
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
4341
4342
|
function warnComponentReference(template, toolName, logger) {
|
|
@@ -4347,7 +4348,18 @@ function warnComponentReference(template, toolName, logger) {
|
|
|
4347
4348
|
`[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
|
);
|
|
4349
4350
|
}
|
|
4350
|
-
|
|
4351
|
+
var COMPILED_JSX_PATTERN = /\b(?:React\.createElement|_?jsx(?:s|DEV)?)\)?\s*\(/;
|
|
4352
|
+
function isComponentReference(template) {
|
|
4353
|
+
if (isReactTypedObject(template)) return true;
|
|
4354
|
+
if (typeof template !== "function" || detectUIType(template) !== "react") return false;
|
|
4355
|
+
return isDefinitelyReactComponent(template) || COMPILED_JSX_PATTERN.test(Function.prototype.toString.call(template));
|
|
4356
|
+
}
|
|
4357
|
+
function warnIfComponentReference(toolName, template, logger = console) {
|
|
4358
|
+
if (!isComponentReference(template)) return false;
|
|
4359
|
+
warnComponentReference(template, toolName, logger);
|
|
4360
|
+
return true;
|
|
4361
|
+
}
|
|
4362
|
+
function isReactTypedObject(value) {
|
|
4351
4363
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
4352
4364
|
}
|
|
4353
4365
|
function isDefinitelyReactComponent(template) {
|
|
@@ -4387,7 +4399,7 @@ function renderToolTemplate(options) {
|
|
|
4387
4399
|
if (isHtmlBuilder) {
|
|
4388
4400
|
try {
|
|
4389
4401
|
rawResult = template(ctx);
|
|
4390
|
-
isHtmlBuilder = !
|
|
4402
|
+
isHtmlBuilder = !isReactTypedObject(rawResult);
|
|
4391
4403
|
} catch (error) {
|
|
4392
4404
|
if (uiType !== "react") throw error;
|
|
4393
4405
|
isHtmlBuilder = false;
|
|
@@ -4416,6 +4428,7 @@ function renderToolTemplate(options) {
|
|
|
4416
4428
|
hash = shellResult.hash;
|
|
4417
4429
|
size = shellResult.size;
|
|
4418
4430
|
} else {
|
|
4431
|
+
if (isReactTypedObject(template)) warnComponentReference(template, toolName, options.logger ?? console);
|
|
4419
4432
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
4420
4433
|
html2 = shellResult.html;
|
|
4421
4434
|
hash = shellResult.hash;
|
|
@@ -4617,5 +4630,6 @@ export {
|
|
|
4617
4630
|
validateCSPDomain,
|
|
4618
4631
|
validateImportMap,
|
|
4619
4632
|
validateShellTemplate,
|
|
4633
|
+
warnIfComponentReference,
|
|
4620
4634
|
wrapDetectedContent
|
|
4621
4635
|
};
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
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.9.
|
|
64
|
+
"@frontmcp/lazy-zod": "1.9.2",
|
|
65
65
|
"esbuild": ">=0.27.0 <1"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
package/index.js
CHANGED
|
@@ -121,6 +121,7 @@ __export(index_exports, {
|
|
|
121
121
|
validateCSPDomain: () => validateCSPDomain,
|
|
122
122
|
validateImportMap: () => validateImportMap,
|
|
123
123
|
validateShellTemplate: () => validateShellTemplate,
|
|
124
|
+
warnIfComponentReference: () => warnIfComponentReference,
|
|
124
125
|
wrapDetectedContent: () => wrapDetectedContent
|
|
125
126
|
});
|
|
126
127
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -4436,7 +4437,7 @@ function noticeStringResult(toolName, logger) {
|
|
|
4436
4437
|
if (noticedStringResultTools.has(toolName)) return;
|
|
4437
4438
|
noticedStringResultTools.add(toolName);
|
|
4438
4439
|
logger.warn(
|
|
4439
|
-
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup
|
|
4440
|
+
`[frontmcp] The UI template of tool "${toolName}" returned a plain string containing markup. FrontMCP 1.9 HTML-escapes plain string results by default, so it is shown as text. Build the markup with ctx.helpers.html\`\u2026\` (interpolated values are escaped) or wrap safe markup with ctx.helpers.trustedHtml(). Set ui.escapeStringResults: false to render plain strings as markup, or true to silence this notice. See ${STRING_RESULT_DOCS}`
|
|
4440
4441
|
);
|
|
4441
4442
|
}
|
|
4442
4443
|
function renderUnwrappedResult(rawResult, options) {
|
|
@@ -4446,13 +4447,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
4446
4447
|
if (typeof rawResult !== "string") {
|
|
4447
4448
|
return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
|
|
4448
4449
|
}
|
|
4449
|
-
|
|
4450
|
-
|
|
4450
|
+
const looksLikeMarkup = detectContentType(rawResult) === "html";
|
|
4451
|
+
if (looksLikeMarkup && options.escapeStringResults === false) {
|
|
4452
|
+
return rawResult;
|
|
4451
4453
|
}
|
|
4452
|
-
if (options.escapeStringResults === void 0) {
|
|
4454
|
+
if (looksLikeMarkup && options.escapeStringResults === void 0) {
|
|
4453
4455
|
noticeStringResult(options.toolName, options.logger ?? console);
|
|
4454
4456
|
}
|
|
4455
|
-
return rawResult;
|
|
4457
|
+
return escapeHtml(rawResult);
|
|
4456
4458
|
}
|
|
4457
4459
|
var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
|
|
4458
4460
|
function warnComponentReference(template, toolName, logger) {
|
|
@@ -4464,7 +4466,18 @@ function warnComponentReference(template, toolName, logger) {
|
|
|
4464
4466
|
`[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
4467
|
);
|
|
4466
4468
|
}
|
|
4467
|
-
|
|
4469
|
+
var COMPILED_JSX_PATTERN = /\b(?:React\.createElement|_?jsx(?:s|DEV)?)\)?\s*\(/;
|
|
4470
|
+
function isComponentReference(template) {
|
|
4471
|
+
if (isReactTypedObject(template)) return true;
|
|
4472
|
+
if (typeof template !== "function" || detectUIType(template) !== "react") return false;
|
|
4473
|
+
return isDefinitelyReactComponent(template) || COMPILED_JSX_PATTERN.test(Function.prototype.toString.call(template));
|
|
4474
|
+
}
|
|
4475
|
+
function warnIfComponentReference(toolName, template, logger = console) {
|
|
4476
|
+
if (!isComponentReference(template)) return false;
|
|
4477
|
+
warnComponentReference(template, toolName, logger);
|
|
4478
|
+
return true;
|
|
4479
|
+
}
|
|
4480
|
+
function isReactTypedObject(value) {
|
|
4468
4481
|
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
4469
4482
|
}
|
|
4470
4483
|
function isDefinitelyReactComponent(template) {
|
|
@@ -4504,7 +4517,7 @@ function renderToolTemplate(options) {
|
|
|
4504
4517
|
if (isHtmlBuilder) {
|
|
4505
4518
|
try {
|
|
4506
4519
|
rawResult = template(ctx);
|
|
4507
|
-
isHtmlBuilder = !
|
|
4520
|
+
isHtmlBuilder = !isReactTypedObject(rawResult);
|
|
4508
4521
|
} catch (error) {
|
|
4509
4522
|
if (uiType !== "react") throw error;
|
|
4510
4523
|
isHtmlBuilder = false;
|
|
@@ -4533,6 +4546,7 @@ function renderToolTemplate(options) {
|
|
|
4533
4546
|
hash = shellResult.hash;
|
|
4534
4547
|
size = shellResult.size;
|
|
4535
4548
|
} else {
|
|
4549
|
+
if (isReactTypedObject(template)) warnComponentReference(template, toolName, options.logger ?? console);
|
|
4536
4550
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
4537
4551
|
html2 = shellResult.html;
|
|
4538
4552
|
hash = shellResult.hash;
|
|
@@ -4735,5 +4749,6 @@ function buildCDNInfoForUIType(uiType) {
|
|
|
4735
4749
|
validateCSPDomain,
|
|
4736
4750
|
validateImportMap,
|
|
4737
4751
|
validateShellTemplate,
|
|
4752
|
+
warnIfComponentReference,
|
|
4738
4753
|
wrapDetectedContent
|
|
4739
4754
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
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.9.
|
|
64
|
+
"@frontmcp/lazy-zod": "1.9.2",
|
|
65
65
|
"esbuild": ">=0.27.0 <1"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
package/shell/trusted-html.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Trusted Markup
|
|
3
3
|
*
|
|
4
4
|
* Explicit markup values for UI template functions. A template result built with {@link html}
|
|
5
|
-
* or wrapped with {@link trustedHtml} is rendered as HTML;
|
|
6
|
-
*
|
|
5
|
+
* or wrapped with {@link trustedHtml} is rendered as HTML; a plain string result is escaped instead
|
|
6
|
+
* (unless `escapeStringResults: false`).
|
|
7
7
|
*
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
package/types/ui-config.d.ts
CHANGED
|
@@ -182,8 +182,8 @@ export interface TemplateHelpers {
|
|
|
182
182
|
*/
|
|
183
183
|
html: (strings: TemplateStringsArray, ...values: unknown[]) => TrustedHtml;
|
|
184
184
|
/**
|
|
185
|
-
* Mark markup you produced or sanitized yourself as trusted, so it renders as HTML
|
|
186
|
-
*
|
|
185
|
+
* Mark markup you produced or sanitized yourself as trusted, so it renders as HTML although plain
|
|
186
|
+
* string results are escaped. Never pass raw tool output or user input.
|
|
187
187
|
*/
|
|
188
188
|
trustedHtml: (markup: string) => TrustedHtml;
|
|
189
189
|
}
|
|
@@ -349,11 +349,11 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
|
|
|
349
349
|
/**
|
|
350
350
|
* HTML-escape a plain string returned by a template function.
|
|
351
351
|
*
|
|
352
|
-
* -
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* - `false`:
|
|
352
|
+
* - unset (1.9 default): plain strings are escaped and shown as text, and a one-time notice per
|
|
353
|
+
* tool is logged when one looked like markup; markup built with `ctx.helpers.html` or wrapped
|
|
354
|
+
* with `ctx.helpers.trustedHtml` still renders as HTML.
|
|
355
|
+
* - `true`: the same, without the notice.
|
|
356
|
+
* - `false`: strings that look like HTML render as markup (the 1.8 behaviour).
|
|
357
357
|
*
|
|
358
358
|
* Overrides the server-wide `@FrontMcp({ ui: { escapeStringResults } })` default.
|
|
359
359
|
* Static string templates (`template: '<div>…</div>'`) are author markup and never escaped.
|