@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/esm/shell/index.mjs
CHANGED
|
@@ -1,158 +1,3 @@
|
|
|
1
|
-
// libs/uipack/src/shell/csp.ts
|
|
2
|
-
var DEFAULT_CDN_DOMAINS = [
|
|
3
|
-
"https://cdn.jsdelivr.net",
|
|
4
|
-
"https://cdnjs.cloudflare.com",
|
|
5
|
-
"https://fonts.googleapis.com",
|
|
6
|
-
"https://fonts.gstatic.com",
|
|
7
|
-
"https://esm.sh"
|
|
8
|
-
];
|
|
9
|
-
var DEFAULT_CSP_DIRECTIVES = [
|
|
10
|
-
"default-src 'none'",
|
|
11
|
-
`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
12
|
-
`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
13
|
-
`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
14
|
-
`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
15
|
-
`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
16
|
-
"object-src 'self' data:"
|
|
17
|
-
];
|
|
18
|
-
var RESTRICTIVE_CSP_DIRECTIVES = [
|
|
19
|
-
"default-src 'none'",
|
|
20
|
-
"script-src 'self' 'unsafe-inline'",
|
|
21
|
-
"style-src 'self' 'unsafe-inline'",
|
|
22
|
-
"img-src 'self' data:",
|
|
23
|
-
"font-src 'self' data:",
|
|
24
|
-
"connect-src 'none'",
|
|
25
|
-
"object-src 'self' data:"
|
|
26
|
-
];
|
|
27
|
-
function buildCSPDirectives(csp) {
|
|
28
|
-
if (!csp) {
|
|
29
|
-
return [...DEFAULT_CSP_DIRECTIVES];
|
|
30
|
-
}
|
|
31
|
-
const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
|
|
32
|
-
const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
|
|
33
|
-
const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
|
|
34
|
-
const directives = [
|
|
35
|
-
"default-src 'none'",
|
|
36
|
-
`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
|
|
37
|
-
`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
|
|
38
|
-
];
|
|
39
|
-
const imgSources = ["'self'", "data:", ...allResourceDomains];
|
|
40
|
-
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
41
|
-
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
42
|
-
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
43
|
-
if (validConnectDomains.length) {
|
|
44
|
-
directives.push(`connect-src ${validConnectDomains.join(" ")}`);
|
|
45
|
-
} else {
|
|
46
|
-
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
47
|
-
}
|
|
48
|
-
directives.push("object-src 'self' data:");
|
|
49
|
-
return directives;
|
|
50
|
-
}
|
|
51
|
-
function buildCSPMetaTag(csp) {
|
|
52
|
-
const directives = buildCSPDirectives(csp);
|
|
53
|
-
const content = directives.join("; ");
|
|
54
|
-
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
55
|
-
}
|
|
56
|
-
function validateCSPDomain(domain) {
|
|
57
|
-
if (domain.startsWith("https://*.")) {
|
|
58
|
-
const rest = domain.slice(10);
|
|
59
|
-
return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
|
|
60
|
-
}
|
|
61
|
-
try {
|
|
62
|
-
const url = new URL(domain);
|
|
63
|
-
return url.protocol === "https:";
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function sanitizeCSPDomains(domains) {
|
|
69
|
-
if (!domains) return [];
|
|
70
|
-
const valid = [];
|
|
71
|
-
for (const domain of domains) {
|
|
72
|
-
if (validateCSPDomain(domain)) {
|
|
73
|
-
valid.push(domain);
|
|
74
|
-
} else {
|
|
75
|
-
console.warn(`Invalid CSP domain ignored: ${domain}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return valid;
|
|
79
|
-
}
|
|
80
|
-
function escapeAttribute(str) {
|
|
81
|
-
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// libs/uipack/src/utils/index.ts
|
|
85
|
-
function escapeHtml(str) {
|
|
86
|
-
if (str === null || str === void 0) {
|
|
87
|
-
return "";
|
|
88
|
-
}
|
|
89
|
-
const s = String(str);
|
|
90
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
91
|
-
}
|
|
92
|
-
function escapeScriptClose(jsonString) {
|
|
93
|
-
return jsonString.replace(/<\//g, "<\\/");
|
|
94
|
-
}
|
|
95
|
-
function safeJsonForScript(value) {
|
|
96
|
-
if (value === void 0) {
|
|
97
|
-
return "null";
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
const jsonString = JSON.stringify(value, (_key, val) => {
|
|
101
|
-
if (typeof val === "bigint") {
|
|
102
|
-
return val.toString();
|
|
103
|
-
}
|
|
104
|
-
return val;
|
|
105
|
-
});
|
|
106
|
-
if (jsonString === void 0) {
|
|
107
|
-
return "null";
|
|
108
|
-
}
|
|
109
|
-
return escapeScriptClose(jsonString);
|
|
110
|
-
} catch {
|
|
111
|
-
return '{"error":"Value could not be serialized"}';
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// libs/uipack/src/shell/data-injector.ts
|
|
116
|
-
function buildDataInjectionScript(options) {
|
|
117
|
-
const { toolName, input, output, structuredContent } = options;
|
|
118
|
-
const lines = [
|
|
119
|
-
`window.__mcpAppsEnabled = true;`,
|
|
120
|
-
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
121
|
-
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
122
|
-
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
123
|
-
`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
|
|
124
|
-
];
|
|
125
|
-
return `<script>
|
|
126
|
-
${lines.join("\n")}
|
|
127
|
-
</script>`;
|
|
128
|
-
}
|
|
129
|
-
var _uniqueIdCounter = 0;
|
|
130
|
-
function createTemplateHelpers() {
|
|
131
|
-
return {
|
|
132
|
-
escapeHtml: (str) => escapeHtml(str),
|
|
133
|
-
formatDate: (date, format) => {
|
|
134
|
-
const d = date instanceof Date ? date : new Date(date);
|
|
135
|
-
if (isNaN(d.getTime())) return String(date);
|
|
136
|
-
if (format === "iso") return d.toISOString();
|
|
137
|
-
if (format === "date") return d.toLocaleDateString();
|
|
138
|
-
if (format === "time") return d.toLocaleTimeString();
|
|
139
|
-
return d.toLocaleString();
|
|
140
|
-
},
|
|
141
|
-
formatCurrency: (amount, currency = "USD") => {
|
|
142
|
-
return new Intl.NumberFormat("en-US", {
|
|
143
|
-
style: "currency",
|
|
144
|
-
currency
|
|
145
|
-
}).format(amount);
|
|
146
|
-
},
|
|
147
|
-
uniqueId: (prefix = "mcp") => {
|
|
148
|
-
return `${prefix}-${++_uniqueIdCounter}`;
|
|
149
|
-
},
|
|
150
|
-
jsonEmbed: (data) => {
|
|
151
|
-
return escapeScriptClose(JSON.stringify(data));
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
1
|
// libs/uipack/src/bridge-runtime/iife-generator.ts
|
|
157
2
|
function generateBridgeIIFE(options = {}) {
|
|
158
3
|
const { debug = false, trustedOrigins = [], minify = false } = options;
|
|
@@ -207,6 +52,8 @@ function generateBridgeIIFE(options = {}) {
|
|
|
207
52
|
parts.push("});");
|
|
208
53
|
parts.push("");
|
|
209
54
|
parts.push("window.FrontMcpBridge = bridge;");
|
|
55
|
+
parts.push("");
|
|
56
|
+
parts.push(generateAutoResize());
|
|
210
57
|
parts.push("function __showLoading() {");
|
|
211
58
|
parts.push(' var root = document.getElementById("root");');
|
|
212
59
|
parts.push(" if (root && !root.hasChildNodes()) {");
|
|
@@ -227,6 +74,112 @@ function generateBridgeIIFE(options = {}) {
|
|
|
227
74
|
}
|
|
228
75
|
return code;
|
|
229
76
|
}
|
|
77
|
+
function generateAutoResize() {
|
|
78
|
+
return `
|
|
79
|
+
function __applySizingCss(sizing) {
|
|
80
|
+
if (typeof document === 'undefined' || !document.documentElement) return;
|
|
81
|
+
function toLen(v) { return typeof v === 'number' ? v + 'px' : v; }
|
|
82
|
+
var de = document.documentElement;
|
|
83
|
+
var body = document.body;
|
|
84
|
+
var root = document.getElementById('root');
|
|
85
|
+
if (sizing.preferredHeight != null) {
|
|
86
|
+
var ph = toLen(sizing.preferredHeight);
|
|
87
|
+
de.style.height = ph;
|
|
88
|
+
if (body) body.style.height = ph;
|
|
89
|
+
if (root && !root.style.minHeight) root.style.minHeight = ph;
|
|
90
|
+
}
|
|
91
|
+
if (sizing.minHeight != null) {
|
|
92
|
+
var mh = toLen(sizing.minHeight);
|
|
93
|
+
de.style.minHeight = mh;
|
|
94
|
+
if (body) body.style.minHeight = mh;
|
|
95
|
+
if (root) root.style.minHeight = mh;
|
|
96
|
+
}
|
|
97
|
+
if (sizing.maxHeight != null) {
|
|
98
|
+
var mx = toLen(sizing.maxHeight);
|
|
99
|
+
de.style.maxHeight = mx;
|
|
100
|
+
if (body) body.style.maxHeight = mx;
|
|
101
|
+
if (root) root.style.maxHeight = mx;
|
|
102
|
+
}
|
|
103
|
+
if (sizing.aspectRatio != null && root) {
|
|
104
|
+
root.style.aspectRatio = String(sizing.aspectRatio);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function __initAutoResize() {
|
|
109
|
+
if (typeof window === 'undefined') return;
|
|
110
|
+
// Idempotent: a re-injected IIFE must not stack observers.
|
|
111
|
+
if (window.__mcpAutoResizeInit) return;
|
|
112
|
+
window.__mcpAutoResizeInit = true;
|
|
113
|
+
var sizing = window.__mcpWidgetSizing;
|
|
114
|
+
if (!sizing || typeof sizing !== 'object') return;
|
|
115
|
+
|
|
116
|
+
// Apply CSS as a runtime fallback (the static <style> may be absent on some
|
|
117
|
+
// render paths). Wait for the body if it isn't ready yet.
|
|
118
|
+
function apply() { try { __applySizingCss(sizing); } catch (e) {} }
|
|
119
|
+
if (typeof document !== 'undefined' && document.readyState === 'loading') {
|
|
120
|
+
document.addEventListener('DOMContentLoaded', apply);
|
|
121
|
+
} else {
|
|
122
|
+
apply();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Auto-resize defaults ON; opt out with autoResize:false.
|
|
126
|
+
if (sizing.autoResize === false) return;
|
|
127
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
128
|
+
|
|
129
|
+
function startObserving() {
|
|
130
|
+
var target = document.getElementById('root') || document.body;
|
|
131
|
+
if (!target) return;
|
|
132
|
+
|
|
133
|
+
var rafId = null;
|
|
134
|
+
var lastReported = -1;
|
|
135
|
+
function report() {
|
|
136
|
+
rafId = null;
|
|
137
|
+
try {
|
|
138
|
+
var rect = target.getBoundingClientRect();
|
|
139
|
+
var height = Math.ceil(rect.height);
|
|
140
|
+
var width = Math.ceil(rect.width);
|
|
141
|
+
if (height === lastReported || height <= 0) return;
|
|
142
|
+
lastReported = height;
|
|
143
|
+
var payload = { height: height, width: width };
|
|
144
|
+
if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
|
|
145
|
+
if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
|
|
146
|
+
window.FrontMcpBridge.setSize(payload).catch(function() {});
|
|
147
|
+
}
|
|
148
|
+
window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
|
|
149
|
+
} catch (e) {}
|
|
150
|
+
}
|
|
151
|
+
function schedule() {
|
|
152
|
+
// Debounce via rAF; fall back to setTimeout if rAF is unavailable.
|
|
153
|
+
if (rafId != null) return;
|
|
154
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
155
|
+
rafId = requestAnimationFrame(report);
|
|
156
|
+
} else {
|
|
157
|
+
rafId = setTimeout(report, 100);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// Disconnect any prior observer before creating a new one (no leaks/dupes).
|
|
163
|
+
if (window.__mcpResizeObserver && typeof window.__mcpResizeObserver.disconnect === 'function') {
|
|
164
|
+
window.__mcpResizeObserver.disconnect();
|
|
165
|
+
}
|
|
166
|
+
var ro = new ResizeObserver(function() { schedule(); });
|
|
167
|
+
ro.observe(target);
|
|
168
|
+
window.__mcpResizeObserver = ro;
|
|
169
|
+
} catch (e) {}
|
|
170
|
+
// Report once on init so the host gets an initial measurement.
|
|
171
|
+
schedule();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (typeof document !== 'undefined' && document.readyState === 'loading') {
|
|
175
|
+
document.addEventListener('DOMContentLoaded', startObserving);
|
|
176
|
+
} else {
|
|
177
|
+
startObserving();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
__initAutoResize();
|
|
181
|
+
`.trim();
|
|
182
|
+
}
|
|
230
183
|
function generateContextDetection() {
|
|
231
184
|
return `
|
|
232
185
|
function detectTheme() {
|
|
@@ -334,6 +287,19 @@ var OpenAIAdapter = {
|
|
|
334
287
|
requestDisplayMode: function(context, mode) {
|
|
335
288
|
return Promise.resolve();
|
|
336
289
|
},
|
|
290
|
+
setSize: function(context, size) {
|
|
291
|
+
// OpenAI Apps SDK measures DOM height itself; if a sizing API surfaces on
|
|
292
|
+
// window.openai, forward to it, otherwise no-op (CSS drives layout).
|
|
293
|
+
try {
|
|
294
|
+
if (window.openai && typeof window.openai.requestDisplayMode === 'function' && size && size.displayMode) {
|
|
295
|
+
window.openai.requestDisplayMode(size.displayMode);
|
|
296
|
+
}
|
|
297
|
+
if (window.openai && typeof window.openai.setWidgetHeight === 'function' && size && typeof size.height === 'number') {
|
|
298
|
+
window.openai.setWidgetHeight(size.height);
|
|
299
|
+
}
|
|
300
|
+
} catch (e) {}
|
|
301
|
+
return Promise.resolve();
|
|
302
|
+
},
|
|
337
303
|
requestClose: function(context) {
|
|
338
304
|
return Promise.resolve();
|
|
339
305
|
}
|
|
@@ -578,6 +544,15 @@ var ExtAppsAdapter = {
|
|
|
578
544
|
requestDisplayMode: function(context, mode) {
|
|
579
545
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
580
546
|
},
|
|
547
|
+
setSize: function(context, size) {
|
|
548
|
+
// FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
|
|
549
|
+
// measured/desired widget dimensions to the host.
|
|
550
|
+
return this.sendRequest('ui/setSize', {
|
|
551
|
+
height: size && size.height,
|
|
552
|
+
width: size && size.width,
|
|
553
|
+
aspectRatio: size && size.aspectRatio
|
|
554
|
+
});
|
|
555
|
+
},
|
|
581
556
|
requestClose: function(context) {
|
|
582
557
|
return this.sendRequest('ui/close', {});
|
|
583
558
|
},
|
|
@@ -666,6 +641,10 @@ var ClaudeAdapter = {
|
|
|
666
641
|
requestDisplayMode: function() {
|
|
667
642
|
return Promise.resolve();
|
|
668
643
|
},
|
|
644
|
+
setSize: function() {
|
|
645
|
+
// Claude measures the rendered DOM height itself \u2014 CSS-only, no reporting.
|
|
646
|
+
return Promise.resolve();
|
|
647
|
+
},
|
|
669
648
|
requestClose: function() {
|
|
670
649
|
return Promise.resolve();
|
|
671
650
|
}
|
|
@@ -717,6 +696,9 @@ var GeminiAdapter = {
|
|
|
717
696
|
requestDisplayMode: function() {
|
|
718
697
|
return Promise.resolve();
|
|
719
698
|
},
|
|
699
|
+
setSize: function() {
|
|
700
|
+
return Promise.resolve();
|
|
701
|
+
},
|
|
720
702
|
requestClose: function() {
|
|
721
703
|
return Promise.resolve();
|
|
722
704
|
}
|
|
@@ -752,6 +734,9 @@ var GenericAdapter = {
|
|
|
752
734
|
requestDisplayMode: function() {
|
|
753
735
|
return Promise.resolve();
|
|
754
736
|
},
|
|
737
|
+
setSize: function() {
|
|
738
|
+
return Promise.resolve();
|
|
739
|
+
},
|
|
755
740
|
requestClose: function() {
|
|
756
741
|
return Promise.resolve();
|
|
757
742
|
}
|
|
@@ -986,6 +971,15 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
986
971
|
});
|
|
987
972
|
};
|
|
988
973
|
|
|
974
|
+
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
975
|
+
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
976
|
+
// ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
|
|
977
|
+
FrontMcpBridge.prototype.setSize = function(size) {
|
|
978
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
979
|
+
if (!this._adapter.setSize) return Promise.resolve();
|
|
980
|
+
return this._adapter.setSize(this._context, size || {});
|
|
981
|
+
};
|
|
982
|
+
|
|
989
983
|
FrontMcpBridge.prototype.requestClose = function() {
|
|
990
984
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
991
985
|
return this._adapter.requestClose(this._context);
|
|
@@ -1184,6 +1178,89 @@ var BRIDGE_SCRIPT_TAGS = {
|
|
|
1184
1178
|
gemini: `<script>${generatePlatformBundle("gemini")}</script>`
|
|
1185
1179
|
};
|
|
1186
1180
|
|
|
1181
|
+
// libs/uipack/src/shell/csp.ts
|
|
1182
|
+
var DEFAULT_CDN_DOMAINS = [
|
|
1183
|
+
"https://cdn.jsdelivr.net",
|
|
1184
|
+
"https://cdnjs.cloudflare.com",
|
|
1185
|
+
"https://fonts.googleapis.com",
|
|
1186
|
+
"https://fonts.gstatic.com",
|
|
1187
|
+
"https://esm.sh"
|
|
1188
|
+
];
|
|
1189
|
+
var DEFAULT_CSP_DIRECTIVES = [
|
|
1190
|
+
"default-src 'none'",
|
|
1191
|
+
`script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1192
|
+
`style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1193
|
+
`img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1194
|
+
`font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1195
|
+
`connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
|
|
1196
|
+
"object-src 'self' data:"
|
|
1197
|
+
];
|
|
1198
|
+
var RESTRICTIVE_CSP_DIRECTIVES = [
|
|
1199
|
+
"default-src 'none'",
|
|
1200
|
+
"script-src 'self' 'unsafe-inline'",
|
|
1201
|
+
"style-src 'self' 'unsafe-inline'",
|
|
1202
|
+
"img-src 'self' data:",
|
|
1203
|
+
"font-src 'self' data:",
|
|
1204
|
+
"connect-src 'none'",
|
|
1205
|
+
"object-src 'self' data:"
|
|
1206
|
+
];
|
|
1207
|
+
function buildCSPDirectives(csp) {
|
|
1208
|
+
if (!csp) {
|
|
1209
|
+
return [...DEFAULT_CSP_DIRECTIVES];
|
|
1210
|
+
}
|
|
1211
|
+
const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
|
|
1212
|
+
const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
|
|
1213
|
+
const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
|
|
1214
|
+
const directives = [
|
|
1215
|
+
"default-src 'none'",
|
|
1216
|
+
`script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
|
|
1217
|
+
`style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
|
|
1218
|
+
];
|
|
1219
|
+
const imgSources = ["'self'", "data:", ...allResourceDomains];
|
|
1220
|
+
directives.push(`img-src ${imgSources.join(" ")}`);
|
|
1221
|
+
const fontSources = ["'self'", "data:", ...allResourceDomains];
|
|
1222
|
+
directives.push(`font-src ${fontSources.join(" ")}`);
|
|
1223
|
+
if (validConnectDomains.length) {
|
|
1224
|
+
directives.push(`connect-src ${validConnectDomains.join(" ")}`);
|
|
1225
|
+
} else {
|
|
1226
|
+
directives.push(`connect-src ${allResourceDomains.join(" ")}`);
|
|
1227
|
+
}
|
|
1228
|
+
directives.push("object-src 'self' data:");
|
|
1229
|
+
return directives;
|
|
1230
|
+
}
|
|
1231
|
+
function buildCSPMetaTag(csp) {
|
|
1232
|
+
const directives = buildCSPDirectives(csp);
|
|
1233
|
+
const content = directives.join("; ");
|
|
1234
|
+
return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
|
|
1235
|
+
}
|
|
1236
|
+
function validateCSPDomain(domain) {
|
|
1237
|
+
if (domain.startsWith("https://*.")) {
|
|
1238
|
+
const rest = domain.slice(10);
|
|
1239
|
+
return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
|
|
1240
|
+
}
|
|
1241
|
+
try {
|
|
1242
|
+
const url = new URL(domain);
|
|
1243
|
+
return url.protocol === "https:";
|
|
1244
|
+
} catch {
|
|
1245
|
+
return false;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
function sanitizeCSPDomains(domains) {
|
|
1249
|
+
if (!domains) return [];
|
|
1250
|
+
const valid = [];
|
|
1251
|
+
for (const domain of domains) {
|
|
1252
|
+
if (validateCSPDomain(domain)) {
|
|
1253
|
+
valid.push(domain);
|
|
1254
|
+
} else {
|
|
1255
|
+
console.warn(`Invalid CSP domain ignored: ${domain}`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return valid;
|
|
1259
|
+
}
|
|
1260
|
+
function escapeAttribute(str) {
|
|
1261
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1187
1264
|
// libs/uipack/src/shell/custom-shell-types.ts
|
|
1188
1265
|
var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
|
|
1189
1266
|
var SHELL_PLACEHOLDERS = {
|
|
@@ -1205,6 +1282,17 @@ function isNpmShellSource(source) {
|
|
|
1205
1282
|
return typeof source === "object" && source !== null && "npm" in source;
|
|
1206
1283
|
}
|
|
1207
1284
|
|
|
1285
|
+
// libs/uipack/src/shell/custom-shell-applier.ts
|
|
1286
|
+
function applyShellTemplate(template, values) {
|
|
1287
|
+
let result = template;
|
|
1288
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
|
|
1289
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
|
|
1290
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
|
|
1291
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
|
|
1292
|
+
result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
|
|
1293
|
+
return result;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1208
1296
|
// libs/uipack/src/shell/custom-shell-validator.ts
|
|
1209
1297
|
function validateShellTemplate(template) {
|
|
1210
1298
|
const found = {};
|
|
@@ -1221,22 +1309,169 @@ function validateShellTemplate(template) {
|
|
|
1221
1309
|
};
|
|
1222
1310
|
}
|
|
1223
1311
|
|
|
1224
|
-
// libs/uipack/src/
|
|
1225
|
-
function
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1312
|
+
// libs/uipack/src/utils/index.ts
|
|
1313
|
+
function escapeHtml(str) {
|
|
1314
|
+
if (str === null || str === void 0) {
|
|
1315
|
+
return "";
|
|
1316
|
+
}
|
|
1317
|
+
const s = String(str);
|
|
1318
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
1319
|
+
}
|
|
1320
|
+
function escapeScriptClose(jsonString) {
|
|
1321
|
+
return jsonString.replace(/<\//g, "<\\/");
|
|
1322
|
+
}
|
|
1323
|
+
function safeJsonForScript(value) {
|
|
1324
|
+
if (value === void 0) {
|
|
1325
|
+
return "null";
|
|
1326
|
+
}
|
|
1327
|
+
try {
|
|
1328
|
+
const jsonString = JSON.stringify(value, (_key, val) => {
|
|
1329
|
+
if (typeof val === "bigint") {
|
|
1330
|
+
return val.toString();
|
|
1331
|
+
}
|
|
1332
|
+
return val;
|
|
1333
|
+
});
|
|
1334
|
+
if (jsonString === void 0) {
|
|
1335
|
+
return "null";
|
|
1336
|
+
}
|
|
1337
|
+
return escapeScriptClose(jsonString);
|
|
1338
|
+
} catch {
|
|
1339
|
+
return '{"error":"Value could not be serialized"}';
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// libs/uipack/src/shell/data-injector.ts
|
|
1344
|
+
function hasSizing(sizing) {
|
|
1345
|
+
if (!sizing) return false;
|
|
1346
|
+
return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
|
|
1347
|
+
}
|
|
1348
|
+
function buildDataInjectionScript(options) {
|
|
1349
|
+
const { toolName, input, output, structuredContent, sizing } = options;
|
|
1350
|
+
const lines = [
|
|
1351
|
+
`window.__mcpAppsEnabled = true;`,
|
|
1352
|
+
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
1353
|
+
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
1354
|
+
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
1355
|
+
`window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
|
|
1356
|
+
];
|
|
1357
|
+
if (hasSizing(sizing)) {
|
|
1358
|
+
lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
|
|
1359
|
+
}
|
|
1360
|
+
return `<script>
|
|
1361
|
+
${lines.join("\n")}
|
|
1362
|
+
</script>`;
|
|
1363
|
+
}
|
|
1364
|
+
function buildCustomDataInjectionScript(descriptor) {
|
|
1365
|
+
if (descriptor.script !== void 0) {
|
|
1366
|
+
return descriptor.script;
|
|
1367
|
+
}
|
|
1368
|
+
if (descriptor.globalKey !== void 0) {
|
|
1369
|
+
return `<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(
|
|
1370
|
+
descriptor.value ?? null
|
|
1371
|
+
)};</script>`;
|
|
1372
|
+
}
|
|
1373
|
+
return "";
|
|
1374
|
+
}
|
|
1375
|
+
var _uniqueIdCounter = 0;
|
|
1376
|
+
function createTemplateHelpers() {
|
|
1377
|
+
return {
|
|
1378
|
+
escapeHtml: (str) => escapeHtml(str),
|
|
1379
|
+
formatDate: (date, format) => {
|
|
1380
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
1381
|
+
if (isNaN(d.getTime())) return String(date);
|
|
1382
|
+
if (format === "iso") return d.toISOString();
|
|
1383
|
+
if (format === "date") return d.toLocaleDateString();
|
|
1384
|
+
if (format === "time") return d.toLocaleTimeString();
|
|
1385
|
+
return d.toLocaleString();
|
|
1386
|
+
},
|
|
1387
|
+
formatCurrency: (amount, currency = "USD") => {
|
|
1388
|
+
return new Intl.NumberFormat("en-US", {
|
|
1389
|
+
style: "currency",
|
|
1390
|
+
currency
|
|
1391
|
+
}).format(amount);
|
|
1392
|
+
},
|
|
1393
|
+
uniqueId: (prefix = "mcp") => {
|
|
1394
|
+
return `${prefix}-${++_uniqueIdCounter}`;
|
|
1395
|
+
},
|
|
1396
|
+
jsonEmbed: (data) => {
|
|
1397
|
+
return escapeScriptClose(JSON.stringify(data));
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// libs/uipack/src/shell/sizing-css.ts
|
|
1403
|
+
function sanitizeCssValue(value) {
|
|
1404
|
+
return value.replace(/[<>{};]/g, "").trim();
|
|
1405
|
+
}
|
|
1406
|
+
function toCssLength(value) {
|
|
1407
|
+
return typeof value === "number" ? `${value}px` : sanitizeCssValue(value);
|
|
1408
|
+
}
|
|
1409
|
+
function buildSizingStyleTag(sizing) {
|
|
1410
|
+
if (!hasSizing(sizing)) return "";
|
|
1411
|
+
const hasCssSizing = sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0;
|
|
1412
|
+
if (!hasCssSizing) return "";
|
|
1413
|
+
const rootRules = [];
|
|
1414
|
+
const docRules = ["margin: 0;"];
|
|
1415
|
+
if (sizing.preferredHeight !== void 0) {
|
|
1416
|
+
const h = toCssLength(sizing.preferredHeight);
|
|
1417
|
+
docRules.push(`height: ${h};`);
|
|
1418
|
+
rootRules.push(`min-height: ${h};`);
|
|
1419
|
+
}
|
|
1420
|
+
if (sizing.minHeight !== void 0) {
|
|
1421
|
+
const mh = toCssLength(sizing.minHeight);
|
|
1422
|
+
docRules.push(`min-height: ${mh};`);
|
|
1423
|
+
rootRules.push(`min-height: ${mh};`);
|
|
1424
|
+
}
|
|
1425
|
+
if (sizing.maxHeight !== void 0) {
|
|
1426
|
+
const mx = toCssLength(sizing.maxHeight);
|
|
1427
|
+
docRules.push(`max-height: ${mx};`);
|
|
1428
|
+
rootRules.push(`max-height: ${mx};`);
|
|
1429
|
+
}
|
|
1430
|
+
if (sizing.aspectRatio !== void 0) {
|
|
1431
|
+
const ar = typeof sizing.aspectRatio === "number" ? String(sizing.aspectRatio) : sanitizeCssValue(sizing.aspectRatio);
|
|
1432
|
+
if (ar) rootRules.push(`aspect-ratio: ${ar};`);
|
|
1433
|
+
}
|
|
1434
|
+
const parts = [`html, body { ${docRules.join(" ")} }`];
|
|
1435
|
+
if (rootRules.length > 0) {
|
|
1436
|
+
parts.push(`#root { ${rootRules.join(" ")} }`);
|
|
1437
|
+
}
|
|
1438
|
+
return `<style>${parts.join("\n")}</style>`;
|
|
1233
1439
|
}
|
|
1234
1440
|
|
|
1235
1441
|
// libs/uipack/src/shell/builder.ts
|
|
1442
|
+
function resolveDataInjectionScript(args) {
|
|
1443
|
+
if (args.dataInjection) {
|
|
1444
|
+
return buildCustomDataInjectionScript(args.dataInjection);
|
|
1445
|
+
}
|
|
1446
|
+
return buildDataInjectionScript({
|
|
1447
|
+
toolName: args.toolName,
|
|
1448
|
+
input: args.input,
|
|
1449
|
+
output: args.output,
|
|
1450
|
+
structuredContent: args.structuredContent,
|
|
1451
|
+
sizing: args.sizing
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1236
1454
|
function buildShell(content, config) {
|
|
1237
|
-
const {
|
|
1238
|
-
|
|
1239
|
-
|
|
1455
|
+
const {
|
|
1456
|
+
toolName,
|
|
1457
|
+
csp,
|
|
1458
|
+
withShell = true,
|
|
1459
|
+
input,
|
|
1460
|
+
output,
|
|
1461
|
+
structuredContent,
|
|
1462
|
+
includeBridge = true,
|
|
1463
|
+
title,
|
|
1464
|
+
sizing
|
|
1465
|
+
} = config;
|
|
1466
|
+
const { customShell, dataInjection } = config;
|
|
1467
|
+
const dataScript = resolveDataInjectionScript({
|
|
1468
|
+
dataInjection,
|
|
1469
|
+
toolName,
|
|
1470
|
+
input,
|
|
1471
|
+
output,
|
|
1472
|
+
structuredContent,
|
|
1473
|
+
sizing
|
|
1474
|
+
});
|
|
1240
1475
|
if (!withShell) {
|
|
1241
1476
|
const html2 = `${dataScript}
|
|
1242
1477
|
${content}`;
|
|
@@ -1254,7 +1489,9 @@ ${content}`;
|
|
|
1254
1489
|
output,
|
|
1255
1490
|
structuredContent,
|
|
1256
1491
|
includeBridge,
|
|
1257
|
-
title
|
|
1492
|
+
title,
|
|
1493
|
+
sizing,
|
|
1494
|
+
dataInjection
|
|
1258
1495
|
});
|
|
1259
1496
|
}
|
|
1260
1497
|
const headParts = [
|
|
@@ -1266,6 +1503,10 @@ ${content}`;
|
|
|
1266
1503
|
}
|
|
1267
1504
|
headParts.push(buildCSPMetaTag(csp));
|
|
1268
1505
|
headParts.push(dataScript);
|
|
1506
|
+
const sizingStyle = buildSizingStyleTag(sizing);
|
|
1507
|
+
if (sizingStyle) {
|
|
1508
|
+
headParts.push(sizingStyle);
|
|
1509
|
+
}
|
|
1269
1510
|
if (includeBridge) {
|
|
1270
1511
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
1271
1512
|
headParts.push(`<script>${bridgeScript}</script>`);
|
|
@@ -1299,12 +1540,17 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
1299
1540
|
template = customShell.template;
|
|
1300
1541
|
}
|
|
1301
1542
|
const cspTag = buildCSPMetaTag(ctx.csp);
|
|
1302
|
-
const dataScript =
|
|
1543
|
+
const dataScript = resolveDataInjectionScript({
|
|
1544
|
+
dataInjection: ctx.dataInjection,
|
|
1303
1545
|
toolName: ctx.toolName,
|
|
1304
1546
|
input: ctx.input,
|
|
1305
1547
|
output: ctx.output,
|
|
1306
|
-
structuredContent: ctx.structuredContent
|
|
1548
|
+
structuredContent: ctx.structuredContent,
|
|
1549
|
+
sizing: ctx.sizing
|
|
1307
1550
|
});
|
|
1551
|
+
const sizingStyle = buildSizingStyleTag(ctx.sizing);
|
|
1552
|
+
const dataWithSizing = sizingStyle ? `${dataScript}
|
|
1553
|
+
${sizingStyle}` : dataScript;
|
|
1308
1554
|
let bridgeHtml = "";
|
|
1309
1555
|
if (ctx.includeBridge) {
|
|
1310
1556
|
const bridgeScript = generateBridgeIIFE({ minify: true });
|
|
@@ -1312,7 +1558,7 @@ function buildCustomShell(content, customShell, ctx) {
|
|
|
1312
1558
|
}
|
|
1313
1559
|
const html = applyShellTemplate(template, {
|
|
1314
1560
|
csp: cspTag,
|
|
1315
|
-
data:
|
|
1561
|
+
data: dataWithSizing,
|
|
1316
1562
|
bridge: bridgeHtml,
|
|
1317
1563
|
content,
|
|
1318
1564
|
title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
|
|
@@ -1454,10 +1700,13 @@ export {
|
|
|
1454
1700
|
applyShellTemplate,
|
|
1455
1701
|
buildCSPDirectives,
|
|
1456
1702
|
buildCSPMetaTag,
|
|
1703
|
+
buildCustomDataInjectionScript,
|
|
1457
1704
|
buildDataInjectionScript,
|
|
1458
1705
|
buildShell,
|
|
1706
|
+
buildSizingStyleTag,
|
|
1459
1707
|
clearShellTemplateCache,
|
|
1460
1708
|
createTemplateHelpers,
|
|
1709
|
+
hasSizing,
|
|
1461
1710
|
isInlineShellSource,
|
|
1462
1711
|
isNpmShellSource,
|
|
1463
1712
|
isUrlShellSource,
|