@frontmcp/uipack 1.3.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/adapters/index.js +1046 -698
  2. package/adapters/template-renderer.d.ts +14 -0
  3. package/adapters/template-renderer.d.ts.map +1 -1
  4. package/bridge-runtime/iife-generator.d.ts.map +1 -1
  5. package/bridge-runtime/index.js +149 -0
  6. package/component/index.d.ts +1 -0
  7. package/component/index.d.ts.map +1 -1
  8. package/component/index.js +468 -145
  9. package/component/loader.d.ts +21 -2
  10. package/component/loader.d.ts.map +1 -1
  11. package/component/renderer.d.ts +2 -2
  12. package/component/renderer.d.ts.map +1 -1
  13. package/component/transpiler.d.ts +16 -1
  14. package/component/transpiler.d.ts.map +1 -1
  15. package/component/types.d.ts +19 -0
  16. package/component/types.d.ts.map +1 -1
  17. package/component/ui-availability.d.ts +27 -0
  18. package/component/ui-availability.d.ts.map +1 -0
  19. package/esm/adapters/index.mjs +1046 -698
  20. package/esm/bridge-runtime/index.mjs +149 -0
  21. package/esm/component/index.mjs +468 -145
  22. package/esm/index.mjs +444 -109
  23. package/esm/package.json +2 -2
  24. package/esm/shell/index.mjs +420 -171
  25. package/index.d.ts +1 -1
  26. package/index.d.ts.map +1 -1
  27. package/index.js +445 -109
  28. package/package.json +2 -2
  29. package/shell/builder.d.ts.map +1 -1
  30. package/shell/data-injector.d.ts +27 -1
  31. package/shell/data-injector.d.ts.map +1 -1
  32. package/shell/index.d.ts +3 -2
  33. package/shell/index.d.ts.map +1 -1
  34. package/shell/index.js +423 -171
  35. package/shell/sizing-css.d.ts +27 -0
  36. package/shell/sizing-css.d.ts.map +1 -0
  37. package/shell/types.d.ts +102 -0
  38. package/shell/types.d.ts.map +1 -1
  39. package/types/index.d.ts +1 -1
  40. package/types/index.d.ts.map +1 -1
  41. package/types/ui-config.d.ts +105 -11
  42. package/types/ui-config.d.ts.map +1 -1
  43. package/types/ui-runtime.d.ts +23 -2
  44. package/types/ui-runtime.d.ts.map +1 -1
@@ -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, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").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, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
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/shell/custom-shell-applier.ts
1225
- function applyShellTemplate(template, values) {
1226
- let result = template;
1227
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1228
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1229
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1230
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1231
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1232
- return result;
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").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 { toolName, csp, withShell = true, input, output, structuredContent, includeBridge = true, title } = config;
1238
- const { customShell } = config;
1239
- const dataScript = buildDataInjectionScript({ toolName, input, output, structuredContent });
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 = buildDataInjectionScript({
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: dataScript,
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,