@frontmcp/uipack 1.8.1 → 1.8.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/esm/index.mjs CHANGED
@@ -2917,6 +2917,29 @@ function validateShellTemplate(template) {
2917
2917
  };
2918
2918
  }
2919
2919
 
2920
+ // libs/uipack/src/shell/trusted-html.ts
2921
+ var TRUSTED_HTML = /* @__PURE__ */ Symbol.for("@frontmcp/uipack/trusted-html");
2922
+ function trustedHtml(markup) {
2923
+ const value = String(markup);
2924
+ return Object.freeze({ [TRUSTED_HTML]: value, toString: () => value });
2925
+ }
2926
+ function isTrustedHtml(value) {
2927
+ return typeof value === "object" && value !== null && typeof value[TRUSTED_HTML] === "string";
2928
+ }
2929
+ function interpolate(value) {
2930
+ if (isTrustedHtml(value)) return value[TRUSTED_HTML];
2931
+ if (Array.isArray(value)) return value.map(interpolate).join("");
2932
+ if (value === false) return "";
2933
+ return escapeHtml(value);
2934
+ }
2935
+ function html(strings, ...values) {
2936
+ let markup = strings[0];
2937
+ for (let index = 0; index < values.length; index++) {
2938
+ markup += interpolate(values[index]) + strings[index + 1];
2939
+ }
2940
+ return trustedHtml(markup);
2941
+ }
2942
+
2920
2943
  // libs/uipack/src/shell/data-injector.ts
2921
2944
  function hasSizing(sizing) {
2922
2945
  if (!sizing) return false;
@@ -2970,7 +2993,9 @@ function createTemplateHelpers() {
2970
2993
  uniqueId: (prefix = "mcp") => {
2971
2994
  return `${prefix}-${++_uniqueIdCounter}`;
2972
2995
  },
2973
- jsonEmbed: (data) => safeJsonForScript(data)
2996
+ jsonEmbed: (data) => safeJsonForScript(data),
2997
+ html,
2998
+ trustedHtml
2974
2999
  };
2975
3000
  }
2976
3001
 
@@ -3048,12 +3073,12 @@ function buildShell(content, config) {
3048
3073
  sizing
3049
3074
  });
3050
3075
  if (!withShell) {
3051
- const html2 = `${dataScript}
3076
+ const html3 = `${dataScript}
3052
3077
  ${content}`;
3053
3078
  return {
3054
- html: html2,
3055
- hash: simpleHash(html2),
3056
- size: Buffer.byteLength(html2, "utf-8")
3079
+ html: html3,
3080
+ hash: simpleHash(html3),
3081
+ size: Buffer.byteLength(html3, "utf-8")
3057
3082
  };
3058
3083
  }
3059
3084
  if (customShell) {
@@ -3086,7 +3111,7 @@ ${content}`;
3086
3111
  const bridgeScript = generateBridgeIIFE({ minify: true });
3087
3112
  headParts.push(`<script>${bridgeScript}</script>`);
3088
3113
  }
3089
- const html = `<!DOCTYPE html>
3114
+ const html2 = `<!DOCTYPE html>
3090
3115
  <html lang="en">
3091
3116
  <head>
3092
3117
  ${headParts.map((p) => ` ${p}`).join("\n")}
@@ -3096,9 +3121,9 @@ ${content}
3096
3121
  </body>
3097
3122
  </html>`;
3098
3123
  return {
3099
- html,
3100
- hash: simpleHash(html),
3101
- size: Buffer.byteLength(html, "utf-8")
3124
+ html: html2,
3125
+ hash: simpleHash(html2),
3126
+ size: Buffer.byteLength(html2, "utf-8")
3102
3127
  };
3103
3128
  }
3104
3129
  function buildCustomShell(content, customShell, ctx) {
@@ -3131,7 +3156,7 @@ ${sizingStyle}` : dataScript;
3131
3156
  const bridgeScript = generateBridgeIIFE({ minify: true });
3132
3157
  bridgeHtml = `<script>${bridgeScript}</script>`;
3133
3158
  }
3134
- const html = applyShellTemplate(template, {
3159
+ const html2 = applyShellTemplate(template, {
3135
3160
  csp: cspTag,
3136
3161
  data: dataWithSizing,
3137
3162
  bridge: bridgeHtml,
@@ -3139,9 +3164,9 @@ ${sizingStyle}` : dataScript;
3139
3164
  title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
3140
3165
  });
3141
3166
  return {
3142
- html,
3143
- hash: simpleHash(html),
3144
- size: Buffer.byteLength(html, "utf-8")
3167
+ html: html2,
3168
+ hash: simpleHash(html2),
3169
+ size: Buffer.byteLength(html2, "utf-8")
3145
3170
  };
3146
3171
  }
3147
3172
  function simpleHash(str) {
@@ -3548,10 +3573,10 @@ function resolveFileSource(source, options = {}) {
3548
3573
  };
3549
3574
  }
3550
3575
  function resolveFunctionSource(source, input, output) {
3551
- const html = source(input, output);
3576
+ const html2 = source(input, output);
3552
3577
  return {
3553
3578
  mode: "inline",
3554
- html,
3579
+ html: html2,
3555
3580
  exportName: "default",
3556
3581
  meta: { mcpAware: false, renderer: "html" },
3557
3582
  peerDependencies: []
@@ -3997,11 +4022,29 @@ function buildCspConfig(resolver) {
3997
4022
  }
3998
4023
  return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
3999
4024
  }
4000
- function renderUnwrappedResult(rawResult) {
4025
+ var STRING_RESULT_DOCS = "https://docs.agentfront.dev/frontmcp/guides/building-tool-ui#trusted-markup";
4026
+ var noticedStringResultTools = /* @__PURE__ */ new Set();
4027
+ function noticeStringResult(toolName, logger) {
4028
+ if (noticedStringResultTools.has(toolName)) return;
4029
+ noticedStringResultTools.add(toolName);
4030
+ logger.warn(
4031
+ `[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}`
4032
+ );
4033
+ }
4034
+ function renderUnwrappedResult(rawResult, options) {
4035
+ if (isTrustedHtml(rawResult)) {
4036
+ return String(rawResult);
4037
+ }
4001
4038
  if (typeof rawResult !== "string") {
4002
4039
  return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
4003
4040
  }
4004
- return detectContentType(rawResult) === "html" ? rawResult : escapeHtml(rawResult);
4041
+ if (detectContentType(rawResult) !== "html" || options.escapeStringResults === true) {
4042
+ return escapeHtml(rawResult);
4043
+ }
4044
+ if (options.escapeStringResults === void 0) {
4045
+ noticeStringResult(options.toolName, options.logger ?? console);
4046
+ }
4047
+ return rawResult;
4005
4048
  }
4006
4049
  function renderToolTemplate(options) {
4007
4050
  const { toolName, input, output, template, resolver, platformType, sizing } = options;
@@ -4015,7 +4058,7 @@ function renderToolTemplate(options) {
4015
4058
  resolver,
4016
4059
  sizing
4017
4060
  };
4018
- let html;
4061
+ let html2;
4019
4062
  let hash = "";
4020
4063
  let size = 0;
4021
4064
  if (typeof template === "object" && template !== null && "file" in template) {
@@ -4024,43 +4067,43 @@ function renderToolTemplate(options) {
4024
4067
  { source: template, inlineReact: resourceMode === "inline" },
4025
4068
  { ...shellConfig, csp: cspConfig }
4026
4069
  );
4027
- html = result.html;
4070
+ html2 = result.html;
4028
4071
  hash = result.hash;
4029
4072
  size = result.size;
4030
4073
  } else if (typeof template === "function") {
4031
4074
  if (uiType === "react") {
4032
4075
  const shellResult = buildShell('<div id="root"></div>', shellConfig);
4033
- html = shellResult.html;
4076
+ html2 = shellResult.html;
4034
4077
  hash = shellResult.hash;
4035
4078
  size = shellResult.size;
4036
4079
  } else {
4037
4080
  const helpers = createTemplateHelpers();
4038
4081
  const ctx = { input, output, helpers };
4039
4082
  const rawResult = template(ctx);
4040
- const wrapped = wrapDetectedContent(rawResult);
4083
+ const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
4041
4084
  if (wrapped) {
4042
- html = wrapped;
4085
+ html2 = wrapped;
4043
4086
  } else {
4044
- const shellResult = buildShell(renderUnwrappedResult(rawResult), shellConfig);
4045
- html = shellResult.html;
4087
+ const shellResult = buildShell(renderUnwrappedResult(rawResult, options), shellConfig);
4088
+ html2 = shellResult.html;
4046
4089
  hash = shellResult.hash;
4047
4090
  size = shellResult.size;
4048
4091
  }
4049
4092
  }
4050
4093
  } else if (typeof template === "string") {
4051
4094
  const shellResult = buildShell(template, shellConfig);
4052
- html = shellResult.html;
4095
+ html2 = shellResult.html;
4053
4096
  hash = shellResult.hash;
4054
4097
  size = shellResult.size;
4055
4098
  } else {
4056
4099
  const shellResult = buildShell('<div id="root"></div>', shellConfig);
4057
- html = shellResult.html;
4100
+ html2 = shellResult.html;
4058
4101
  hash = shellResult.hash;
4059
4102
  size = shellResult.size;
4060
4103
  }
4061
4104
  const htmlKey = "ui/html";
4062
4105
  const meta = {
4063
- [htmlKey]: html,
4106
+ [htmlKey]: html2,
4064
4107
  "ui/type": uiType,
4065
4108
  "ui/mimeType": MCP_APPS_MIME_TYPE
4066
4109
  };
@@ -4071,10 +4114,10 @@ function renderToolTemplate(options) {
4071
4114
  if (sizing.aspectRatio !== void 0) meta["ui/aspectRatio"] = sizing.aspectRatio;
4072
4115
  }
4073
4116
  return {
4074
- html,
4117
+ html: html2,
4075
4118
  uiType,
4076
4119
  hash,
4077
- size: size || Buffer.byteLength(html, "utf8"),
4120
+ size: size || Buffer.byteLength(html2, "utf8"),
4078
4121
  meta
4079
4122
  };
4080
4123
  }
@@ -4217,6 +4260,7 @@ export {
4217
4260
  getRegisteredPackages,
4218
4261
  getSubpathFromSpecifier,
4219
4262
  globalTypeCache,
4263
+ html,
4220
4264
  isBundlingMode,
4221
4265
  isDisplayMode,
4222
4266
  isFileSource,
@@ -4229,6 +4273,7 @@ export {
4229
4273
  isPackageRegistered,
4230
4274
  isRelativeImport,
4231
4275
  isResourceMode,
4276
+ isTrustedHtml,
4232
4277
  isUIRenderFailure,
4233
4278
  isUIType,
4234
4279
  isUrlShellSource,
@@ -4248,6 +4293,7 @@ export {
4248
4293
  safeStringify,
4249
4294
  sanitizeCSPDomains,
4250
4295
  transpileReactSource,
4296
+ trustedHtml,
4251
4297
  validateCSPDomain,
4252
4298
  validateImportMap,
4253
4299
  validateShellTemplate,
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.8.1",
3
+ "version": "1.8.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,6 +61,6 @@
61
61
  "typescript": "^5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@frontmcp/lazy-zod": "1.8.1"
64
+ "@frontmcp/lazy-zod": "1.8.2"
65
65
  }
66
66
  }
@@ -1343,6 +1343,29 @@ function safeJsonForScript(value) {
1343
1343
  }
1344
1344
  }
1345
1345
 
1346
+ // libs/uipack/src/shell/trusted-html.ts
1347
+ var TRUSTED_HTML = /* @__PURE__ */ Symbol.for("@frontmcp/uipack/trusted-html");
1348
+ function trustedHtml(markup) {
1349
+ const value = String(markup);
1350
+ return Object.freeze({ [TRUSTED_HTML]: value, toString: () => value });
1351
+ }
1352
+ function isTrustedHtml(value) {
1353
+ return typeof value === "object" && value !== null && typeof value[TRUSTED_HTML] === "string";
1354
+ }
1355
+ function interpolate(value) {
1356
+ if (isTrustedHtml(value)) return value[TRUSTED_HTML];
1357
+ if (Array.isArray(value)) return value.map(interpolate).join("");
1358
+ if (value === false) return "";
1359
+ return escapeHtml(value);
1360
+ }
1361
+ function html(strings, ...values) {
1362
+ let markup = strings[0];
1363
+ for (let index = 0; index < values.length; index++) {
1364
+ markup += interpolate(values[index]) + strings[index + 1];
1365
+ }
1366
+ return trustedHtml(markup);
1367
+ }
1368
+
1346
1369
  // libs/uipack/src/shell/data-injector.ts
1347
1370
  function hasSizing(sizing) {
1348
1371
  if (!sizing) return false;
@@ -1396,7 +1419,9 @@ function createTemplateHelpers() {
1396
1419
  uniqueId: (prefix = "mcp") => {
1397
1420
  return `${prefix}-${++_uniqueIdCounter}`;
1398
1421
  },
1399
- jsonEmbed: (data) => safeJsonForScript(data)
1422
+ jsonEmbed: (data) => safeJsonForScript(data),
1423
+ html,
1424
+ trustedHtml
1400
1425
  };
1401
1426
  }
1402
1427
 
@@ -1474,12 +1499,12 @@ function buildShell(content, config) {
1474
1499
  sizing
1475
1500
  });
1476
1501
  if (!withShell) {
1477
- const html2 = `${dataScript}
1502
+ const html3 = `${dataScript}
1478
1503
  ${content}`;
1479
1504
  return {
1480
- html: html2,
1481
- hash: simpleHash(html2),
1482
- size: Buffer.byteLength(html2, "utf-8")
1505
+ html: html3,
1506
+ hash: simpleHash(html3),
1507
+ size: Buffer.byteLength(html3, "utf-8")
1483
1508
  };
1484
1509
  }
1485
1510
  if (customShell) {
@@ -1512,7 +1537,7 @@ ${content}`;
1512
1537
  const bridgeScript = generateBridgeIIFE({ minify: true });
1513
1538
  headParts.push(`<script>${bridgeScript}</script>`);
1514
1539
  }
1515
- const html = `<!DOCTYPE html>
1540
+ const html2 = `<!DOCTYPE html>
1516
1541
  <html lang="en">
1517
1542
  <head>
1518
1543
  ${headParts.map((p) => ` ${p}`).join("\n")}
@@ -1522,9 +1547,9 @@ ${content}
1522
1547
  </body>
1523
1548
  </html>`;
1524
1549
  return {
1525
- html,
1526
- hash: simpleHash(html),
1527
- size: Buffer.byteLength(html, "utf-8")
1550
+ html: html2,
1551
+ hash: simpleHash(html2),
1552
+ size: Buffer.byteLength(html2, "utf-8")
1528
1553
  };
1529
1554
  }
1530
1555
  function buildCustomShell(content, customShell, ctx) {
@@ -1557,7 +1582,7 @@ ${sizingStyle}` : dataScript;
1557
1582
  const bridgeScript = generateBridgeIIFE({ minify: true });
1558
1583
  bridgeHtml = `<script>${bridgeScript}</script>`;
1559
1584
  }
1560
- const html = applyShellTemplate(template, {
1585
+ const html2 = applyShellTemplate(template, {
1561
1586
  csp: cspTag,
1562
1587
  data: dataWithSizing,
1563
1588
  bridge: bridgeHtml,
@@ -1565,9 +1590,9 @@ ${sizingStyle}` : dataScript;
1565
1590
  title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
1566
1591
  });
1567
1592
  return {
1568
- html,
1569
- hash: simpleHash(html),
1570
- size: Buffer.byteLength(html, "utf-8")
1593
+ html: html2,
1594
+ hash: simpleHash(html2),
1595
+ size: Buffer.byteLength(html2, "utf-8")
1571
1596
  };
1572
1597
  }
1573
1598
  function simpleHash(str) {
@@ -1708,11 +1733,14 @@ export {
1708
1733
  clearShellTemplateCache,
1709
1734
  createTemplateHelpers,
1710
1735
  hasSizing,
1736
+ html,
1711
1737
  isInlineShellSource,
1712
1738
  isNpmShellSource,
1739
+ isTrustedHtml,
1713
1740
  isUrlShellSource,
1714
1741
  resolveShellTemplate,
1715
1742
  sanitizeCSPDomains,
1743
+ trustedHtml,
1716
1744
  validateCSPDomain,
1717
1745
  validateShellTemplate
1718
1746
  };
package/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export * from './utils';
17
17
  export { generateBridgeIIFE, generatePlatformBundle, UNIVERSAL_BRIDGE_SCRIPT, BRIDGE_SCRIPT_TAGS, type IIFEGeneratorOptions, } from './bridge-runtime';
18
18
  export { type TypeFetchResult, type TypeFetchError, type TypeFetchErrorCode, type TypeFetchBatchRequest, type TypeFetchBatchResult, type TypeCacheEntry, type TypeCacheStats, type DtsImport, type DtsParseResult, type TypeFetcherOptions, type PackageResolution, DEFAULT_TYPE_FETCHER_OPTIONS, TYPE_CACHE_PREFIX, DEFAULT_TYPE_CACHE_TTL, type TypeCacheAdapter, type TypeCacheOptions, DEFAULT_CACHE_OPTIONS as DEFAULT_TYPE_CACHE_OPTIONS, MemoryTypeCache, globalTypeCache, parseDtsImports, isRelativeImport, getPackageFromSpecifier, getSubpathFromSpecifier, parseImportStatement, combineDtsContents, TypeFetcher, createTypeFetcher, } from './typings';
19
19
  export * from './resolver';
20
- export { type ShellConfig, type ShellResult, type CSPConfig, type ShellMountDescriptor, type ShellDataInjectionDescriptor, buildShell, DEFAULT_CDN_DOMAINS, DEFAULT_CSP_DIRECTIVES, RESTRICTIVE_CSP_DIRECTIVES, buildCSPDirectives, buildCSPMetaTag, validateCSPDomain, sanitizeCSPDomains, buildDataInjectionScript, buildCustomDataInjectionScript, createTemplateHelpers, type ShellPlaceholderName, type InlineShellSource, type UrlShellSource, type NpmShellSource, type CustomShellSource, type ShellTemplateValidation, type ResolvedShellTemplate, type ShellPlaceholderValues, type ResolveShellOptions, SHELL_PLACEHOLDER_NAMES, SHELL_PLACEHOLDERS, REQUIRED_PLACEHOLDERS, OPTIONAL_PLACEHOLDERS, isInlineShellSource, isUrlShellSource, isNpmShellSource, validateShellTemplate, applyShellTemplate, resolveShellTemplate, clearShellTemplateCache, } from './shell';
20
+ export { type ShellConfig, type ShellResult, type CSPConfig, type ShellMountDescriptor, type ShellDataInjectionDescriptor, buildShell, DEFAULT_CDN_DOMAINS, DEFAULT_CSP_DIRECTIVES, RESTRICTIVE_CSP_DIRECTIVES, buildCSPDirectives, buildCSPMetaTag, validateCSPDomain, sanitizeCSPDomains, buildDataInjectionScript, buildCustomDataInjectionScript, createTemplateHelpers, html, trustedHtml, isTrustedHtml, type TrustedHtml, type ShellPlaceholderName, type InlineShellSource, type UrlShellSource, type NpmShellSource, type CustomShellSource, type ShellTemplateValidation, type ResolvedShellTemplate, type ShellPlaceholderValues, type ResolveShellOptions, SHELL_PLACEHOLDER_NAMES, SHELL_PLACEHOLDERS, REQUIRED_PLACEHOLDERS, OPTIONAL_PLACEHOLDERS, isInlineShellSource, isUrlShellSource, isNpmShellSource, validateShellTemplate, applyShellTemplate, resolveShellTemplate, clearShellTemplateCache, } from './shell';
21
21
  export * from './component';
22
22
  export * from './adapters';
23
23
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,cAAc,SAAS,CAAC;AAKxB,cAAc,SAAS,CAAC;AAKxB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC;AAK1B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,4BAA4B,EAC5B,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,qBAAqB,IAAI,0BAA0B,EACnD,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,EACX,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAKnB,cAAc,YAAY,CAAC;AAM3B,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EACjC,UAAU,EACV,mBAAmB,EACnB,sBAAsB,EACtB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,8BAA8B,EAC9B,qBAAqB,EAErB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AAKjB,cAAc,aAAa,CAAC;AAK5B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,cAAc,SAAS,CAAC;AAKxB,cAAc,SAAS,CAAC;AAKxB,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC;AAK1B,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,4BAA4B,EAC5B,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,qBAAqB,IAAI,0BAA0B,EACnD,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,WAAW,EACX,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAKnB,cAAc,YAAY,CAAC;AAM3B,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EACjC,UAAU,EACV,mBAAmB,EACnB,sBAAsB,EACtB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,8BAA8B,EAC9B,qBAAqB,EAErB,IAAI,EACJ,WAAW,EACX,aAAa,EACb,KAAK,WAAW,EAEhB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,SAAS,CAAC;AAKjB,cAAc,aAAa,CAAC;AAK5B,cAAc,YAAY,CAAC"}
package/index.js CHANGED
@@ -84,6 +84,7 @@ __export(index_exports, {
84
84
  getRegisteredPackages: () => getRegisteredPackages,
85
85
  getSubpathFromSpecifier: () => getSubpathFromSpecifier,
86
86
  globalTypeCache: () => globalTypeCache,
87
+ html: () => html,
87
88
  isBundlingMode: () => isBundlingMode,
88
89
  isDisplayMode: () => isDisplayMode,
89
90
  isFileSource: () => isFileSource,
@@ -96,6 +97,7 @@ __export(index_exports, {
96
97
  isPackageRegistered: () => isPackageRegistered,
97
98
  isRelativeImport: () => isRelativeImport,
98
99
  isResourceMode: () => isResourceMode,
100
+ isTrustedHtml: () => isTrustedHtml,
99
101
  isUIRenderFailure: () => isUIRenderFailure,
100
102
  isUIType: () => isUIType,
101
103
  isUrlShellSource: () => isUrlShellSource,
@@ -115,6 +117,7 @@ __export(index_exports, {
115
117
  safeStringify: () => safeStringify,
116
118
  sanitizeCSPDomains: () => sanitizeCSPDomains,
117
119
  transpileReactSource: () => transpileReactSource,
120
+ trustedHtml: () => trustedHtml,
118
121
  validateCSPDomain: () => validateCSPDomain,
119
122
  validateImportMap: () => validateImportMap,
120
123
  validateShellTemplate: () => validateShellTemplate,
@@ -3034,6 +3037,29 @@ function validateShellTemplate(template) {
3034
3037
  };
3035
3038
  }
3036
3039
 
3040
+ // libs/uipack/src/shell/trusted-html.ts
3041
+ var TRUSTED_HTML = /* @__PURE__ */ Symbol.for("@frontmcp/uipack/trusted-html");
3042
+ function trustedHtml(markup) {
3043
+ const value = String(markup);
3044
+ return Object.freeze({ [TRUSTED_HTML]: value, toString: () => value });
3045
+ }
3046
+ function isTrustedHtml(value) {
3047
+ return typeof value === "object" && value !== null && typeof value[TRUSTED_HTML] === "string";
3048
+ }
3049
+ function interpolate(value) {
3050
+ if (isTrustedHtml(value)) return value[TRUSTED_HTML];
3051
+ if (Array.isArray(value)) return value.map(interpolate).join("");
3052
+ if (value === false) return "";
3053
+ return escapeHtml(value);
3054
+ }
3055
+ function html(strings, ...values) {
3056
+ let markup = strings[0];
3057
+ for (let index = 0; index < values.length; index++) {
3058
+ markup += interpolate(values[index]) + strings[index + 1];
3059
+ }
3060
+ return trustedHtml(markup);
3061
+ }
3062
+
3037
3063
  // libs/uipack/src/shell/data-injector.ts
3038
3064
  function hasSizing(sizing) {
3039
3065
  if (!sizing) return false;
@@ -3087,7 +3113,9 @@ function createTemplateHelpers() {
3087
3113
  uniqueId: (prefix = "mcp") => {
3088
3114
  return `${prefix}-${++_uniqueIdCounter}`;
3089
3115
  },
3090
- jsonEmbed: (data) => safeJsonForScript(data)
3116
+ jsonEmbed: (data) => safeJsonForScript(data),
3117
+ html,
3118
+ trustedHtml
3091
3119
  };
3092
3120
  }
3093
3121
 
@@ -3165,12 +3193,12 @@ function buildShell(content, config) {
3165
3193
  sizing
3166
3194
  });
3167
3195
  if (!withShell) {
3168
- const html2 = `${dataScript}
3196
+ const html3 = `${dataScript}
3169
3197
  ${content}`;
3170
3198
  return {
3171
- html: html2,
3172
- hash: simpleHash(html2),
3173
- size: Buffer.byteLength(html2, "utf-8")
3199
+ html: html3,
3200
+ hash: simpleHash(html3),
3201
+ size: Buffer.byteLength(html3, "utf-8")
3174
3202
  };
3175
3203
  }
3176
3204
  if (customShell) {
@@ -3203,7 +3231,7 @@ ${content}`;
3203
3231
  const bridgeScript = generateBridgeIIFE({ minify: true });
3204
3232
  headParts.push(`<script>${bridgeScript}</script>`);
3205
3233
  }
3206
- const html = `<!DOCTYPE html>
3234
+ const html2 = `<!DOCTYPE html>
3207
3235
  <html lang="en">
3208
3236
  <head>
3209
3237
  ${headParts.map((p) => ` ${p}`).join("\n")}
@@ -3213,9 +3241,9 @@ ${content}
3213
3241
  </body>
3214
3242
  </html>`;
3215
3243
  return {
3216
- html,
3217
- hash: simpleHash(html),
3218
- size: Buffer.byteLength(html, "utf-8")
3244
+ html: html2,
3245
+ hash: simpleHash(html2),
3246
+ size: Buffer.byteLength(html2, "utf-8")
3219
3247
  };
3220
3248
  }
3221
3249
  function buildCustomShell(content, customShell, ctx) {
@@ -3248,7 +3276,7 @@ ${sizingStyle}` : dataScript;
3248
3276
  const bridgeScript = generateBridgeIIFE({ minify: true });
3249
3277
  bridgeHtml = `<script>${bridgeScript}</script>`;
3250
3278
  }
3251
- const html = applyShellTemplate(template, {
3279
+ const html2 = applyShellTemplate(template, {
3252
3280
  csp: cspTag,
3253
3281
  data: dataWithSizing,
3254
3282
  bridge: bridgeHtml,
@@ -3256,9 +3284,9 @@ ${sizingStyle}` : dataScript;
3256
3284
  title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
3257
3285
  });
3258
3286
  return {
3259
- html,
3260
- hash: simpleHash(html),
3261
- size: Buffer.byteLength(html, "utf-8")
3287
+ html: html2,
3288
+ hash: simpleHash(html2),
3289
+ size: Buffer.byteLength(html2, "utf-8")
3262
3290
  };
3263
3291
  }
3264
3292
  function simpleHash(str) {
@@ -3665,10 +3693,10 @@ function resolveFileSource(source, options = {}) {
3665
3693
  };
3666
3694
  }
3667
3695
  function resolveFunctionSource(source, input, output) {
3668
- const html = source(input, output);
3696
+ const html2 = source(input, output);
3669
3697
  return {
3670
3698
  mode: "inline",
3671
- html,
3699
+ html: html2,
3672
3700
  exportName: "default",
3673
3701
  meta: { mcpAware: false, renderer: "html" },
3674
3702
  peerDependencies: []
@@ -4114,11 +4142,29 @@ function buildCspConfig(resolver) {
4114
4142
  }
4115
4143
  return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
4116
4144
  }
4117
- function renderUnwrappedResult(rawResult) {
4145
+ var STRING_RESULT_DOCS = "https://docs.agentfront.dev/frontmcp/guides/building-tool-ui#trusted-markup";
4146
+ var noticedStringResultTools = /* @__PURE__ */ new Set();
4147
+ function noticeStringResult(toolName, logger) {
4148
+ if (noticedStringResultTools.has(toolName)) return;
4149
+ noticedStringResultTools.add(toolName);
4150
+ logger.warn(
4151
+ `[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}`
4152
+ );
4153
+ }
4154
+ function renderUnwrappedResult(rawResult, options) {
4155
+ if (isTrustedHtml(rawResult)) {
4156
+ return String(rawResult);
4157
+ }
4118
4158
  if (typeof rawResult !== "string") {
4119
4159
  return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
4120
4160
  }
4121
- return detectContentType(rawResult) === "html" ? rawResult : escapeHtml(rawResult);
4161
+ if (detectContentType(rawResult) !== "html" || options.escapeStringResults === true) {
4162
+ return escapeHtml(rawResult);
4163
+ }
4164
+ if (options.escapeStringResults === void 0) {
4165
+ noticeStringResult(options.toolName, options.logger ?? console);
4166
+ }
4167
+ return rawResult;
4122
4168
  }
4123
4169
  function renderToolTemplate(options) {
4124
4170
  const { toolName, input, output, template, resolver, platformType, sizing } = options;
@@ -4132,7 +4178,7 @@ function renderToolTemplate(options) {
4132
4178
  resolver,
4133
4179
  sizing
4134
4180
  };
4135
- let html;
4181
+ let html2;
4136
4182
  let hash = "";
4137
4183
  let size = 0;
4138
4184
  if (typeof template === "object" && template !== null && "file" in template) {
@@ -4141,43 +4187,43 @@ function renderToolTemplate(options) {
4141
4187
  { source: template, inlineReact: resourceMode === "inline" },
4142
4188
  { ...shellConfig, csp: cspConfig }
4143
4189
  );
4144
- html = result.html;
4190
+ html2 = result.html;
4145
4191
  hash = result.hash;
4146
4192
  size = result.size;
4147
4193
  } else if (typeof template === "function") {
4148
4194
  if (uiType === "react") {
4149
4195
  const shellResult = buildShell('<div id="root"></div>', shellConfig);
4150
- html = shellResult.html;
4196
+ html2 = shellResult.html;
4151
4197
  hash = shellResult.hash;
4152
4198
  size = shellResult.size;
4153
4199
  } else {
4154
4200
  const helpers = createTemplateHelpers();
4155
4201
  const ctx = { input, output, helpers };
4156
4202
  const rawResult = template(ctx);
4157
- const wrapped = wrapDetectedContent(rawResult);
4203
+ const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
4158
4204
  if (wrapped) {
4159
- html = wrapped;
4205
+ html2 = wrapped;
4160
4206
  } else {
4161
- const shellResult = buildShell(renderUnwrappedResult(rawResult), shellConfig);
4162
- html = shellResult.html;
4207
+ const shellResult = buildShell(renderUnwrappedResult(rawResult, options), shellConfig);
4208
+ html2 = shellResult.html;
4163
4209
  hash = shellResult.hash;
4164
4210
  size = shellResult.size;
4165
4211
  }
4166
4212
  }
4167
4213
  } else if (typeof template === "string") {
4168
4214
  const shellResult = buildShell(template, shellConfig);
4169
- html = shellResult.html;
4215
+ html2 = shellResult.html;
4170
4216
  hash = shellResult.hash;
4171
4217
  size = shellResult.size;
4172
4218
  } else {
4173
4219
  const shellResult = buildShell('<div id="root"></div>', shellConfig);
4174
- html = shellResult.html;
4220
+ html2 = shellResult.html;
4175
4221
  hash = shellResult.hash;
4176
4222
  size = shellResult.size;
4177
4223
  }
4178
4224
  const htmlKey = "ui/html";
4179
4225
  const meta = {
4180
- [htmlKey]: html,
4226
+ [htmlKey]: html2,
4181
4227
  "ui/type": uiType,
4182
4228
  "ui/mimeType": MCP_APPS_MIME_TYPE
4183
4229
  };
@@ -4188,10 +4234,10 @@ function renderToolTemplate(options) {
4188
4234
  if (sizing.aspectRatio !== void 0) meta["ui/aspectRatio"] = sizing.aspectRatio;
4189
4235
  }
4190
4236
  return {
4191
- html,
4237
+ html: html2,
4192
4238
  uiType,
4193
4239
  hash,
4194
- size: size || Buffer.byteLength(html, "utf8"),
4240
+ size: size || Buffer.byteLength(html2, "utf8"),
4195
4241
  meta
4196
4242
  };
4197
4243
  }
@@ -4335,6 +4381,7 @@ function buildCDNInfoForUIType(uiType) {
4335
4381
  getRegisteredPackages,
4336
4382
  getSubpathFromSpecifier,
4337
4383
  globalTypeCache,
4384
+ html,
4338
4385
  isBundlingMode,
4339
4386
  isDisplayMode,
4340
4387
  isFileSource,
@@ -4347,6 +4394,7 @@ function buildCDNInfoForUIType(uiType) {
4347
4394
  isPackageRegistered,
4348
4395
  isRelativeImport,
4349
4396
  isResourceMode,
4397
+ isTrustedHtml,
4350
4398
  isUIRenderFailure,
4351
4399
  isUIType,
4352
4400
  isUrlShellSource,
@@ -4366,6 +4414,7 @@ function buildCDNInfoForUIType(uiType) {
4366
4414
  safeStringify,
4367
4415
  sanitizeCSPDomains,
4368
4416
  transpileReactSource,
4417
+ trustedHtml,
4369
4418
  validateCSPDomain,
4370
4419
  validateImportMap,
4371
4420
  validateShellTemplate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.8.1",
3
+ "version": "1.8.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,6 +61,6 @@
61
61
  "typescript": "^5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@frontmcp/lazy-zod": "1.8.1"
64
+ "@frontmcp/lazy-zod": "1.8.2"
65
65
  }
66
66
  }