@frontmcp/uipack 1.3.0 → 1.4.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.
Files changed (44) hide show
  1. package/adapters/index.js +1046 -698
  2. package/adapters/template-renderer.d.ts +14 -0
  3. package/adapters/template-renderer.d.ts.map +1 -1
  4. package/bridge-runtime/iife-generator.d.ts.map +1 -1
  5. package/bridge-runtime/index.js +149 -0
  6. package/component/index.d.ts +1 -0
  7. package/component/index.d.ts.map +1 -1
  8. package/component/index.js +468 -145
  9. package/component/loader.d.ts +21 -2
  10. package/component/loader.d.ts.map +1 -1
  11. package/component/renderer.d.ts +2 -2
  12. package/component/renderer.d.ts.map +1 -1
  13. package/component/transpiler.d.ts +16 -1
  14. package/component/transpiler.d.ts.map +1 -1
  15. package/component/types.d.ts +19 -0
  16. package/component/types.d.ts.map +1 -1
  17. package/component/ui-availability.d.ts +27 -0
  18. package/component/ui-availability.d.ts.map +1 -0
  19. package/esm/adapters/index.mjs +1046 -698
  20. package/esm/bridge-runtime/index.mjs +149 -0
  21. package/esm/component/index.mjs +468 -145
  22. package/esm/index.mjs +444 -109
  23. package/esm/package.json +2 -2
  24. package/esm/shell/index.mjs +420 -171
  25. package/index.d.ts +1 -1
  26. package/index.d.ts.map +1 -1
  27. package/index.js +445 -109
  28. package/package.json +2 -2
  29. package/shell/builder.d.ts.map +1 -1
  30. package/shell/data-injector.d.ts +27 -1
  31. package/shell/data-injector.d.ts.map +1 -1
  32. package/shell/index.d.ts +3 -2
  33. package/shell/index.d.ts.map +1 -1
  34. package/shell/index.js +423 -171
  35. package/shell/sizing-css.d.ts +27 -0
  36. package/shell/sizing-css.d.ts.map +1 -0
  37. package/shell/types.d.ts +102 -0
  38. package/shell/types.d.ts.map +1 -1
  39. package/types/index.d.ts +1 -1
  40. package/types/index.d.ts.map +1 -1
  41. package/types/ui-config.d.ts +105 -11
  42. package/types/ui-config.d.ts.map +1 -1
  43. package/types/ui-runtime.d.ts +23 -2
  44. package/types/ui-runtime.d.ts.map +1 -1
@@ -444,6 +444,29 @@ function safeJsonForScript(value) {
444
444
  }
445
445
  }
446
446
 
447
+ // libs/uipack/src/component/ui-availability.ts
448
+ function isFrontmcpUiResolvable(...candidatePaths) {
449
+ try {
450
+ const nodeFs = __require("fs");
451
+ const nodePath = __require("path");
452
+ const ORG = "@frontmcp";
453
+ const PKG = "ui";
454
+ for (const candidate of candidatePaths) {
455
+ let dir = candidate;
456
+ while (true) {
457
+ const pkgJson = nodePath.join(dir, "node_modules", ORG, PKG, "package.json");
458
+ if (nodeFs.existsSync(pkgJson)) return true;
459
+ const parent = nodePath.dirname(dir);
460
+ if (parent === dir) break;
461
+ dir = parent;
462
+ }
463
+ }
464
+ return false;
465
+ } catch {
466
+ return false;
467
+ }
468
+ }
469
+
447
470
  // libs/uipack/src/component/transpiler.ts
448
471
  function transpileReactSource(source, filename) {
449
472
  const esbuild = __require("esbuild");
@@ -459,7 +482,12 @@ function transpileReactSource(source, filename) {
459
482
  });
460
483
  return result.code;
461
484
  }
462
- function bundleFileSource(source, filename, resolveDir, componentName) {
485
+ function bundleFileSource(source, filename, resolveDir, componentName, options = {}) {
486
+ if (!isFrontmcpUiResolvable(resolveDir, process.cwd())) {
487
+ throw new Error(
488
+ `FileSource widget "${filename}" requires the @frontmcp/ui package, which provides the React bridge mount that's injected at bundle time. Install it (e.g. \`npm install @frontmcp/ui\` or \`yarn add @frontmcp/ui\`) and try again.`
489
+ );
490
+ }
463
491
  const esbuild = __require("esbuild");
464
492
  const mountCode = `
465
493
  // --- Auto-generated mount ---
@@ -541,7 +569,11 @@ if (__root) {
541
569
  format: "esm",
542
570
  target: "es2020",
543
571
  jsx: "automatic",
544
- external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
572
+ // When `bundleReact` is set (resourceMode: 'inline'), React itself is
573
+ // bundled — required for hosts that block external script execution
574
+ // such as Claude (#454). Otherwise React stays external and is loaded
575
+ // via the import map emitted by the renderer.
576
+ external: options.bundleReact ? [] : ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
545
577
  alias,
546
578
  define: { "process.env.NODE_ENV": '"production"' },
547
579
  platform: "browser",
@@ -553,7 +585,7 @@ if (__root) {
553
585
  } catch (err) {
554
586
  const message = err instanceof Error ? err.message : String(err);
555
587
  throw new Error(
556
- `Failed to bundle FileSource "${filename}": ${message}. Ensure workspace packages are built (e.g. nx build ui).`
588
+ `Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`
557
589
  );
558
590
  }
559
591
  }
@@ -571,6 +603,9 @@ var DEFAULT_META = {
571
603
  renderer: "auto"
572
604
  };
573
605
  function resolveUISource(source, options) {
606
+ if (options?.inlineReact && options?.transformOnly) {
607
+ throw new Error("resolveUISource: `inlineReact` and `transformOnly` are mutually exclusive \u2014 set at most one.");
608
+ }
574
609
  const resolver = options?.resolver ?? createEsmShResolver();
575
610
  if (isNpmSource(source)) {
576
611
  return resolveNpmSource(source, resolver);
@@ -579,7 +614,7 @@ function resolveUISource(source, options) {
579
614
  return resolveImportSource(source);
580
615
  }
581
616
  if (isFileSource(source)) {
582
- return resolveFileSource(source);
617
+ return resolveFileSource(source, { inlineReact: options?.inlineReact, transformOnly: options?.transformOnly });
583
618
  }
584
619
  if (isFunctionSource(source)) {
585
620
  return resolveFunctionSource(source, options?.input, options?.output);
@@ -612,15 +647,42 @@ function resolveImportSource(source) {
612
647
  peerDependencies: []
613
648
  };
614
649
  }
615
- function resolveFileSource(source) {
650
+ function resolveFileSource(source, options = {}) {
616
651
  const path = __require("path");
617
652
  const ext = path.extname(source.file).toLowerCase();
618
653
  if (ext === ".tsx" || ext === ".jsx") {
619
654
  const fs = __require("fs");
620
- const filePath = path.isAbsolute(source.file) ? source.file : path.resolve(process.cwd(), source.file);
621
- const rawSource = fs.readFileSync(filePath, "utf-8");
655
+ const wasRelative = !path.isAbsolute(source.file);
656
+ const filePath = wasRelative ? path.resolve(process.cwd(), source.file) : source.file;
657
+ let rawSource;
658
+ try {
659
+ rawSource = fs.readFileSync(filePath, "utf-8");
660
+ } catch (err) {
661
+ const isNotFound = err?.code === "ENOENT";
662
+ if (isNotFound && wasRelative) {
663
+ throw new Error(
664
+ `FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`
665
+ );
666
+ }
667
+ throw err;
668
+ }
622
669
  const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
623
- const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName);
670
+ if (options.transformOnly === true) {
671
+ const code = transpileReactSource(rawSource, path.basename(filePath));
672
+ const parsed2 = parseImports(code);
673
+ return {
674
+ mode: "module",
675
+ code,
676
+ imports: [...new Set(parsed2.externalImports.map((i) => i.specifier))],
677
+ exportName: componentName,
678
+ meta: { mcpAware: true, renderer: "react" },
679
+ peerDependencies: source.peerDependencies || ["react", "react-dom"],
680
+ bundled: false
681
+ };
682
+ }
683
+ const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName, {
684
+ bundleReact: options.inlineReact === true
685
+ });
624
686
  const parsed = parseImports(bundled.code);
625
687
  return {
626
688
  mode: "module",
@@ -688,92 +750,27 @@ function generateMappedPropsCode(mapping) {
688
750
  return `({ ${entries} })`;
689
751
  }
690
752
 
691
- // libs/uipack/src/shell/csp.ts
692
- var DEFAULT_CDN_DOMAINS = [
693
- "https://cdn.jsdelivr.net",
694
- "https://cdnjs.cloudflare.com",
695
- "https://fonts.googleapis.com",
696
- "https://fonts.gstatic.com",
697
- "https://esm.sh"
698
- ];
699
- var DEFAULT_CSP_DIRECTIVES = [
700
- "default-src 'none'",
701
- `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
702
- `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
703
- `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
704
- `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
705
- `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
706
- "object-src 'self' data:"
707
- ];
708
- function buildCSPDirectives(csp) {
709
- if (!csp) {
710
- return [...DEFAULT_CSP_DIRECTIVES];
711
- }
712
- const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
713
- const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
714
- const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
715
- const directives = [
716
- "default-src 'none'",
717
- `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
718
- `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
719
- ];
720
- const imgSources = ["'self'", "data:", ...allResourceDomains];
721
- directives.push(`img-src ${imgSources.join(" ")}`);
722
- const fontSources = ["'self'", "data:", ...allResourceDomains];
723
- directives.push(`font-src ${fontSources.join(" ")}`);
724
- if (validConnectDomains.length) {
725
- directives.push(`connect-src ${validConnectDomains.join(" ")}`);
726
- } else {
727
- directives.push(`connect-src ${allResourceDomains.join(" ")}`);
728
- }
729
- directives.push("object-src 'self' data:");
730
- return directives;
731
- }
732
- function buildCSPMetaTag(csp) {
733
- const directives = buildCSPDirectives(csp);
734
- const content = directives.join("; ");
735
- return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
736
- }
737
- function validateCSPDomain(domain) {
738
- if (domain.startsWith("https://*.")) {
739
- const rest = domain.slice(10);
740
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
741
- }
742
- try {
743
- const url = new URL(domain);
744
- return url.protocol === "https:";
745
- } catch {
746
- return false;
747
- }
748
- }
749
- function sanitizeCSPDomains(domains) {
750
- if (!domains) return [];
751
- const valid = [];
752
- for (const domain of domains) {
753
- if (validateCSPDomain(domain)) {
754
- valid.push(domain);
755
- } else {
756
- console.warn(`Invalid CSP domain ignored: ${domain}`);
753
+ // libs/uipack/src/resolver/import-map.ts
754
+ function createImportMapFromResolved(resolved) {
755
+ const imports = {};
756
+ const integrity = {};
757
+ for (const [specifier, res] of Object.entries(resolved)) {
758
+ if (res.type === "url") {
759
+ imports[specifier] = res.value;
760
+ if (res.integrity) {
761
+ integrity[res.value] = res.integrity;
762
+ }
757
763
  }
758
764
  }
759
- return valid;
760
- }
761
- function escapeAttribute(str) {
762
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
765
+ return {
766
+ imports,
767
+ integrity: Object.keys(integrity).length > 0 ? integrity : void 0
768
+ };
763
769
  }
764
-
765
- // libs/uipack/src/shell/data-injector.ts
766
- function buildDataInjectionScript(options) {
767
- const { toolName, input, output, structuredContent } = options;
768
- const lines = [
769
- `window.__mcpAppsEnabled = true;`,
770
- `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
771
- `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
772
- `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
773
- `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
774
- ];
775
- return `<script>
776
- ${lines.join("\n")}
770
+ function generateImportMapScriptTag(map) {
771
+ const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
772
+ return `<script type="importmap">
773
+ ${json}
777
774
  </script>`;
778
775
  }
779
776
 
@@ -831,6 +828,8 @@ function generateBridgeIIFE(options = {}) {
831
828
  parts.push("});");
832
829
  parts.push("");
833
830
  parts.push("window.FrontMcpBridge = bridge;");
831
+ parts.push("");
832
+ parts.push(generateAutoResize());
834
833
  parts.push("function __showLoading() {");
835
834
  parts.push(' var root = document.getElementById("root");');
836
835
  parts.push(" if (root && !root.hasChildNodes()) {");
@@ -851,6 +850,112 @@ function generateBridgeIIFE(options = {}) {
851
850
  }
852
851
  return code;
853
852
  }
853
+ function generateAutoResize() {
854
+ return `
855
+ function __applySizingCss(sizing) {
856
+ if (typeof document === 'undefined' || !document.documentElement) return;
857
+ function toLen(v) { return typeof v === 'number' ? v + 'px' : v; }
858
+ var de = document.documentElement;
859
+ var body = document.body;
860
+ var root = document.getElementById('root');
861
+ if (sizing.preferredHeight != null) {
862
+ var ph = toLen(sizing.preferredHeight);
863
+ de.style.height = ph;
864
+ if (body) body.style.height = ph;
865
+ if (root && !root.style.minHeight) root.style.minHeight = ph;
866
+ }
867
+ if (sizing.minHeight != null) {
868
+ var mh = toLen(sizing.minHeight);
869
+ de.style.minHeight = mh;
870
+ if (body) body.style.minHeight = mh;
871
+ if (root) root.style.minHeight = mh;
872
+ }
873
+ if (sizing.maxHeight != null) {
874
+ var mx = toLen(sizing.maxHeight);
875
+ de.style.maxHeight = mx;
876
+ if (body) body.style.maxHeight = mx;
877
+ if (root) root.style.maxHeight = mx;
878
+ }
879
+ if (sizing.aspectRatio != null && root) {
880
+ root.style.aspectRatio = String(sizing.aspectRatio);
881
+ }
882
+ }
883
+
884
+ function __initAutoResize() {
885
+ if (typeof window === 'undefined') return;
886
+ // Idempotent: a re-injected IIFE must not stack observers.
887
+ if (window.__mcpAutoResizeInit) return;
888
+ window.__mcpAutoResizeInit = true;
889
+ var sizing = window.__mcpWidgetSizing;
890
+ if (!sizing || typeof sizing !== 'object') return;
891
+
892
+ // Apply CSS as a runtime fallback (the static <style> may be absent on some
893
+ // render paths). Wait for the body if it isn't ready yet.
894
+ function apply() { try { __applySizingCss(sizing); } catch (e) {} }
895
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
896
+ document.addEventListener('DOMContentLoaded', apply);
897
+ } else {
898
+ apply();
899
+ }
900
+
901
+ // Auto-resize defaults ON; opt out with autoResize:false.
902
+ if (sizing.autoResize === false) return;
903
+ if (typeof ResizeObserver === 'undefined') return;
904
+
905
+ function startObserving() {
906
+ var target = document.getElementById('root') || document.body;
907
+ if (!target) return;
908
+
909
+ var rafId = null;
910
+ var lastReported = -1;
911
+ function report() {
912
+ rafId = null;
913
+ try {
914
+ var rect = target.getBoundingClientRect();
915
+ var height = Math.ceil(rect.height);
916
+ var width = Math.ceil(rect.width);
917
+ if (height === lastReported || height <= 0) return;
918
+ lastReported = height;
919
+ var payload = { height: height, width: width };
920
+ if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
921
+ if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
922
+ window.FrontMcpBridge.setSize(payload).catch(function() {});
923
+ }
924
+ window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
925
+ } catch (e) {}
926
+ }
927
+ function schedule() {
928
+ // Debounce via rAF; fall back to setTimeout if rAF is unavailable.
929
+ if (rafId != null) return;
930
+ if (typeof requestAnimationFrame === 'function') {
931
+ rafId = requestAnimationFrame(report);
932
+ } else {
933
+ rafId = setTimeout(report, 100);
934
+ }
935
+ }
936
+
937
+ try {
938
+ // Disconnect any prior observer before creating a new one (no leaks/dupes).
939
+ if (window.__mcpResizeObserver && typeof window.__mcpResizeObserver.disconnect === 'function') {
940
+ window.__mcpResizeObserver.disconnect();
941
+ }
942
+ var ro = new ResizeObserver(function() { schedule(); });
943
+ ro.observe(target);
944
+ window.__mcpResizeObserver = ro;
945
+ } catch (e) {}
946
+ // Report once on init so the host gets an initial measurement.
947
+ schedule();
948
+ }
949
+
950
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
951
+ document.addEventListener('DOMContentLoaded', startObserving);
952
+ } else {
953
+ startObserving();
954
+ }
955
+ }
956
+ __initAutoResize();
957
+ `.trim();
958
+ }
854
959
  function generateContextDetection() {
855
960
  return `
856
961
  function detectTheme() {
@@ -958,6 +1063,19 @@ var OpenAIAdapter = {
958
1063
  requestDisplayMode: function(context, mode) {
959
1064
  return Promise.resolve();
960
1065
  },
1066
+ setSize: function(context, size) {
1067
+ // OpenAI Apps SDK measures DOM height itself; if a sizing API surfaces on
1068
+ // window.openai, forward to it, otherwise no-op (CSS drives layout).
1069
+ try {
1070
+ if (window.openai && typeof window.openai.requestDisplayMode === 'function' && size && size.displayMode) {
1071
+ window.openai.requestDisplayMode(size.displayMode);
1072
+ }
1073
+ if (window.openai && typeof window.openai.setWidgetHeight === 'function' && size && typeof size.height === 'number') {
1074
+ window.openai.setWidgetHeight(size.height);
1075
+ }
1076
+ } catch (e) {}
1077
+ return Promise.resolve();
1078
+ },
961
1079
  requestClose: function(context) {
962
1080
  return Promise.resolve();
963
1081
  }
@@ -1202,6 +1320,15 @@ var ExtAppsAdapter = {
1202
1320
  requestDisplayMode: function(context, mode) {
1203
1321
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
1204
1322
  },
1323
+ setSize: function(context, size) {
1324
+ // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
1325
+ // measured/desired widget dimensions to the host.
1326
+ return this.sendRequest('ui/setSize', {
1327
+ height: size && size.height,
1328
+ width: size && size.width,
1329
+ aspectRatio: size && size.aspectRatio
1330
+ });
1331
+ },
1205
1332
  requestClose: function(context) {
1206
1333
  return this.sendRequest('ui/close', {});
1207
1334
  },
@@ -1290,6 +1417,10 @@ var ClaudeAdapter = {
1290
1417
  requestDisplayMode: function() {
1291
1418
  return Promise.resolve();
1292
1419
  },
1420
+ setSize: function() {
1421
+ // Claude measures the rendered DOM height itself \u2014 CSS-only, no reporting.
1422
+ return Promise.resolve();
1423
+ },
1293
1424
  requestClose: function() {
1294
1425
  return Promise.resolve();
1295
1426
  }
@@ -1341,6 +1472,9 @@ var GeminiAdapter = {
1341
1472
  requestDisplayMode: function() {
1342
1473
  return Promise.resolve();
1343
1474
  },
1475
+ setSize: function() {
1476
+ return Promise.resolve();
1477
+ },
1344
1478
  requestClose: function() {
1345
1479
  return Promise.resolve();
1346
1480
  }
@@ -1376,6 +1510,9 @@ var GenericAdapter = {
1376
1510
  requestDisplayMode: function() {
1377
1511
  return Promise.resolve();
1378
1512
  },
1513
+ setSize: function() {
1514
+ return Promise.resolve();
1515
+ },
1379
1516
  requestClose: function() {
1380
1517
  return Promise.resolve();
1381
1518
  }
@@ -1610,6 +1747,15 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1610
1747
  });
1611
1748
  };
1612
1749
 
1750
+ // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1751
+ // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1752
+ // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1753
+ FrontMcpBridge.prototype.setSize = function(size) {
1754
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1755
+ if (!this._adapter.setSize) return Promise.resolve();
1756
+ return this._adapter.setSize(this._context, size || {});
1757
+ };
1758
+
1613
1759
  FrontMcpBridge.prototype.requestClose = function() {
1614
1760
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1615
1761
  return this._adapter.requestClose(this._context);
@@ -1808,6 +1954,80 @@ var BRIDGE_SCRIPT_TAGS = {
1808
1954
  gemini: `<script>${generatePlatformBundle("gemini")}</script>`
1809
1955
  };
1810
1956
 
1957
+ // libs/uipack/src/shell/csp.ts
1958
+ var DEFAULT_CDN_DOMAINS = [
1959
+ "https://cdn.jsdelivr.net",
1960
+ "https://cdnjs.cloudflare.com",
1961
+ "https://fonts.googleapis.com",
1962
+ "https://fonts.gstatic.com",
1963
+ "https://esm.sh"
1964
+ ];
1965
+ var DEFAULT_CSP_DIRECTIVES = [
1966
+ "default-src 'none'",
1967
+ `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1968
+ `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1969
+ `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1970
+ `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1971
+ `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1972
+ "object-src 'self' data:"
1973
+ ];
1974
+ function buildCSPDirectives(csp) {
1975
+ if (!csp) {
1976
+ return [...DEFAULT_CSP_DIRECTIVES];
1977
+ }
1978
+ const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
1979
+ const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
1980
+ const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
1981
+ const directives = [
1982
+ "default-src 'none'",
1983
+ `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
1984
+ `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
1985
+ ];
1986
+ const imgSources = ["'self'", "data:", ...allResourceDomains];
1987
+ directives.push(`img-src ${imgSources.join(" ")}`);
1988
+ const fontSources = ["'self'", "data:", ...allResourceDomains];
1989
+ directives.push(`font-src ${fontSources.join(" ")}`);
1990
+ if (validConnectDomains.length) {
1991
+ directives.push(`connect-src ${validConnectDomains.join(" ")}`);
1992
+ } else {
1993
+ directives.push(`connect-src ${allResourceDomains.join(" ")}`);
1994
+ }
1995
+ directives.push("object-src 'self' data:");
1996
+ return directives;
1997
+ }
1998
+ function buildCSPMetaTag(csp) {
1999
+ const directives = buildCSPDirectives(csp);
2000
+ const content = directives.join("; ");
2001
+ return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
2002
+ }
2003
+ function validateCSPDomain(domain) {
2004
+ if (domain.startsWith("https://*.")) {
2005
+ const rest = domain.slice(10);
2006
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
2007
+ }
2008
+ try {
2009
+ const url = new URL(domain);
2010
+ return url.protocol === "https:";
2011
+ } catch {
2012
+ return false;
2013
+ }
2014
+ }
2015
+ function sanitizeCSPDomains(domains) {
2016
+ if (!domains) return [];
2017
+ const valid = [];
2018
+ for (const domain of domains) {
2019
+ if (validateCSPDomain(domain)) {
2020
+ valid.push(domain);
2021
+ } else {
2022
+ console.warn(`Invalid CSP domain ignored: ${domain}`);
2023
+ }
2024
+ }
2025
+ return valid;
2026
+ }
2027
+ function escapeAttribute(str) {
2028
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2029
+ }
2030
+
1811
2031
  // libs/uipack/src/shell/custom-shell-types.ts
1812
2032
  var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
1813
2033
  var SHELL_PLACEHOLDERS = {
@@ -1820,6 +2040,17 @@ var SHELL_PLACEHOLDERS = {
1820
2040
  var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1821
2041
  var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1822
2042
 
2043
+ // libs/uipack/src/shell/custom-shell-applier.ts
2044
+ function applyShellTemplate(template, values) {
2045
+ let result = template;
2046
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
2047
+ result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
2048
+ result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
2049
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
2050
+ result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
2051
+ return result;
2052
+ }
2053
+
1823
2054
  // libs/uipack/src/shell/custom-shell-validator.ts
1824
2055
  function validateShellTemplate(template) {
1825
2056
  const found = {};
@@ -1836,22 +2067,112 @@ function validateShellTemplate(template) {
1836
2067
  };
1837
2068
  }
1838
2069
 
1839
- // libs/uipack/src/shell/custom-shell-applier.ts
1840
- function applyShellTemplate(template, values) {
1841
- let result = template;
1842
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1843
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1844
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1845
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1846
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1847
- return result;
2070
+ // libs/uipack/src/shell/data-injector.ts
2071
+ function hasSizing(sizing) {
2072
+ if (!sizing) return false;
2073
+ return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
2074
+ }
2075
+ function buildDataInjectionScript(options) {
2076
+ const { toolName, input, output, structuredContent, sizing } = options;
2077
+ const lines = [
2078
+ `window.__mcpAppsEnabled = true;`,
2079
+ `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
2080
+ `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
2081
+ `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
2082
+ `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
2083
+ ];
2084
+ if (hasSizing(sizing)) {
2085
+ lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
2086
+ }
2087
+ return `<script>
2088
+ ${lines.join("\n")}
2089
+ </script>`;
2090
+ }
2091
+ function buildCustomDataInjectionScript(descriptor) {
2092
+ if (descriptor.script !== void 0) {
2093
+ return descriptor.script;
2094
+ }
2095
+ if (descriptor.globalKey !== void 0) {
2096
+ return `<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(
2097
+ descriptor.value ?? null
2098
+ )};</script>`;
2099
+ }
2100
+ return "";
2101
+ }
2102
+
2103
+ // libs/uipack/src/shell/sizing-css.ts
2104
+ function sanitizeCssValue(value) {
2105
+ return value.replace(/[<>{};]/g, "").trim();
2106
+ }
2107
+ function toCssLength(value) {
2108
+ return typeof value === "number" ? `${value}px` : sanitizeCssValue(value);
2109
+ }
2110
+ function buildSizingStyleTag(sizing) {
2111
+ if (!hasSizing(sizing)) return "";
2112
+ const hasCssSizing = sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0;
2113
+ if (!hasCssSizing) return "";
2114
+ const rootRules = [];
2115
+ const docRules = ["margin: 0;"];
2116
+ if (sizing.preferredHeight !== void 0) {
2117
+ const h = toCssLength(sizing.preferredHeight);
2118
+ docRules.push(`height: ${h};`);
2119
+ rootRules.push(`min-height: ${h};`);
2120
+ }
2121
+ if (sizing.minHeight !== void 0) {
2122
+ const mh = toCssLength(sizing.minHeight);
2123
+ docRules.push(`min-height: ${mh};`);
2124
+ rootRules.push(`min-height: ${mh};`);
2125
+ }
2126
+ if (sizing.maxHeight !== void 0) {
2127
+ const mx = toCssLength(sizing.maxHeight);
2128
+ docRules.push(`max-height: ${mx};`);
2129
+ rootRules.push(`max-height: ${mx};`);
2130
+ }
2131
+ if (sizing.aspectRatio !== void 0) {
2132
+ const ar = typeof sizing.aspectRatio === "number" ? String(sizing.aspectRatio) : sanitizeCssValue(sizing.aspectRatio);
2133
+ if (ar) rootRules.push(`aspect-ratio: ${ar};`);
2134
+ }
2135
+ const parts = [`html, body { ${docRules.join(" ")} }`];
2136
+ if (rootRules.length > 0) {
2137
+ parts.push(`#root { ${rootRules.join(" ")} }`);
2138
+ }
2139
+ return `<style>${parts.join("\n")}</style>`;
1848
2140
  }
1849
2141
 
1850
2142
  // libs/uipack/src/shell/builder.ts
2143
+ function resolveDataInjectionScript(args) {
2144
+ if (args.dataInjection) {
2145
+ return buildCustomDataInjectionScript(args.dataInjection);
2146
+ }
2147
+ return buildDataInjectionScript({
2148
+ toolName: args.toolName,
2149
+ input: args.input,
2150
+ output: args.output,
2151
+ structuredContent: args.structuredContent,
2152
+ sizing: args.sizing
2153
+ });
2154
+ }
1851
2155
  function buildShell(content, config) {
1852
- const { toolName, csp, withShell = true, input, output, structuredContent, includeBridge = true, title } = config;
1853
- const { customShell } = config;
1854
- const dataScript = buildDataInjectionScript({ toolName, input, output, structuredContent });
2156
+ const {
2157
+ toolName,
2158
+ csp,
2159
+ withShell = true,
2160
+ input,
2161
+ output,
2162
+ structuredContent,
2163
+ includeBridge = true,
2164
+ title,
2165
+ sizing
2166
+ } = config;
2167
+ const { customShell, dataInjection } = config;
2168
+ const dataScript = resolveDataInjectionScript({
2169
+ dataInjection,
2170
+ toolName,
2171
+ input,
2172
+ output,
2173
+ structuredContent,
2174
+ sizing
2175
+ });
1855
2176
  if (!withShell) {
1856
2177
  const html2 = `${dataScript}
1857
2178
  ${content}`;
@@ -1869,7 +2190,9 @@ ${content}`;
1869
2190
  output,
1870
2191
  structuredContent,
1871
2192
  includeBridge,
1872
- title
2193
+ title,
2194
+ sizing,
2195
+ dataInjection
1873
2196
  });
1874
2197
  }
1875
2198
  const headParts = [
@@ -1881,6 +2204,10 @@ ${content}`;
1881
2204
  }
1882
2205
  headParts.push(buildCSPMetaTag(csp));
1883
2206
  headParts.push(dataScript);
2207
+ const sizingStyle = buildSizingStyleTag(sizing);
2208
+ if (sizingStyle) {
2209
+ headParts.push(sizingStyle);
2210
+ }
1884
2211
  if (includeBridge) {
1885
2212
  const bridgeScript = generateBridgeIIFE({ minify: true });
1886
2213
  headParts.push(`<script>${bridgeScript}</script>`);
@@ -1914,12 +2241,17 @@ function buildCustomShell(content, customShell, ctx) {
1914
2241
  template = customShell.template;
1915
2242
  }
1916
2243
  const cspTag = buildCSPMetaTag(ctx.csp);
1917
- const dataScript = buildDataInjectionScript({
2244
+ const dataScript = resolveDataInjectionScript({
2245
+ dataInjection: ctx.dataInjection,
1918
2246
  toolName: ctx.toolName,
1919
2247
  input: ctx.input,
1920
2248
  output: ctx.output,
1921
- structuredContent: ctx.structuredContent
2249
+ structuredContent: ctx.structuredContent,
2250
+ sizing: ctx.sizing
1922
2251
  });
2252
+ const sizingStyle = buildSizingStyleTag(ctx.sizing);
2253
+ const dataWithSizing = sizingStyle ? `${dataScript}
2254
+ ${sizingStyle}` : dataScript;
1923
2255
  let bridgeHtml = "";
1924
2256
  if (ctx.includeBridge) {
1925
2257
  const bridgeScript = generateBridgeIIFE({ minify: true });
@@ -1927,7 +2259,7 @@ function buildCustomShell(content, customShell, ctx) {
1927
2259
  }
1928
2260
  const html = applyShellTemplate(template, {
1929
2261
  csp: cspTag,
1930
- data: dataScript,
2262
+ data: dataWithSizing,
1931
2263
  bridge: bridgeHtml,
1932
2264
  content,
1933
2265
  title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
@@ -1950,37 +2282,19 @@ function escapeHtmlForTag(str) {
1950
2282
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1951
2283
  }
1952
2284
 
1953
- // libs/uipack/src/resolver/import-map.ts
1954
- function createImportMapFromResolved(resolved) {
1955
- const imports = {};
1956
- const integrity = {};
1957
- for (const [specifier, res] of Object.entries(resolved)) {
1958
- if (res.type === "url") {
1959
- imports[specifier] = res.value;
1960
- if (res.integrity) {
1961
- integrity[res.value] = res.integrity;
1962
- }
1963
- }
1964
- }
1965
- return {
1966
- imports,
1967
- integrity: Object.keys(integrity).length > 0 ? integrity : void 0
1968
- };
1969
- }
1970
- function generateImportMapScriptTag(map) {
1971
- const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
1972
- return `<script type="importmap">
1973
- ${json}
1974
- </script>`;
1975
- }
1976
-
1977
2285
  // libs/uipack/src/component/renderer.ts
2286
+ var DEFAULT_WIDGET_MOUNT = {
2287
+ moduleSpecifier: "@frontmcp/ui/react",
2288
+ wrapperImportName: "McpBridgeProvider"
2289
+ };
1978
2290
  function renderComponent(config, shellConfig) {
1979
2291
  const resolver = shellConfig.resolver ?? createEsmShResolver();
1980
2292
  const resolved = resolveUISource(config.source, {
1981
2293
  resolver,
1982
2294
  input: shellConfig.input,
1983
- output: shellConfig.output
2295
+ output: shellConfig.output,
2296
+ inlineReact: config.inlineReact === true,
2297
+ transformOnly: config.transformOnly === true
1984
2298
  });
1985
2299
  const mergedShellConfig = {
1986
2300
  ...shellConfig,
@@ -1991,10 +2305,10 @@ function renderComponent(config, shellConfig) {
1991
2305
  if (resolved.mode === "inline") {
1992
2306
  return buildShell(resolved.html ?? "", mergedShellConfig);
1993
2307
  }
1994
- const content = buildModuleContent(resolved, resolver, config.props);
2308
+ const content = buildModuleContent(resolved, resolver, config.props, shellConfig.mount);
1995
2309
  return buildShell(content, mergedShellConfig);
1996
2310
  }
1997
- function buildModuleContent(resolved, resolver, propsMapping) {
2311
+ function buildModuleContent(resolved, resolver, propsMapping, mount = DEFAULT_WIDGET_MOUNT) {
1998
2312
  const parts = [];
1999
2313
  if (resolved.code && resolved.bundled) {
2000
2314
  const importEntries = {};
@@ -2018,14 +2332,16 @@ ${resolved.code}
2018
2332
  ...resolved.imports || [],
2019
2333
  "react-dom/client",
2020
2334
  // needed by mount script
2021
- "@frontmcp/ui/react",
2022
- // needed for McpBridgeProvider
2335
+ mount.moduleSpecifier,
2336
+ // mounter/provider package (e.g. @frontmcp/ui/react)
2023
2337
  // React subpath entries needed by esm.sh externalized modules:
2024
2338
  "react/jsx-runtime",
2025
- "react/jsx-dev-runtime",
2026
- "react-dom/server",
2027
- "react-dom/static"
2339
+ "react/jsx-dev-runtime"
2028
2340
  ]);
2341
+ if (mount === DEFAULT_WIDGET_MOUNT) {
2342
+ allSpecifiers.add("react-dom/server");
2343
+ allSpecifiers.add("react-dom/static");
2344
+ }
2029
2345
  const coreDeps = /* @__PURE__ */ new Set([
2030
2346
  "react",
2031
2347
  "react-dom",
@@ -2056,8 +2372,9 @@ ${resolved.code}
2056
2372
  const importMap = createImportMapFromResolved(importEntries);
2057
2373
  parts.push(generateImportMapScriptTag(importMap));
2058
2374
  }
2059
- parts.push('<div id="root"></div>');
2060
- const mountCode = generateInlineMountCode(resolved.exportName);
2375
+ const mountNodeId = mount.mountNodeId ?? "root";
2376
+ parts.push(`<div id="${mountNodeId}">${mount.mountNodeInnerHtml ?? ""}</div>`);
2377
+ const mountCode = generateInlineMountCode(resolved.exportName, mount);
2061
2378
  parts.push(`<script type="module">
2062
2379
  ${resolved.code}
2063
2380
  ${mountCode}
@@ -2094,15 +2411,21 @@ function addExternalParam(url, externals) {
2094
2411
  const sep = url.includes("?") ? "&" : "?";
2095
2412
  return `${url}${sep}external=${externals.join(",")}`;
2096
2413
  }
2097
- function generateInlineMountCode(componentName) {
2414
+ function generateInlineMountCode(componentName, mount) {
2415
+ if (mount.generate) {
2416
+ return mount.generate(componentName);
2417
+ }
2418
+ const wrapperName = mount.wrapperImportName ?? "McpBridgeProvider";
2419
+ const wrapperAlias = `__${wrapperName}`;
2420
+ const mountNodeId = mount.mountNodeId ?? "root";
2098
2421
  return `
2099
2422
  // --- Mount ---
2100
2423
  import { createRoot as __createRoot } from 'react-dom/client';
2101
- import { McpBridgeProvider as __McpBridgeProvider } from '@frontmcp/ui/react';
2102
- const __root = document.getElementById('root');
2424
+ import { ${wrapperName} as ${wrapperAlias} } from '${mount.moduleSpecifier}';
2425
+ const __root = document.getElementById('${mountNodeId}');
2103
2426
  if (__root) {
2104
2427
  __createRoot(__root).render(
2105
- React.createElement(__McpBridgeProvider, null,
2428
+ React.createElement(${wrapperAlias}, null,
2106
2429
  React.createElement(${componentName})
2107
2430
  )
2108
2431
  );