@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.
- package/adapters/index.js +1046 -698
- package/adapters/template-renderer.d.ts +14 -0
- package/adapters/template-renderer.d.ts.map +1 -1
- package/bridge-runtime/iife-generator.d.ts.map +1 -1
- package/bridge-runtime/index.js +149 -0
- package/component/index.d.ts +1 -0
- package/component/index.d.ts.map +1 -1
- package/component/index.js +468 -145
- package/component/loader.d.ts +21 -2
- package/component/loader.d.ts.map +1 -1
- package/component/renderer.d.ts +2 -2
- package/component/renderer.d.ts.map +1 -1
- package/component/transpiler.d.ts +16 -1
- package/component/transpiler.d.ts.map +1 -1
- package/component/types.d.ts +19 -0
- package/component/types.d.ts.map +1 -1
- package/component/ui-availability.d.ts +27 -0
- package/component/ui-availability.d.ts.map +1 -0
- package/esm/adapters/index.mjs +1046 -698
- package/esm/bridge-runtime/index.mjs +149 -0
- package/esm/component/index.mjs +468 -145
- package/esm/index.mjs +444 -109
- package/esm/package.json +2 -2
- package/esm/shell/index.mjs +420 -171
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +445 -109
- package/package.json +2 -2
- package/shell/builder.d.ts.map +1 -1
- package/shell/data-injector.d.ts +27 -1
- package/shell/data-injector.d.ts.map +1 -1
- package/shell/index.d.ts +3 -2
- package/shell/index.d.ts.map +1 -1
- package/shell/index.js +423 -171
- package/shell/sizing-css.d.ts +27 -0
- package/shell/sizing-css.d.ts.map +1 -0
- package/shell/types.d.ts +102 -0
- package/shell/types.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/types/index.d.ts.map +1 -1
- package/types/ui-config.d.ts +105 -11
- package/types/ui-config.d.ts.map +1 -1
- package/types/ui-runtime.d.ts +23 -2
- package/types/ui-runtime.d.ts.map +1 -1
package/component/index.js
CHANGED
|
@@ -473,6 +473,29 @@ function safeJsonForScript(value) {
|
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
// libs/uipack/src/component/ui-availability.ts
|
|
477
|
+
function isFrontmcpUiResolvable(...candidatePaths) {
|
|
478
|
+
try {
|
|
479
|
+
const nodeFs = require("fs");
|
|
480
|
+
const nodePath = require("path");
|
|
481
|
+
const ORG = "@frontmcp";
|
|
482
|
+
const PKG = "ui";
|
|
483
|
+
for (const candidate of candidatePaths) {
|
|
484
|
+
let dir = candidate;
|
|
485
|
+
while (true) {
|
|
486
|
+
const pkgJson = nodePath.join(dir, "node_modules", ORG, PKG, "package.json");
|
|
487
|
+
if (nodeFs.existsSync(pkgJson)) return true;
|
|
488
|
+
const parent = nodePath.dirname(dir);
|
|
489
|
+
if (parent === dir) break;
|
|
490
|
+
dir = parent;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return false;
|
|
494
|
+
} catch {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
476
499
|
// libs/uipack/src/component/transpiler.ts
|
|
477
500
|
function transpileReactSource(source, filename) {
|
|
478
501
|
const esbuild = require("esbuild");
|
|
@@ -488,7 +511,12 @@ function transpileReactSource(source, filename) {
|
|
|
488
511
|
});
|
|
489
512
|
return result.code;
|
|
490
513
|
}
|
|
491
|
-
function bundleFileSource(source, filename, resolveDir, componentName) {
|
|
514
|
+
function bundleFileSource(source, filename, resolveDir, componentName, options = {}) {
|
|
515
|
+
if (!isFrontmcpUiResolvable(resolveDir, process.cwd())) {
|
|
516
|
+
throw new Error(
|
|
517
|
+
`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.`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
492
520
|
const esbuild = require("esbuild");
|
|
493
521
|
const mountCode = `
|
|
494
522
|
// --- Auto-generated mount ---
|
|
@@ -570,7 +598,11 @@ if (__root) {
|
|
|
570
598
|
format: "esm",
|
|
571
599
|
target: "es2020",
|
|
572
600
|
jsx: "automatic",
|
|
573
|
-
|
|
601
|
+
// When `bundleReact` is set (resourceMode: 'inline'), React itself is
|
|
602
|
+
// bundled — required for hosts that block external script execution
|
|
603
|
+
// such as Claude (#454). Otherwise React stays external and is loaded
|
|
604
|
+
// via the import map emitted by the renderer.
|
|
605
|
+
external: options.bundleReact ? [] : ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
|
|
574
606
|
alias,
|
|
575
607
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
576
608
|
platform: "browser",
|
|
@@ -582,7 +614,7 @@ if (__root) {
|
|
|
582
614
|
} catch (err) {
|
|
583
615
|
const message = err instanceof Error ? err.message : String(err);
|
|
584
616
|
throw new Error(
|
|
585
|
-
`Failed to bundle FileSource "${filename}": ${message}.
|
|
617
|
+
`Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`
|
|
586
618
|
);
|
|
587
619
|
}
|
|
588
620
|
}
|
|
@@ -600,6 +632,9 @@ var DEFAULT_META = {
|
|
|
600
632
|
renderer: "auto"
|
|
601
633
|
};
|
|
602
634
|
function resolveUISource(source, options) {
|
|
635
|
+
if (options?.inlineReact && options?.transformOnly) {
|
|
636
|
+
throw new Error("resolveUISource: `inlineReact` and `transformOnly` are mutually exclusive \u2014 set at most one.");
|
|
637
|
+
}
|
|
603
638
|
const resolver = options?.resolver ?? createEsmShResolver();
|
|
604
639
|
if (isNpmSource(source)) {
|
|
605
640
|
return resolveNpmSource(source, resolver);
|
|
@@ -608,7 +643,7 @@ function resolveUISource(source, options) {
|
|
|
608
643
|
return resolveImportSource(source);
|
|
609
644
|
}
|
|
610
645
|
if (isFileSource(source)) {
|
|
611
|
-
return resolveFileSource(source);
|
|
646
|
+
return resolveFileSource(source, { inlineReact: options?.inlineReact, transformOnly: options?.transformOnly });
|
|
612
647
|
}
|
|
613
648
|
if (isFunctionSource(source)) {
|
|
614
649
|
return resolveFunctionSource(source, options?.input, options?.output);
|
|
@@ -641,15 +676,42 @@ function resolveImportSource(source) {
|
|
|
641
676
|
peerDependencies: []
|
|
642
677
|
};
|
|
643
678
|
}
|
|
644
|
-
function resolveFileSource(source) {
|
|
679
|
+
function resolveFileSource(source, options = {}) {
|
|
645
680
|
const path = require("path");
|
|
646
681
|
const ext = path.extname(source.file).toLowerCase();
|
|
647
682
|
if (ext === ".tsx" || ext === ".jsx") {
|
|
648
683
|
const fs = require("fs");
|
|
649
|
-
const
|
|
650
|
-
const
|
|
684
|
+
const wasRelative = !path.isAbsolute(source.file);
|
|
685
|
+
const filePath = wasRelative ? path.resolve(process.cwd(), source.file) : source.file;
|
|
686
|
+
let rawSource;
|
|
687
|
+
try {
|
|
688
|
+
rawSource = fs.readFileSync(filePath, "utf-8");
|
|
689
|
+
} catch (err) {
|
|
690
|
+
const isNotFound = err?.code === "ENOENT";
|
|
691
|
+
if (isNotFound && wasRelative) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
`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).`
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
throw err;
|
|
697
|
+
}
|
|
651
698
|
const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
|
|
652
|
-
|
|
699
|
+
if (options.transformOnly === true) {
|
|
700
|
+
const code = transpileReactSource(rawSource, path.basename(filePath));
|
|
701
|
+
const parsed2 = parseImports(code);
|
|
702
|
+
return {
|
|
703
|
+
mode: "module",
|
|
704
|
+
code,
|
|
705
|
+
imports: [...new Set(parsed2.externalImports.map((i) => i.specifier))],
|
|
706
|
+
exportName: componentName,
|
|
707
|
+
meta: { mcpAware: true, renderer: "react" },
|
|
708
|
+
peerDependencies: source.peerDependencies || ["react", "react-dom"],
|
|
709
|
+
bundled: false
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName, {
|
|
713
|
+
bundleReact: options.inlineReact === true
|
|
714
|
+
});
|
|
653
715
|
const parsed = parseImports(bundled.code);
|
|
654
716
|
return {
|
|
655
717
|
mode: "module",
|
|
@@ -717,92 +779,27 @@ function generateMappedPropsCode(mapping) {
|
|
|
717
779
|
return `({ ${entries} })`;
|
|
718
780
|
}
|
|
719
781
|
|
|
720
|
-
// libs/uipack/src/
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
731
|
-
`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
732
|
-
`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
733
|
-
`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
734
|
-
`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
735
|
-
"object-src 'self' data:"
|
|
736
|
-
];
|
|
737
|
-
function buildCSPDirectives(csp) {
|
|
738
|
-
if (!csp) {
|
|
739
|
-
return [...DEFAULT_CSP_DIRECTIVES];
|
|
740
|
-
}
|
|
741
|
-
const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
|
|
742
|
-
const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
|
|
743
|
-
const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
|
|
744
|
-
const directives = [
|
|
745
|
-
"default-src 'none'",
|
|
746
|
-
`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
|
|
747
|
-
`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
|
|
748
|
-
];
|
|
749
|
-
const imgSources = ["'self'", "data:", ...allResourceDomains];
|
|
750
|
-
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
751
|
-
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
752
|
-
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
753
|
-
if (validConnectDomains.length) {
|
|
754
|
-
directives.push(`connect-src ${validConnectDomains.join(" ")}`);
|
|
755
|
-
} else {
|
|
756
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
757
|
-
}
|
|
758
|
-
directives.push("object-src 'self' data:");
|
|
759
|
-
return directives;
|
|
760
|
-
}
|
|
761
|
-
function buildCSPMetaTag(csp) {
|
|
762
|
-
const directives = buildCSPDirectives(csp);
|
|
763
|
-
const content = directives.join("; ");
|
|
764
|
-
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
765
|
-
}
|
|
766
|
-
function validateCSPDomain(domain) {
|
|
767
|
-
if (domain.startsWith("https://*.")) {
|
|
768
|
-
const rest = domain.slice(10);
|
|
769
|
-
return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
|
|
770
|
-
}
|
|
771
|
-
try {
|
|
772
|
-
const url = new URL(domain);
|
|
773
|
-
return url.protocol === "https:";
|
|
774
|
-
} catch {
|
|
775
|
-
return false;
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
function sanitizeCSPDomains(domains) {
|
|
779
|
-
if (!domains) return [];
|
|
780
|
-
const valid = [];
|
|
781
|
-
for (const domain of domains) {
|
|
782
|
-
if (validateCSPDomain(domain)) {
|
|
783
|
-
valid.push(domain);
|
|
784
|
-
} else {
|
|
785
|
-
console.warn(`Invalid CSP domain ignored: ${domain}`);
|
|
782
|
+
// libs/uipack/src/resolver/import-map.ts
|
|
783
|
+
function createImportMapFromResolved(resolved) {
|
|
784
|
+
const imports = {};
|
|
785
|
+
const integrity = {};
|
|
786
|
+
for (const [specifier, res] of Object.entries(resolved)) {
|
|
787
|
+
if (res.type === "url") {
|
|
788
|
+
imports[specifier] = res.value;
|
|
789
|
+
if (res.integrity) {
|
|
790
|
+
integrity[res.value] = res.integrity;
|
|
791
|
+
}
|
|
786
792
|
}
|
|
787
793
|
}
|
|
788
|
-
return
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
794
|
+
return {
|
|
795
|
+
imports,
|
|
796
|
+
integrity: Object.keys(integrity).length > 0 ? integrity : void 0
|
|
797
|
+
};
|
|
792
798
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const lines = [
|
|
798
|
-
`window.__mcpAppsEnabled = true;`,
|
|
799
|
-
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
800
|
-
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
801
|
-
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
802
|
-
`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
|
|
803
|
-
];
|
|
804
|
-
return `<script>
|
|
805
|
-
${lines.join("\n")}
|
|
799
|
+
function generateImportMapScriptTag(map) {
|
|
800
|
+
const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
|
|
801
|
+
return `<script type="importmap">
|
|
802
|
+
${json}
|
|
806
803
|
</script>`;
|
|
807
804
|
}
|
|
808
805
|
|
|
@@ -860,6 +857,8 @@ function generateBridgeIIFE(options = {}) {
|
|
|
860
857
|
parts.push("});");
|
|
861
858
|
parts.push("");
|
|
862
859
|
parts.push("window.FrontMcpBridge = bridge;");
|
|
860
|
+
parts.push("");
|
|
861
|
+
parts.push(generateAutoResize());
|
|
863
862
|
parts.push("function __showLoading() {");
|
|
864
863
|
parts.push(' var root = document.getElementById("root");');
|
|
865
864
|
parts.push(" if (root && !root.hasChildNodes()) {");
|
|
@@ -880,6 +879,112 @@ function generateBridgeIIFE(options = {}) {
|
|
|
880
879
|
}
|
|
881
880
|
return code;
|
|
882
881
|
}
|
|
882
|
+
function generateAutoResize() {
|
|
883
|
+
return `
|
|
884
|
+
function __applySizingCss(sizing) {
|
|
885
|
+
if (typeof document === 'undefined' || !document.documentElement) return;
|
|
886
|
+
function toLen(v) { return typeof v === 'number' ? v + 'px' : v; }
|
|
887
|
+
var de = document.documentElement;
|
|
888
|
+
var body = document.body;
|
|
889
|
+
var root = document.getElementById('root');
|
|
890
|
+
if (sizing.preferredHeight != null) {
|
|
891
|
+
var ph = toLen(sizing.preferredHeight);
|
|
892
|
+
de.style.height = ph;
|
|
893
|
+
if (body) body.style.height = ph;
|
|
894
|
+
if (root && !root.style.minHeight) root.style.minHeight = ph;
|
|
895
|
+
}
|
|
896
|
+
if (sizing.minHeight != null) {
|
|
897
|
+
var mh = toLen(sizing.minHeight);
|
|
898
|
+
de.style.minHeight = mh;
|
|
899
|
+
if (body) body.style.minHeight = mh;
|
|
900
|
+
if (root) root.style.minHeight = mh;
|
|
901
|
+
}
|
|
902
|
+
if (sizing.maxHeight != null) {
|
|
903
|
+
var mx = toLen(sizing.maxHeight);
|
|
904
|
+
de.style.maxHeight = mx;
|
|
905
|
+
if (body) body.style.maxHeight = mx;
|
|
906
|
+
if (root) root.style.maxHeight = mx;
|
|
907
|
+
}
|
|
908
|
+
if (sizing.aspectRatio != null && root) {
|
|
909
|
+
root.style.aspectRatio = String(sizing.aspectRatio);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function __initAutoResize() {
|
|
914
|
+
if (typeof window === 'undefined') return;
|
|
915
|
+
// Idempotent: a re-injected IIFE must not stack observers.
|
|
916
|
+
if (window.__mcpAutoResizeInit) return;
|
|
917
|
+
window.__mcpAutoResizeInit = true;
|
|
918
|
+
var sizing = window.__mcpWidgetSizing;
|
|
919
|
+
if (!sizing || typeof sizing !== 'object') return;
|
|
920
|
+
|
|
921
|
+
// Apply CSS as a runtime fallback (the static <style> may be absent on some
|
|
922
|
+
// render paths). Wait for the body if it isn't ready yet.
|
|
923
|
+
function apply() { try { __applySizingCss(sizing); } catch (e) {} }
|
|
924
|
+
if (typeof document !== 'undefined' && document.readyState === 'loading') {
|
|
925
|
+
document.addEventListener('DOMContentLoaded', apply);
|
|
926
|
+
} else {
|
|
927
|
+
apply();
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// Auto-resize defaults ON; opt out with autoResize:false.
|
|
931
|
+
if (sizing.autoResize === false) return;
|
|
932
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
933
|
+
|
|
934
|
+
function startObserving() {
|
|
935
|
+
var target = document.getElementById('root') || document.body;
|
|
936
|
+
if (!target) return;
|
|
937
|
+
|
|
938
|
+
var rafId = null;
|
|
939
|
+
var lastReported = -1;
|
|
940
|
+
function report() {
|
|
941
|
+
rafId = null;
|
|
942
|
+
try {
|
|
943
|
+
var rect = target.getBoundingClientRect();
|
|
944
|
+
var height = Math.ceil(rect.height);
|
|
945
|
+
var width = Math.ceil(rect.width);
|
|
946
|
+
if (height === lastReported || height <= 0) return;
|
|
947
|
+
lastReported = height;
|
|
948
|
+
var payload = { height: height, width: width };
|
|
949
|
+
if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
|
|
950
|
+
if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
|
|
951
|
+
window.FrontMcpBridge.setSize(payload).catch(function() {});
|
|
952
|
+
}
|
|
953
|
+
window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
|
|
954
|
+
} catch (e) {}
|
|
955
|
+
}
|
|
956
|
+
function schedule() {
|
|
957
|
+
// Debounce via rAF; fall back to setTimeout if rAF is unavailable.
|
|
958
|
+
if (rafId != null) return;
|
|
959
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
960
|
+
rafId = requestAnimationFrame(report);
|
|
961
|
+
} else {
|
|
962
|
+
rafId = setTimeout(report, 100);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
try {
|
|
967
|
+
// Disconnect any prior observer before creating a new one (no leaks/dupes).
|
|
968
|
+
if (window.__mcpResizeObserver && typeof window.__mcpResizeObserver.disconnect === 'function') {
|
|
969
|
+
window.__mcpResizeObserver.disconnect();
|
|
970
|
+
}
|
|
971
|
+
var ro = new ResizeObserver(function() { schedule(); });
|
|
972
|
+
ro.observe(target);
|
|
973
|
+
window.__mcpResizeObserver = ro;
|
|
974
|
+
} catch (e) {}
|
|
975
|
+
// Report once on init so the host gets an initial measurement.
|
|
976
|
+
schedule();
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
if (typeof document !== 'undefined' && document.readyState === 'loading') {
|
|
980
|
+
document.addEventListener('DOMContentLoaded', startObserving);
|
|
981
|
+
} else {
|
|
982
|
+
startObserving();
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
__initAutoResize();
|
|
986
|
+
`.trim();
|
|
987
|
+
}
|
|
883
988
|
function generateContextDetection() {
|
|
884
989
|
return `
|
|
885
990
|
function detectTheme() {
|
|
@@ -987,6 +1092,19 @@ var OpenAIAdapter = {
|
|
|
987
1092
|
requestDisplayMode: function(context, mode) {
|
|
988
1093
|
return Promise.resolve();
|
|
989
1094
|
},
|
|
1095
|
+
setSize: function(context, size) {
|
|
1096
|
+
// OpenAI Apps SDK measures DOM height itself; if a sizing API surfaces on
|
|
1097
|
+
// window.openai, forward to it, otherwise no-op (CSS drives layout).
|
|
1098
|
+
try {
|
|
1099
|
+
if (window.openai && typeof window.openai.requestDisplayMode === 'function' && size && size.displayMode) {
|
|
1100
|
+
window.openai.requestDisplayMode(size.displayMode);
|
|
1101
|
+
}
|
|
1102
|
+
if (window.openai && typeof window.openai.setWidgetHeight === 'function' && size && typeof size.height === 'number') {
|
|
1103
|
+
window.openai.setWidgetHeight(size.height);
|
|
1104
|
+
}
|
|
1105
|
+
} catch (e) {}
|
|
1106
|
+
return Promise.resolve();
|
|
1107
|
+
},
|
|
990
1108
|
requestClose: function(context) {
|
|
991
1109
|
return Promise.resolve();
|
|
992
1110
|
}
|
|
@@ -1231,6 +1349,15 @@ var ExtAppsAdapter = {
|
|
|
1231
1349
|
requestDisplayMode: function(context, mode) {
|
|
1232
1350
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
1233
1351
|
},
|
|
1352
|
+
setSize: function(context, size) {
|
|
1353
|
+
// FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
|
|
1354
|
+
// measured/desired widget dimensions to the host.
|
|
1355
|
+
return this.sendRequest('ui/setSize', {
|
|
1356
|
+
height: size && size.height,
|
|
1357
|
+
width: size && size.width,
|
|
1358
|
+
aspectRatio: size && size.aspectRatio
|
|
1359
|
+
});
|
|
1360
|
+
},
|
|
1234
1361
|
requestClose: function(context) {
|
|
1235
1362
|
return this.sendRequest('ui/close', {});
|
|
1236
1363
|
},
|
|
@@ -1319,6 +1446,10 @@ var ClaudeAdapter = {
|
|
|
1319
1446
|
requestDisplayMode: function() {
|
|
1320
1447
|
return Promise.resolve();
|
|
1321
1448
|
},
|
|
1449
|
+
setSize: function() {
|
|
1450
|
+
// Claude measures the rendered DOM height itself \u2014 CSS-only, no reporting.
|
|
1451
|
+
return Promise.resolve();
|
|
1452
|
+
},
|
|
1322
1453
|
requestClose: function() {
|
|
1323
1454
|
return Promise.resolve();
|
|
1324
1455
|
}
|
|
@@ -1370,6 +1501,9 @@ var GeminiAdapter = {
|
|
|
1370
1501
|
requestDisplayMode: function() {
|
|
1371
1502
|
return Promise.resolve();
|
|
1372
1503
|
},
|
|
1504
|
+
setSize: function() {
|
|
1505
|
+
return Promise.resolve();
|
|
1506
|
+
},
|
|
1373
1507
|
requestClose: function() {
|
|
1374
1508
|
return Promise.resolve();
|
|
1375
1509
|
}
|
|
@@ -1405,6 +1539,9 @@ var GenericAdapter = {
|
|
|
1405
1539
|
requestDisplayMode: function() {
|
|
1406
1540
|
return Promise.resolve();
|
|
1407
1541
|
},
|
|
1542
|
+
setSize: function() {
|
|
1543
|
+
return Promise.resolve();
|
|
1544
|
+
},
|
|
1408
1545
|
requestClose: function() {
|
|
1409
1546
|
return Promise.resolve();
|
|
1410
1547
|
}
|
|
@@ -1639,6 +1776,15 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1639
1776
|
});
|
|
1640
1777
|
};
|
|
1641
1778
|
|
|
1779
|
+
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1780
|
+
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1781
|
+
// ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
|
|
1782
|
+
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1783
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1784
|
+
if (!this._adapter.setSize) return Promise.resolve();
|
|
1785
|
+
return this._adapter.setSize(this._context, size || {});
|
|
1786
|
+
};
|
|
1787
|
+
|
|
1642
1788
|
FrontMcpBridge.prototype.requestClose = function() {
|
|
1643
1789
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1644
1790
|
return this._adapter.requestClose(this._context);
|
|
@@ -1837,6 +1983,80 @@ var BRIDGE_SCRIPT_TAGS = {
|
|
|
1837
1983
|
gemini: `<script>${generatePlatformBundle("gemini")}</script>`
|
|
1838
1984
|
};
|
|
1839
1985
|
|
|
1986
|
+
// libs/uipack/src/shell/csp.ts
|
|
1987
|
+
var DEFAULT_CDN_DOMAINS = [
|
|
1988
|
+
"https://cdn.jsdelivr.net",
|
|
1989
|
+
"https://cdnjs.cloudflare.com",
|
|
1990
|
+
"https://fonts.googleapis.com",
|
|
1991
|
+
"https://fonts.gstatic.com",
|
|
1992
|
+
"https://esm.sh"
|
|
1993
|
+
];
|
|
1994
|
+
var DEFAULT_CSP_DIRECTIVES = [
|
|
1995
|
+
"default-src 'none'",
|
|
1996
|
+
`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1997
|
+
`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1998
|
+
`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1999
|
+
`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
2000
|
+
`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
2001
|
+
"object-src 'self' data:"
|
|
2002
|
+
];
|
|
2003
|
+
function buildCSPDirectives(csp) {
|
|
2004
|
+
if (!csp) {
|
|
2005
|
+
return [...DEFAULT_CSP_DIRECTIVES];
|
|
2006
|
+
}
|
|
2007
|
+
const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
|
|
2008
|
+
const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
|
|
2009
|
+
const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
|
|
2010
|
+
const directives = [
|
|
2011
|
+
"default-src 'none'",
|
|
2012
|
+
`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
|
|
2013
|
+
`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
|
|
2014
|
+
];
|
|
2015
|
+
const imgSources = ["'self'", "data:", ...allResourceDomains];
|
|
2016
|
+
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
2017
|
+
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
2018
|
+
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
2019
|
+
if (validConnectDomains.length) {
|
|
2020
|
+
directives.push(`connect-src ${validConnectDomains.join(" ")}`);
|
|
2021
|
+
} else {
|
|
2022
|
+
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
2023
|
+
}
|
|
2024
|
+
directives.push("object-src 'self' data:");
|
|
2025
|
+
return directives;
|
|
2026
|
+
}
|
|
2027
|
+
function buildCSPMetaTag(csp) {
|
|
2028
|
+
const directives = buildCSPDirectives(csp);
|
|
2029
|
+
const content = directives.join("; ");
|
|
2030
|
+
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
2031
|
+
}
|
|
2032
|
+
function validateCSPDomain(domain) {
|
|
2033
|
+
if (domain.startsWith("https://*.")) {
|
|
2034
|
+
const rest = domain.slice(10);
|
|
2035
|
+
return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
|
|
2036
|
+
}
|
|
2037
|
+
try {
|
|
2038
|
+
const url = new URL(domain);
|
|
2039
|
+
return url.protocol === "https:";
|
|
2040
|
+
} catch {
|
|
2041
|
+
return false;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
function sanitizeCSPDomains(domains) {
|
|
2045
|
+
if (!domains) return [];
|
|
2046
|
+
const valid = [];
|
|
2047
|
+
for (const domain of domains) {
|
|
2048
|
+
if (validateCSPDomain(domain)) {
|
|
2049
|
+
valid.push(domain);
|
|
2050
|
+
} else {
|
|
2051
|
+
console.warn(`Invalid CSP domain ignored: ${domain}`);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
return valid;
|
|
2055
|
+
}
|
|
2056
|
+
function escapeAttribute(str) {
|
|
2057
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1840
2060
|
// libs/uipack/src/shell/custom-shell-types.ts
|
|
1841
2061
|
var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
|
|
1842
2062
|
var SHELL_PLACEHOLDERS = {
|
|
@@ -1849,6 +2069,17 @@ var SHELL_PLACEHOLDERS = {
|
|
|
1849
2069
|
var REQUIRED_PLACEHOLDERS = ["CONTENT"];
|
|
1850
2070
|
var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
|
|
1851
2071
|
|
|
2072
|
+
// libs/uipack/src/shell/custom-shell-applier.ts
|
|
2073
|
+
function applyShellTemplate(template, values) {
|
|
2074
|
+
let result = template;
|
|
2075
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
|
|
2076
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
|
|
2077
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
|
|
2078
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
|
|
2079
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
|
|
2080
|
+
return result;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
1852
2083
|
// libs/uipack/src/shell/custom-shell-validator.ts
|
|
1853
2084
|
function validateShellTemplate(template) {
|
|
1854
2085
|
const found = {};
|
|
@@ -1865,22 +2096,112 @@ function validateShellTemplate(template) {
|
|
|
1865
2096
|
};
|
|
1866
2097
|
}
|
|
1867
2098
|
|
|
1868
|
-
// libs/uipack/src/shell/
|
|
1869
|
-
function
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2099
|
+
// libs/uipack/src/shell/data-injector.ts
|
|
2100
|
+
function hasSizing(sizing) {
|
|
2101
|
+
if (!sizing) return false;
|
|
2102
|
+
return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
|
|
2103
|
+
}
|
|
2104
|
+
function buildDataInjectionScript(options) {
|
|
2105
|
+
const { toolName, input, output, structuredContent, sizing } = options;
|
|
2106
|
+
const lines = [
|
|
2107
|
+
`window.__mcpAppsEnabled = true;`,
|
|
2108
|
+
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
2109
|
+
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
2110
|
+
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
2111
|
+
`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
|
|
2112
|
+
];
|
|
2113
|
+
if (hasSizing(sizing)) {
|
|
2114
|
+
lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
|
|
2115
|
+
}
|
|
2116
|
+
return `<script>
|
|
2117
|
+
${lines.join("\n")}
|
|
2118
|
+
</script>`;
|
|
2119
|
+
}
|
|
2120
|
+
function buildCustomDataInjectionScript(descriptor) {
|
|
2121
|
+
if (descriptor.script !== void 0) {
|
|
2122
|
+
return descriptor.script;
|
|
2123
|
+
}
|
|
2124
|
+
if (descriptor.globalKey !== void 0) {
|
|
2125
|
+
return `<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(
|
|
2126
|
+
descriptor.value ?? null
|
|
2127
|
+
)};</script>`;
|
|
2128
|
+
}
|
|
2129
|
+
return "";
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
// libs/uipack/src/shell/sizing-css.ts
|
|
2133
|
+
function sanitizeCssValue(value) {
|
|
2134
|
+
return value.replace(/[<>{};]/g, "").trim();
|
|
2135
|
+
}
|
|
2136
|
+
function toCssLength(value) {
|
|
2137
|
+
return typeof value === "number" ? `${value}px` : sanitizeCssValue(value);
|
|
2138
|
+
}
|
|
2139
|
+
function buildSizingStyleTag(sizing) {
|
|
2140
|
+
if (!hasSizing(sizing)) return "";
|
|
2141
|
+
const hasCssSizing = sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0;
|
|
2142
|
+
if (!hasCssSizing) return "";
|
|
2143
|
+
const rootRules = [];
|
|
2144
|
+
const docRules = ["margin: 0;"];
|
|
2145
|
+
if (sizing.preferredHeight !== void 0) {
|
|
2146
|
+
const h = toCssLength(sizing.preferredHeight);
|
|
2147
|
+
docRules.push(`height: ${h};`);
|
|
2148
|
+
rootRules.push(`min-height: ${h};`);
|
|
2149
|
+
}
|
|
2150
|
+
if (sizing.minHeight !== void 0) {
|
|
2151
|
+
const mh = toCssLength(sizing.minHeight);
|
|
2152
|
+
docRules.push(`min-height: ${mh};`);
|
|
2153
|
+
rootRules.push(`min-height: ${mh};`);
|
|
2154
|
+
}
|
|
2155
|
+
if (sizing.maxHeight !== void 0) {
|
|
2156
|
+
const mx = toCssLength(sizing.maxHeight);
|
|
2157
|
+
docRules.push(`max-height: ${mx};`);
|
|
2158
|
+
rootRules.push(`max-height: ${mx};`);
|
|
2159
|
+
}
|
|
2160
|
+
if (sizing.aspectRatio !== void 0) {
|
|
2161
|
+
const ar = typeof sizing.aspectRatio === "number" ? String(sizing.aspectRatio) : sanitizeCssValue(sizing.aspectRatio);
|
|
2162
|
+
if (ar) rootRules.push(`aspect-ratio: ${ar};`);
|
|
2163
|
+
}
|
|
2164
|
+
const parts = [`html, body { ${docRules.join(" ")} }`];
|
|
2165
|
+
if (rootRules.length > 0) {
|
|
2166
|
+
parts.push(`#root { ${rootRules.join(" ")} }`);
|
|
2167
|
+
}
|
|
2168
|
+
return `<style>${parts.join("\n")}</style>`;
|
|
1877
2169
|
}
|
|
1878
2170
|
|
|
1879
2171
|
// libs/uipack/src/shell/builder.ts
|
|
2172
|
+
function resolveDataInjectionScript(args) {
|
|
2173
|
+
if (args.dataInjection) {
|
|
2174
|
+
return buildCustomDataInjectionScript(args.dataInjection);
|
|
2175
|
+
}
|
|
2176
|
+
return buildDataInjectionScript({
|
|
2177
|
+
toolName: args.toolName,
|
|
2178
|
+
input: args.input,
|
|
2179
|
+
output: args.output,
|
|
2180
|
+
structuredContent: args.structuredContent,
|
|
2181
|
+
sizing: args.sizing
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
1880
2184
|
function buildShell(content, config) {
|
|
1881
|
-
const {
|
|
1882
|
-
|
|
1883
|
-
|
|
2185
|
+
const {
|
|
2186
|
+
toolName,
|
|
2187
|
+
csp,
|
|
2188
|
+
withShell = true,
|
|
2189
|
+
input,
|
|
2190
|
+
output,
|
|
2191
|
+
structuredContent,
|
|
2192
|
+
includeBridge = true,
|
|
2193
|
+
title,
|
|
2194
|
+
sizing
|
|
2195
|
+
} = config;
|
|
2196
|
+
const { customShell, dataInjection } = config;
|
|
2197
|
+
const dataScript = resolveDataInjectionScript({
|
|
2198
|
+
dataInjection,
|
|
2199
|
+
toolName,
|
|
2200
|
+
input,
|
|
2201
|
+
output,
|
|
2202
|
+
structuredContent,
|
|
2203
|
+
sizing
|
|
2204
|
+
});
|
|
1884
2205
|
if (!withShell) {
|
|
1885
2206
|
const html2 = `${dataScript}
|
|
1886
2207
|
${content}`;
|
|
@@ -1898,7 +2219,9 @@ ${content}`;
|
|
|
1898
2219
|
output,
|
|
1899
2220
|
structuredContent,
|
|
1900
2221
|
includeBridge,
|
|
1901
|
-
title
|
|
2222
|
+
title,
|
|
2223
|
+
sizing,
|
|
2224
|
+
dataInjection
|
|
1902
2225
|
});
|
|
1903
2226
|
}
|
|
1904
2227
|
const headParts = [
|
|
@@ -1910,6 +2233,10 @@ ${content}`;
|
|
|
1910
2233
|
}
|
|
1911
2234
|
headParts.push(buildCSPMetaTag(csp));
|
|
1912
2235
|
headParts.push(dataScript);
|
|
2236
|
+
const sizingStyle = buildSizingStyleTag(sizing);
|
|
2237
|
+
if (sizingStyle) {
|
|
2238
|
+
headParts.push(sizingStyle);
|
|
2239
|
+
}
|
|
1913
2240
|
if (includeBridge) {
|
|
1914
2241
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
1915
2242
|
headParts.push(`<script>${bridgeScript}</script>`);
|
|
@@ -1943,12 +2270,17 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
1943
2270
|
template = customShell.template;
|
|
1944
2271
|
}
|
|
1945
2272
|
const cspTag = buildCSPMetaTag(ctx.csp);
|
|
1946
|
-
const dataScript =
|
|
2273
|
+
const dataScript = resolveDataInjectionScript({
|
|
2274
|
+
dataInjection: ctx.dataInjection,
|
|
1947
2275
|
toolName: ctx.toolName,
|
|
1948
2276
|
input: ctx.input,
|
|
1949
2277
|
output: ctx.output,
|
|
1950
|
-
structuredContent: ctx.structuredContent
|
|
2278
|
+
structuredContent: ctx.structuredContent,
|
|
2279
|
+
sizing: ctx.sizing
|
|
1951
2280
|
});
|
|
2281
|
+
const sizingStyle = buildSizingStyleTag(ctx.sizing);
|
|
2282
|
+
const dataWithSizing = sizingStyle ? `${dataScript}
|
|
2283
|
+
${sizingStyle}` : dataScript;
|
|
1952
2284
|
let bridgeHtml = "";
|
|
1953
2285
|
if (ctx.includeBridge) {
|
|
1954
2286
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
@@ -1956,7 +2288,7 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
1956
2288
|
}
|
|
1957
2289
|
const html = applyShellTemplate(template, {
|
|
1958
2290
|
csp: cspTag,
|
|
1959
|
-
data:
|
|
2291
|
+
data: dataWithSizing,
|
|
1960
2292
|
bridge: bridgeHtml,
|
|
1961
2293
|
content,
|
|
1962
2294
|
title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
|
|
@@ -1979,37 +2311,19 @@ function escapeHtmlForTag(str) {
|
|
|
1979
2311
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1980
2312
|
}
|
|
1981
2313
|
|
|
1982
|
-
// libs/uipack/src/resolver/import-map.ts
|
|
1983
|
-
function createImportMapFromResolved(resolved) {
|
|
1984
|
-
const imports = {};
|
|
1985
|
-
const integrity = {};
|
|
1986
|
-
for (const [specifier, res] of Object.entries(resolved)) {
|
|
1987
|
-
if (res.type === "url") {
|
|
1988
|
-
imports[specifier] = res.value;
|
|
1989
|
-
if (res.integrity) {
|
|
1990
|
-
integrity[res.value] = res.integrity;
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
return {
|
|
1995
|
-
imports,
|
|
1996
|
-
integrity: Object.keys(integrity).length > 0 ? integrity : void 0
|
|
1997
|
-
};
|
|
1998
|
-
}
|
|
1999
|
-
function generateImportMapScriptTag(map) {
|
|
2000
|
-
const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
|
|
2001
|
-
return `<script type="importmap">
|
|
2002
|
-
${json}
|
|
2003
|
-
</script>`;
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
2314
|
// libs/uipack/src/component/renderer.ts
|
|
2315
|
+
var DEFAULT_WIDGET_MOUNT = {
|
|
2316
|
+
moduleSpecifier: "@frontmcp/ui/react",
|
|
2317
|
+
wrapperImportName: "McpBridgeProvider"
|
|
2318
|
+
};
|
|
2007
2319
|
function renderComponent(config, shellConfig) {
|
|
2008
2320
|
const resolver = shellConfig.resolver ?? createEsmShResolver();
|
|
2009
2321
|
const resolved = resolveUISource(config.source, {
|
|
2010
2322
|
resolver,
|
|
2011
2323
|
input: shellConfig.input,
|
|
2012
|
-
output: shellConfig.output
|
|
2324
|
+
output: shellConfig.output,
|
|
2325
|
+
inlineReact: config.inlineReact === true,
|
|
2326
|
+
transformOnly: config.transformOnly === true
|
|
2013
2327
|
});
|
|
2014
2328
|
const mergedShellConfig = {
|
|
2015
2329
|
...shellConfig,
|
|
@@ -2020,10 +2334,10 @@ function renderComponent(config, shellConfig) {
|
|
|
2020
2334
|
if (resolved.mode === "inline") {
|
|
2021
2335
|
return buildShell(resolved.html ?? "", mergedShellConfig);
|
|
2022
2336
|
}
|
|
2023
|
-
const content = buildModuleContent(resolved, resolver, config.props);
|
|
2337
|
+
const content = buildModuleContent(resolved, resolver, config.props, shellConfig.mount);
|
|
2024
2338
|
return buildShell(content, mergedShellConfig);
|
|
2025
2339
|
}
|
|
2026
|
-
function buildModuleContent(resolved, resolver, propsMapping) {
|
|
2340
|
+
function buildModuleContent(resolved, resolver, propsMapping, mount = DEFAULT_WIDGET_MOUNT) {
|
|
2027
2341
|
const parts = [];
|
|
2028
2342
|
if (resolved.code && resolved.bundled) {
|
|
2029
2343
|
const importEntries = {};
|
|
@@ -2047,14 +2361,16 @@ ${resolved.code}
|
|
|
2047
2361
|
...resolved.imports || [],
|
|
2048
2362
|
"react-dom/client",
|
|
2049
2363
|
// needed by mount script
|
|
2050
|
-
|
|
2051
|
-
//
|
|
2364
|
+
mount.moduleSpecifier,
|
|
2365
|
+
// mounter/provider package (e.g. @frontmcp/ui/react)
|
|
2052
2366
|
// React subpath entries needed by esm.sh externalized modules:
|
|
2053
2367
|
"react/jsx-runtime",
|
|
2054
|
-
"react/jsx-dev-runtime"
|
|
2055
|
-
"react-dom/server",
|
|
2056
|
-
"react-dom/static"
|
|
2368
|
+
"react/jsx-dev-runtime"
|
|
2057
2369
|
]);
|
|
2370
|
+
if (mount === DEFAULT_WIDGET_MOUNT) {
|
|
2371
|
+
allSpecifiers.add("react-dom/server");
|
|
2372
|
+
allSpecifiers.add("react-dom/static");
|
|
2373
|
+
}
|
|
2058
2374
|
const coreDeps = /* @__PURE__ */ new Set([
|
|
2059
2375
|
"react",
|
|
2060
2376
|
"react-dom",
|
|
@@ -2085,8 +2401,9 @@ ${resolved.code}
|
|
|
2085
2401
|
const importMap = createImportMapFromResolved(importEntries);
|
|
2086
2402
|
parts.push(generateImportMapScriptTag(importMap));
|
|
2087
2403
|
}
|
|
2088
|
-
|
|
2089
|
-
|
|
2404
|
+
const mountNodeId = mount.mountNodeId ?? "root";
|
|
2405
|
+
parts.push(`<div id="${mountNodeId}">${mount.mountNodeInnerHtml ?? ""}</div>`);
|
|
2406
|
+
const mountCode = generateInlineMountCode(resolved.exportName, mount);
|
|
2090
2407
|
parts.push(`<script type="module">
|
|
2091
2408
|
${resolved.code}
|
|
2092
2409
|
${mountCode}
|
|
@@ -2123,15 +2440,21 @@ function addExternalParam(url, externals) {
|
|
|
2123
2440
|
const sep = url.includes("?") ? "&" : "?";
|
|
2124
2441
|
return `${url}${sep}external=${externals.join(",")}`;
|
|
2125
2442
|
}
|
|
2126
|
-
function generateInlineMountCode(componentName) {
|
|
2443
|
+
function generateInlineMountCode(componentName, mount) {
|
|
2444
|
+
if (mount.generate) {
|
|
2445
|
+
return mount.generate(componentName);
|
|
2446
|
+
}
|
|
2447
|
+
const wrapperName = mount.wrapperImportName ?? "McpBridgeProvider";
|
|
2448
|
+
const wrapperAlias = `__${wrapperName}`;
|
|
2449
|
+
const mountNodeId = mount.mountNodeId ?? "root";
|
|
2127
2450
|
return `
|
|
2128
2451
|
// --- Mount ---
|
|
2129
2452
|
import { createRoot as __createRoot } from 'react-dom/client';
|
|
2130
|
-
import {
|
|
2131
|
-
const __root = document.getElementById('
|
|
2453
|
+
import { ${wrapperName} as ${wrapperAlias} } from '${mount.moduleSpecifier}';
|
|
2454
|
+
const __root = document.getElementById('${mountNodeId}');
|
|
2132
2455
|
if (__root) {
|
|
2133
2456
|
__createRoot(__root).render(
|
|
2134
|
-
React.createElement(
|
|
2457
|
+
React.createElement(${wrapperAlias}, null,
|
|
2135
2458
|
React.createElement(${componentName})
|
|
2136
2459
|
)
|
|
2137
2460
|
);
|