@frontmcp/uipack 1.3.0 → 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.
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
package/shell/index.js CHANGED
@@ -30,10 +30,13 @@ __export(shell_exports, {
30
30
  applyShellTemplate: () => applyShellTemplate,
31
31
  buildCSPDirectives: () => buildCSPDirectives,
32
32
  buildCSPMetaTag: () => buildCSPMetaTag,
33
+ buildCustomDataInjectionScript: () => buildCustomDataInjectionScript,
33
34
  buildDataInjectionScript: () => buildDataInjectionScript,
34
35
  buildShell: () => buildShell,
36
+ buildSizingStyleTag: () => buildSizingStyleTag,
35
37
  clearShellTemplateCache: () => clearShellTemplateCache,
36
38
  createTemplateHelpers: () => createTemplateHelpers,
39
+ hasSizing: () => hasSizing,
37
40
  isInlineShellSource: () => isInlineShellSource,
38
41
  isNpmShellSource: () => isNpmShellSource,
39
42
  isUrlShellSource: () => isUrlShellSource,
@@ -44,161 +47,6 @@ __export(shell_exports, {
44
47
  });
45
48
  module.exports = __toCommonJS(shell_exports);
46
49
 
47
- // libs/uipack/src/shell/csp.ts
48
- var DEFAULT_CDN_DOMAINS = [
49
- "https://cdn.jsdelivr.net",
50
- "https://cdnjs.cloudflare.com",
51
- "https://fonts.googleapis.com",
52
- "https://fonts.gstatic.com",
53
- "https://esm.sh"
54
- ];
55
- var DEFAULT_CSP_DIRECTIVES = [
56
- "default-src 'none'",
57
- `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
58
- `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
59
- `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
60
- `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
61
- `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
62
- "object-src 'self' data:"
63
- ];
64
- var RESTRICTIVE_CSP_DIRECTIVES = [
65
- "default-src 'none'",
66
- "script-src 'self' 'unsafe-inline'",
67
- "style-src 'self' 'unsafe-inline'",
68
- "img-src 'self' data:",
69
- "font-src 'self' data:",
70
- "connect-src 'none'",
71
- "object-src 'self' data:"
72
- ];
73
- function buildCSPDirectives(csp) {
74
- if (!csp) {
75
- return [...DEFAULT_CSP_DIRECTIVES];
76
- }
77
- const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
78
- const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
79
- const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
80
- const directives = [
81
- "default-src 'none'",
82
- `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
83
- `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
84
- ];
85
- const imgSources = ["'self'", "data:", ...allResourceDomains];
86
- directives.push(`img-src ${imgSources.join(" ")}`);
87
- const fontSources = ["'self'", "data:", ...allResourceDomains];
88
- directives.push(`font-src ${fontSources.join(" ")}`);
89
- if (validConnectDomains.length) {
90
- directives.push(`connect-src ${validConnectDomains.join(" ")}`);
91
- } else {
92
- directives.push(`connect-src ${allResourceDomains.join(" ")}`);
93
- }
94
- directives.push("object-src 'self' data:");
95
- return directives;
96
- }
97
- function buildCSPMetaTag(csp) {
98
- const directives = buildCSPDirectives(csp);
99
- const content = directives.join("; ");
100
- return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
101
- }
102
- function validateCSPDomain(domain) {
103
- if (domain.startsWith("https://*.")) {
104
- const rest = domain.slice(10);
105
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
106
- }
107
- try {
108
- const url = new URL(domain);
109
- return url.protocol === "https:";
110
- } catch {
111
- return false;
112
- }
113
- }
114
- function sanitizeCSPDomains(domains) {
115
- if (!domains) return [];
116
- const valid = [];
117
- for (const domain of domains) {
118
- if (validateCSPDomain(domain)) {
119
- valid.push(domain);
120
- } else {
121
- console.warn(`Invalid CSP domain ignored: ${domain}`);
122
- }
123
- }
124
- return valid;
125
- }
126
- function escapeAttribute(str) {
127
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
128
- }
129
-
130
- // libs/uipack/src/utils/index.ts
131
- function escapeHtml(str) {
132
- if (str === null || str === void 0) {
133
- return "";
134
- }
135
- const s = String(str);
136
- 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");
137
- }
138
- function escapeScriptClose(jsonString) {
139
- return jsonString.replace(/<\//g, "<\\/");
140
- }
141
- function safeJsonForScript(value) {
142
- if (value === void 0) {
143
- return "null";
144
- }
145
- try {
146
- const jsonString = JSON.stringify(value, (_key, val) => {
147
- if (typeof val === "bigint") {
148
- return val.toString();
149
- }
150
- return val;
151
- });
152
- if (jsonString === void 0) {
153
- return "null";
154
- }
155
- return escapeScriptClose(jsonString);
156
- } catch {
157
- return '{"error":"Value could not be serialized"}';
158
- }
159
- }
160
-
161
- // libs/uipack/src/shell/data-injector.ts
162
- function buildDataInjectionScript(options) {
163
- const { toolName, input, output, structuredContent } = options;
164
- const lines = [
165
- `window.__mcpAppsEnabled = true;`,
166
- `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
167
- `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
168
- `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
169
- `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
170
- ];
171
- return `<script>
172
- ${lines.join("\n")}
173
- </script>`;
174
- }
175
- var _uniqueIdCounter = 0;
176
- function createTemplateHelpers() {
177
- return {
178
- escapeHtml: (str) => escapeHtml(str),
179
- formatDate: (date, format) => {
180
- const d = date instanceof Date ? date : new Date(date);
181
- if (isNaN(d.getTime())) return String(date);
182
- if (format === "iso") return d.toISOString();
183
- if (format === "date") return d.toLocaleDateString();
184
- if (format === "time") return d.toLocaleTimeString();
185
- return d.toLocaleString();
186
- },
187
- formatCurrency: (amount, currency = "USD") => {
188
- return new Intl.NumberFormat("en-US", {
189
- style: "currency",
190
- currency
191
- }).format(amount);
192
- },
193
- uniqueId: (prefix = "mcp") => {
194
- return `${prefix}-${++_uniqueIdCounter}`;
195
- },
196
- jsonEmbed: (data) => {
197
- return escapeScriptClose(JSON.stringify(data));
198
- }
199
- };
200
- }
201
-
202
50
  // libs/uipack/src/bridge-runtime/iife-generator.ts
203
51
  function generateBridgeIIFE(options = {}) {
204
52
  const { debug = false, trustedOrigins = [], minify = false } = options;
@@ -253,6 +101,8 @@ function generateBridgeIIFE(options = {}) {
253
101
  parts.push("});");
254
102
  parts.push("");
255
103
  parts.push("window.FrontMcpBridge = bridge;");
104
+ parts.push("");
105
+ parts.push(generateAutoResize());
256
106
  parts.push("function __showLoading() {");
257
107
  parts.push(' var root = document.getElementById("root");');
258
108
  parts.push(" if (root && !root.hasChildNodes()) {");
@@ -273,6 +123,112 @@ function generateBridgeIIFE(options = {}) {
273
123
  }
274
124
  return code;
275
125
  }
126
+ function generateAutoResize() {
127
+ return `
128
+ function __applySizingCss(sizing) {
129
+ if (typeof document === 'undefined' || !document.documentElement) return;
130
+ function toLen(v) { return typeof v === 'number' ? v + 'px' : v; }
131
+ var de = document.documentElement;
132
+ var body = document.body;
133
+ var root = document.getElementById('root');
134
+ if (sizing.preferredHeight != null) {
135
+ var ph = toLen(sizing.preferredHeight);
136
+ de.style.height = ph;
137
+ if (body) body.style.height = ph;
138
+ if (root && !root.style.minHeight) root.style.minHeight = ph;
139
+ }
140
+ if (sizing.minHeight != null) {
141
+ var mh = toLen(sizing.minHeight);
142
+ de.style.minHeight = mh;
143
+ if (body) body.style.minHeight = mh;
144
+ if (root) root.style.minHeight = mh;
145
+ }
146
+ if (sizing.maxHeight != null) {
147
+ var mx = toLen(sizing.maxHeight);
148
+ de.style.maxHeight = mx;
149
+ if (body) body.style.maxHeight = mx;
150
+ if (root) root.style.maxHeight = mx;
151
+ }
152
+ if (sizing.aspectRatio != null && root) {
153
+ root.style.aspectRatio = String(sizing.aspectRatio);
154
+ }
155
+ }
156
+
157
+ function __initAutoResize() {
158
+ if (typeof window === 'undefined') return;
159
+ // Idempotent: a re-injected IIFE must not stack observers.
160
+ if (window.__mcpAutoResizeInit) return;
161
+ window.__mcpAutoResizeInit = true;
162
+ var sizing = window.__mcpWidgetSizing;
163
+ if (!sizing || typeof sizing !== 'object') return;
164
+
165
+ // Apply CSS as a runtime fallback (the static <style> may be absent on some
166
+ // render paths). Wait for the body if it isn't ready yet.
167
+ function apply() { try { __applySizingCss(sizing); } catch (e) {} }
168
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
169
+ document.addEventListener('DOMContentLoaded', apply);
170
+ } else {
171
+ apply();
172
+ }
173
+
174
+ // Auto-resize defaults ON; opt out with autoResize:false.
175
+ if (sizing.autoResize === false) return;
176
+ if (typeof ResizeObserver === 'undefined') return;
177
+
178
+ function startObserving() {
179
+ var target = document.getElementById('root') || document.body;
180
+ if (!target) return;
181
+
182
+ var rafId = null;
183
+ var lastReported = -1;
184
+ function report() {
185
+ rafId = null;
186
+ try {
187
+ var rect = target.getBoundingClientRect();
188
+ var height = Math.ceil(rect.height);
189
+ var width = Math.ceil(rect.width);
190
+ if (height === lastReported || height <= 0) return;
191
+ lastReported = height;
192
+ var payload = { height: height, width: width };
193
+ if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
194
+ if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
195
+ window.FrontMcpBridge.setSize(payload).catch(function() {});
196
+ }
197
+ window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
198
+ } catch (e) {}
199
+ }
200
+ function schedule() {
201
+ // Debounce via rAF; fall back to setTimeout if rAF is unavailable.
202
+ if (rafId != null) return;
203
+ if (typeof requestAnimationFrame === 'function') {
204
+ rafId = requestAnimationFrame(report);
205
+ } else {
206
+ rafId = setTimeout(report, 100);
207
+ }
208
+ }
209
+
210
+ try {
211
+ // Disconnect any prior observer before creating a new one (no leaks/dupes).
212
+ if (window.__mcpResizeObserver && typeof window.__mcpResizeObserver.disconnect === 'function') {
213
+ window.__mcpResizeObserver.disconnect();
214
+ }
215
+ var ro = new ResizeObserver(function() { schedule(); });
216
+ ro.observe(target);
217
+ window.__mcpResizeObserver = ro;
218
+ } catch (e) {}
219
+ // Report once on init so the host gets an initial measurement.
220
+ schedule();
221
+ }
222
+
223
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
224
+ document.addEventListener('DOMContentLoaded', startObserving);
225
+ } else {
226
+ startObserving();
227
+ }
228
+ }
229
+ __initAutoResize();
230
+ `.trim();
231
+ }
276
232
  function generateContextDetection() {
277
233
  return `
278
234
  function detectTheme() {
@@ -380,6 +336,19 @@ var OpenAIAdapter = {
380
336
  requestDisplayMode: function(context, mode) {
381
337
  return Promise.resolve();
382
338
  },
339
+ setSize: function(context, size) {
340
+ // OpenAI Apps SDK measures DOM height itself; if a sizing API surfaces on
341
+ // window.openai, forward to it, otherwise no-op (CSS drives layout).
342
+ try {
343
+ if (window.openai && typeof window.openai.requestDisplayMode === 'function' && size && size.displayMode) {
344
+ window.openai.requestDisplayMode(size.displayMode);
345
+ }
346
+ if (window.openai && typeof window.openai.setWidgetHeight === 'function' && size && typeof size.height === 'number') {
347
+ window.openai.setWidgetHeight(size.height);
348
+ }
349
+ } catch (e) {}
350
+ return Promise.resolve();
351
+ },
383
352
  requestClose: function(context) {
384
353
  return Promise.resolve();
385
354
  }
@@ -624,6 +593,15 @@ var ExtAppsAdapter = {
624
593
  requestDisplayMode: function(context, mode) {
625
594
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
626
595
  },
596
+ setSize: function(context, size) {
597
+ // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
598
+ // measured/desired widget dimensions to the host.
599
+ return this.sendRequest('ui/setSize', {
600
+ height: size && size.height,
601
+ width: size && size.width,
602
+ aspectRatio: size && size.aspectRatio
603
+ });
604
+ },
627
605
  requestClose: function(context) {
628
606
  return this.sendRequest('ui/close', {});
629
607
  },
@@ -712,6 +690,10 @@ var ClaudeAdapter = {
712
690
  requestDisplayMode: function() {
713
691
  return Promise.resolve();
714
692
  },
693
+ setSize: function() {
694
+ // Claude measures the rendered DOM height itself \u2014 CSS-only, no reporting.
695
+ return Promise.resolve();
696
+ },
715
697
  requestClose: function() {
716
698
  return Promise.resolve();
717
699
  }
@@ -763,6 +745,9 @@ var GeminiAdapter = {
763
745
  requestDisplayMode: function() {
764
746
  return Promise.resolve();
765
747
  },
748
+ setSize: function() {
749
+ return Promise.resolve();
750
+ },
766
751
  requestClose: function() {
767
752
  return Promise.resolve();
768
753
  }
@@ -798,6 +783,9 @@ var GenericAdapter = {
798
783
  requestDisplayMode: function() {
799
784
  return Promise.resolve();
800
785
  },
786
+ setSize: function() {
787
+ return Promise.resolve();
788
+ },
801
789
  requestClose: function() {
802
790
  return Promise.resolve();
803
791
  }
@@ -1032,6 +1020,15 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1032
1020
  });
1033
1021
  };
1034
1022
 
1023
+ // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1024
+ // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1025
+ // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1026
+ FrontMcpBridge.prototype.setSize = function(size) {
1027
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1028
+ if (!this._adapter.setSize) return Promise.resolve();
1029
+ return this._adapter.setSize(this._context, size || {});
1030
+ };
1031
+
1035
1032
  FrontMcpBridge.prototype.requestClose = function() {
1036
1033
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1037
1034
  return this._adapter.requestClose(this._context);
@@ -1230,6 +1227,89 @@ var BRIDGE_SCRIPT_TAGS = {
1230
1227
  gemini: `<script>${generatePlatformBundle("gemini")}</script>`
1231
1228
  };
1232
1229
 
1230
+ // libs/uipack/src/shell/csp.ts
1231
+ var DEFAULT_CDN_DOMAINS = [
1232
+ "https://cdn.jsdelivr.net",
1233
+ "https://cdnjs.cloudflare.com",
1234
+ "https://fonts.googleapis.com",
1235
+ "https://fonts.gstatic.com",
1236
+ "https://esm.sh"
1237
+ ];
1238
+ var DEFAULT_CSP_DIRECTIVES = [
1239
+ "default-src 'none'",
1240
+ `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1241
+ `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1242
+ `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1243
+ `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1244
+ `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1245
+ "object-src 'self' data:"
1246
+ ];
1247
+ var RESTRICTIVE_CSP_DIRECTIVES = [
1248
+ "default-src 'none'",
1249
+ "script-src 'self' 'unsafe-inline'",
1250
+ "style-src 'self' 'unsafe-inline'",
1251
+ "img-src 'self' data:",
1252
+ "font-src 'self' data:",
1253
+ "connect-src 'none'",
1254
+ "object-src 'self' data:"
1255
+ ];
1256
+ function buildCSPDirectives(csp) {
1257
+ if (!csp) {
1258
+ return [...DEFAULT_CSP_DIRECTIVES];
1259
+ }
1260
+ const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
1261
+ const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
1262
+ const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
1263
+ const directives = [
1264
+ "default-src 'none'",
1265
+ `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
1266
+ `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
1267
+ ];
1268
+ const imgSources = ["'self'", "data:", ...allResourceDomains];
1269
+ directives.push(`img-src ${imgSources.join(" ")}`);
1270
+ const fontSources = ["'self'", "data:", ...allResourceDomains];
1271
+ directives.push(`font-src ${fontSources.join(" ")}`);
1272
+ if (validConnectDomains.length) {
1273
+ directives.push(`connect-src ${validConnectDomains.join(" ")}`);
1274
+ } else {
1275
+ directives.push(`connect-src ${allResourceDomains.join(" ")}`);
1276
+ }
1277
+ directives.push("object-src 'self' data:");
1278
+ return directives;
1279
+ }
1280
+ function buildCSPMetaTag(csp) {
1281
+ const directives = buildCSPDirectives(csp);
1282
+ const content = directives.join("; ");
1283
+ return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
1284
+ }
1285
+ function validateCSPDomain(domain) {
1286
+ if (domain.startsWith("https://*.")) {
1287
+ const rest = domain.slice(10);
1288
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
1289
+ }
1290
+ try {
1291
+ const url = new URL(domain);
1292
+ return url.protocol === "https:";
1293
+ } catch {
1294
+ return false;
1295
+ }
1296
+ }
1297
+ function sanitizeCSPDomains(domains) {
1298
+ if (!domains) return [];
1299
+ const valid = [];
1300
+ for (const domain of domains) {
1301
+ if (validateCSPDomain(domain)) {
1302
+ valid.push(domain);
1303
+ } else {
1304
+ console.warn(`Invalid CSP domain ignored: ${domain}`);
1305
+ }
1306
+ }
1307
+ return valid;
1308
+ }
1309
+ function escapeAttribute(str) {
1310
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1311
+ }
1312
+
1233
1313
  // libs/uipack/src/shell/custom-shell-types.ts
1234
1314
  var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
1235
1315
  var SHELL_PLACEHOLDERS = {
@@ -1251,6 +1331,17 @@ function isNpmShellSource(source) {
1251
1331
  return typeof source === "object" && source !== null && "npm" in source;
1252
1332
  }
1253
1333
 
1334
+ // libs/uipack/src/shell/custom-shell-applier.ts
1335
+ function applyShellTemplate(template, values) {
1336
+ let result = template;
1337
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1338
+ result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1339
+ result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1340
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1341
+ result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1342
+ return result;
1343
+ }
1344
+
1254
1345
  // libs/uipack/src/shell/custom-shell-validator.ts
1255
1346
  function validateShellTemplate(template) {
1256
1347
  const found = {};
@@ -1267,22 +1358,169 @@ function validateShellTemplate(template) {
1267
1358
  };
1268
1359
  }
1269
1360
 
1270
- // libs/uipack/src/shell/custom-shell-applier.ts
1271
- function applyShellTemplate(template, values) {
1272
- let result = template;
1273
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1274
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1275
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1276
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1277
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1278
- return result;
1361
+ // libs/uipack/src/utils/index.ts
1362
+ function escapeHtml(str) {
1363
+ if (str === null || str === void 0) {
1364
+ return "";
1365
+ }
1366
+ const s = String(str);
1367
+ 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");
1368
+ }
1369
+ function escapeScriptClose(jsonString) {
1370
+ return jsonString.replace(/<\//g, "<\\/");
1371
+ }
1372
+ function safeJsonForScript(value) {
1373
+ if (value === void 0) {
1374
+ return "null";
1375
+ }
1376
+ try {
1377
+ const jsonString = JSON.stringify(value, (_key, val) => {
1378
+ if (typeof val === "bigint") {
1379
+ return val.toString();
1380
+ }
1381
+ return val;
1382
+ });
1383
+ if (jsonString === void 0) {
1384
+ return "null";
1385
+ }
1386
+ return escapeScriptClose(jsonString);
1387
+ } catch {
1388
+ return '{"error":"Value could not be serialized"}';
1389
+ }
1390
+ }
1391
+
1392
+ // libs/uipack/src/shell/data-injector.ts
1393
+ function hasSizing(sizing) {
1394
+ if (!sizing) return false;
1395
+ return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
1396
+ }
1397
+ function buildDataInjectionScript(options) {
1398
+ const { toolName, input, output, structuredContent, sizing } = options;
1399
+ const lines = [
1400
+ `window.__mcpAppsEnabled = true;`,
1401
+ `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
1402
+ `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
1403
+ `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
1404
+ `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
1405
+ ];
1406
+ if (hasSizing(sizing)) {
1407
+ lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
1408
+ }
1409
+ return `<script>
1410
+ ${lines.join("\n")}
1411
+ </script>`;
1412
+ }
1413
+ function buildCustomDataInjectionScript(descriptor) {
1414
+ if (descriptor.script !== void 0) {
1415
+ return descriptor.script;
1416
+ }
1417
+ if (descriptor.globalKey !== void 0) {
1418
+ return `<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(
1419
+ descriptor.value ?? null
1420
+ )};</script>`;
1421
+ }
1422
+ return "";
1423
+ }
1424
+ var _uniqueIdCounter = 0;
1425
+ function createTemplateHelpers() {
1426
+ return {
1427
+ escapeHtml: (str) => escapeHtml(str),
1428
+ formatDate: (date, format) => {
1429
+ const d = date instanceof Date ? date : new Date(date);
1430
+ if (isNaN(d.getTime())) return String(date);
1431
+ if (format === "iso") return d.toISOString();
1432
+ if (format === "date") return d.toLocaleDateString();
1433
+ if (format === "time") return d.toLocaleTimeString();
1434
+ return d.toLocaleString();
1435
+ },
1436
+ formatCurrency: (amount, currency = "USD") => {
1437
+ return new Intl.NumberFormat("en-US", {
1438
+ style: "currency",
1439
+ currency
1440
+ }).format(amount);
1441
+ },
1442
+ uniqueId: (prefix = "mcp") => {
1443
+ return `${prefix}-${++_uniqueIdCounter}`;
1444
+ },
1445
+ jsonEmbed: (data) => {
1446
+ return escapeScriptClose(JSON.stringify(data));
1447
+ }
1448
+ };
1449
+ }
1450
+
1451
+ // libs/uipack/src/shell/sizing-css.ts
1452
+ function sanitizeCssValue(value) {
1453
+ return value.replace(/[<>{};]/g, "").trim();
1454
+ }
1455
+ function toCssLength(value) {
1456
+ return typeof value === "number" ? `${value}px` : sanitizeCssValue(value);
1457
+ }
1458
+ function buildSizingStyleTag(sizing) {
1459
+ if (!hasSizing(sizing)) return "";
1460
+ const hasCssSizing = sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0;
1461
+ if (!hasCssSizing) return "";
1462
+ const rootRules = [];
1463
+ const docRules = ["margin: 0;"];
1464
+ if (sizing.preferredHeight !== void 0) {
1465
+ const h = toCssLength(sizing.preferredHeight);
1466
+ docRules.push(`height: ${h};`);
1467
+ rootRules.push(`min-height: ${h};`);
1468
+ }
1469
+ if (sizing.minHeight !== void 0) {
1470
+ const mh = toCssLength(sizing.minHeight);
1471
+ docRules.push(`min-height: ${mh};`);
1472
+ rootRules.push(`min-height: ${mh};`);
1473
+ }
1474
+ if (sizing.maxHeight !== void 0) {
1475
+ const mx = toCssLength(sizing.maxHeight);
1476
+ docRules.push(`max-height: ${mx};`);
1477
+ rootRules.push(`max-height: ${mx};`);
1478
+ }
1479
+ if (sizing.aspectRatio !== void 0) {
1480
+ const ar = typeof sizing.aspectRatio === "number" ? String(sizing.aspectRatio) : sanitizeCssValue(sizing.aspectRatio);
1481
+ if (ar) rootRules.push(`aspect-ratio: ${ar};`);
1482
+ }
1483
+ const parts = [`html, body { ${docRules.join(" ")} }`];
1484
+ if (rootRules.length > 0) {
1485
+ parts.push(`#root { ${rootRules.join(" ")} }`);
1486
+ }
1487
+ return `<style>${parts.join("\n")}</style>`;
1279
1488
  }
1280
1489
 
1281
1490
  // libs/uipack/src/shell/builder.ts
1491
+ function resolveDataInjectionScript(args) {
1492
+ if (args.dataInjection) {
1493
+ return buildCustomDataInjectionScript(args.dataInjection);
1494
+ }
1495
+ return buildDataInjectionScript({
1496
+ toolName: args.toolName,
1497
+ input: args.input,
1498
+ output: args.output,
1499
+ structuredContent: args.structuredContent,
1500
+ sizing: args.sizing
1501
+ });
1502
+ }
1282
1503
  function buildShell(content, config) {
1283
- const { toolName, csp, withShell = true, input, output, structuredContent, includeBridge = true, title } = config;
1284
- const { customShell } = config;
1285
- const dataScript = buildDataInjectionScript({ toolName, input, output, structuredContent });
1504
+ const {
1505
+ toolName,
1506
+ csp,
1507
+ withShell = true,
1508
+ input,
1509
+ output,
1510
+ structuredContent,
1511
+ includeBridge = true,
1512
+ title,
1513
+ sizing
1514
+ } = config;
1515
+ const { customShell, dataInjection } = config;
1516
+ const dataScript = resolveDataInjectionScript({
1517
+ dataInjection,
1518
+ toolName,
1519
+ input,
1520
+ output,
1521
+ structuredContent,
1522
+ sizing
1523
+ });
1286
1524
  if (!withShell) {
1287
1525
  const html2 = `${dataScript}
1288
1526
  ${content}`;
@@ -1300,7 +1538,9 @@ ${content}`;
1300
1538
  output,
1301
1539
  structuredContent,
1302
1540
  includeBridge,
1303
- title
1541
+ title,
1542
+ sizing,
1543
+ dataInjection
1304
1544
  });
1305
1545
  }
1306
1546
  const headParts = [
@@ -1312,6 +1552,10 @@ ${content}`;
1312
1552
  }
1313
1553
  headParts.push(buildCSPMetaTag(csp));
1314
1554
  headParts.push(dataScript);
1555
+ const sizingStyle = buildSizingStyleTag(sizing);
1556
+ if (sizingStyle) {
1557
+ headParts.push(sizingStyle);
1558
+ }
1315
1559
  if (includeBridge) {
1316
1560
  const bridgeScript = generateBridgeIIFE({ minify: true });
1317
1561
  headParts.push(`<script>${bridgeScript}</script>`);
@@ -1345,12 +1589,17 @@ function buildCustomShell(content, customShell, ctx) {
1345
1589
  template = customShell.template;
1346
1590
  }
1347
1591
  const cspTag = buildCSPMetaTag(ctx.csp);
1348
- const dataScript = buildDataInjectionScript({
1592
+ const dataScript = resolveDataInjectionScript({
1593
+ dataInjection: ctx.dataInjection,
1349
1594
  toolName: ctx.toolName,
1350
1595
  input: ctx.input,
1351
1596
  output: ctx.output,
1352
- structuredContent: ctx.structuredContent
1597
+ structuredContent: ctx.structuredContent,
1598
+ sizing: ctx.sizing
1353
1599
  });
1600
+ const sizingStyle = buildSizingStyleTag(ctx.sizing);
1601
+ const dataWithSizing = sizingStyle ? `${dataScript}
1602
+ ${sizingStyle}` : dataScript;
1354
1603
  let bridgeHtml = "";
1355
1604
  if (ctx.includeBridge) {
1356
1605
  const bridgeScript = generateBridgeIIFE({ minify: true });
@@ -1358,7 +1607,7 @@ function buildCustomShell(content, customShell, ctx) {
1358
1607
  }
1359
1608
  const html = applyShellTemplate(template, {
1360
1609
  csp: cspTag,
1361
- data: dataScript,
1610
+ data: dataWithSizing,
1362
1611
  bridge: bridgeHtml,
1363
1612
  content,
1364
1613
  title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
@@ -1501,10 +1750,13 @@ function escapeRegExp(str) {
1501
1750
  applyShellTemplate,
1502
1751
  buildCSPDirectives,
1503
1752
  buildCSPMetaTag,
1753
+ buildCustomDataInjectionScript,
1504
1754
  buildDataInjectionScript,
1505
1755
  buildShell,
1756
+ buildSizingStyleTag,
1506
1757
  clearShellTemplateCache,
1507
1758
  createTemplateHelpers,
1759
+ hasSizing,
1508
1760
  isInlineShellSource,
1509
1761
  isNpmShellSource,
1510
1762
  isUrlShellSource,