@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
@@ -74,78 +74,404 @@ function isUIRenderFailure(result) {
74
74
  return typeof rec["reason"] === "string" && !("meta" in rec);
75
75
  }
76
76
 
77
- // libs/uipack/src/shell/csp.ts
78
- var DEFAULT_CDN_DOMAINS = [
79
- "https://cdn.jsdelivr.net",
80
- "https://cdnjs.cloudflare.com",
81
- "https://fonts.googleapis.com",
82
- "https://fonts.gstatic.com",
83
- "https://esm.sh"
84
- ];
85
- var DEFAULT_CSP_DIRECTIVES = [
86
- "default-src 'none'",
87
- `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
88
- `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
89
- `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
90
- `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
91
- `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
92
- "object-src 'self' data:"
93
- ];
94
- function buildCSPDirectives(csp) {
95
- if (!csp) {
96
- return [...DEFAULT_CSP_DIRECTIVES];
77
+ // libs/uipack/src/resolver/cdn-registry.ts
78
+ var DEFAULT_CDN_REGISTRY = {
79
+ react: {
80
+ packageName: "react",
81
+ defaultVersion: "19.2.4",
82
+ providers: {
83
+ cloudflare: {
84
+ url: "https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",
85
+ global: "React",
86
+ crossorigin: "anonymous"
87
+ },
88
+ jsdelivr: {
89
+ url: "https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",
90
+ global: "React",
91
+ crossorigin: "anonymous"
92
+ },
93
+ unpkg: {
94
+ url: "https://unpkg.com/react@19.2.4/umd/react.production.min.js",
95
+ global: "React",
96
+ crossorigin: "anonymous"
97
+ },
98
+ "esm.sh": {
99
+ url: "https://esm.sh/react@19.2.4",
100
+ esm: true,
101
+ crossorigin: "anonymous"
102
+ }
103
+ },
104
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
105
+ metadata: {
106
+ description: "A JavaScript library for building user interfaces",
107
+ homepage: "https://react.dev",
108
+ license: "MIT"
109
+ }
110
+ },
111
+ "react-dom": {
112
+ packageName: "react-dom",
113
+ defaultVersion: "19.2.4",
114
+ providers: {
115
+ cloudflare: {
116
+ url: "https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",
117
+ global: "ReactDOM",
118
+ crossorigin: "anonymous",
119
+ peerDependencies: ["react"]
120
+ },
121
+ jsdelivr: {
122
+ url: "https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",
123
+ global: "ReactDOM",
124
+ crossorigin: "anonymous",
125
+ peerDependencies: ["react"]
126
+ },
127
+ unpkg: {
128
+ url: "https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",
129
+ global: "ReactDOM",
130
+ crossorigin: "anonymous",
131
+ peerDependencies: ["react"]
132
+ },
133
+ "esm.sh": {
134
+ url: "https://esm.sh/react-dom@19.2.4",
135
+ esm: true,
136
+ crossorigin: "anonymous",
137
+ peerDependencies: ["react"]
138
+ }
139
+ },
140
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
141
+ metadata: {
142
+ description: "React package for working with the DOM",
143
+ homepage: "https://react.dev",
144
+ license: "MIT"
145
+ }
146
+ },
147
+ "chart.js": {
148
+ packageName: "chart.js",
149
+ defaultVersion: "4.5.1",
150
+ providers: {
151
+ cloudflare: {
152
+ url: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",
153
+ global: "Chart",
154
+ crossorigin: "anonymous"
155
+ },
156
+ jsdelivr: {
157
+ url: "https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",
158
+ global: "Chart",
159
+ crossorigin: "anonymous"
160
+ },
161
+ unpkg: {
162
+ url: "https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",
163
+ global: "Chart",
164
+ crossorigin: "anonymous"
165
+ }
166
+ },
167
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
168
+ metadata: {
169
+ description: "Simple yet flexible JavaScript charting library",
170
+ homepage: "https://www.chartjs.org",
171
+ license: "MIT"
172
+ }
173
+ },
174
+ d3: {
175
+ packageName: "d3",
176
+ defaultVersion: "7.9.0",
177
+ providers: {
178
+ cloudflare: {
179
+ url: "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",
180
+ integrity: "sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",
181
+ global: "d3",
182
+ crossorigin: "anonymous"
183
+ },
184
+ jsdelivr: {
185
+ url: "https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",
186
+ global: "d3",
187
+ crossorigin: "anonymous"
188
+ },
189
+ unpkg: {
190
+ url: "https://unpkg.com/d3@7.9.0/dist/d3.min.js",
191
+ global: "d3",
192
+ crossorigin: "anonymous"
193
+ }
194
+ },
195
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
196
+ metadata: {
197
+ description: "Data-Driven Documents",
198
+ homepage: "https://d3js.org",
199
+ license: "ISC"
200
+ }
201
+ },
202
+ lodash: {
203
+ packageName: "lodash",
204
+ defaultVersion: "4.17.21",
205
+ providers: {
206
+ cloudflare: {
207
+ url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
208
+ integrity: "sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",
209
+ global: "_",
210
+ crossorigin: "anonymous"
211
+ },
212
+ jsdelivr: {
213
+ url: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",
214
+ global: "_",
215
+ crossorigin: "anonymous"
216
+ },
217
+ unpkg: {
218
+ url: "https://unpkg.com/lodash@4.17.21/lodash.min.js",
219
+ global: "_",
220
+ crossorigin: "anonymous"
221
+ }
222
+ },
223
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
224
+ metadata: {
225
+ description: "A modern JavaScript utility library",
226
+ homepage: "https://lodash.com",
227
+ license: "MIT"
228
+ }
97
229
  }
98
- const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
99
- const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
100
- const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
101
- const directives = [
102
- "default-src 'none'",
103
- `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
104
- `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
105
- ];
106
- const imgSources = ["'self'", "data:", ...allResourceDomains];
107
- directives.push(`img-src ${imgSources.join(" ")}`);
108
- const fontSources = ["'self'", "data:", ...allResourceDomains];
109
- directives.push(`font-src ${fontSources.join(" ")}`);
110
- if (validConnectDomains.length) {
111
- directives.push(`connect-src ${validConnectDomains.join(" ")}`);
112
- } else {
113
- directives.push(`connect-src ${allResourceDomains.join(" ")}`);
230
+ };
231
+ function lookupPackage(packageName, registry = DEFAULT_CDN_REGISTRY) {
232
+ return registry[packageName];
233
+ }
234
+
235
+ // libs/uipack/src/resolver/import-parser.ts
236
+ var IMPORT_PATTERNS = {
237
+ named: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
238
+ default: /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
239
+ namespace: /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
240
+ sideEffect: /import\s*['"]([^'"]+)['"]/g,
241
+ dynamic: /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
242
+ defaultAndNamed: /import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
243
+ defaultAndNamespace: /import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
244
+ reExport: /export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,
245
+ reExportAll: /export\s*\*\s*from\s*['"]([^'"]+)['"]/g
246
+ };
247
+ function parseNamedImports(namedString) {
248
+ return namedString.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => {
249
+ const asMatch = s.match(/^(\w+)\s+as\s+\w+$/);
250
+ return asMatch ? asMatch[1] : s;
251
+ });
252
+ }
253
+ function isRelativeImport(specifier) {
254
+ return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
255
+ }
256
+ function isExternalImport(specifier) {
257
+ return !isRelativeImport(specifier) && !specifier.startsWith("#");
258
+ }
259
+ function getPackageName(specifier) {
260
+ if (specifier.startsWith("@")) {
261
+ const parts = specifier.split("/");
262
+ if (parts.length >= 2) {
263
+ return `${parts[0]}/${parts[1]}`;
264
+ }
265
+ return specifier;
114
266
  }
115
- directives.push("object-src 'self' data:");
116
- return directives;
267
+ const firstSlash = specifier.indexOf("/");
268
+ return firstSlash === -1 ? specifier : specifier.slice(0, firstSlash);
117
269
  }
118
- function buildCSPMetaTag(csp) {
119
- const directives = buildCSPDirectives(csp);
120
- const content = directives.join("; ");
121
- return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
270
+ function getLineNumber(source, index) {
271
+ let line = 1;
272
+ for (let i = 0; i < index && i < source.length; i++) {
273
+ if (source[i] === "\n") {
274
+ line++;
275
+ }
276
+ }
277
+ return line;
122
278
  }
123
- function validateCSPDomain(domain) {
124
- if (domain.startsWith("https://*.")) {
125
- const rest = domain.slice(10);
126
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
279
+ function getColumnNumber(source, index) {
280
+ let column = 0;
281
+ for (let i = index - 1; i >= 0 && source[i] !== "\n"; i--) {
282
+ column++;
127
283
  }
128
- try {
129
- const url = new URL(domain);
130
- return url.protocol === "https:";
131
- } catch {
132
- return false;
284
+ return column;
285
+ }
286
+ function parseImports(source) {
287
+ const imports = [];
288
+ const seenStatements = /* @__PURE__ */ new Set();
289
+ const addImport = (imp) => {
290
+ const key = `${imp.type}:${imp.specifier}:${imp.statement}`;
291
+ if (!seenStatements.has(key)) {
292
+ seenStatements.add(key);
293
+ imports.push(imp);
294
+ }
295
+ };
296
+ let match;
297
+ const defaultAndNamedRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamed.source, "g");
298
+ while ((match = defaultAndNamedRegex.exec(source)) !== null) {
299
+ const [statement, defaultName, namedString, specifier] = match;
300
+ addImport({
301
+ statement,
302
+ specifier,
303
+ type: "default",
304
+ defaultImport: defaultName,
305
+ namedImports: parseNamedImports(namedString),
306
+ line: getLineNumber(source, match.index),
307
+ column: getColumnNumber(source, match.index)
308
+ });
133
309
  }
310
+ const defaultAndNamespaceRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source, "g");
311
+ while ((match = defaultAndNamespaceRegex.exec(source)) !== null) {
312
+ const [statement, defaultName, namespaceName, specifier] = match;
313
+ addImport({
314
+ statement,
315
+ specifier,
316
+ type: "default",
317
+ defaultImport: defaultName,
318
+ namespaceImport: namespaceName,
319
+ line: getLineNumber(source, match.index),
320
+ column: getColumnNumber(source, match.index)
321
+ });
322
+ }
323
+ const namedRegex = new RegExp(IMPORT_PATTERNS.named.source, "g");
324
+ while ((match = namedRegex.exec(source)) !== null) {
325
+ const [statement, namedString, specifier] = match;
326
+ addImport({
327
+ statement,
328
+ specifier,
329
+ type: "named",
330
+ namedImports: parseNamedImports(namedString),
331
+ line: getLineNumber(source, match.index),
332
+ column: getColumnNumber(source, match.index)
333
+ });
334
+ }
335
+ const defaultRegex = new RegExp(IMPORT_PATTERNS.default.source, "g");
336
+ while ((match = defaultRegex.exec(source)) !== null) {
337
+ const [statement, defaultName, specifier] = match;
338
+ const afterMatch = source.slice(match.index + match[0].length - specifier.length - 2);
339
+ if (afterMatch.startsWith(",")) continue;
340
+ addImport({
341
+ statement,
342
+ specifier,
343
+ type: "default",
344
+ defaultImport: defaultName,
345
+ line: getLineNumber(source, match.index),
346
+ column: getColumnNumber(source, match.index)
347
+ });
348
+ }
349
+ const namespaceRegex = new RegExp(IMPORT_PATTERNS.namespace.source, "g");
350
+ while ((match = namespaceRegex.exec(source)) !== null) {
351
+ const [statement, namespaceName, specifier] = match;
352
+ addImport({
353
+ statement,
354
+ specifier,
355
+ type: "namespace",
356
+ namespaceImport: namespaceName,
357
+ line: getLineNumber(source, match.index),
358
+ column: getColumnNumber(source, match.index)
359
+ });
360
+ }
361
+ const sideEffectRegex = new RegExp(IMPORT_PATTERNS.sideEffect.source, "g");
362
+ while ((match = sideEffectRegex.exec(source)) !== null) {
363
+ const [statement, specifier] = match;
364
+ const beforeMatch = source.slice(Math.max(0, match.index - 50), match.index);
365
+ if (beforeMatch.includes("from")) continue;
366
+ addImport({
367
+ statement,
368
+ specifier,
369
+ type: "side-effect",
370
+ line: getLineNumber(source, match.index),
371
+ column: getColumnNumber(source, match.index)
372
+ });
373
+ }
374
+ const dynamicRegex = new RegExp(IMPORT_PATTERNS.dynamic.source, "g");
375
+ while ((match = dynamicRegex.exec(source)) !== null) {
376
+ const [statement, specifier] = match;
377
+ addImport({
378
+ statement,
379
+ specifier,
380
+ type: "dynamic",
381
+ line: getLineNumber(source, match.index),
382
+ column: getColumnNumber(source, match.index)
383
+ });
384
+ }
385
+ const reExportRegex = new RegExp(IMPORT_PATTERNS.reExport.source, "g");
386
+ while ((match = reExportRegex.exec(source)) !== null) {
387
+ const [statement, specifier] = match;
388
+ addImport({
389
+ statement,
390
+ specifier,
391
+ type: "named",
392
+ line: getLineNumber(source, match.index),
393
+ column: getColumnNumber(source, match.index)
394
+ });
395
+ }
396
+ const reExportAllRegex = new RegExp(IMPORT_PATTERNS.reExportAll.source, "g");
397
+ while ((match = reExportAllRegex.exec(source)) !== null) {
398
+ const [statement, specifier] = match;
399
+ addImport({
400
+ statement,
401
+ specifier,
402
+ type: "namespace",
403
+ line: getLineNumber(source, match.index),
404
+ column: getColumnNumber(source, match.index)
405
+ });
406
+ }
407
+ const externalImports = imports.filter((imp) => isExternalImport(imp.specifier));
408
+ const relativeImports = imports.filter((imp) => isRelativeImport(imp.specifier));
409
+ const externalPackages = [...new Set(externalImports.map((imp) => getPackageName(imp.specifier)))];
410
+ return {
411
+ imports,
412
+ externalImports,
413
+ relativeImports,
414
+ externalPackages
415
+ };
134
416
  }
135
- function sanitizeCSPDomains(domains) {
136
- if (!domains) return [];
137
- const valid = [];
138
- for (const domain of domains) {
139
- if (validateCSPDomain(domain)) {
140
- valid.push(domain);
141
- } else {
142
- console.warn(`Invalid CSP domain ignored: ${domain}`);
417
+
418
+ // libs/uipack/src/resolver/esm-sh.resolver.ts
419
+ var DEFAULT_FALLBACK_CDN = "https://esm.sh";
420
+ function createEsmShResolver(options = {}) {
421
+ const {
422
+ fallbackCdnBase = DEFAULT_FALLBACK_CDN,
423
+ registry = DEFAULT_CDN_REGISTRY,
424
+ providerOrder = ["esm.sh", "cloudflare", "jsdelivr", "unpkg"]
425
+ } = options;
426
+ return {
427
+ resolve(specifier, _context) {
428
+ if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) {
429
+ return null;
430
+ }
431
+ if (specifier.startsWith("node:") || specifier.startsWith("#")) {
432
+ return null;
433
+ }
434
+ const pkgName = getPackageName(specifier);
435
+ const entry = lookupPackage(pkgName, registry);
436
+ if (entry) {
437
+ for (const provider of providerOrder) {
438
+ const config = entry.providers[provider];
439
+ if (config?.url) {
440
+ const subpath = specifier.slice(pkgName.length);
441
+ if (subpath && config.url.includes("esm.sh/")) {
442
+ return {
443
+ value: config.url + subpath,
444
+ type: "url",
445
+ integrity: config.integrity
446
+ };
447
+ }
448
+ if (subpath) {
449
+ return {
450
+ value: `${fallbackCdnBase}/${specifier}`,
451
+ type: "url"
452
+ };
453
+ }
454
+ if (config.global && !config.esm) {
455
+ return {
456
+ value: config.url,
457
+ type: "url",
458
+ integrity: config.integrity
459
+ };
460
+ }
461
+ return {
462
+ value: config.url,
463
+ type: "url",
464
+ integrity: config.integrity
465
+ };
466
+ }
467
+ }
468
+ }
469
+ return {
470
+ value: `${fallbackCdnBase}/${specifier}`,
471
+ type: "url"
472
+ };
143
473
  }
144
- }
145
- return valid;
146
- }
147
- function escapeAttribute(str) {
148
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
474
+ };
149
475
  }
150
476
 
151
477
  // libs/uipack/src/utils/index.ts
@@ -179,46 +505,29 @@ function safeJsonForScript(value) {
179
505
  }
180
506
  }
181
507
 
182
- // libs/uipack/src/shell/data-injector.ts
183
- function buildDataInjectionScript(options) {
184
- const { toolName, input, output, structuredContent } = options;
185
- const lines = [
186
- `window.__mcpAppsEnabled = true;`,
187
- `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
188
- `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
189
- `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
190
- `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
191
- ];
192
- return `<script>
193
- ${lines.join("\n")}
194
- </script>`;
195
- }
196
- var _uniqueIdCounter = 0;
197
- function createTemplateHelpers() {
198
- return {
199
- escapeHtml: (str) => escapeHtml(str),
200
- formatDate: (date, format) => {
201
- const d = date instanceof Date ? date : new Date(date);
202
- if (isNaN(d.getTime())) return String(date);
203
- if (format === "iso") return d.toISOString();
204
- if (format === "date") return d.toLocaleDateString();
205
- if (format === "time") return d.toLocaleTimeString();
206
- return d.toLocaleString();
207
- },
208
- formatCurrency: (amount, currency = "USD") => {
209
- return new Intl.NumberFormat("en-US", {
210
- style: "currency",
211
- currency
212
- }).format(amount);
213
- },
214
- uniqueId: (prefix = "mcp") => {
215
- return `${prefix}-${++_uniqueIdCounter}`;
216
- },
217
- jsonEmbed: (data) => {
218
- return escapeScriptClose(JSON.stringify(data));
508
+ // libs/uipack/src/resolver/import-map.ts
509
+ function createImportMapFromResolved(resolved) {
510
+ const imports = {};
511
+ const integrity = {};
512
+ for (const [specifier, res] of Object.entries(resolved)) {
513
+ if (res.type === "url") {
514
+ imports[specifier] = res.value;
515
+ if (res.integrity) {
516
+ integrity[res.value] = res.integrity;
517
+ }
219
518
  }
519
+ }
520
+ return {
521
+ imports,
522
+ integrity: Object.keys(integrity).length > 0 ? integrity : void 0
220
523
  };
221
524
  }
525
+ function generateImportMapScriptTag(map) {
526
+ const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
527
+ return `<script type="importmap">
528
+ ${json}
529
+ </script>`;
530
+ }
222
531
 
223
532
  // libs/uipack/src/bridge-runtime/iife-generator.ts
224
533
  function generateBridgeIIFE(options = {}) {
@@ -274,6 +583,8 @@ function generateBridgeIIFE(options = {}) {
274
583
  parts.push("});");
275
584
  parts.push("");
276
585
  parts.push("window.FrontMcpBridge = bridge;");
586
+ parts.push("");
587
+ parts.push(generateAutoResize());
277
588
  parts.push("function __showLoading() {");
278
589
  parts.push(' var root = document.getElementById("root");');
279
590
  parts.push(" if (root && !root.hasChildNodes()) {");
@@ -294,6 +605,112 @@ function generateBridgeIIFE(options = {}) {
294
605
  }
295
606
  return code;
296
607
  }
608
+ function generateAutoResize() {
609
+ return `
610
+ function __applySizingCss(sizing) {
611
+ if (typeof document === 'undefined' || !document.documentElement) return;
612
+ function toLen(v) { return typeof v === 'number' ? v + 'px' : v; }
613
+ var de = document.documentElement;
614
+ var body = document.body;
615
+ var root = document.getElementById('root');
616
+ if (sizing.preferredHeight != null) {
617
+ var ph = toLen(sizing.preferredHeight);
618
+ de.style.height = ph;
619
+ if (body) body.style.height = ph;
620
+ if (root && !root.style.minHeight) root.style.minHeight = ph;
621
+ }
622
+ if (sizing.minHeight != null) {
623
+ var mh = toLen(sizing.minHeight);
624
+ de.style.minHeight = mh;
625
+ if (body) body.style.minHeight = mh;
626
+ if (root) root.style.minHeight = mh;
627
+ }
628
+ if (sizing.maxHeight != null) {
629
+ var mx = toLen(sizing.maxHeight);
630
+ de.style.maxHeight = mx;
631
+ if (body) body.style.maxHeight = mx;
632
+ if (root) root.style.maxHeight = mx;
633
+ }
634
+ if (sizing.aspectRatio != null && root) {
635
+ root.style.aspectRatio = String(sizing.aspectRatio);
636
+ }
637
+ }
638
+
639
+ function __initAutoResize() {
640
+ if (typeof window === 'undefined') return;
641
+ // Idempotent: a re-injected IIFE must not stack observers.
642
+ if (window.__mcpAutoResizeInit) return;
643
+ window.__mcpAutoResizeInit = true;
644
+ var sizing = window.__mcpWidgetSizing;
645
+ if (!sizing || typeof sizing !== 'object') return;
646
+
647
+ // Apply CSS as a runtime fallback (the static <style> may be absent on some
648
+ // render paths). Wait for the body if it isn't ready yet.
649
+ function apply() { try { __applySizingCss(sizing); } catch (e) {} }
650
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
651
+ document.addEventListener('DOMContentLoaded', apply);
652
+ } else {
653
+ apply();
654
+ }
655
+
656
+ // Auto-resize defaults ON; opt out with autoResize:false.
657
+ if (sizing.autoResize === false) return;
658
+ if (typeof ResizeObserver === 'undefined') return;
659
+
660
+ function startObserving() {
661
+ var target = document.getElementById('root') || document.body;
662
+ if (!target) return;
663
+
664
+ var rafId = null;
665
+ var lastReported = -1;
666
+ function report() {
667
+ rafId = null;
668
+ try {
669
+ var rect = target.getBoundingClientRect();
670
+ var height = Math.ceil(rect.height);
671
+ var width = Math.ceil(rect.width);
672
+ if (height === lastReported || height <= 0) return;
673
+ lastReported = height;
674
+ var payload = { height: height, width: width };
675
+ if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
676
+ if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
677
+ window.FrontMcpBridge.setSize(payload).catch(function() {});
678
+ }
679
+ window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
680
+ } catch (e) {}
681
+ }
682
+ function schedule() {
683
+ // Debounce via rAF; fall back to setTimeout if rAF is unavailable.
684
+ if (rafId != null) return;
685
+ if (typeof requestAnimationFrame === 'function') {
686
+ rafId = requestAnimationFrame(report);
687
+ } else {
688
+ rafId = setTimeout(report, 100);
689
+ }
690
+ }
691
+
692
+ try {
693
+ // Disconnect any prior observer before creating a new one (no leaks/dupes).
694
+ if (window.__mcpResizeObserver && typeof window.__mcpResizeObserver.disconnect === 'function') {
695
+ window.__mcpResizeObserver.disconnect();
696
+ }
697
+ var ro = new ResizeObserver(function() { schedule(); });
698
+ ro.observe(target);
699
+ window.__mcpResizeObserver = ro;
700
+ } catch (e) {}
701
+ // Report once on init so the host gets an initial measurement.
702
+ schedule();
703
+ }
704
+
705
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
706
+ document.addEventListener('DOMContentLoaded', startObserving);
707
+ } else {
708
+ startObserving();
709
+ }
710
+ }
711
+ __initAutoResize();
712
+ `.trim();
713
+ }
297
714
  function generateContextDetection() {
298
715
  return `
299
716
  function detectTheme() {
@@ -401,6 +818,19 @@ var OpenAIAdapter = {
401
818
  requestDisplayMode: function(context, mode) {
402
819
  return Promise.resolve();
403
820
  },
821
+ setSize: function(context, size) {
822
+ // OpenAI Apps SDK measures DOM height itself; if a sizing API surfaces on
823
+ // window.openai, forward to it, otherwise no-op (CSS drives layout).
824
+ try {
825
+ if (window.openai && typeof window.openai.requestDisplayMode === 'function' && size && size.displayMode) {
826
+ window.openai.requestDisplayMode(size.displayMode);
827
+ }
828
+ if (window.openai && typeof window.openai.setWidgetHeight === 'function' && size && typeof size.height === 'number') {
829
+ window.openai.setWidgetHeight(size.height);
830
+ }
831
+ } catch (e) {}
832
+ return Promise.resolve();
833
+ },
404
834
  requestClose: function(context) {
405
835
  return Promise.resolve();
406
836
  }
@@ -645,6 +1075,15 @@ var ExtAppsAdapter = {
645
1075
  requestDisplayMode: function(context, mode) {
646
1076
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
647
1077
  },
1078
+ setSize: function(context, size) {
1079
+ // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
1080
+ // measured/desired widget dimensions to the host.
1081
+ return this.sendRequest('ui/setSize', {
1082
+ height: size && size.height,
1083
+ width: size && size.width,
1084
+ aspectRatio: size && size.aspectRatio
1085
+ });
1086
+ },
648
1087
  requestClose: function(context) {
649
1088
  return this.sendRequest('ui/close', {});
650
1089
  },
@@ -733,6 +1172,10 @@ var ClaudeAdapter = {
733
1172
  requestDisplayMode: function() {
734
1173
  return Promise.resolve();
735
1174
  },
1175
+ setSize: function() {
1176
+ // Claude measures the rendered DOM height itself \u2014 CSS-only, no reporting.
1177
+ return Promise.resolve();
1178
+ },
736
1179
  requestClose: function() {
737
1180
  return Promise.resolve();
738
1181
  }
@@ -784,6 +1227,9 @@ var GeminiAdapter = {
784
1227
  requestDisplayMode: function() {
785
1228
  return Promise.resolve();
786
1229
  },
1230
+ setSize: function() {
1231
+ return Promise.resolve();
1232
+ },
787
1233
  requestClose: function() {
788
1234
  return Promise.resolve();
789
1235
  }
@@ -819,6 +1265,9 @@ var GenericAdapter = {
819
1265
  requestDisplayMode: function() {
820
1266
  return Promise.resolve();
821
1267
  },
1268
+ setSize: function() {
1269
+ return Promise.resolve();
1270
+ },
822
1271
  requestClose: function() {
823
1272
  return Promise.resolve();
824
1273
  }
@@ -1053,6 +1502,15 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1053
1502
  });
1054
1503
  };
1055
1504
 
1505
+ // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1506
+ // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1507
+ // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1508
+ FrontMcpBridge.prototype.setSize = function(size) {
1509
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1510
+ if (!this._adapter.setSize) return Promise.resolve();
1511
+ return this._adapter.setSize(this._context, size || {});
1512
+ };
1513
+
1056
1514
  FrontMcpBridge.prototype.requestClose = function() {
1057
1515
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1058
1516
  return this._adapter.requestClose(this._context);
@@ -1251,564 +1709,404 @@ var BRIDGE_SCRIPT_TAGS = {
1251
1709
  gemini: `<script>${generatePlatformBundle("gemini")}</script>`
1252
1710
  };
1253
1711
 
1254
- // libs/uipack/src/shell/custom-shell-types.ts
1255
- var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
1256
- var SHELL_PLACEHOLDERS = {
1257
- CSP: "{{CSP}}",
1258
- DATA: "{{DATA}}",
1259
- BRIDGE: "{{BRIDGE}}",
1260
- CONTENT: "{{CONTENT}}",
1261
- TITLE: "{{TITLE}}"
1262
- };
1263
- var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1264
- var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1265
-
1266
- // libs/uipack/src/shell/custom-shell-validator.ts
1267
- function validateShellTemplate(template) {
1268
- const found = {};
1269
- for (const name of SHELL_PLACEHOLDER_NAMES) {
1270
- found[name] = template.includes(SHELL_PLACEHOLDERS[name]);
1271
- }
1272
- const missingRequired = REQUIRED_PLACEHOLDERS.filter((name) => !found[name]);
1273
- const missingOptional = OPTIONAL_PLACEHOLDERS.filter((name) => !found[name]);
1274
- return {
1275
- valid: missingRequired.length === 0,
1276
- found,
1277
- missingRequired,
1278
- missingOptional
1279
- };
1280
- }
1281
-
1282
- // libs/uipack/src/shell/custom-shell-applier.ts
1283
- function applyShellTemplate(template, values) {
1284
- let result = template;
1285
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1286
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1287
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1288
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1289
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1290
- return result;
1291
- }
1292
-
1293
- // libs/uipack/src/shell/builder.ts
1294
- function buildShell(content, config) {
1295
- const { toolName, csp, withShell = true, input, output, structuredContent, includeBridge = true, title } = config;
1296
- const { customShell } = config;
1297
- const dataScript = buildDataInjectionScript({ toolName, input, output, structuredContent });
1298
- if (!withShell) {
1299
- const html2 = `${dataScript}
1300
- ${content}`;
1301
- return {
1302
- html: html2,
1303
- hash: simpleHash(html2),
1304
- size: Buffer.byteLength(html2, "utf-8")
1305
- };
1306
- }
1307
- if (customShell) {
1308
- return buildCustomShell(content, customShell, {
1309
- csp,
1310
- toolName,
1311
- input,
1312
- output,
1313
- structuredContent,
1314
- includeBridge,
1315
- title
1316
- });
1317
- }
1318
- const headParts = [
1319
- '<meta charset="UTF-8">',
1320
- '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
1321
- ];
1322
- if (title) {
1323
- headParts.push(`<title>${escapeHtmlForTag(title)}</title>`);
1324
- }
1325
- headParts.push(buildCSPMetaTag(csp));
1326
- headParts.push(dataScript);
1327
- if (includeBridge) {
1328
- const bridgeScript = generateBridgeIIFE({ minify: true });
1329
- headParts.push(`<script>${bridgeScript}</script>`);
1330
- }
1331
- const html = `<!DOCTYPE html>
1332
- <html lang="en">
1333
- <head>
1334
- ${headParts.map((p) => ` ${p}`).join("\n")}
1335
- </head>
1336
- <body>
1337
- ${content}
1338
- </body>
1339
- </html>`;
1340
- return {
1341
- html,
1342
- hash: simpleHash(html),
1343
- size: Buffer.byteLength(html, "utf-8")
1344
- };
1345
- }
1346
- function buildCustomShell(content, customShell, ctx) {
1347
- let template;
1348
- if (typeof customShell === "string") {
1349
- const validation = validateShellTemplate(customShell);
1350
- if (!validation.valid) {
1351
- throw new Error(
1352
- `Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
1353
- );
1354
- }
1355
- template = customShell;
1356
- } else {
1357
- template = customShell.template;
1712
+ // libs/uipack/src/shell/csp.ts
1713
+ var DEFAULT_CDN_DOMAINS = [
1714
+ "https://cdn.jsdelivr.net",
1715
+ "https://cdnjs.cloudflare.com",
1716
+ "https://fonts.googleapis.com",
1717
+ "https://fonts.gstatic.com",
1718
+ "https://esm.sh"
1719
+ ];
1720
+ var DEFAULT_CSP_DIRECTIVES = [
1721
+ "default-src 'none'",
1722
+ `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1723
+ `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1724
+ `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1725
+ `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1726
+ `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1727
+ "object-src 'self' data:"
1728
+ ];
1729
+ function buildCSPDirectives(csp) {
1730
+ if (!csp) {
1731
+ return [...DEFAULT_CSP_DIRECTIVES];
1358
1732
  }
1359
- const cspTag = buildCSPMetaTag(ctx.csp);
1360
- const dataScript = buildDataInjectionScript({
1361
- toolName: ctx.toolName,
1362
- input: ctx.input,
1363
- output: ctx.output,
1364
- structuredContent: ctx.structuredContent
1365
- });
1366
- let bridgeHtml = "";
1367
- if (ctx.includeBridge) {
1368
- const bridgeScript = generateBridgeIIFE({ minify: true });
1369
- bridgeHtml = `<script>${bridgeScript}</script>`;
1733
+ const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
1734
+ const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
1735
+ const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
1736
+ const directives = [
1737
+ "default-src 'none'",
1738
+ `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
1739
+ `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
1740
+ ];
1741
+ const imgSources = ["'self'", "data:", ...allResourceDomains];
1742
+ directives.push(`img-src ${imgSources.join(" ")}`);
1743
+ const fontSources = ["'self'", "data:", ...allResourceDomains];
1744
+ directives.push(`font-src ${fontSources.join(" ")}`);
1745
+ if (validConnectDomains.length) {
1746
+ directives.push(`connect-src ${validConnectDomains.join(" ")}`);
1747
+ } else {
1748
+ directives.push(`connect-src ${allResourceDomains.join(" ")}`);
1370
1749
  }
1371
- const html = applyShellTemplate(template, {
1372
- csp: cspTag,
1373
- data: dataScript,
1374
- bridge: bridgeHtml,
1375
- content,
1376
- title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
1377
- });
1378
- return {
1379
- html,
1380
- hash: simpleHash(html),
1381
- size: Buffer.byteLength(html, "utf-8")
1382
- };
1750
+ directives.push("object-src 'self' data:");
1751
+ return directives;
1383
1752
  }
1384
- function simpleHash(str) {
1385
- let hash = 0;
1386
- for (let i = 0; i < str.length; i++) {
1387
- const char = str.charCodeAt(i);
1388
- hash = (hash << 5) - hash + char | 0;
1389
- }
1390
- return Math.abs(hash).toString(16).padStart(8, "0");
1753
+ function buildCSPMetaTag(csp) {
1754
+ const directives = buildCSPDirectives(csp);
1755
+ const content = directives.join("; ");
1756
+ return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
1391
1757
  }
1392
- function escapeHtmlForTag(str) {
1393
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1758
+ function validateCSPDomain(domain) {
1759
+ if (domain.startsWith("https://*.")) {
1760
+ const rest = domain.slice(10);
1761
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
1762
+ }
1763
+ try {
1764
+ const url = new URL(domain);
1765
+ return url.protocol === "https:";
1766
+ } catch {
1767
+ return false;
1768
+ }
1394
1769
  }
1395
-
1396
- // libs/uipack/src/resolver/cdn-registry.ts
1397
- var DEFAULT_CDN_REGISTRY = {
1398
- react: {
1399
- packageName: "react",
1400
- defaultVersion: "19.2.4",
1401
- providers: {
1402
- cloudflare: {
1403
- url: "https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",
1404
- global: "React",
1405
- crossorigin: "anonymous"
1406
- },
1407
- jsdelivr: {
1408
- url: "https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",
1409
- global: "React",
1410
- crossorigin: "anonymous"
1411
- },
1412
- unpkg: {
1413
- url: "https://unpkg.com/react@19.2.4/umd/react.production.min.js",
1414
- global: "React",
1415
- crossorigin: "anonymous"
1416
- },
1417
- "esm.sh": {
1418
- url: "https://esm.sh/react@19.2.4",
1419
- esm: true,
1420
- crossorigin: "anonymous"
1421
- }
1422
- },
1423
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1424
- metadata: {
1425
- description: "A JavaScript library for building user interfaces",
1426
- homepage: "https://react.dev",
1427
- license: "MIT"
1428
- }
1429
- },
1430
- "react-dom": {
1431
- packageName: "react-dom",
1432
- defaultVersion: "19.2.4",
1433
- providers: {
1434
- cloudflare: {
1435
- url: "https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",
1436
- global: "ReactDOM",
1437
- crossorigin: "anonymous",
1438
- peerDependencies: ["react"]
1439
- },
1440
- jsdelivr: {
1441
- url: "https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",
1442
- global: "ReactDOM",
1443
- crossorigin: "anonymous",
1444
- peerDependencies: ["react"]
1445
- },
1446
- unpkg: {
1447
- url: "https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",
1448
- global: "ReactDOM",
1449
- crossorigin: "anonymous",
1450
- peerDependencies: ["react"]
1451
- },
1452
- "esm.sh": {
1453
- url: "https://esm.sh/react-dom@19.2.4",
1454
- esm: true,
1455
- crossorigin: "anonymous",
1456
- peerDependencies: ["react"]
1457
- }
1458
- },
1459
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1460
- metadata: {
1461
- description: "React package for working with the DOM",
1462
- homepage: "https://react.dev",
1463
- license: "MIT"
1464
- }
1465
- },
1466
- "chart.js": {
1467
- packageName: "chart.js",
1468
- defaultVersion: "4.5.1",
1469
- providers: {
1470
- cloudflare: {
1471
- url: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",
1472
- global: "Chart",
1473
- crossorigin: "anonymous"
1474
- },
1475
- jsdelivr: {
1476
- url: "https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",
1477
- global: "Chart",
1478
- crossorigin: "anonymous"
1479
- },
1480
- unpkg: {
1481
- url: "https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",
1482
- global: "Chart",
1483
- crossorigin: "anonymous"
1484
- }
1485
- },
1486
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1487
- metadata: {
1488
- description: "Simple yet flexible JavaScript charting library",
1489
- homepage: "https://www.chartjs.org",
1490
- license: "MIT"
1491
- }
1492
- },
1493
- d3: {
1494
- packageName: "d3",
1495
- defaultVersion: "7.9.0",
1496
- providers: {
1497
- cloudflare: {
1498
- url: "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",
1499
- integrity: "sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",
1500
- global: "d3",
1501
- crossorigin: "anonymous"
1502
- },
1503
- jsdelivr: {
1504
- url: "https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",
1505
- global: "d3",
1506
- crossorigin: "anonymous"
1507
- },
1508
- unpkg: {
1509
- url: "https://unpkg.com/d3@7.9.0/dist/d3.min.js",
1510
- global: "d3",
1511
- crossorigin: "anonymous"
1512
- }
1513
- },
1514
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1515
- metadata: {
1516
- description: "Data-Driven Documents",
1517
- homepage: "https://d3js.org",
1518
- license: "ISC"
1519
- }
1520
- },
1521
- lodash: {
1522
- packageName: "lodash",
1523
- defaultVersion: "4.17.21",
1524
- providers: {
1525
- cloudflare: {
1526
- url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
1527
- integrity: "sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",
1528
- global: "_",
1529
- crossorigin: "anonymous"
1530
- },
1531
- jsdelivr: {
1532
- url: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",
1533
- global: "_",
1534
- crossorigin: "anonymous"
1535
- },
1536
- unpkg: {
1537
- url: "https://unpkg.com/lodash@4.17.21/lodash.min.js",
1538
- global: "_",
1539
- crossorigin: "anonymous"
1540
- }
1541
- },
1542
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1543
- metadata: {
1544
- description: "A modern JavaScript utility library",
1545
- homepage: "https://lodash.com",
1546
- license: "MIT"
1770
+ function sanitizeCSPDomains(domains) {
1771
+ if (!domains) return [];
1772
+ const valid = [];
1773
+ for (const domain of domains) {
1774
+ if (validateCSPDomain(domain)) {
1775
+ valid.push(domain);
1776
+ } else {
1777
+ console.warn(`Invalid CSP domain ignored: ${domain}`);
1547
1778
  }
1548
1779
  }
1549
- };
1550
- function lookupPackage(packageName, registry = DEFAULT_CDN_REGISTRY) {
1551
- return registry[packageName];
1780
+ return valid;
1781
+ }
1782
+ function escapeAttribute(str) {
1783
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1552
1784
  }
1553
1785
 
1554
- // libs/uipack/src/resolver/import-parser.ts
1555
- var IMPORT_PATTERNS = {
1556
- named: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
1557
- default: /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1558
- namespace: /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1559
- sideEffect: /import\s*['"]([^'"]+)['"]/g,
1560
- dynamic: /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
1561
- defaultAndNamed: /import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
1562
- defaultAndNamespace: /import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1563
- reExport: /export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,
1564
- reExportAll: /export\s*\*\s*from\s*['"]([^'"]+)['"]/g
1786
+ // libs/uipack/src/shell/custom-shell-types.ts
1787
+ var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
1788
+ var SHELL_PLACEHOLDERS = {
1789
+ CSP: "{{CSP}}",
1790
+ DATA: "{{DATA}}",
1791
+ BRIDGE: "{{BRIDGE}}",
1792
+ CONTENT: "{{CONTENT}}",
1793
+ TITLE: "{{TITLE}}"
1565
1794
  };
1566
- function parseNamedImports(namedString) {
1567
- return namedString.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => {
1568
- const asMatch = s.match(/^(\w+)\s+as\s+\w+$/);
1569
- return asMatch ? asMatch[1] : s;
1570
- });
1795
+ var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1796
+ var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1797
+
1798
+ // libs/uipack/src/shell/custom-shell-applier.ts
1799
+ function applyShellTemplate(template, values) {
1800
+ let result = template;
1801
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1802
+ result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1803
+ result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1804
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1805
+ result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1806
+ return result;
1571
1807
  }
1572
- function isRelativeImport(specifier) {
1573
- return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
1808
+
1809
+ // libs/uipack/src/shell/custom-shell-validator.ts
1810
+ function validateShellTemplate(template) {
1811
+ const found = {};
1812
+ for (const name of SHELL_PLACEHOLDER_NAMES) {
1813
+ found[name] = template.includes(SHELL_PLACEHOLDERS[name]);
1814
+ }
1815
+ const missingRequired = REQUIRED_PLACEHOLDERS.filter((name) => !found[name]);
1816
+ const missingOptional = OPTIONAL_PLACEHOLDERS.filter((name) => !found[name]);
1817
+ return {
1818
+ valid: missingRequired.length === 0,
1819
+ found,
1820
+ missingRequired,
1821
+ missingOptional
1822
+ };
1574
1823
  }
1575
- function isExternalImport(specifier) {
1576
- return !isRelativeImport(specifier) && !specifier.startsWith("#");
1824
+
1825
+ // libs/uipack/src/shell/data-injector.ts
1826
+ function hasSizing(sizing) {
1827
+ if (!sizing) return false;
1828
+ return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
1577
1829
  }
1578
- function getPackageName(specifier) {
1579
- if (specifier.startsWith("@")) {
1580
- const parts = specifier.split("/");
1581
- if (parts.length >= 2) {
1582
- return `${parts[0]}/${parts[1]}`;
1583
- }
1584
- return specifier;
1830
+ function buildDataInjectionScript(options) {
1831
+ const { toolName, input, output, structuredContent, sizing } = options;
1832
+ const lines = [
1833
+ `window.__mcpAppsEnabled = true;`,
1834
+ `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
1835
+ `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
1836
+ `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
1837
+ `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
1838
+ ];
1839
+ if (hasSizing(sizing)) {
1840
+ lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
1585
1841
  }
1586
- const firstSlash = specifier.indexOf("/");
1587
- return firstSlash === -1 ? specifier : specifier.slice(0, firstSlash);
1842
+ return `<script>
1843
+ ${lines.join("\n")}
1844
+ </script>`;
1588
1845
  }
1589
- function getLineNumber(source, index) {
1590
- let line = 1;
1591
- for (let i = 0; i < index && i < source.length; i++) {
1592
- if (source[i] === "\n") {
1593
- line++;
1594
- }
1846
+ function buildCustomDataInjectionScript(descriptor) {
1847
+ if (descriptor.script !== void 0) {
1848
+ return descriptor.script;
1595
1849
  }
1596
- return line;
1597
- }
1598
- function getColumnNumber(source, index) {
1599
- let column = 0;
1600
- for (let i = index - 1; i >= 0 && source[i] !== "\n"; i--) {
1601
- column++;
1850
+ if (descriptor.globalKey !== void 0) {
1851
+ return `<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(
1852
+ descriptor.value ?? null
1853
+ )};</script>`;
1602
1854
  }
1603
- return column;
1855
+ return "";
1604
1856
  }
1605
- function parseImports(source) {
1606
- const imports = [];
1607
- const seenStatements = /* @__PURE__ */ new Set();
1608
- const addImport = (imp) => {
1609
- const key = `${imp.type}:${imp.specifier}:${imp.statement}`;
1610
- if (!seenStatements.has(key)) {
1611
- seenStatements.add(key);
1612
- imports.push(imp);
1857
+ var _uniqueIdCounter = 0;
1858
+ function createTemplateHelpers() {
1859
+ return {
1860
+ escapeHtml: (str) => escapeHtml(str),
1861
+ formatDate: (date, format) => {
1862
+ const d = date instanceof Date ? date : new Date(date);
1863
+ if (isNaN(d.getTime())) return String(date);
1864
+ if (format === "iso") return d.toISOString();
1865
+ if (format === "date") return d.toLocaleDateString();
1866
+ if (format === "time") return d.toLocaleTimeString();
1867
+ return d.toLocaleString();
1868
+ },
1869
+ formatCurrency: (amount, currency = "USD") => {
1870
+ return new Intl.NumberFormat("en-US", {
1871
+ style: "currency",
1872
+ currency
1873
+ }).format(amount);
1874
+ },
1875
+ uniqueId: (prefix = "mcp") => {
1876
+ return `${prefix}-${++_uniqueIdCounter}`;
1877
+ },
1878
+ jsonEmbed: (data) => {
1879
+ return escapeScriptClose(JSON.stringify(data));
1613
1880
  }
1614
1881
  };
1615
- let match;
1616
- const defaultAndNamedRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamed.source, "g");
1617
- while ((match = defaultAndNamedRegex.exec(source)) !== null) {
1618
- const [statement, defaultName, namedString, specifier] = match;
1619
- addImport({
1620
- statement,
1621
- specifier,
1622
- type: "default",
1623
- defaultImport: defaultName,
1624
- namedImports: parseNamedImports(namedString),
1625
- line: getLineNumber(source, match.index),
1626
- column: getColumnNumber(source, match.index)
1627
- });
1882
+ }
1883
+
1884
+ // libs/uipack/src/shell/sizing-css.ts
1885
+ function sanitizeCssValue(value) {
1886
+ return value.replace(/[<>{};]/g, "").trim();
1887
+ }
1888
+ function toCssLength(value) {
1889
+ return typeof value === "number" ? `${value}px` : sanitizeCssValue(value);
1890
+ }
1891
+ function buildSizingStyleTag(sizing) {
1892
+ if (!hasSizing(sizing)) return "";
1893
+ const hasCssSizing = sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0;
1894
+ if (!hasCssSizing) return "";
1895
+ const rootRules = [];
1896
+ const docRules = ["margin: 0;"];
1897
+ if (sizing.preferredHeight !== void 0) {
1898
+ const h = toCssLength(sizing.preferredHeight);
1899
+ docRules.push(`height: ${h};`);
1900
+ rootRules.push(`min-height: ${h};`);
1628
1901
  }
1629
- const defaultAndNamespaceRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source, "g");
1630
- while ((match = defaultAndNamespaceRegex.exec(source)) !== null) {
1631
- const [statement, defaultName, namespaceName, specifier] = match;
1632
- addImport({
1633
- statement,
1634
- specifier,
1635
- type: "default",
1636
- defaultImport: defaultName,
1637
- namespaceImport: namespaceName,
1638
- line: getLineNumber(source, match.index),
1639
- column: getColumnNumber(source, match.index)
1640
- });
1902
+ if (sizing.minHeight !== void 0) {
1903
+ const mh = toCssLength(sizing.minHeight);
1904
+ docRules.push(`min-height: ${mh};`);
1905
+ rootRules.push(`min-height: ${mh};`);
1641
1906
  }
1642
- const namedRegex = new RegExp(IMPORT_PATTERNS.named.source, "g");
1643
- while ((match = namedRegex.exec(source)) !== null) {
1644
- const [statement, namedString, specifier] = match;
1645
- addImport({
1646
- statement,
1647
- specifier,
1648
- type: "named",
1649
- namedImports: parseNamedImports(namedString),
1650
- line: getLineNumber(source, match.index),
1651
- column: getColumnNumber(source, match.index)
1652
- });
1907
+ if (sizing.maxHeight !== void 0) {
1908
+ const mx = toCssLength(sizing.maxHeight);
1909
+ docRules.push(`max-height: ${mx};`);
1910
+ rootRules.push(`max-height: ${mx};`);
1653
1911
  }
1654
- const defaultRegex = new RegExp(IMPORT_PATTERNS.default.source, "g");
1655
- while ((match = defaultRegex.exec(source)) !== null) {
1656
- const [statement, defaultName, specifier] = match;
1657
- const afterMatch = source.slice(match.index + match[0].length - specifier.length - 2);
1658
- if (afterMatch.startsWith(",")) continue;
1659
- addImport({
1660
- statement,
1661
- specifier,
1662
- type: "default",
1663
- defaultImport: defaultName,
1664
- line: getLineNumber(source, match.index),
1665
- column: getColumnNumber(source, match.index)
1666
- });
1912
+ if (sizing.aspectRatio !== void 0) {
1913
+ const ar = typeof sizing.aspectRatio === "number" ? String(sizing.aspectRatio) : sanitizeCssValue(sizing.aspectRatio);
1914
+ if (ar) rootRules.push(`aspect-ratio: ${ar};`);
1667
1915
  }
1668
- const namespaceRegex = new RegExp(IMPORT_PATTERNS.namespace.source, "g");
1669
- while ((match = namespaceRegex.exec(source)) !== null) {
1670
- const [statement, namespaceName, specifier] = match;
1671
- addImport({
1672
- statement,
1673
- specifier,
1674
- type: "namespace",
1675
- namespaceImport: namespaceName,
1676
- line: getLineNumber(source, match.index),
1677
- column: getColumnNumber(source, match.index)
1678
- });
1916
+ const parts = [`html, body { ${docRules.join(" ")} }`];
1917
+ if (rootRules.length > 0) {
1918
+ parts.push(`#root { ${rootRules.join(" ")} }`);
1679
1919
  }
1680
- const sideEffectRegex = new RegExp(IMPORT_PATTERNS.sideEffect.source, "g");
1681
- while ((match = sideEffectRegex.exec(source)) !== null) {
1682
- const [statement, specifier] = match;
1683
- const beforeMatch = source.slice(Math.max(0, match.index - 50), match.index);
1684
- if (beforeMatch.includes("from")) continue;
1685
- addImport({
1686
- statement,
1687
- specifier,
1688
- type: "side-effect",
1689
- line: getLineNumber(source, match.index),
1690
- column: getColumnNumber(source, match.index)
1691
- });
1920
+ return `<style>${parts.join("\n")}</style>`;
1921
+ }
1922
+
1923
+ // libs/uipack/src/shell/builder.ts
1924
+ function resolveDataInjectionScript(args) {
1925
+ if (args.dataInjection) {
1926
+ return buildCustomDataInjectionScript(args.dataInjection);
1927
+ }
1928
+ return buildDataInjectionScript({
1929
+ toolName: args.toolName,
1930
+ input: args.input,
1931
+ output: args.output,
1932
+ structuredContent: args.structuredContent,
1933
+ sizing: args.sizing
1934
+ });
1935
+ }
1936
+ function buildShell(content, config) {
1937
+ const {
1938
+ toolName,
1939
+ csp,
1940
+ withShell = true,
1941
+ input,
1942
+ output,
1943
+ structuredContent,
1944
+ includeBridge = true,
1945
+ title,
1946
+ sizing
1947
+ } = config;
1948
+ const { customShell, dataInjection } = config;
1949
+ const dataScript = resolveDataInjectionScript({
1950
+ dataInjection,
1951
+ toolName,
1952
+ input,
1953
+ output,
1954
+ structuredContent,
1955
+ sizing
1956
+ });
1957
+ if (!withShell) {
1958
+ const html2 = `${dataScript}
1959
+ ${content}`;
1960
+ return {
1961
+ html: html2,
1962
+ hash: simpleHash(html2),
1963
+ size: Buffer.byteLength(html2, "utf-8")
1964
+ };
1692
1965
  }
1693
- const dynamicRegex = new RegExp(IMPORT_PATTERNS.dynamic.source, "g");
1694
- while ((match = dynamicRegex.exec(source)) !== null) {
1695
- const [statement, specifier] = match;
1696
- addImport({
1697
- statement,
1698
- specifier,
1699
- type: "dynamic",
1700
- line: getLineNumber(source, match.index),
1701
- column: getColumnNumber(source, match.index)
1966
+ if (customShell) {
1967
+ return buildCustomShell(content, customShell, {
1968
+ csp,
1969
+ toolName,
1970
+ input,
1971
+ output,
1972
+ structuredContent,
1973
+ includeBridge,
1974
+ title,
1975
+ sizing,
1976
+ dataInjection
1702
1977
  });
1703
1978
  }
1704
- const reExportRegex = new RegExp(IMPORT_PATTERNS.reExport.source, "g");
1705
- while ((match = reExportRegex.exec(source)) !== null) {
1706
- const [statement, specifier] = match;
1707
- addImport({
1708
- statement,
1709
- specifier,
1710
- type: "named",
1711
- line: getLineNumber(source, match.index),
1712
- column: getColumnNumber(source, match.index)
1713
- });
1979
+ const headParts = [
1980
+ '<meta charset="UTF-8">',
1981
+ '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
1982
+ ];
1983
+ if (title) {
1984
+ headParts.push(`<title>${escapeHtmlForTag(title)}</title>`);
1714
1985
  }
1715
- const reExportAllRegex = new RegExp(IMPORT_PATTERNS.reExportAll.source, "g");
1716
- while ((match = reExportAllRegex.exec(source)) !== null) {
1717
- const [statement, specifier] = match;
1718
- addImport({
1719
- statement,
1720
- specifier,
1721
- type: "namespace",
1722
- line: getLineNumber(source, match.index),
1723
- column: getColumnNumber(source, match.index)
1724
- });
1986
+ headParts.push(buildCSPMetaTag(csp));
1987
+ headParts.push(dataScript);
1988
+ const sizingStyle = buildSizingStyleTag(sizing);
1989
+ if (sizingStyle) {
1990
+ headParts.push(sizingStyle);
1725
1991
  }
1726
- const externalImports = imports.filter((imp) => isExternalImport(imp.specifier));
1727
- const relativeImports = imports.filter((imp) => isRelativeImport(imp.specifier));
1728
- const externalPackages = [...new Set(externalImports.map((imp) => getPackageName(imp.specifier)))];
1992
+ if (includeBridge) {
1993
+ const bridgeScript = generateBridgeIIFE({ minify: true });
1994
+ headParts.push(`<script>${bridgeScript}</script>`);
1995
+ }
1996
+ const html = `<!DOCTYPE html>
1997
+ <html lang="en">
1998
+ <head>
1999
+ ${headParts.map((p) => ` ${p}`).join("\n")}
2000
+ </head>
2001
+ <body>
2002
+ ${content}
2003
+ </body>
2004
+ </html>`;
1729
2005
  return {
1730
- imports,
1731
- externalImports,
1732
- relativeImports,
1733
- externalPackages
2006
+ html,
2007
+ hash: simpleHash(html),
2008
+ size: Buffer.byteLength(html, "utf-8")
1734
2009
  };
1735
2010
  }
1736
-
1737
- // libs/uipack/src/resolver/esm-sh.resolver.ts
1738
- var DEFAULT_FALLBACK_CDN = "https://esm.sh";
1739
- function createEsmShResolver(options = {}) {
1740
- const {
1741
- fallbackCdnBase = DEFAULT_FALLBACK_CDN,
1742
- registry = DEFAULT_CDN_REGISTRY,
1743
- providerOrder = ["esm.sh", "cloudflare", "jsdelivr", "unpkg"]
1744
- } = options;
1745
- return {
1746
- resolve(specifier, _context) {
1747
- if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) {
1748
- return null;
1749
- }
1750
- if (specifier.startsWith("node:") || specifier.startsWith("#")) {
1751
- return null;
1752
- }
1753
- const pkgName = getPackageName(specifier);
1754
- const entry = lookupPackage(pkgName, registry);
1755
- if (entry) {
1756
- for (const provider of providerOrder) {
1757
- const config = entry.providers[provider];
1758
- if (config?.url) {
1759
- const subpath = specifier.slice(pkgName.length);
1760
- if (subpath && config.url.includes("esm.sh/")) {
1761
- return {
1762
- value: config.url + subpath,
1763
- type: "url",
1764
- integrity: config.integrity
1765
- };
1766
- }
1767
- if (subpath) {
1768
- return {
1769
- value: `${fallbackCdnBase}/${specifier}`,
1770
- type: "url"
1771
- };
1772
- }
1773
- if (config.global && !config.esm) {
1774
- return {
1775
- value: config.url,
1776
- type: "url",
1777
- integrity: config.integrity
1778
- };
1779
- }
1780
- return {
1781
- value: config.url,
1782
- type: "url",
1783
- integrity: config.integrity
1784
- };
1785
- }
1786
- }
1787
- }
1788
- return {
1789
- value: `${fallbackCdnBase}/${specifier}`,
1790
- type: "url"
1791
- };
2011
+ function buildCustomShell(content, customShell, ctx) {
2012
+ let template;
2013
+ if (typeof customShell === "string") {
2014
+ const validation = validateShellTemplate(customShell);
2015
+ if (!validation.valid) {
2016
+ throw new Error(
2017
+ `Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
2018
+ );
1792
2019
  }
2020
+ template = customShell;
2021
+ } else {
2022
+ template = customShell.template;
2023
+ }
2024
+ const cspTag = buildCSPMetaTag(ctx.csp);
2025
+ const dataScript = resolveDataInjectionScript({
2026
+ dataInjection: ctx.dataInjection,
2027
+ toolName: ctx.toolName,
2028
+ input: ctx.input,
2029
+ output: ctx.output,
2030
+ structuredContent: ctx.structuredContent,
2031
+ sizing: ctx.sizing
2032
+ });
2033
+ const sizingStyle = buildSizingStyleTag(ctx.sizing);
2034
+ const dataWithSizing = sizingStyle ? `${dataScript}
2035
+ ${sizingStyle}` : dataScript;
2036
+ let bridgeHtml = "";
2037
+ if (ctx.includeBridge) {
2038
+ const bridgeScript = generateBridgeIIFE({ minify: true });
2039
+ bridgeHtml = `<script>${bridgeScript}</script>`;
2040
+ }
2041
+ const html = applyShellTemplate(template, {
2042
+ csp: cspTag,
2043
+ data: dataWithSizing,
2044
+ bridge: bridgeHtml,
2045
+ content,
2046
+ title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
2047
+ });
2048
+ return {
2049
+ html,
2050
+ hash: simpleHash(html),
2051
+ size: Buffer.byteLength(html, "utf-8")
1793
2052
  };
1794
2053
  }
1795
-
1796
- // libs/uipack/src/component/types.ts
1797
- function isNpmSource(source) {
1798
- return typeof source === "object" && source !== null && "npm" in source;
1799
- }
1800
- function isFileSource(source) {
1801
- return typeof source === "object" && source !== null && "file" in source;
2054
+ function simpleHash(str) {
2055
+ let hash = 0;
2056
+ for (let i = 0; i < str.length; i++) {
2057
+ const char = str.charCodeAt(i);
2058
+ hash = (hash << 5) - hash + char | 0;
2059
+ }
2060
+ return Math.abs(hash).toString(16).padStart(8, "0");
1802
2061
  }
1803
- function isImportSource(source) {
1804
- return typeof source === "object" && source !== null && "import" in source;
2062
+ function escapeHtmlForTag(str) {
2063
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1805
2064
  }
1806
- function isFunctionSource(source) {
1807
- return typeof source === "function";
2065
+
2066
+ // libs/uipack/src/component/ui-availability.ts
2067
+ function isFrontmcpUiResolvable(...candidatePaths) {
2068
+ try {
2069
+ const nodeFs = __require("fs");
2070
+ const nodePath = __require("path");
2071
+ const ORG = "@frontmcp";
2072
+ const PKG = "ui";
2073
+ for (const candidate of candidatePaths) {
2074
+ let dir = candidate;
2075
+ while (true) {
2076
+ const pkgJson = nodePath.join(dir, "node_modules", ORG, PKG, "package.json");
2077
+ if (nodeFs.existsSync(pkgJson)) return true;
2078
+ const parent = nodePath.dirname(dir);
2079
+ if (parent === dir) break;
2080
+ dir = parent;
2081
+ }
2082
+ }
2083
+ return false;
2084
+ } catch {
2085
+ return false;
2086
+ }
1808
2087
  }
1809
2088
 
1810
2089
  // libs/uipack/src/component/transpiler.ts
1811
- function bundleFileSource(source, filename, resolveDir, componentName) {
2090
+ function transpileReactSource(source, filename) {
2091
+ const esbuild = __require("esbuild");
2092
+ const loader = filename?.endsWith(".tsx") ? "tsx" : "jsx";
2093
+ const result = esbuild.transformSync(source, {
2094
+ loader,
2095
+ jsx: "transform",
2096
+ jsxFactory: "React.createElement",
2097
+ jsxFragment: "React.Fragment",
2098
+ format: "esm",
2099
+ target: "es2020",
2100
+ define: { "process.env.NODE_ENV": '"production"' }
2101
+ });
2102
+ return result.code;
2103
+ }
2104
+ function bundleFileSource(source, filename, resolveDir, componentName, options = {}) {
2105
+ if (!isFrontmcpUiResolvable(resolveDir, process.cwd())) {
2106
+ throw new Error(
2107
+ `FileSource widget "${filename}" requires the @frontmcp/ui package, which provides the React bridge mount that's injected at bundle time. Install it (e.g. \`npm install @frontmcp/ui\` or \`yarn add @frontmcp/ui\`) and try again.`
2108
+ );
2109
+ }
1812
2110
  const esbuild = __require("esbuild");
1813
2111
  const mountCode = `
1814
2112
  // --- Auto-generated mount ---
@@ -1890,7 +2188,11 @@ if (__root) {
1890
2188
  format: "esm",
1891
2189
  target: "es2020",
1892
2190
  jsx: "automatic",
1893
- external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
2191
+ // When `bundleReact` is set (resourceMode: 'inline'), React itself is
2192
+ // bundled — required for hosts that block external script execution
2193
+ // such as Claude (#454). Otherwise React stays external and is loaded
2194
+ // via the import map emitted by the renderer.
2195
+ external: options.bundleReact ? [] : ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
1894
2196
  alias,
1895
2197
  define: { "process.env.NODE_ENV": '"production"' },
1896
2198
  platform: "browser",
@@ -1902,7 +2204,7 @@ if (__root) {
1902
2204
  } catch (err) {
1903
2205
  const message = err instanceof Error ? err.message : String(err);
1904
2206
  throw new Error(
1905
- `Failed to bundle FileSource "${filename}": ${message}. Ensure workspace packages are built (e.g. nx build ui).`
2207
+ `Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`
1906
2208
  );
1907
2209
  }
1908
2210
  }
@@ -1914,12 +2216,29 @@ function extractDefaultExportName(code) {
1914
2216
  return null;
1915
2217
  }
1916
2218
 
2219
+ // libs/uipack/src/component/types.ts
2220
+ function isNpmSource(source) {
2221
+ return typeof source === "object" && source !== null && "npm" in source;
2222
+ }
2223
+ function isFileSource(source) {
2224
+ return typeof source === "object" && source !== null && "file" in source;
2225
+ }
2226
+ function isImportSource(source) {
2227
+ return typeof source === "object" && source !== null && "import" in source;
2228
+ }
2229
+ function isFunctionSource(source) {
2230
+ return typeof source === "function";
2231
+ }
2232
+
1917
2233
  // libs/uipack/src/component/loader.ts
1918
2234
  var DEFAULT_META = {
1919
2235
  mcpAware: false,
1920
2236
  renderer: "auto"
1921
2237
  };
1922
2238
  function resolveUISource(source, options) {
2239
+ if (options?.inlineReact && options?.transformOnly) {
2240
+ throw new Error("resolveUISource: `inlineReact` and `transformOnly` are mutually exclusive \u2014 set at most one.");
2241
+ }
1923
2242
  const resolver = options?.resolver ?? createEsmShResolver();
1924
2243
  if (isNpmSource(source)) {
1925
2244
  return resolveNpmSource(source, resolver);
@@ -1928,7 +2247,7 @@ function resolveUISource(source, options) {
1928
2247
  return resolveImportSource(source);
1929
2248
  }
1930
2249
  if (isFileSource(source)) {
1931
- return resolveFileSource(source);
2250
+ return resolveFileSource(source, { inlineReact: options?.inlineReact, transformOnly: options?.transformOnly });
1932
2251
  }
1933
2252
  if (isFunctionSource(source)) {
1934
2253
  return resolveFunctionSource(source, options?.input, options?.output);
@@ -1961,15 +2280,42 @@ function resolveImportSource(source) {
1961
2280
  peerDependencies: []
1962
2281
  };
1963
2282
  }
1964
- function resolveFileSource(source) {
2283
+ function resolveFileSource(source, options = {}) {
1965
2284
  const path = __require("path");
1966
2285
  const ext = path.extname(source.file).toLowerCase();
1967
2286
  if (ext === ".tsx" || ext === ".jsx") {
1968
2287
  const fs = __require("fs");
1969
- const filePath = path.isAbsolute(source.file) ? source.file : path.resolve(process.cwd(), source.file);
1970
- const rawSource = fs.readFileSync(filePath, "utf-8");
2288
+ const wasRelative = !path.isAbsolute(source.file);
2289
+ const filePath = wasRelative ? path.resolve(process.cwd(), source.file) : source.file;
2290
+ let rawSource;
2291
+ try {
2292
+ rawSource = fs.readFileSync(filePath, "utf-8");
2293
+ } catch (err) {
2294
+ const isNotFound = err?.code === "ENOENT";
2295
+ if (isNotFound && wasRelative) {
2296
+ throw new Error(
2297
+ `FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`
2298
+ );
2299
+ }
2300
+ throw err;
2301
+ }
1971
2302
  const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
1972
- const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName);
2303
+ if (options.transformOnly === true) {
2304
+ const code = transpileReactSource(rawSource, path.basename(filePath));
2305
+ const parsed2 = parseImports(code);
2306
+ return {
2307
+ mode: "module",
2308
+ code,
2309
+ imports: [...new Set(parsed2.externalImports.map((i) => i.specifier))],
2310
+ exportName: componentName,
2311
+ meta: { mcpAware: true, renderer: "react" },
2312
+ peerDependencies: source.peerDependencies || ["react", "react-dom"],
2313
+ bundled: false
2314
+ };
2315
+ }
2316
+ const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName, {
2317
+ bundleReact: options.inlineReact === true
2318
+ });
1973
2319
  const parsed = parseImports(bundled.code);
1974
2320
  return {
1975
2321
  mode: "module",
@@ -2037,37 +2383,19 @@ function generateMappedPropsCode(mapping) {
2037
2383
  return `({ ${entries} })`;
2038
2384
  }
2039
2385
 
2040
- // libs/uipack/src/resolver/import-map.ts
2041
- function createImportMapFromResolved(resolved) {
2042
- const imports = {};
2043
- const integrity = {};
2044
- for (const [specifier, res] of Object.entries(resolved)) {
2045
- if (res.type === "url") {
2046
- imports[specifier] = res.value;
2047
- if (res.integrity) {
2048
- integrity[res.value] = res.integrity;
2049
- }
2050
- }
2051
- }
2052
- return {
2053
- imports,
2054
- integrity: Object.keys(integrity).length > 0 ? integrity : void 0
2055
- };
2056
- }
2057
- function generateImportMapScriptTag(map) {
2058
- const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
2059
- return `<script type="importmap">
2060
- ${json}
2061
- </script>`;
2062
- }
2063
-
2064
2386
  // libs/uipack/src/component/renderer.ts
2387
+ var DEFAULT_WIDGET_MOUNT = {
2388
+ moduleSpecifier: "@frontmcp/ui/react",
2389
+ wrapperImportName: "McpBridgeProvider"
2390
+ };
2065
2391
  function renderComponent(config, shellConfig) {
2066
2392
  const resolver = shellConfig.resolver ?? createEsmShResolver();
2067
2393
  const resolved = resolveUISource(config.source, {
2068
2394
  resolver,
2069
2395
  input: shellConfig.input,
2070
- output: shellConfig.output
2396
+ output: shellConfig.output,
2397
+ inlineReact: config.inlineReact === true,
2398
+ transformOnly: config.transformOnly === true
2071
2399
  });
2072
2400
  const mergedShellConfig = {
2073
2401
  ...shellConfig,
@@ -2078,10 +2406,10 @@ function renderComponent(config, shellConfig) {
2078
2406
  if (resolved.mode === "inline") {
2079
2407
  return buildShell(resolved.html ?? "", mergedShellConfig);
2080
2408
  }
2081
- const content = buildModuleContent(resolved, resolver, config.props);
2409
+ const content = buildModuleContent(resolved, resolver, config.props, shellConfig.mount);
2082
2410
  return buildShell(content, mergedShellConfig);
2083
2411
  }
2084
- function buildModuleContent(resolved, resolver, propsMapping) {
2412
+ function buildModuleContent(resolved, resolver, propsMapping, mount = DEFAULT_WIDGET_MOUNT) {
2085
2413
  const parts = [];
2086
2414
  if (resolved.code && resolved.bundled) {
2087
2415
  const importEntries = {};
@@ -2105,14 +2433,16 @@ ${resolved.code}
2105
2433
  ...resolved.imports || [],
2106
2434
  "react-dom/client",
2107
2435
  // needed by mount script
2108
- "@frontmcp/ui/react",
2109
- // needed for McpBridgeProvider
2436
+ mount.moduleSpecifier,
2437
+ // mounter/provider package (e.g. @frontmcp/ui/react)
2110
2438
  // React subpath entries needed by esm.sh externalized modules:
2111
2439
  "react/jsx-runtime",
2112
- "react/jsx-dev-runtime",
2113
- "react-dom/server",
2114
- "react-dom/static"
2440
+ "react/jsx-dev-runtime"
2115
2441
  ]);
2442
+ if (mount === DEFAULT_WIDGET_MOUNT) {
2443
+ allSpecifiers.add("react-dom/server");
2444
+ allSpecifiers.add("react-dom/static");
2445
+ }
2116
2446
  const coreDeps = /* @__PURE__ */ new Set([
2117
2447
  "react",
2118
2448
  "react-dom",
@@ -2143,8 +2473,9 @@ ${resolved.code}
2143
2473
  const importMap = createImportMapFromResolved(importEntries);
2144
2474
  parts.push(generateImportMapScriptTag(importMap));
2145
2475
  }
2146
- parts.push('<div id="root"></div>');
2147
- const mountCode = generateInlineMountCode(resolved.exportName);
2476
+ const mountNodeId = mount.mountNodeId ?? "root";
2477
+ parts.push(`<div id="${mountNodeId}">${mount.mountNodeInnerHtml ?? ""}</div>`);
2478
+ const mountCode = generateInlineMountCode(resolved.exportName, mount);
2148
2479
  parts.push(`<script type="module">
2149
2480
  ${resolved.code}
2150
2481
  ${mountCode}
@@ -2181,53 +2512,27 @@ function addExternalParam(url, externals) {
2181
2512
  const sep = url.includes("?") ? "&" : "?";
2182
2513
  return `${url}${sep}external=${externals.join(",")}`;
2183
2514
  }
2184
- function generateInlineMountCode(componentName) {
2515
+ function generateInlineMountCode(componentName, mount) {
2516
+ if (mount.generate) {
2517
+ return mount.generate(componentName);
2518
+ }
2519
+ const wrapperName = mount.wrapperImportName ?? "McpBridgeProvider";
2520
+ const wrapperAlias = `__${wrapperName}`;
2521
+ const mountNodeId = mount.mountNodeId ?? "root";
2185
2522
  return `
2186
2523
  // --- Mount ---
2187
2524
  import { createRoot as __createRoot } from 'react-dom/client';
2188
- import { McpBridgeProvider as __McpBridgeProvider } from '@frontmcp/ui/react';
2189
- const __root = document.getElementById('root');
2525
+ import { ${wrapperName} as ${wrapperAlias} } from '${mount.moduleSpecifier}';
2526
+ const __root = document.getElementById('${mountNodeId}');
2190
2527
  if (__root) {
2191
2528
  __createRoot(__root).render(
2192
- React.createElement(__McpBridgeProvider, null,
2529
+ React.createElement(${wrapperAlias}, null,
2193
2530
  React.createElement(${componentName})
2194
2531
  )
2195
2532
  );
2196
2533
  }`;
2197
2534
  }
2198
2535
 
2199
- // libs/uipack/src/adapters/type-detector.ts
2200
- function detectUIType(template) {
2201
- if (template === null || template === void 0) {
2202
- return "auto";
2203
- }
2204
- if (typeof template === "object" && template !== null && "file" in template) {
2205
- const file = template.file;
2206
- if (/\.(tsx|jsx)$/i.test(file)) return "react";
2207
- }
2208
- if (typeof template === "function") {
2209
- const proto = template.prototype;
2210
- if (proto && typeof proto.render === "function") {
2211
- return "react";
2212
- }
2213
- const asRecord = template;
2214
- if (asRecord["$$typeof"] !== void 0) {
2215
- return "react";
2216
- }
2217
- if (template.name && /^[A-Z]/.test(template.name)) {
2218
- return "react";
2219
- }
2220
- return "html";
2221
- }
2222
- if (typeof template === "string") {
2223
- if (template.includes("<") && template.includes(">")) {
2224
- return "html";
2225
- }
2226
- return "markdown";
2227
- }
2228
- return "auto";
2229
- }
2230
-
2231
2536
  // libs/uipack/src/adapters/content-detector.ts
2232
2537
  var CHART_TYPES = /* @__PURE__ */ new Set(["bar", "line", "pie", "area", "scatter", "doughnut", "radar", "polarArea", "bubble"]);
2233
2538
  var MERMAID_PREFIXES = [
@@ -2368,6 +2673,38 @@ function wrapDetectedContent(value) {
2368
2673
  }
2369
2674
  }
2370
2675
 
2676
+ // libs/uipack/src/adapters/type-detector.ts
2677
+ function detectUIType(template) {
2678
+ if (template === null || template === void 0) {
2679
+ return "auto";
2680
+ }
2681
+ if (typeof template === "object" && template !== null && "file" in template) {
2682
+ const file = template.file;
2683
+ if (/\.(tsx|jsx)$/i.test(file)) return "react";
2684
+ }
2685
+ if (typeof template === "function") {
2686
+ const proto = template.prototype;
2687
+ if (proto && typeof proto.render === "function") {
2688
+ return "react";
2689
+ }
2690
+ const asRecord = template;
2691
+ if (asRecord["$$typeof"] !== void 0) {
2692
+ return "react";
2693
+ }
2694
+ if (template.name && /^[A-Z]/.test(template.name)) {
2695
+ return "react";
2696
+ }
2697
+ return "html";
2698
+ }
2699
+ if (typeof template === "string") {
2700
+ if (template.includes("<") && template.includes(">")) {
2701
+ return "html";
2702
+ }
2703
+ return "markdown";
2704
+ }
2705
+ return "auto";
2706
+ }
2707
+
2371
2708
  // libs/uipack/src/adapters/template-renderer.ts
2372
2709
  function buildCspConfig(resolver) {
2373
2710
  const cspResourceDomains = ["https://esm.sh"];
@@ -2388,21 +2725,26 @@ function buildCspConfig(resolver) {
2388
2725
  return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
2389
2726
  }
2390
2727
  function renderToolTemplate(options) {
2391
- const { toolName, input, output, template, resolver } = options;
2728
+ const { toolName, input, output, template, resolver, platformType, sizing } = options;
2392
2729
  const uiType = detectUIType(template);
2730
+ const resourceMode = options.resourceMode ?? (platformType === "claude" ? "inline" : "cdn");
2393
2731
  const shellConfig = {
2394
2732
  toolName,
2395
2733
  input,
2396
2734
  output,
2397
2735
  includeBridge: true,
2398
- resolver
2736
+ resolver,
2737
+ sizing
2399
2738
  };
2400
2739
  let html;
2401
2740
  let hash = "";
2402
2741
  let size = 0;
2403
2742
  if (typeof template === "object" && template !== null && "file" in template) {
2404
2743
  const cspConfig = buildCspConfig(resolver);
2405
- const result = renderComponent({ source: template }, { ...shellConfig, csp: cspConfig });
2744
+ const result = renderComponent(
2745
+ { source: template, inlineReact: resourceMode === "inline" },
2746
+ { ...shellConfig, csp: cspConfig }
2747
+ );
2406
2748
  html = result.html;
2407
2749
  hash = result.hash;
2408
2750
  size = result.size;
@@ -2444,6 +2786,12 @@ function renderToolTemplate(options) {
2444
2786
  "ui/type": uiType,
2445
2787
  "ui/mimeType": MCP_APPS_MIME_TYPE
2446
2788
  };
2789
+ if (hasSizing(sizing)) {
2790
+ if (sizing.preferredHeight !== void 0) meta["ui/preferredHeight"] = sizing.preferredHeight;
2791
+ if (sizing.minHeight !== void 0) meta["ui/minHeight"] = sizing.minHeight;
2792
+ if (sizing.maxHeight !== void 0) meta["ui/maxHeight"] = sizing.maxHeight;
2793
+ if (sizing.aspectRatio !== void 0) meta["ui/aspectRatio"] = sizing.aspectRatio;
2794
+ }
2447
2795
  return {
2448
2796
  html,
2449
2797
  uiType,