@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
package/adapters/index.js CHANGED
@@ -106,78 +106,404 @@ function isUIRenderFailure(result) {
106
106
  return typeof rec["reason"] === "string" && !("meta" in rec);
107
107
  }
108
108
 
109
- // libs/uipack/src/shell/csp.ts
110
- var DEFAULT_CDN_DOMAINS = [
111
- "https://cdn.jsdelivr.net",
112
- "https://cdnjs.cloudflare.com",
113
- "https://fonts.googleapis.com",
114
- "https://fonts.gstatic.com",
115
- "https://esm.sh"
116
- ];
117
- var DEFAULT_CSP_DIRECTIVES = [
118
- "default-src 'none'",
119
- `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
120
- `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
121
- `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
122
- `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
123
- `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
124
- "object-src 'self' data:"
125
- ];
126
- function buildCSPDirectives(csp) {
127
- if (!csp) {
128
- return [...DEFAULT_CSP_DIRECTIVES];
109
+ // libs/uipack/src/resolver/cdn-registry.ts
110
+ var DEFAULT_CDN_REGISTRY = {
111
+ react: {
112
+ packageName: "react",
113
+ defaultVersion: "19.2.4",
114
+ providers: {
115
+ cloudflare: {
116
+ url: "https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",
117
+ global: "React",
118
+ crossorigin: "anonymous"
119
+ },
120
+ jsdelivr: {
121
+ url: "https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",
122
+ global: "React",
123
+ crossorigin: "anonymous"
124
+ },
125
+ unpkg: {
126
+ url: "https://unpkg.com/react@19.2.4/umd/react.production.min.js",
127
+ global: "React",
128
+ crossorigin: "anonymous"
129
+ },
130
+ "esm.sh": {
131
+ url: "https://esm.sh/react@19.2.4",
132
+ esm: true,
133
+ crossorigin: "anonymous"
134
+ }
135
+ },
136
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
137
+ metadata: {
138
+ description: "A JavaScript library for building user interfaces",
139
+ homepage: "https://react.dev",
140
+ license: "MIT"
141
+ }
142
+ },
143
+ "react-dom": {
144
+ packageName: "react-dom",
145
+ defaultVersion: "19.2.4",
146
+ providers: {
147
+ cloudflare: {
148
+ url: "https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",
149
+ global: "ReactDOM",
150
+ crossorigin: "anonymous",
151
+ peerDependencies: ["react"]
152
+ },
153
+ jsdelivr: {
154
+ url: "https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",
155
+ global: "ReactDOM",
156
+ crossorigin: "anonymous",
157
+ peerDependencies: ["react"]
158
+ },
159
+ unpkg: {
160
+ url: "https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",
161
+ global: "ReactDOM",
162
+ crossorigin: "anonymous",
163
+ peerDependencies: ["react"]
164
+ },
165
+ "esm.sh": {
166
+ url: "https://esm.sh/react-dom@19.2.4",
167
+ esm: true,
168
+ crossorigin: "anonymous",
169
+ peerDependencies: ["react"]
170
+ }
171
+ },
172
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
173
+ metadata: {
174
+ description: "React package for working with the DOM",
175
+ homepage: "https://react.dev",
176
+ license: "MIT"
177
+ }
178
+ },
179
+ "chart.js": {
180
+ packageName: "chart.js",
181
+ defaultVersion: "4.5.1",
182
+ providers: {
183
+ cloudflare: {
184
+ url: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",
185
+ global: "Chart",
186
+ crossorigin: "anonymous"
187
+ },
188
+ jsdelivr: {
189
+ url: "https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",
190
+ global: "Chart",
191
+ crossorigin: "anonymous"
192
+ },
193
+ unpkg: {
194
+ url: "https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",
195
+ global: "Chart",
196
+ crossorigin: "anonymous"
197
+ }
198
+ },
199
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
200
+ metadata: {
201
+ description: "Simple yet flexible JavaScript charting library",
202
+ homepage: "https://www.chartjs.org",
203
+ license: "MIT"
204
+ }
205
+ },
206
+ d3: {
207
+ packageName: "d3",
208
+ defaultVersion: "7.9.0",
209
+ providers: {
210
+ cloudflare: {
211
+ url: "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",
212
+ integrity: "sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",
213
+ global: "d3",
214
+ crossorigin: "anonymous"
215
+ },
216
+ jsdelivr: {
217
+ url: "https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",
218
+ global: "d3",
219
+ crossorigin: "anonymous"
220
+ },
221
+ unpkg: {
222
+ url: "https://unpkg.com/d3@7.9.0/dist/d3.min.js",
223
+ global: "d3",
224
+ crossorigin: "anonymous"
225
+ }
226
+ },
227
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
228
+ metadata: {
229
+ description: "Data-Driven Documents",
230
+ homepage: "https://d3js.org",
231
+ license: "ISC"
232
+ }
233
+ },
234
+ lodash: {
235
+ packageName: "lodash",
236
+ defaultVersion: "4.17.21",
237
+ providers: {
238
+ cloudflare: {
239
+ url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
240
+ integrity: "sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",
241
+ global: "_",
242
+ crossorigin: "anonymous"
243
+ },
244
+ jsdelivr: {
245
+ url: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",
246
+ global: "_",
247
+ crossorigin: "anonymous"
248
+ },
249
+ unpkg: {
250
+ url: "https://unpkg.com/lodash@4.17.21/lodash.min.js",
251
+ global: "_",
252
+ crossorigin: "anonymous"
253
+ }
254
+ },
255
+ preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
256
+ metadata: {
257
+ description: "A modern JavaScript utility library",
258
+ homepage: "https://lodash.com",
259
+ license: "MIT"
260
+ }
129
261
  }
130
- const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
131
- const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
132
- const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
133
- const directives = [
134
- "default-src 'none'",
135
- `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
136
- `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
137
- ];
138
- const imgSources = ["'self'", "data:", ...allResourceDomains];
139
- directives.push(`img-src ${imgSources.join(" ")}`);
140
- const fontSources = ["'self'", "data:", ...allResourceDomains];
141
- directives.push(`font-src ${fontSources.join(" ")}`);
142
- if (validConnectDomains.length) {
143
- directives.push(`connect-src ${validConnectDomains.join(" ")}`);
144
- } else {
145
- directives.push(`connect-src ${allResourceDomains.join(" ")}`);
262
+ };
263
+ function lookupPackage(packageName, registry = DEFAULT_CDN_REGISTRY) {
264
+ return registry[packageName];
265
+ }
266
+
267
+ // libs/uipack/src/resolver/import-parser.ts
268
+ var IMPORT_PATTERNS = {
269
+ named: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
270
+ default: /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
271
+ namespace: /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
272
+ sideEffect: /import\s*['"]([^'"]+)['"]/g,
273
+ dynamic: /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
274
+ defaultAndNamed: /import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
275
+ defaultAndNamespace: /import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
276
+ reExport: /export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,
277
+ reExportAll: /export\s*\*\s*from\s*['"]([^'"]+)['"]/g
278
+ };
279
+ function parseNamedImports(namedString) {
280
+ return namedString.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => {
281
+ const asMatch = s.match(/^(\w+)\s+as\s+\w+$/);
282
+ return asMatch ? asMatch[1] : s;
283
+ });
284
+ }
285
+ function isRelativeImport(specifier) {
286
+ return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
287
+ }
288
+ function isExternalImport(specifier) {
289
+ return !isRelativeImport(specifier) && !specifier.startsWith("#");
290
+ }
291
+ function getPackageName(specifier) {
292
+ if (specifier.startsWith("@")) {
293
+ const parts = specifier.split("/");
294
+ if (parts.length >= 2) {
295
+ return `${parts[0]}/${parts[1]}`;
296
+ }
297
+ return specifier;
146
298
  }
147
- directives.push("object-src 'self' data:");
148
- return directives;
299
+ const firstSlash = specifier.indexOf("/");
300
+ return firstSlash === -1 ? specifier : specifier.slice(0, firstSlash);
149
301
  }
150
- function buildCSPMetaTag(csp) {
151
- const directives = buildCSPDirectives(csp);
152
- const content = directives.join("; ");
153
- return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
302
+ function getLineNumber(source, index) {
303
+ let line = 1;
304
+ for (let i = 0; i < index && i < source.length; i++) {
305
+ if (source[i] === "\n") {
306
+ line++;
307
+ }
308
+ }
309
+ return line;
154
310
  }
155
- function validateCSPDomain(domain) {
156
- if (domain.startsWith("https://*.")) {
157
- const rest = domain.slice(10);
158
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
311
+ function getColumnNumber(source, index) {
312
+ let column = 0;
313
+ for (let i = index - 1; i >= 0 && source[i] !== "\n"; i--) {
314
+ column++;
159
315
  }
160
- try {
161
- const url = new URL(domain);
162
- return url.protocol === "https:";
163
- } catch {
164
- return false;
316
+ return column;
317
+ }
318
+ function parseImports(source) {
319
+ const imports = [];
320
+ const seenStatements = /* @__PURE__ */ new Set();
321
+ const addImport = (imp) => {
322
+ const key = `${imp.type}:${imp.specifier}:${imp.statement}`;
323
+ if (!seenStatements.has(key)) {
324
+ seenStatements.add(key);
325
+ imports.push(imp);
326
+ }
327
+ };
328
+ let match;
329
+ const defaultAndNamedRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamed.source, "g");
330
+ while ((match = defaultAndNamedRegex.exec(source)) !== null) {
331
+ const [statement, defaultName, namedString, specifier] = match;
332
+ addImport({
333
+ statement,
334
+ specifier,
335
+ type: "default",
336
+ defaultImport: defaultName,
337
+ namedImports: parseNamedImports(namedString),
338
+ line: getLineNumber(source, match.index),
339
+ column: getColumnNumber(source, match.index)
340
+ });
165
341
  }
342
+ const defaultAndNamespaceRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source, "g");
343
+ while ((match = defaultAndNamespaceRegex.exec(source)) !== null) {
344
+ const [statement, defaultName, namespaceName, specifier] = match;
345
+ addImport({
346
+ statement,
347
+ specifier,
348
+ type: "default",
349
+ defaultImport: defaultName,
350
+ namespaceImport: namespaceName,
351
+ line: getLineNumber(source, match.index),
352
+ column: getColumnNumber(source, match.index)
353
+ });
354
+ }
355
+ const namedRegex = new RegExp(IMPORT_PATTERNS.named.source, "g");
356
+ while ((match = namedRegex.exec(source)) !== null) {
357
+ const [statement, namedString, specifier] = match;
358
+ addImport({
359
+ statement,
360
+ specifier,
361
+ type: "named",
362
+ namedImports: parseNamedImports(namedString),
363
+ line: getLineNumber(source, match.index),
364
+ column: getColumnNumber(source, match.index)
365
+ });
366
+ }
367
+ const defaultRegex = new RegExp(IMPORT_PATTERNS.default.source, "g");
368
+ while ((match = defaultRegex.exec(source)) !== null) {
369
+ const [statement, defaultName, specifier] = match;
370
+ const afterMatch = source.slice(match.index + match[0].length - specifier.length - 2);
371
+ if (afterMatch.startsWith(",")) continue;
372
+ addImport({
373
+ statement,
374
+ specifier,
375
+ type: "default",
376
+ defaultImport: defaultName,
377
+ line: getLineNumber(source, match.index),
378
+ column: getColumnNumber(source, match.index)
379
+ });
380
+ }
381
+ const namespaceRegex = new RegExp(IMPORT_PATTERNS.namespace.source, "g");
382
+ while ((match = namespaceRegex.exec(source)) !== null) {
383
+ const [statement, namespaceName, specifier] = match;
384
+ addImport({
385
+ statement,
386
+ specifier,
387
+ type: "namespace",
388
+ namespaceImport: namespaceName,
389
+ line: getLineNumber(source, match.index),
390
+ column: getColumnNumber(source, match.index)
391
+ });
392
+ }
393
+ const sideEffectRegex = new RegExp(IMPORT_PATTERNS.sideEffect.source, "g");
394
+ while ((match = sideEffectRegex.exec(source)) !== null) {
395
+ const [statement, specifier] = match;
396
+ const beforeMatch = source.slice(Math.max(0, match.index - 50), match.index);
397
+ if (beforeMatch.includes("from")) continue;
398
+ addImport({
399
+ statement,
400
+ specifier,
401
+ type: "side-effect",
402
+ line: getLineNumber(source, match.index),
403
+ column: getColumnNumber(source, match.index)
404
+ });
405
+ }
406
+ const dynamicRegex = new RegExp(IMPORT_PATTERNS.dynamic.source, "g");
407
+ while ((match = dynamicRegex.exec(source)) !== null) {
408
+ const [statement, specifier] = match;
409
+ addImport({
410
+ statement,
411
+ specifier,
412
+ type: "dynamic",
413
+ line: getLineNumber(source, match.index),
414
+ column: getColumnNumber(source, match.index)
415
+ });
416
+ }
417
+ const reExportRegex = new RegExp(IMPORT_PATTERNS.reExport.source, "g");
418
+ while ((match = reExportRegex.exec(source)) !== null) {
419
+ const [statement, specifier] = match;
420
+ addImport({
421
+ statement,
422
+ specifier,
423
+ type: "named",
424
+ line: getLineNumber(source, match.index),
425
+ column: getColumnNumber(source, match.index)
426
+ });
427
+ }
428
+ const reExportAllRegex = new RegExp(IMPORT_PATTERNS.reExportAll.source, "g");
429
+ while ((match = reExportAllRegex.exec(source)) !== null) {
430
+ const [statement, specifier] = match;
431
+ addImport({
432
+ statement,
433
+ specifier,
434
+ type: "namespace",
435
+ line: getLineNumber(source, match.index),
436
+ column: getColumnNumber(source, match.index)
437
+ });
438
+ }
439
+ const externalImports = imports.filter((imp) => isExternalImport(imp.specifier));
440
+ const relativeImports = imports.filter((imp) => isRelativeImport(imp.specifier));
441
+ const externalPackages = [...new Set(externalImports.map((imp) => getPackageName(imp.specifier)))];
442
+ return {
443
+ imports,
444
+ externalImports,
445
+ relativeImports,
446
+ externalPackages
447
+ };
166
448
  }
167
- function sanitizeCSPDomains(domains) {
168
- if (!domains) return [];
169
- const valid = [];
170
- for (const domain of domains) {
171
- if (validateCSPDomain(domain)) {
172
- valid.push(domain);
173
- } else {
174
- console.warn(`Invalid CSP domain ignored: ${domain}`);
449
+
450
+ // libs/uipack/src/resolver/esm-sh.resolver.ts
451
+ var DEFAULT_FALLBACK_CDN = "https://esm.sh";
452
+ function createEsmShResolver(options = {}) {
453
+ const {
454
+ fallbackCdnBase = DEFAULT_FALLBACK_CDN,
455
+ registry = DEFAULT_CDN_REGISTRY,
456
+ providerOrder = ["esm.sh", "cloudflare", "jsdelivr", "unpkg"]
457
+ } = options;
458
+ return {
459
+ resolve(specifier, _context) {
460
+ if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) {
461
+ return null;
462
+ }
463
+ if (specifier.startsWith("node:") || specifier.startsWith("#")) {
464
+ return null;
465
+ }
466
+ const pkgName = getPackageName(specifier);
467
+ const entry = lookupPackage(pkgName, registry);
468
+ if (entry) {
469
+ for (const provider of providerOrder) {
470
+ const config = entry.providers[provider];
471
+ if (config?.url) {
472
+ const subpath = specifier.slice(pkgName.length);
473
+ if (subpath && config.url.includes("esm.sh/")) {
474
+ return {
475
+ value: config.url + subpath,
476
+ type: "url",
477
+ integrity: config.integrity
478
+ };
479
+ }
480
+ if (subpath) {
481
+ return {
482
+ value: `${fallbackCdnBase}/${specifier}`,
483
+ type: "url"
484
+ };
485
+ }
486
+ if (config.global && !config.esm) {
487
+ return {
488
+ value: config.url,
489
+ type: "url",
490
+ integrity: config.integrity
491
+ };
492
+ }
493
+ return {
494
+ value: config.url,
495
+ type: "url",
496
+ integrity: config.integrity
497
+ };
498
+ }
499
+ }
500
+ }
501
+ return {
502
+ value: `${fallbackCdnBase}/${specifier}`,
503
+ type: "url"
504
+ };
175
505
  }
176
- }
177
- return valid;
178
- }
179
- function escapeAttribute(str) {
180
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
506
+ };
181
507
  }
182
508
 
183
509
  // libs/uipack/src/utils/index.ts
@@ -211,46 +537,29 @@ function safeJsonForScript(value) {
211
537
  }
212
538
  }
213
539
 
214
- // libs/uipack/src/shell/data-injector.ts
215
- function buildDataInjectionScript(options) {
216
- const { toolName, input, output, structuredContent } = options;
217
- const lines = [
218
- `window.__mcpAppsEnabled = true;`,
219
- `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
220
- `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
221
- `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
222
- `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
223
- ];
224
- return `<script>
225
- ${lines.join("\n")}
226
- </script>`;
227
- }
228
- var _uniqueIdCounter = 0;
229
- function createTemplateHelpers() {
230
- return {
231
- escapeHtml: (str) => escapeHtml(str),
232
- formatDate: (date, format) => {
233
- const d = date instanceof Date ? date : new Date(date);
234
- if (isNaN(d.getTime())) return String(date);
235
- if (format === "iso") return d.toISOString();
236
- if (format === "date") return d.toLocaleDateString();
237
- if (format === "time") return d.toLocaleTimeString();
238
- return d.toLocaleString();
239
- },
240
- formatCurrency: (amount, currency = "USD") => {
241
- return new Intl.NumberFormat("en-US", {
242
- style: "currency",
243
- currency
244
- }).format(amount);
245
- },
246
- uniqueId: (prefix = "mcp") => {
247
- return `${prefix}-${++_uniqueIdCounter}`;
248
- },
249
- jsonEmbed: (data) => {
250
- return escapeScriptClose(JSON.stringify(data));
540
+ // libs/uipack/src/resolver/import-map.ts
541
+ function createImportMapFromResolved(resolved) {
542
+ const imports = {};
543
+ const integrity = {};
544
+ for (const [specifier, res] of Object.entries(resolved)) {
545
+ if (res.type === "url") {
546
+ imports[specifier] = res.value;
547
+ if (res.integrity) {
548
+ integrity[res.value] = res.integrity;
549
+ }
251
550
  }
551
+ }
552
+ return {
553
+ imports,
554
+ integrity: Object.keys(integrity).length > 0 ? integrity : void 0
252
555
  };
253
556
  }
557
+ function generateImportMapScriptTag(map) {
558
+ const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
559
+ return `<script type="importmap">
560
+ ${json}
561
+ </script>`;
562
+ }
254
563
 
255
564
  // libs/uipack/src/bridge-runtime/iife-generator.ts
256
565
  function generateBridgeIIFE(options = {}) {
@@ -306,6 +615,8 @@ function generateBridgeIIFE(options = {}) {
306
615
  parts.push("});");
307
616
  parts.push("");
308
617
  parts.push("window.FrontMcpBridge = bridge;");
618
+ parts.push("");
619
+ parts.push(generateAutoResize());
309
620
  parts.push("function __showLoading() {");
310
621
  parts.push(' var root = document.getElementById("root");');
311
622
  parts.push(" if (root && !root.hasChildNodes()) {");
@@ -326,6 +637,112 @@ function generateBridgeIIFE(options = {}) {
326
637
  }
327
638
  return code;
328
639
  }
640
+ function generateAutoResize() {
641
+ return `
642
+ function __applySizingCss(sizing) {
643
+ if (typeof document === 'undefined' || !document.documentElement) return;
644
+ function toLen(v) { return typeof v === 'number' ? v + 'px' : v; }
645
+ var de = document.documentElement;
646
+ var body = document.body;
647
+ var root = document.getElementById('root');
648
+ if (sizing.preferredHeight != null) {
649
+ var ph = toLen(sizing.preferredHeight);
650
+ de.style.height = ph;
651
+ if (body) body.style.height = ph;
652
+ if (root && !root.style.minHeight) root.style.minHeight = ph;
653
+ }
654
+ if (sizing.minHeight != null) {
655
+ var mh = toLen(sizing.minHeight);
656
+ de.style.minHeight = mh;
657
+ if (body) body.style.minHeight = mh;
658
+ if (root) root.style.minHeight = mh;
659
+ }
660
+ if (sizing.maxHeight != null) {
661
+ var mx = toLen(sizing.maxHeight);
662
+ de.style.maxHeight = mx;
663
+ if (body) body.style.maxHeight = mx;
664
+ if (root) root.style.maxHeight = mx;
665
+ }
666
+ if (sizing.aspectRatio != null && root) {
667
+ root.style.aspectRatio = String(sizing.aspectRatio);
668
+ }
669
+ }
670
+
671
+ function __initAutoResize() {
672
+ if (typeof window === 'undefined') return;
673
+ // Idempotent: a re-injected IIFE must not stack observers.
674
+ if (window.__mcpAutoResizeInit) return;
675
+ window.__mcpAutoResizeInit = true;
676
+ var sizing = window.__mcpWidgetSizing;
677
+ if (!sizing || typeof sizing !== 'object') return;
678
+
679
+ // Apply CSS as a runtime fallback (the static <style> may be absent on some
680
+ // render paths). Wait for the body if it isn't ready yet.
681
+ function apply() { try { __applySizingCss(sizing); } catch (e) {} }
682
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
683
+ document.addEventListener('DOMContentLoaded', apply);
684
+ } else {
685
+ apply();
686
+ }
687
+
688
+ // Auto-resize defaults ON; opt out with autoResize:false.
689
+ if (sizing.autoResize === false) return;
690
+ if (typeof ResizeObserver === 'undefined') return;
691
+
692
+ function startObserving() {
693
+ var target = document.getElementById('root') || document.body;
694
+ if (!target) return;
695
+
696
+ var rafId = null;
697
+ var lastReported = -1;
698
+ function report() {
699
+ rafId = null;
700
+ try {
701
+ var rect = target.getBoundingClientRect();
702
+ var height = Math.ceil(rect.height);
703
+ var width = Math.ceil(rect.width);
704
+ if (height === lastReported || height <= 0) return;
705
+ lastReported = height;
706
+ var payload = { height: height, width: width };
707
+ if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
708
+ if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
709
+ window.FrontMcpBridge.setSize(payload).catch(function() {});
710
+ }
711
+ window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
712
+ } catch (e) {}
713
+ }
714
+ function schedule() {
715
+ // Debounce via rAF; fall back to setTimeout if rAF is unavailable.
716
+ if (rafId != null) return;
717
+ if (typeof requestAnimationFrame === 'function') {
718
+ rafId = requestAnimationFrame(report);
719
+ } else {
720
+ rafId = setTimeout(report, 100);
721
+ }
722
+ }
723
+
724
+ try {
725
+ // Disconnect any prior observer before creating a new one (no leaks/dupes).
726
+ if (window.__mcpResizeObserver && typeof window.__mcpResizeObserver.disconnect === 'function') {
727
+ window.__mcpResizeObserver.disconnect();
728
+ }
729
+ var ro = new ResizeObserver(function() { schedule(); });
730
+ ro.observe(target);
731
+ window.__mcpResizeObserver = ro;
732
+ } catch (e) {}
733
+ // Report once on init so the host gets an initial measurement.
734
+ schedule();
735
+ }
736
+
737
+ if (typeof document !== 'undefined' && document.readyState === 'loading') {
738
+ document.addEventListener('DOMContentLoaded', startObserving);
739
+ } else {
740
+ startObserving();
741
+ }
742
+ }
743
+ __initAutoResize();
744
+ `.trim();
745
+ }
329
746
  function generateContextDetection() {
330
747
  return `
331
748
  function detectTheme() {
@@ -433,6 +850,19 @@ var OpenAIAdapter = {
433
850
  requestDisplayMode: function(context, mode) {
434
851
  return Promise.resolve();
435
852
  },
853
+ setSize: function(context, size) {
854
+ // OpenAI Apps SDK measures DOM height itself; if a sizing API surfaces on
855
+ // window.openai, forward to it, otherwise no-op (CSS drives layout).
856
+ try {
857
+ if (window.openai && typeof window.openai.requestDisplayMode === 'function' && size && size.displayMode) {
858
+ window.openai.requestDisplayMode(size.displayMode);
859
+ }
860
+ if (window.openai && typeof window.openai.setWidgetHeight === 'function' && size && typeof size.height === 'number') {
861
+ window.openai.setWidgetHeight(size.height);
862
+ }
863
+ } catch (e) {}
864
+ return Promise.resolve();
865
+ },
436
866
  requestClose: function(context) {
437
867
  return Promise.resolve();
438
868
  }
@@ -677,6 +1107,15 @@ var ExtAppsAdapter = {
677
1107
  requestDisplayMode: function(context, mode) {
678
1108
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
679
1109
  },
1110
+ setSize: function(context, size) {
1111
+ // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
1112
+ // measured/desired widget dimensions to the host.
1113
+ return this.sendRequest('ui/setSize', {
1114
+ height: size && size.height,
1115
+ width: size && size.width,
1116
+ aspectRatio: size && size.aspectRatio
1117
+ });
1118
+ },
680
1119
  requestClose: function(context) {
681
1120
  return this.sendRequest('ui/close', {});
682
1121
  },
@@ -765,6 +1204,10 @@ var ClaudeAdapter = {
765
1204
  requestDisplayMode: function() {
766
1205
  return Promise.resolve();
767
1206
  },
1207
+ setSize: function() {
1208
+ // Claude measures the rendered DOM height itself \u2014 CSS-only, no reporting.
1209
+ return Promise.resolve();
1210
+ },
768
1211
  requestClose: function() {
769
1212
  return Promise.resolve();
770
1213
  }
@@ -816,6 +1259,9 @@ var GeminiAdapter = {
816
1259
  requestDisplayMode: function() {
817
1260
  return Promise.resolve();
818
1261
  },
1262
+ setSize: function() {
1263
+ return Promise.resolve();
1264
+ },
819
1265
  requestClose: function() {
820
1266
  return Promise.resolve();
821
1267
  }
@@ -851,6 +1297,9 @@ var GenericAdapter = {
851
1297
  requestDisplayMode: function() {
852
1298
  return Promise.resolve();
853
1299
  },
1300
+ setSize: function() {
1301
+ return Promise.resolve();
1302
+ },
854
1303
  requestClose: function() {
855
1304
  return Promise.resolve();
856
1305
  }
@@ -1085,6 +1534,15 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1085
1534
  });
1086
1535
  };
1087
1536
 
1537
+ // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1538
+ // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1539
+ // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1540
+ FrontMcpBridge.prototype.setSize = function(size) {
1541
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1542
+ if (!this._adapter.setSize) return Promise.resolve();
1543
+ return this._adapter.setSize(this._context, size || {});
1544
+ };
1545
+
1088
1546
  FrontMcpBridge.prototype.requestClose = function() {
1089
1547
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1090
1548
  return this._adapter.requestClose(this._context);
@@ -1283,564 +1741,404 @@ var BRIDGE_SCRIPT_TAGS = {
1283
1741
  gemini: `<script>${generatePlatformBundle("gemini")}</script>`
1284
1742
  };
1285
1743
 
1286
- // libs/uipack/src/shell/custom-shell-types.ts
1287
- var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
1288
- var SHELL_PLACEHOLDERS = {
1289
- CSP: "{{CSP}}",
1290
- DATA: "{{DATA}}",
1291
- BRIDGE: "{{BRIDGE}}",
1292
- CONTENT: "{{CONTENT}}",
1293
- TITLE: "{{TITLE}}"
1294
- };
1295
- var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1296
- var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1297
-
1298
- // libs/uipack/src/shell/custom-shell-validator.ts
1299
- function validateShellTemplate(template) {
1300
- const found = {};
1301
- for (const name of SHELL_PLACEHOLDER_NAMES) {
1302
- found[name] = template.includes(SHELL_PLACEHOLDERS[name]);
1303
- }
1304
- const missingRequired = REQUIRED_PLACEHOLDERS.filter((name) => !found[name]);
1305
- const missingOptional = OPTIONAL_PLACEHOLDERS.filter((name) => !found[name]);
1306
- return {
1307
- valid: missingRequired.length === 0,
1308
- found,
1309
- missingRequired,
1310
- missingOptional
1311
- };
1312
- }
1313
-
1314
- // libs/uipack/src/shell/custom-shell-applier.ts
1315
- function applyShellTemplate(template, values) {
1316
- let result = template;
1317
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1318
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1319
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1320
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1321
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1322
- return result;
1323
- }
1324
-
1325
- // libs/uipack/src/shell/builder.ts
1326
- function buildShell(content, config) {
1327
- const { toolName, csp, withShell = true, input, output, structuredContent, includeBridge = true, title } = config;
1328
- const { customShell } = config;
1329
- const dataScript = buildDataInjectionScript({ toolName, input, output, structuredContent });
1330
- if (!withShell) {
1331
- const html2 = `${dataScript}
1332
- ${content}`;
1333
- return {
1334
- html: html2,
1335
- hash: simpleHash(html2),
1336
- size: Buffer.byteLength(html2, "utf-8")
1337
- };
1338
- }
1339
- if (customShell) {
1340
- return buildCustomShell(content, customShell, {
1341
- csp,
1342
- toolName,
1343
- input,
1344
- output,
1345
- structuredContent,
1346
- includeBridge,
1347
- title
1348
- });
1349
- }
1350
- const headParts = [
1351
- '<meta charset="UTF-8">',
1352
- '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
1353
- ];
1354
- if (title) {
1355
- headParts.push(`<title>${escapeHtmlForTag(title)}</title>`);
1356
- }
1357
- headParts.push(buildCSPMetaTag(csp));
1358
- headParts.push(dataScript);
1359
- if (includeBridge) {
1360
- const bridgeScript = generateBridgeIIFE({ minify: true });
1361
- headParts.push(`<script>${bridgeScript}</script>`);
1362
- }
1363
- const html = `<!DOCTYPE html>
1364
- <html lang="en">
1365
- <head>
1366
- ${headParts.map((p) => ` ${p}`).join("\n")}
1367
- </head>
1368
- <body>
1369
- ${content}
1370
- </body>
1371
- </html>`;
1372
- return {
1373
- html,
1374
- hash: simpleHash(html),
1375
- size: Buffer.byteLength(html, "utf-8")
1376
- };
1377
- }
1378
- function buildCustomShell(content, customShell, ctx) {
1379
- let template;
1380
- if (typeof customShell === "string") {
1381
- const validation = validateShellTemplate(customShell);
1382
- if (!validation.valid) {
1383
- throw new Error(
1384
- `Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
1385
- );
1386
- }
1387
- template = customShell;
1388
- } else {
1389
- template = customShell.template;
1744
+ // libs/uipack/src/shell/csp.ts
1745
+ var DEFAULT_CDN_DOMAINS = [
1746
+ "https://cdn.jsdelivr.net",
1747
+ "https://cdnjs.cloudflare.com",
1748
+ "https://fonts.googleapis.com",
1749
+ "https://fonts.gstatic.com",
1750
+ "https://esm.sh"
1751
+ ];
1752
+ var DEFAULT_CSP_DIRECTIVES = [
1753
+ "default-src 'none'",
1754
+ `script-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1755
+ `style-src 'self' 'unsafe-inline' ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1756
+ `img-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1757
+ `font-src 'self' data: ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1758
+ `connect-src ${DEFAULT_CDN_DOMAINS.join(" ")}`,
1759
+ "object-src 'self' data:"
1760
+ ];
1761
+ function buildCSPDirectives(csp) {
1762
+ if (!csp) {
1763
+ return [...DEFAULT_CSP_DIRECTIVES];
1390
1764
  }
1391
- const cspTag = buildCSPMetaTag(ctx.csp);
1392
- const dataScript = buildDataInjectionScript({
1393
- toolName: ctx.toolName,
1394
- input: ctx.input,
1395
- output: ctx.output,
1396
- structuredContent: ctx.structuredContent
1397
- });
1398
- let bridgeHtml = "";
1399
- if (ctx.includeBridge) {
1400
- const bridgeScript = generateBridgeIIFE({ minify: true });
1401
- bridgeHtml = `<script>${bridgeScript}</script>`;
1765
+ const validResourceDomains = sanitizeCSPDomains(csp.resourceDomains);
1766
+ const validConnectDomains = sanitizeCSPDomains(csp.connectDomains);
1767
+ const allResourceDomains = [.../* @__PURE__ */ new Set([...DEFAULT_CDN_DOMAINS, ...validResourceDomains])];
1768
+ const directives = [
1769
+ "default-src 'none'",
1770
+ `script-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`,
1771
+ `style-src 'self' 'unsafe-inline' ${allResourceDomains.join(" ")}`
1772
+ ];
1773
+ const imgSources = ["'self'", "data:", ...allResourceDomains];
1774
+ directives.push(`img-src ${imgSources.join(" ")}`);
1775
+ const fontSources = ["'self'", "data:", ...allResourceDomains];
1776
+ directives.push(`font-src ${fontSources.join(" ")}`);
1777
+ if (validConnectDomains.length) {
1778
+ directives.push(`connect-src ${validConnectDomains.join(" ")}`);
1779
+ } else {
1780
+ directives.push(`connect-src ${allResourceDomains.join(" ")}`);
1402
1781
  }
1403
- const html = applyShellTemplate(template, {
1404
- csp: cspTag,
1405
- data: dataScript,
1406
- bridge: bridgeHtml,
1407
- content,
1408
- title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
1409
- });
1410
- return {
1411
- html,
1412
- hash: simpleHash(html),
1413
- size: Buffer.byteLength(html, "utf-8")
1414
- };
1782
+ directives.push("object-src 'self' data:");
1783
+ return directives;
1415
1784
  }
1416
- function simpleHash(str) {
1417
- let hash = 0;
1418
- for (let i = 0; i < str.length; i++) {
1419
- const char = str.charCodeAt(i);
1420
- hash = (hash << 5) - hash + char | 0;
1421
- }
1422
- return Math.abs(hash).toString(16).padStart(8, "0");
1785
+ function buildCSPMetaTag(csp) {
1786
+ const directives = buildCSPDirectives(csp);
1787
+ const content = directives.join("; ");
1788
+ return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
1423
1789
  }
1424
- function escapeHtmlForTag(str) {
1425
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1790
+ function validateCSPDomain(domain) {
1791
+ if (domain.startsWith("https://*.")) {
1792
+ const rest = domain.slice(10);
1793
+ return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
1794
+ }
1795
+ try {
1796
+ const url = new URL(domain);
1797
+ return url.protocol === "https:";
1798
+ } catch {
1799
+ return false;
1800
+ }
1426
1801
  }
1427
-
1428
- // libs/uipack/src/resolver/cdn-registry.ts
1429
- var DEFAULT_CDN_REGISTRY = {
1430
- react: {
1431
- packageName: "react",
1432
- defaultVersion: "19.2.4",
1433
- providers: {
1434
- cloudflare: {
1435
- url: "https://cdnjs.cloudflare.com/ajax/libs/react/19.2.4/umd/react.production.min.js",
1436
- global: "React",
1437
- crossorigin: "anonymous"
1438
- },
1439
- jsdelivr: {
1440
- url: "https://cdn.jsdelivr.net/npm/react@19.2.4/umd/react.production.min.js",
1441
- global: "React",
1442
- crossorigin: "anonymous"
1443
- },
1444
- unpkg: {
1445
- url: "https://unpkg.com/react@19.2.4/umd/react.production.min.js",
1446
- global: "React",
1447
- crossorigin: "anonymous"
1448
- },
1449
- "esm.sh": {
1450
- url: "https://esm.sh/react@19.2.4",
1451
- esm: true,
1452
- crossorigin: "anonymous"
1453
- }
1454
- },
1455
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1456
- metadata: {
1457
- description: "A JavaScript library for building user interfaces",
1458
- homepage: "https://react.dev",
1459
- license: "MIT"
1460
- }
1461
- },
1462
- "react-dom": {
1463
- packageName: "react-dom",
1464
- defaultVersion: "19.2.4",
1465
- providers: {
1466
- cloudflare: {
1467
- url: "https://cdnjs.cloudflare.com/ajax/libs/react-dom/19.2.4/umd/react-dom.production.min.js",
1468
- global: "ReactDOM",
1469
- crossorigin: "anonymous",
1470
- peerDependencies: ["react"]
1471
- },
1472
- jsdelivr: {
1473
- url: "https://cdn.jsdelivr.net/npm/react-dom@19.2.4/umd/react-dom.production.min.js",
1474
- global: "ReactDOM",
1475
- crossorigin: "anonymous",
1476
- peerDependencies: ["react"]
1477
- },
1478
- unpkg: {
1479
- url: "https://unpkg.com/react-dom@19.2.4/umd/react-dom.production.min.js",
1480
- global: "ReactDOM",
1481
- crossorigin: "anonymous",
1482
- peerDependencies: ["react"]
1483
- },
1484
- "esm.sh": {
1485
- url: "https://esm.sh/react-dom@19.2.4",
1486
- esm: true,
1487
- crossorigin: "anonymous",
1488
- peerDependencies: ["react"]
1489
- }
1490
- },
1491
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1492
- metadata: {
1493
- description: "React package for working with the DOM",
1494
- homepage: "https://react.dev",
1495
- license: "MIT"
1496
- }
1497
- },
1498
- "chart.js": {
1499
- packageName: "chart.js",
1500
- defaultVersion: "4.5.1",
1501
- providers: {
1502
- cloudflare: {
1503
- url: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.5.1/chart.umd.min.js",
1504
- global: "Chart",
1505
- crossorigin: "anonymous"
1506
- },
1507
- jsdelivr: {
1508
- url: "https://cdn.jsdelivr.net/npm/chart.js@4.5.1/dist/chart.umd.min.js",
1509
- global: "Chart",
1510
- crossorigin: "anonymous"
1511
- },
1512
- unpkg: {
1513
- url: "https://unpkg.com/chart.js@4.5.1/dist/chart.umd.min.js",
1514
- global: "Chart",
1515
- crossorigin: "anonymous"
1516
- }
1517
- },
1518
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1519
- metadata: {
1520
- description: "Simple yet flexible JavaScript charting library",
1521
- homepage: "https://www.chartjs.org",
1522
- license: "MIT"
1523
- }
1524
- },
1525
- d3: {
1526
- packageName: "d3",
1527
- defaultVersion: "7.9.0",
1528
- providers: {
1529
- cloudflare: {
1530
- url: "https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js",
1531
- integrity: "sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==",
1532
- global: "d3",
1533
- crossorigin: "anonymous"
1534
- },
1535
- jsdelivr: {
1536
- url: "https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js",
1537
- global: "d3",
1538
- crossorigin: "anonymous"
1539
- },
1540
- unpkg: {
1541
- url: "https://unpkg.com/d3@7.9.0/dist/d3.min.js",
1542
- global: "d3",
1543
- crossorigin: "anonymous"
1544
- }
1545
- },
1546
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1547
- metadata: {
1548
- description: "Data-Driven Documents",
1549
- homepage: "https://d3js.org",
1550
- license: "ISC"
1551
- }
1552
- },
1553
- lodash: {
1554
- packageName: "lodash",
1555
- defaultVersion: "4.17.21",
1556
- providers: {
1557
- cloudflare: {
1558
- url: "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js",
1559
- integrity: "sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==",
1560
- global: "_",
1561
- crossorigin: "anonymous"
1562
- },
1563
- jsdelivr: {
1564
- url: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js",
1565
- global: "_",
1566
- crossorigin: "anonymous"
1567
- },
1568
- unpkg: {
1569
- url: "https://unpkg.com/lodash@4.17.21/lodash.min.js",
1570
- global: "_",
1571
- crossorigin: "anonymous"
1572
- }
1573
- },
1574
- preferredProviders: ["esm.sh", "cloudflare", "jsdelivr", "unpkg"],
1575
- metadata: {
1576
- description: "A modern JavaScript utility library",
1577
- homepage: "https://lodash.com",
1578
- license: "MIT"
1802
+ function sanitizeCSPDomains(domains) {
1803
+ if (!domains) return [];
1804
+ const valid = [];
1805
+ for (const domain of domains) {
1806
+ if (validateCSPDomain(domain)) {
1807
+ valid.push(domain);
1808
+ } else {
1809
+ console.warn(`Invalid CSP domain ignored: ${domain}`);
1579
1810
  }
1580
1811
  }
1581
- };
1582
- function lookupPackage(packageName, registry = DEFAULT_CDN_REGISTRY) {
1583
- return registry[packageName];
1812
+ return valid;
1813
+ }
1814
+ function escapeAttribute(str) {
1815
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1584
1816
  }
1585
1817
 
1586
- // libs/uipack/src/resolver/import-parser.ts
1587
- var IMPORT_PATTERNS = {
1588
- named: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
1589
- default: /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1590
- namespace: /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1591
- sideEffect: /import\s*['"]([^'"]+)['"]/g,
1592
- dynamic: /(?:await\s+)?import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
1593
- defaultAndNamed: /import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
1594
- defaultAndNamespace: /import\s+(\w+)\s*,\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
1595
- reExport: /export\s*\{[^}]+\}\s*from\s*['"]([^'"]+)['"]/g,
1596
- reExportAll: /export\s*\*\s*from\s*['"]([^'"]+)['"]/g
1818
+ // libs/uipack/src/shell/custom-shell-types.ts
1819
+ var SHELL_PLACEHOLDER_NAMES = ["CSP", "DATA", "BRIDGE", "CONTENT", "TITLE"];
1820
+ var SHELL_PLACEHOLDERS = {
1821
+ CSP: "{{CSP}}",
1822
+ DATA: "{{DATA}}",
1823
+ BRIDGE: "{{BRIDGE}}",
1824
+ CONTENT: "{{CONTENT}}",
1825
+ TITLE: "{{TITLE}}"
1597
1826
  };
1598
- function parseNamedImports(namedString) {
1599
- return namedString.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => {
1600
- const asMatch = s.match(/^(\w+)\s+as\s+\w+$/);
1601
- return asMatch ? asMatch[1] : s;
1602
- });
1827
+ var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1828
+ var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1829
+
1830
+ // libs/uipack/src/shell/custom-shell-applier.ts
1831
+ function applyShellTemplate(template, values) {
1832
+ let result = template;
1833
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1834
+ result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1835
+ result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1836
+ result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1837
+ result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1838
+ return result;
1603
1839
  }
1604
- function isRelativeImport(specifier) {
1605
- return specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/");
1840
+
1841
+ // libs/uipack/src/shell/custom-shell-validator.ts
1842
+ function validateShellTemplate(template) {
1843
+ const found = {};
1844
+ for (const name of SHELL_PLACEHOLDER_NAMES) {
1845
+ found[name] = template.includes(SHELL_PLACEHOLDERS[name]);
1846
+ }
1847
+ const missingRequired = REQUIRED_PLACEHOLDERS.filter((name) => !found[name]);
1848
+ const missingOptional = OPTIONAL_PLACEHOLDERS.filter((name) => !found[name]);
1849
+ return {
1850
+ valid: missingRequired.length === 0,
1851
+ found,
1852
+ missingRequired,
1853
+ missingOptional
1854
+ };
1606
1855
  }
1607
- function isExternalImport(specifier) {
1608
- return !isRelativeImport(specifier) && !specifier.startsWith("#");
1856
+
1857
+ // libs/uipack/src/shell/data-injector.ts
1858
+ function hasSizing(sizing) {
1859
+ if (!sizing) return false;
1860
+ return sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0 || sizing.autoResize !== void 0;
1609
1861
  }
1610
- function getPackageName(specifier) {
1611
- if (specifier.startsWith("@")) {
1612
- const parts = specifier.split("/");
1613
- if (parts.length >= 2) {
1614
- return `${parts[0]}/${parts[1]}`;
1615
- }
1616
- return specifier;
1862
+ function buildDataInjectionScript(options) {
1863
+ const { toolName, input, output, structuredContent, sizing } = options;
1864
+ const lines = [
1865
+ `window.__mcpAppsEnabled = true;`,
1866
+ `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
1867
+ `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
1868
+ `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
1869
+ `window.__mcpStructuredContent = ${safeJsonForScript(structuredContent ?? null)};`
1870
+ ];
1871
+ if (hasSizing(sizing)) {
1872
+ lines.push(`window.__mcpWidgetSizing = ${safeJsonForScript(sizing)};`);
1617
1873
  }
1618
- const firstSlash = specifier.indexOf("/");
1619
- return firstSlash === -1 ? specifier : specifier.slice(0, firstSlash);
1874
+ return `<script>
1875
+ ${lines.join("\n")}
1876
+ </script>`;
1620
1877
  }
1621
- function getLineNumber(source, index) {
1622
- let line = 1;
1623
- for (let i = 0; i < index && i < source.length; i++) {
1624
- if (source[i] === "\n") {
1625
- line++;
1626
- }
1878
+ function buildCustomDataInjectionScript(descriptor) {
1879
+ if (descriptor.script !== void 0) {
1880
+ return descriptor.script;
1627
1881
  }
1628
- return line;
1629
- }
1630
- function getColumnNumber(source, index) {
1631
- let column = 0;
1632
- for (let i = index - 1; i >= 0 && source[i] !== "\n"; i--) {
1633
- column++;
1882
+ if (descriptor.globalKey !== void 0) {
1883
+ return `<script>window[${safeJsonForScript(descriptor.globalKey)}] = ${safeJsonForScript(
1884
+ descriptor.value ?? null
1885
+ )};</script>`;
1634
1886
  }
1635
- return column;
1887
+ return "";
1636
1888
  }
1637
- function parseImports(source) {
1638
- const imports = [];
1639
- const seenStatements = /* @__PURE__ */ new Set();
1640
- const addImport = (imp) => {
1641
- const key = `${imp.type}:${imp.specifier}:${imp.statement}`;
1642
- if (!seenStatements.has(key)) {
1643
- seenStatements.add(key);
1644
- imports.push(imp);
1889
+ var _uniqueIdCounter = 0;
1890
+ function createTemplateHelpers() {
1891
+ return {
1892
+ escapeHtml: (str) => escapeHtml(str),
1893
+ formatDate: (date, format) => {
1894
+ const d = date instanceof Date ? date : new Date(date);
1895
+ if (isNaN(d.getTime())) return String(date);
1896
+ if (format === "iso") return d.toISOString();
1897
+ if (format === "date") return d.toLocaleDateString();
1898
+ if (format === "time") return d.toLocaleTimeString();
1899
+ return d.toLocaleString();
1900
+ },
1901
+ formatCurrency: (amount, currency = "USD") => {
1902
+ return new Intl.NumberFormat("en-US", {
1903
+ style: "currency",
1904
+ currency
1905
+ }).format(amount);
1906
+ },
1907
+ uniqueId: (prefix = "mcp") => {
1908
+ return `${prefix}-${++_uniqueIdCounter}`;
1909
+ },
1910
+ jsonEmbed: (data) => {
1911
+ return escapeScriptClose(JSON.stringify(data));
1645
1912
  }
1646
1913
  };
1647
- let match;
1648
- const defaultAndNamedRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamed.source, "g");
1649
- while ((match = defaultAndNamedRegex.exec(source)) !== null) {
1650
- const [statement, defaultName, namedString, specifier] = match;
1651
- addImport({
1652
- statement,
1653
- specifier,
1654
- type: "default",
1655
- defaultImport: defaultName,
1656
- namedImports: parseNamedImports(namedString),
1657
- line: getLineNumber(source, match.index),
1658
- column: getColumnNumber(source, match.index)
1659
- });
1914
+ }
1915
+
1916
+ // libs/uipack/src/shell/sizing-css.ts
1917
+ function sanitizeCssValue(value) {
1918
+ return value.replace(/[<>{};]/g, "").trim();
1919
+ }
1920
+ function toCssLength(value) {
1921
+ return typeof value === "number" ? `${value}px` : sanitizeCssValue(value);
1922
+ }
1923
+ function buildSizingStyleTag(sizing) {
1924
+ if (!hasSizing(sizing)) return "";
1925
+ const hasCssSizing = sizing.preferredHeight !== void 0 || sizing.minHeight !== void 0 || sizing.maxHeight !== void 0 || sizing.aspectRatio !== void 0;
1926
+ if (!hasCssSizing) return "";
1927
+ const rootRules = [];
1928
+ const docRules = ["margin: 0;"];
1929
+ if (sizing.preferredHeight !== void 0) {
1930
+ const h = toCssLength(sizing.preferredHeight);
1931
+ docRules.push(`height: ${h};`);
1932
+ rootRules.push(`min-height: ${h};`);
1660
1933
  }
1661
- const defaultAndNamespaceRegex = new RegExp(IMPORT_PATTERNS.defaultAndNamespace.source, "g");
1662
- while ((match = defaultAndNamespaceRegex.exec(source)) !== null) {
1663
- const [statement, defaultName, namespaceName, specifier] = match;
1664
- addImport({
1665
- statement,
1666
- specifier,
1667
- type: "default",
1668
- defaultImport: defaultName,
1669
- namespaceImport: namespaceName,
1670
- line: getLineNumber(source, match.index),
1671
- column: getColumnNumber(source, match.index)
1672
- });
1934
+ if (sizing.minHeight !== void 0) {
1935
+ const mh = toCssLength(sizing.minHeight);
1936
+ docRules.push(`min-height: ${mh};`);
1937
+ rootRules.push(`min-height: ${mh};`);
1673
1938
  }
1674
- const namedRegex = new RegExp(IMPORT_PATTERNS.named.source, "g");
1675
- while ((match = namedRegex.exec(source)) !== null) {
1676
- const [statement, namedString, specifier] = match;
1677
- addImport({
1678
- statement,
1679
- specifier,
1680
- type: "named",
1681
- namedImports: parseNamedImports(namedString),
1682
- line: getLineNumber(source, match.index),
1683
- column: getColumnNumber(source, match.index)
1684
- });
1939
+ if (sizing.maxHeight !== void 0) {
1940
+ const mx = toCssLength(sizing.maxHeight);
1941
+ docRules.push(`max-height: ${mx};`);
1942
+ rootRules.push(`max-height: ${mx};`);
1685
1943
  }
1686
- const defaultRegex = new RegExp(IMPORT_PATTERNS.default.source, "g");
1687
- while ((match = defaultRegex.exec(source)) !== null) {
1688
- const [statement, defaultName, specifier] = match;
1689
- const afterMatch = source.slice(match.index + match[0].length - specifier.length - 2);
1690
- if (afterMatch.startsWith(",")) continue;
1691
- addImport({
1692
- statement,
1693
- specifier,
1694
- type: "default",
1695
- defaultImport: defaultName,
1696
- line: getLineNumber(source, match.index),
1697
- column: getColumnNumber(source, match.index)
1698
- });
1944
+ if (sizing.aspectRatio !== void 0) {
1945
+ const ar = typeof sizing.aspectRatio === "number" ? String(sizing.aspectRatio) : sanitizeCssValue(sizing.aspectRatio);
1946
+ if (ar) rootRules.push(`aspect-ratio: ${ar};`);
1699
1947
  }
1700
- const namespaceRegex = new RegExp(IMPORT_PATTERNS.namespace.source, "g");
1701
- while ((match = namespaceRegex.exec(source)) !== null) {
1702
- const [statement, namespaceName, specifier] = match;
1703
- addImport({
1704
- statement,
1705
- specifier,
1706
- type: "namespace",
1707
- namespaceImport: namespaceName,
1708
- line: getLineNumber(source, match.index),
1709
- column: getColumnNumber(source, match.index)
1710
- });
1948
+ const parts = [`html, body { ${docRules.join(" ")} }`];
1949
+ if (rootRules.length > 0) {
1950
+ parts.push(`#root { ${rootRules.join(" ")} }`);
1711
1951
  }
1712
- const sideEffectRegex = new RegExp(IMPORT_PATTERNS.sideEffect.source, "g");
1713
- while ((match = sideEffectRegex.exec(source)) !== null) {
1714
- const [statement, specifier] = match;
1715
- const beforeMatch = source.slice(Math.max(0, match.index - 50), match.index);
1716
- if (beforeMatch.includes("from")) continue;
1717
- addImport({
1718
- statement,
1719
- specifier,
1720
- type: "side-effect",
1721
- line: getLineNumber(source, match.index),
1722
- column: getColumnNumber(source, match.index)
1723
- });
1952
+ return `<style>${parts.join("\n")}</style>`;
1953
+ }
1954
+
1955
+ // libs/uipack/src/shell/builder.ts
1956
+ function resolveDataInjectionScript(args) {
1957
+ if (args.dataInjection) {
1958
+ return buildCustomDataInjectionScript(args.dataInjection);
1959
+ }
1960
+ return buildDataInjectionScript({
1961
+ toolName: args.toolName,
1962
+ input: args.input,
1963
+ output: args.output,
1964
+ structuredContent: args.structuredContent,
1965
+ sizing: args.sizing
1966
+ });
1967
+ }
1968
+ function buildShell(content, config) {
1969
+ const {
1970
+ toolName,
1971
+ csp,
1972
+ withShell = true,
1973
+ input,
1974
+ output,
1975
+ structuredContent,
1976
+ includeBridge = true,
1977
+ title,
1978
+ sizing
1979
+ } = config;
1980
+ const { customShell, dataInjection } = config;
1981
+ const dataScript = resolveDataInjectionScript({
1982
+ dataInjection,
1983
+ toolName,
1984
+ input,
1985
+ output,
1986
+ structuredContent,
1987
+ sizing
1988
+ });
1989
+ if (!withShell) {
1990
+ const html2 = `${dataScript}
1991
+ ${content}`;
1992
+ return {
1993
+ html: html2,
1994
+ hash: simpleHash(html2),
1995
+ size: Buffer.byteLength(html2, "utf-8")
1996
+ };
1724
1997
  }
1725
- const dynamicRegex = new RegExp(IMPORT_PATTERNS.dynamic.source, "g");
1726
- while ((match = dynamicRegex.exec(source)) !== null) {
1727
- const [statement, specifier] = match;
1728
- addImport({
1729
- statement,
1730
- specifier,
1731
- type: "dynamic",
1732
- line: getLineNumber(source, match.index),
1733
- column: getColumnNumber(source, match.index)
1998
+ if (customShell) {
1999
+ return buildCustomShell(content, customShell, {
2000
+ csp,
2001
+ toolName,
2002
+ input,
2003
+ output,
2004
+ structuredContent,
2005
+ includeBridge,
2006
+ title,
2007
+ sizing,
2008
+ dataInjection
1734
2009
  });
1735
2010
  }
1736
- const reExportRegex = new RegExp(IMPORT_PATTERNS.reExport.source, "g");
1737
- while ((match = reExportRegex.exec(source)) !== null) {
1738
- const [statement, specifier] = match;
1739
- addImport({
1740
- statement,
1741
- specifier,
1742
- type: "named",
1743
- line: getLineNumber(source, match.index),
1744
- column: getColumnNumber(source, match.index)
1745
- });
2011
+ const headParts = [
2012
+ '<meta charset="UTF-8">',
2013
+ '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
2014
+ ];
2015
+ if (title) {
2016
+ headParts.push(`<title>${escapeHtmlForTag(title)}</title>`);
1746
2017
  }
1747
- const reExportAllRegex = new RegExp(IMPORT_PATTERNS.reExportAll.source, "g");
1748
- while ((match = reExportAllRegex.exec(source)) !== null) {
1749
- const [statement, specifier] = match;
1750
- addImport({
1751
- statement,
1752
- specifier,
1753
- type: "namespace",
1754
- line: getLineNumber(source, match.index),
1755
- column: getColumnNumber(source, match.index)
1756
- });
2018
+ headParts.push(buildCSPMetaTag(csp));
2019
+ headParts.push(dataScript);
2020
+ const sizingStyle = buildSizingStyleTag(sizing);
2021
+ if (sizingStyle) {
2022
+ headParts.push(sizingStyle);
1757
2023
  }
1758
- const externalImports = imports.filter((imp) => isExternalImport(imp.specifier));
1759
- const relativeImports = imports.filter((imp) => isRelativeImport(imp.specifier));
1760
- const externalPackages = [...new Set(externalImports.map((imp) => getPackageName(imp.specifier)))];
2024
+ if (includeBridge) {
2025
+ const bridgeScript = generateBridgeIIFE({ minify: true });
2026
+ headParts.push(`<script>${bridgeScript}</script>`);
2027
+ }
2028
+ const html = `<!DOCTYPE html>
2029
+ <html lang="en">
2030
+ <head>
2031
+ ${headParts.map((p) => ` ${p}`).join("\n")}
2032
+ </head>
2033
+ <body>
2034
+ ${content}
2035
+ </body>
2036
+ </html>`;
1761
2037
  return {
1762
- imports,
1763
- externalImports,
1764
- relativeImports,
1765
- externalPackages
2038
+ html,
2039
+ hash: simpleHash(html),
2040
+ size: Buffer.byteLength(html, "utf-8")
1766
2041
  };
1767
2042
  }
1768
-
1769
- // libs/uipack/src/resolver/esm-sh.resolver.ts
1770
- var DEFAULT_FALLBACK_CDN = "https://esm.sh";
1771
- function createEsmShResolver(options = {}) {
1772
- const {
1773
- fallbackCdnBase = DEFAULT_FALLBACK_CDN,
1774
- registry = DEFAULT_CDN_REGISTRY,
1775
- providerOrder = ["esm.sh", "cloudflare", "jsdelivr", "unpkg"]
1776
- } = options;
1777
- return {
1778
- resolve(specifier, _context) {
1779
- if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) {
1780
- return null;
1781
- }
1782
- if (specifier.startsWith("node:") || specifier.startsWith("#")) {
1783
- return null;
1784
- }
1785
- const pkgName = getPackageName(specifier);
1786
- const entry = lookupPackage(pkgName, registry);
1787
- if (entry) {
1788
- for (const provider of providerOrder) {
1789
- const config = entry.providers[provider];
1790
- if (config?.url) {
1791
- const subpath = specifier.slice(pkgName.length);
1792
- if (subpath && config.url.includes("esm.sh/")) {
1793
- return {
1794
- value: config.url + subpath,
1795
- type: "url",
1796
- integrity: config.integrity
1797
- };
1798
- }
1799
- if (subpath) {
1800
- return {
1801
- value: `${fallbackCdnBase}/${specifier}`,
1802
- type: "url"
1803
- };
1804
- }
1805
- if (config.global && !config.esm) {
1806
- return {
1807
- value: config.url,
1808
- type: "url",
1809
- integrity: config.integrity
1810
- };
1811
- }
1812
- return {
1813
- value: config.url,
1814
- type: "url",
1815
- integrity: config.integrity
1816
- };
1817
- }
1818
- }
1819
- }
1820
- return {
1821
- value: `${fallbackCdnBase}/${specifier}`,
1822
- type: "url"
1823
- };
2043
+ function buildCustomShell(content, customShell, ctx) {
2044
+ let template;
2045
+ if (typeof customShell === "string") {
2046
+ const validation = validateShellTemplate(customShell);
2047
+ if (!validation.valid) {
2048
+ throw new Error(
2049
+ `Custom shell template is missing required placeholder(s): ${validation.missingRequired.map((n) => `{{${n}}}`).join(", ")}`
2050
+ );
1824
2051
  }
2052
+ template = customShell;
2053
+ } else {
2054
+ template = customShell.template;
2055
+ }
2056
+ const cspTag = buildCSPMetaTag(ctx.csp);
2057
+ const dataScript = resolveDataInjectionScript({
2058
+ dataInjection: ctx.dataInjection,
2059
+ toolName: ctx.toolName,
2060
+ input: ctx.input,
2061
+ output: ctx.output,
2062
+ structuredContent: ctx.structuredContent,
2063
+ sizing: ctx.sizing
2064
+ });
2065
+ const sizingStyle = buildSizingStyleTag(ctx.sizing);
2066
+ const dataWithSizing = sizingStyle ? `${dataScript}
2067
+ ${sizingStyle}` : dataScript;
2068
+ let bridgeHtml = "";
2069
+ if (ctx.includeBridge) {
2070
+ const bridgeScript = generateBridgeIIFE({ minify: true });
2071
+ bridgeHtml = `<script>${bridgeScript}</script>`;
2072
+ }
2073
+ const html = applyShellTemplate(template, {
2074
+ csp: cspTag,
2075
+ data: dataWithSizing,
2076
+ bridge: bridgeHtml,
2077
+ content,
2078
+ title: ctx.title ? escapeHtmlForTag(ctx.title) : ""
2079
+ });
2080
+ return {
2081
+ html,
2082
+ hash: simpleHash(html),
2083
+ size: Buffer.byteLength(html, "utf-8")
1825
2084
  };
1826
2085
  }
1827
-
1828
- // libs/uipack/src/component/types.ts
1829
- function isNpmSource(source) {
1830
- return typeof source === "object" && source !== null && "npm" in source;
1831
- }
1832
- function isFileSource(source) {
1833
- return typeof source === "object" && source !== null && "file" in source;
2086
+ function simpleHash(str) {
2087
+ let hash = 0;
2088
+ for (let i = 0; i < str.length; i++) {
2089
+ const char = str.charCodeAt(i);
2090
+ hash = (hash << 5) - hash + char | 0;
2091
+ }
2092
+ return Math.abs(hash).toString(16).padStart(8, "0");
1834
2093
  }
1835
- function isImportSource(source) {
1836
- return typeof source === "object" && source !== null && "import" in source;
2094
+ function escapeHtmlForTag(str) {
2095
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1837
2096
  }
1838
- function isFunctionSource(source) {
1839
- return typeof source === "function";
2097
+
2098
+ // libs/uipack/src/component/ui-availability.ts
2099
+ function isFrontmcpUiResolvable(...candidatePaths) {
2100
+ try {
2101
+ const nodeFs = require("fs");
2102
+ const nodePath = require("path");
2103
+ const ORG = "@frontmcp";
2104
+ const PKG = "ui";
2105
+ for (const candidate of candidatePaths) {
2106
+ let dir = candidate;
2107
+ while (true) {
2108
+ const pkgJson = nodePath.join(dir, "node_modules", ORG, PKG, "package.json");
2109
+ if (nodeFs.existsSync(pkgJson)) return true;
2110
+ const parent = nodePath.dirname(dir);
2111
+ if (parent === dir) break;
2112
+ dir = parent;
2113
+ }
2114
+ }
2115
+ return false;
2116
+ } catch {
2117
+ return false;
2118
+ }
1840
2119
  }
1841
2120
 
1842
2121
  // libs/uipack/src/component/transpiler.ts
1843
- function bundleFileSource(source, filename, resolveDir, componentName) {
2122
+ function transpileReactSource(source, filename) {
2123
+ const esbuild = require("esbuild");
2124
+ const loader = filename?.endsWith(".tsx") ? "tsx" : "jsx";
2125
+ const result = esbuild.transformSync(source, {
2126
+ loader,
2127
+ jsx: "transform",
2128
+ jsxFactory: "React.createElement",
2129
+ jsxFragment: "React.Fragment",
2130
+ format: "esm",
2131
+ target: "es2020",
2132
+ define: { "process.env.NODE_ENV": '"production"' }
2133
+ });
2134
+ return result.code;
2135
+ }
2136
+ function bundleFileSource(source, filename, resolveDir, componentName, options = {}) {
2137
+ if (!isFrontmcpUiResolvable(resolveDir, process.cwd())) {
2138
+ throw new Error(
2139
+ `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.`
2140
+ );
2141
+ }
1844
2142
  const esbuild = require("esbuild");
1845
2143
  const mountCode = `
1846
2144
  // --- Auto-generated mount ---
@@ -1922,7 +2220,11 @@ if (__root) {
1922
2220
  format: "esm",
1923
2221
  target: "es2020",
1924
2222
  jsx: "automatic",
1925
- external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
2223
+ // When `bundleReact` is set (resourceMode: 'inline'), React itself is
2224
+ // bundled — required for hosts that block external script execution
2225
+ // such as Claude (#454). Otherwise React stays external and is loaded
2226
+ // via the import map emitted by the renderer.
2227
+ external: options.bundleReact ? [] : ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
1926
2228
  alias,
1927
2229
  define: { "process.env.NODE_ENV": '"production"' },
1928
2230
  platform: "browser",
@@ -1934,7 +2236,7 @@ if (__root) {
1934
2236
  } catch (err) {
1935
2237
  const message = err instanceof Error ? err.message : String(err);
1936
2238
  throw new Error(
1937
- `Failed to bundle FileSource "${filename}": ${message}. Ensure workspace packages are built (e.g. nx build ui).`
2239
+ `Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`
1938
2240
  );
1939
2241
  }
1940
2242
  }
@@ -1946,12 +2248,29 @@ function extractDefaultExportName(code) {
1946
2248
  return null;
1947
2249
  }
1948
2250
 
2251
+ // libs/uipack/src/component/types.ts
2252
+ function isNpmSource(source) {
2253
+ return typeof source === "object" && source !== null && "npm" in source;
2254
+ }
2255
+ function isFileSource(source) {
2256
+ return typeof source === "object" && source !== null && "file" in source;
2257
+ }
2258
+ function isImportSource(source) {
2259
+ return typeof source === "object" && source !== null && "import" in source;
2260
+ }
2261
+ function isFunctionSource(source) {
2262
+ return typeof source === "function";
2263
+ }
2264
+
1949
2265
  // libs/uipack/src/component/loader.ts
1950
2266
  var DEFAULT_META = {
1951
2267
  mcpAware: false,
1952
2268
  renderer: "auto"
1953
2269
  };
1954
2270
  function resolveUISource(source, options) {
2271
+ if (options?.inlineReact && options?.transformOnly) {
2272
+ throw new Error("resolveUISource: `inlineReact` and `transformOnly` are mutually exclusive \u2014 set at most one.");
2273
+ }
1955
2274
  const resolver = options?.resolver ?? createEsmShResolver();
1956
2275
  if (isNpmSource(source)) {
1957
2276
  return resolveNpmSource(source, resolver);
@@ -1960,7 +2279,7 @@ function resolveUISource(source, options) {
1960
2279
  return resolveImportSource(source);
1961
2280
  }
1962
2281
  if (isFileSource(source)) {
1963
- return resolveFileSource(source);
2282
+ return resolveFileSource(source, { inlineReact: options?.inlineReact, transformOnly: options?.transformOnly });
1964
2283
  }
1965
2284
  if (isFunctionSource(source)) {
1966
2285
  return resolveFunctionSource(source, options?.input, options?.output);
@@ -1993,15 +2312,42 @@ function resolveImportSource(source) {
1993
2312
  peerDependencies: []
1994
2313
  };
1995
2314
  }
1996
- function resolveFileSource(source) {
2315
+ function resolveFileSource(source, options = {}) {
1997
2316
  const path = require("path");
1998
2317
  const ext = path.extname(source.file).toLowerCase();
1999
2318
  if (ext === ".tsx" || ext === ".jsx") {
2000
2319
  const fs = require("fs");
2001
- const filePath = path.isAbsolute(source.file) ? source.file : path.resolve(process.cwd(), source.file);
2002
- const rawSource = fs.readFileSync(filePath, "utf-8");
2320
+ const wasRelative = !path.isAbsolute(source.file);
2321
+ const filePath = wasRelative ? path.resolve(process.cwd(), source.file) : source.file;
2322
+ let rawSource;
2323
+ try {
2324
+ rawSource = fs.readFileSync(filePath, "utf-8");
2325
+ } catch (err) {
2326
+ const isNotFound = err?.code === "ENOENT";
2327
+ if (isNotFound && wasRelative) {
2328
+ throw new Error(
2329
+ `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).`
2330
+ );
2331
+ }
2332
+ throw err;
2333
+ }
2003
2334
  const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
2004
- const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName);
2335
+ if (options.transformOnly === true) {
2336
+ const code = transpileReactSource(rawSource, path.basename(filePath));
2337
+ const parsed2 = parseImports(code);
2338
+ return {
2339
+ mode: "module",
2340
+ code,
2341
+ imports: [...new Set(parsed2.externalImports.map((i) => i.specifier))],
2342
+ exportName: componentName,
2343
+ meta: { mcpAware: true, renderer: "react" },
2344
+ peerDependencies: source.peerDependencies || ["react", "react-dom"],
2345
+ bundled: false
2346
+ };
2347
+ }
2348
+ const bundled = bundleFileSource(rawSource, source.file, path.dirname(filePath), componentName, {
2349
+ bundleReact: options.inlineReact === true
2350
+ });
2005
2351
  const parsed = parseImports(bundled.code);
2006
2352
  return {
2007
2353
  mode: "module",
@@ -2069,37 +2415,19 @@ function generateMappedPropsCode(mapping) {
2069
2415
  return `({ ${entries} })`;
2070
2416
  }
2071
2417
 
2072
- // libs/uipack/src/resolver/import-map.ts
2073
- function createImportMapFromResolved(resolved) {
2074
- const imports = {};
2075
- const integrity = {};
2076
- for (const [specifier, res] of Object.entries(resolved)) {
2077
- if (res.type === "url") {
2078
- imports[specifier] = res.value;
2079
- if (res.integrity) {
2080
- integrity[res.value] = res.integrity;
2081
- }
2082
- }
2083
- }
2084
- return {
2085
- imports,
2086
- integrity: Object.keys(integrity).length > 0 ? integrity : void 0
2087
- };
2088
- }
2089
- function generateImportMapScriptTag(map) {
2090
- const json = JSON.stringify(map, null, 2).replace(/<\//g, "<\\/");
2091
- return `<script type="importmap">
2092
- ${json}
2093
- </script>`;
2094
- }
2095
-
2096
2418
  // libs/uipack/src/component/renderer.ts
2419
+ var DEFAULT_WIDGET_MOUNT = {
2420
+ moduleSpecifier: "@frontmcp/ui/react",
2421
+ wrapperImportName: "McpBridgeProvider"
2422
+ };
2097
2423
  function renderComponent(config, shellConfig) {
2098
2424
  const resolver = shellConfig.resolver ?? createEsmShResolver();
2099
2425
  const resolved = resolveUISource(config.source, {
2100
2426
  resolver,
2101
2427
  input: shellConfig.input,
2102
- output: shellConfig.output
2428
+ output: shellConfig.output,
2429
+ inlineReact: config.inlineReact === true,
2430
+ transformOnly: config.transformOnly === true
2103
2431
  });
2104
2432
  const mergedShellConfig = {
2105
2433
  ...shellConfig,
@@ -2110,10 +2438,10 @@ function renderComponent(config, shellConfig) {
2110
2438
  if (resolved.mode === "inline") {
2111
2439
  return buildShell(resolved.html ?? "", mergedShellConfig);
2112
2440
  }
2113
- const content = buildModuleContent(resolved, resolver, config.props);
2441
+ const content = buildModuleContent(resolved, resolver, config.props, shellConfig.mount);
2114
2442
  return buildShell(content, mergedShellConfig);
2115
2443
  }
2116
- function buildModuleContent(resolved, resolver, propsMapping) {
2444
+ function buildModuleContent(resolved, resolver, propsMapping, mount = DEFAULT_WIDGET_MOUNT) {
2117
2445
  const parts = [];
2118
2446
  if (resolved.code && resolved.bundled) {
2119
2447
  const importEntries = {};
@@ -2137,14 +2465,16 @@ ${resolved.code}
2137
2465
  ...resolved.imports || [],
2138
2466
  "react-dom/client",
2139
2467
  // needed by mount script
2140
- "@frontmcp/ui/react",
2141
- // needed for McpBridgeProvider
2468
+ mount.moduleSpecifier,
2469
+ // mounter/provider package (e.g. @frontmcp/ui/react)
2142
2470
  // React subpath entries needed by esm.sh externalized modules:
2143
2471
  "react/jsx-runtime",
2144
- "react/jsx-dev-runtime",
2145
- "react-dom/server",
2146
- "react-dom/static"
2472
+ "react/jsx-dev-runtime"
2147
2473
  ]);
2474
+ if (mount === DEFAULT_WIDGET_MOUNT) {
2475
+ allSpecifiers.add("react-dom/server");
2476
+ allSpecifiers.add("react-dom/static");
2477
+ }
2148
2478
  const coreDeps = /* @__PURE__ */ new Set([
2149
2479
  "react",
2150
2480
  "react-dom",
@@ -2175,8 +2505,9 @@ ${resolved.code}
2175
2505
  const importMap = createImportMapFromResolved(importEntries);
2176
2506
  parts.push(generateImportMapScriptTag(importMap));
2177
2507
  }
2178
- parts.push('<div id="root"></div>');
2179
- const mountCode = generateInlineMountCode(resolved.exportName);
2508
+ const mountNodeId = mount.mountNodeId ?? "root";
2509
+ parts.push(`<div id="${mountNodeId}">${mount.mountNodeInnerHtml ?? ""}</div>`);
2510
+ const mountCode = generateInlineMountCode(resolved.exportName, mount);
2180
2511
  parts.push(`<script type="module">
2181
2512
  ${resolved.code}
2182
2513
  ${mountCode}
@@ -2213,53 +2544,27 @@ function addExternalParam(url, externals) {
2213
2544
  const sep = url.includes("?") ? "&" : "?";
2214
2545
  return `${url}${sep}external=${externals.join(",")}`;
2215
2546
  }
2216
- function generateInlineMountCode(componentName) {
2547
+ function generateInlineMountCode(componentName, mount) {
2548
+ if (mount.generate) {
2549
+ return mount.generate(componentName);
2550
+ }
2551
+ const wrapperName = mount.wrapperImportName ?? "McpBridgeProvider";
2552
+ const wrapperAlias = `__${wrapperName}`;
2553
+ const mountNodeId = mount.mountNodeId ?? "root";
2217
2554
  return `
2218
2555
  // --- Mount ---
2219
2556
  import { createRoot as __createRoot } from 'react-dom/client';
2220
- import { McpBridgeProvider as __McpBridgeProvider } from '@frontmcp/ui/react';
2221
- const __root = document.getElementById('root');
2557
+ import { ${wrapperName} as ${wrapperAlias} } from '${mount.moduleSpecifier}';
2558
+ const __root = document.getElementById('${mountNodeId}');
2222
2559
  if (__root) {
2223
2560
  __createRoot(__root).render(
2224
- React.createElement(__McpBridgeProvider, null,
2561
+ React.createElement(${wrapperAlias}, null,
2225
2562
  React.createElement(${componentName})
2226
2563
  )
2227
2564
  );
2228
2565
  }`;
2229
2566
  }
2230
2567
 
2231
- // libs/uipack/src/adapters/type-detector.ts
2232
- function detectUIType(template) {
2233
- if (template === null || template === void 0) {
2234
- return "auto";
2235
- }
2236
- if (typeof template === "object" && template !== null && "file" in template) {
2237
- const file = template.file;
2238
- if (/\.(tsx|jsx)$/i.test(file)) return "react";
2239
- }
2240
- if (typeof template === "function") {
2241
- const proto = template.prototype;
2242
- if (proto && typeof proto.render === "function") {
2243
- return "react";
2244
- }
2245
- const asRecord = template;
2246
- if (asRecord["$$typeof"] !== void 0) {
2247
- return "react";
2248
- }
2249
- if (template.name && /^[A-Z]/.test(template.name)) {
2250
- return "react";
2251
- }
2252
- return "html";
2253
- }
2254
- if (typeof template === "string") {
2255
- if (template.includes("<") && template.includes(">")) {
2256
- return "html";
2257
- }
2258
- return "markdown";
2259
- }
2260
- return "auto";
2261
- }
2262
-
2263
2568
  // libs/uipack/src/adapters/content-detector.ts
2264
2569
  var CHART_TYPES = /* @__PURE__ */ new Set(["bar", "line", "pie", "area", "scatter", "doughnut", "radar", "polarArea", "bubble"]);
2265
2570
  var MERMAID_PREFIXES = [
@@ -2400,6 +2705,38 @@ function wrapDetectedContent(value) {
2400
2705
  }
2401
2706
  }
2402
2707
 
2708
+ // libs/uipack/src/adapters/type-detector.ts
2709
+ function detectUIType(template) {
2710
+ if (template === null || template === void 0) {
2711
+ return "auto";
2712
+ }
2713
+ if (typeof template === "object" && template !== null && "file" in template) {
2714
+ const file = template.file;
2715
+ if (/\.(tsx|jsx)$/i.test(file)) return "react";
2716
+ }
2717
+ if (typeof template === "function") {
2718
+ const proto = template.prototype;
2719
+ if (proto && typeof proto.render === "function") {
2720
+ return "react";
2721
+ }
2722
+ const asRecord = template;
2723
+ if (asRecord["$$typeof"] !== void 0) {
2724
+ return "react";
2725
+ }
2726
+ if (template.name && /^[A-Z]/.test(template.name)) {
2727
+ return "react";
2728
+ }
2729
+ return "html";
2730
+ }
2731
+ if (typeof template === "string") {
2732
+ if (template.includes("<") && template.includes(">")) {
2733
+ return "html";
2734
+ }
2735
+ return "markdown";
2736
+ }
2737
+ return "auto";
2738
+ }
2739
+
2403
2740
  // libs/uipack/src/adapters/template-renderer.ts
2404
2741
  function buildCspConfig(resolver) {
2405
2742
  const cspResourceDomains = ["https://esm.sh"];
@@ -2420,21 +2757,26 @@ function buildCspConfig(resolver) {
2420
2757
  return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
2421
2758
  }
2422
2759
  function renderToolTemplate(options) {
2423
- const { toolName, input, output, template, resolver } = options;
2760
+ const { toolName, input, output, template, resolver, platformType, sizing } = options;
2424
2761
  const uiType = detectUIType(template);
2762
+ const resourceMode = options.resourceMode ?? (platformType === "claude" ? "inline" : "cdn");
2425
2763
  const shellConfig = {
2426
2764
  toolName,
2427
2765
  input,
2428
2766
  output,
2429
2767
  includeBridge: true,
2430
- resolver
2768
+ resolver,
2769
+ sizing
2431
2770
  };
2432
2771
  let html;
2433
2772
  let hash = "";
2434
2773
  let size = 0;
2435
2774
  if (typeof template === "object" && template !== null && "file" in template) {
2436
2775
  const cspConfig = buildCspConfig(resolver);
2437
- const result = renderComponent({ source: template }, { ...shellConfig, csp: cspConfig });
2776
+ const result = renderComponent(
2777
+ { source: template, inlineReact: resourceMode === "inline" },
2778
+ { ...shellConfig, csp: cspConfig }
2779
+ );
2438
2780
  html = result.html;
2439
2781
  hash = result.hash;
2440
2782
  size = result.size;
@@ -2476,6 +2818,12 @@ function renderToolTemplate(options) {
2476
2818
  "ui/type": uiType,
2477
2819
  "ui/mimeType": MCP_APPS_MIME_TYPE
2478
2820
  };
2821
+ if (hasSizing(sizing)) {
2822
+ if (sizing.preferredHeight !== void 0) meta["ui/preferredHeight"] = sizing.preferredHeight;
2823
+ if (sizing.minHeight !== void 0) meta["ui/minHeight"] = sizing.minHeight;
2824
+ if (sizing.maxHeight !== void 0) meta["ui/maxHeight"] = sizing.maxHeight;
2825
+ if (sizing.aspectRatio !== void 0) meta["ui/aspectRatio"] = sizing.aspectRatio;
2826
+ }
2479
2827
  return {
2480
2828
  html,
2481
2829
  uiType,