@frontmcp/uipack 1.2.1 → 1.4.0
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/esm/component/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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}.
|
|
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
|
|
621
|
-
const
|
|
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
|
-
|
|
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/
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
765
|
+
return {
|
|
766
|
+
imports,
|
|
767
|
+
integrity: Object.keys(integrity).length > 0 ? integrity : void 0
|
|
768
|
+
};
|
|
763
769
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
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/
|
|
1840
|
-
function
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
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 {
|
|
1853
|
-
|
|
1854
|
-
|
|
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 =
|
|
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:
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
|
|
2022
|
-
//
|
|
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
|
-
|
|
2060
|
-
|
|
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 {
|
|
2102
|
-
const __root = document.getElementById('
|
|
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(
|
|
2428
|
+
React.createElement(${wrapperAlias}, null,
|
|
2106
2429
|
React.createElement(${componentName})
|
|
2107
2430
|
)
|
|
2108
2431
|
);
|