@frontmcp/uipack 1.8.6 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -518,12 +518,38 @@ function __applyHostTheme(theme) {
518
518
  } catch (e) {}
519
519
  }
520
520
 
521
+ function extractToolOutput(params) {
522
+ // A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
523
+ if (!params) return undefined;
524
+ if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
525
+ var content = params.content;
526
+ if (Array.isArray(content)) {
527
+ for (var i = 0; i < content.length; i++) {
528
+ var item = content[i];
529
+ if (item && item.type === 'text' && typeof item.text === 'string') {
530
+ try { return JSON.parse(item.text); } catch (e) { return item.text; }
531
+ }
532
+ }
533
+ }
534
+ return content;
535
+ }
536
+
521
537
  function readInjectedData() {
522
538
  var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
523
539
  if (typeof window !== 'undefined') {
524
540
  if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
525
541
  if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
526
542
  if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
543
+ // The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
544
+ // (toolOutput is the tool's structuredContent).
545
+ var oa = window.openai;
546
+ if (oa && typeof oa === 'object') {
547
+ if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
548
+ if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
549
+ if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
550
+ if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
551
+ }
552
+ }
527
553
  }
528
554
  return data;
529
555
  }
@@ -572,7 +598,24 @@ var OpenAIAdapter = {
572
598
  if (sdk.displayMode) {
573
599
  context.hostContext.displayMode = sdk.displayMode;
574
600
  }
575
- // Note: OpenAI SDK does not have an onContextChange equivalent
601
+ // The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
602
+ window.addEventListener('openai:set_globals', function(event) {
603
+ var globals = (event && event.detail && event.detail.globals) || window.openai || {};
604
+ if (globals.toolInput !== undefined && globals.toolInput !== null) {
605
+ context.toolInput = globals.toolInput;
606
+ window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
607
+ }
608
+ if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
609
+ context.structuredContent = globals.toolOutput;
610
+ context.notifyToolResult(globals.toolOutput);
611
+ }
612
+ if (globals.theme && globals.theme !== context.hostContext.theme) {
613
+ context.notifyContextChange({ theme: globals.theme });
614
+ }
615
+ if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
616
+ context.notifyContextChange({ displayMode: globals.displayMode });
617
+ }
618
+ });
576
619
  return Promise.resolve();
577
620
  },
578
621
  callTool: function(context, name, args) {
@@ -719,9 +762,10 @@ var ExtAppsAdapter = {
719
762
  window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
720
763
  break;
721
764
  case 'ui/notifications/tool-result':
722
- context.toolOutput = params.content;
765
+ var resultOutput = extractToolOutput(params);
766
+ context.toolOutput = resultOutput;
723
767
  context.structuredContent = params.structuredContent;
724
- context.notifyToolResult(params.content);
768
+ context.notifyToolResult(resultOutput);
725
769
  window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
726
770
  break;
727
771
  case 'ui/notifications/host-context-changed':
@@ -775,6 +819,15 @@ var ExtAppsAdapter = {
775
819
  window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
776
820
  });
777
821
  },
822
+ sendNotification: function(method, params) {
823
+ // JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
824
+ if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
825
+ return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
826
+ }
827
+ var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
828
+ window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
829
+ return Promise.resolve();
830
+ },
778
831
  sendHandshake: function(context) {
779
832
  // Send ui/initialize using '*' as target origin since TOFU hasn't
780
833
  // been established yet. The response from the host will establish
@@ -840,7 +893,7 @@ var ExtAppsAdapter = {
840
893
  var pending = this.pendingSize;
841
894
  if (!pending) return;
842
895
  this.pendingSize = null;
843
- this.sendRequest('ui/setSize', pending.params).then(function(result) {
896
+ this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
844
897
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
845
898
  }, function(err) {
846
899
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
@@ -867,13 +920,11 @@ var ExtAppsAdapter = {
867
920
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
868
921
  },
869
922
  setSize: function(context, size) {
870
- // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
871
- // measured/desired widget dimensions to the host.
872
- var params = {
873
- height: size && size.height,
874
- width: size && size.width,
875
- aspectRatio: size && size.aspectRatio
876
- };
923
+ // Standard ext-apps sizing: the view tells the host its dimensions with the
924
+ // ui/notifications/size-changed notification ({ width?, height? } in px).
925
+ var params = {};
926
+ if (size && typeof size.width === 'number') params.width = size.width;
927
+ if (size && typeof size.height === 'number') params.height = size.height;
877
928
  if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
878
929
  // No host origin yet: keep only the latest size and send it when the
879
930
  // handshake settles; every caller's promise follows that one request.
@@ -884,7 +935,7 @@ var ExtAppsAdapter = {
884
935
  self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
885
936
  });
886
937
  }
887
- return this.sendRequest('ui/setSize', params);
938
+ return this.sendNotification('ui/notifications/size-changed', params);
888
939
  },
889
940
  requestClose: function(context) {
890
941
  return this.sendRequest('ui/close', {});
@@ -1307,7 +1358,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1307
1358
 
1308
1359
  // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1309
1360
  // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1310
- // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1361
+ // ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
1311
1362
  FrontMcpBridge.prototype.setSize = function(size) {
1312
1363
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1313
1364
  if (!this._adapter.setSize) return Promise.resolve();
@@ -1409,7 +1460,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
1409
1460
  target.classList.add('opacity-50', 'cursor-not-allowed');
1410
1461
 
1411
1462
  // Add spinner for buttons
1412
- var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
1463
+ var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" style="width:1em;height:1em;vertical-align:-0.125em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
1413
1464
  if (target.tagName === 'BUTTON') {
1414
1465
  target.innerHTML = spinner + 'Loading...';
1415
1466
  }
@@ -1490,7 +1541,22 @@ function minifyJS(code) {
1490
1541
  if (code.length > MAX_MINIFY_CODE_LENGTH) {
1491
1542
  return code;
1492
1543
  }
1493
- return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
1544
+ const out = [];
1545
+ let inBlockComment = false;
1546
+ for (const raw of code.split("\n")) {
1547
+ const line = raw.trim();
1548
+ if (inBlockComment) {
1549
+ if (line.includes("*/")) inBlockComment = false;
1550
+ continue;
1551
+ }
1552
+ if (line === "" || line.startsWith("//")) continue;
1553
+ if (line.startsWith("/*")) {
1554
+ if (!line.includes("*/")) inBlockComment = true;
1555
+ continue;
1556
+ }
1557
+ out.push(line);
1558
+ }
1559
+ return out.join("\n");
1494
1560
  }
1495
1561
  function generatePlatformBundle(platform, options = {}) {
1496
1562
  const platformAdapters = {
@@ -3027,11 +3093,8 @@ function buildCSPDirectives(csp) {
3027
3093
  directives.push(`img-src ${imgSources.join(" ")}`);
3028
3094
  const fontSources = ["'self'", "data:", ...allResourceDomains];
3029
3095
  directives.push(`font-src ${fontSources.join(" ")}`);
3030
- if (validConnectDomains.length) {
3031
- directives.push(`connect-src ${validConnectDomains.join(" ")}`);
3032
- } else {
3033
- directives.push(`connect-src ${allResourceDomains.join(" ")}`);
3034
- }
3096
+ const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
3097
+ directives.push(`connect-src ${connectSources.join(" ")}`);
3035
3098
  directives.push("object-src 'self' data:");
3036
3099
  return directives;
3037
3100
  }
@@ -3040,26 +3103,34 @@ function buildCSPMetaTag(csp) {
3040
3103
  const content = directives.join("; ");
3041
3104
  return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
3042
3105
  }
3106
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
3043
3107
  function validateCSPDomain(domain) {
3044
- if (domain.startsWith("https://*.")) {
3045
- const rest = domain.slice(10);
3046
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
3108
+ if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
3109
+ if (/[?#]/.test(domain)) return false;
3110
+ if (domain.includes("*")) {
3111
+ const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
3112
+ return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
3047
3113
  }
3048
3114
  try {
3049
3115
  const url = new URL(domain);
3050
- return url.protocol === "https:";
3116
+ if (url.protocol === "https:" || url.protocol === "wss:") return true;
3117
+ return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
3051
3118
  } catch {
3052
3119
  return false;
3053
3120
  }
3054
3121
  }
3122
+ var reportedInvalidDomains = /* @__PURE__ */ new Set();
3055
3123
  function sanitizeCSPDomains(domains) {
3056
3124
  if (!domains) return [];
3057
3125
  const valid = [];
3058
3126
  for (const domain of domains) {
3059
3127
  if (validateCSPDomain(domain)) {
3060
3128
  valid.push(domain);
3061
- } else {
3062
- console.warn(`Invalid CSP domain ignored: ${domain}`);
3129
+ } else if (!reportedInvalidDomains.has(domain)) {
3130
+ reportedInvalidDomains.add(domain);
3131
+ console.warn(
3132
+ `Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
3133
+ );
3063
3134
  }
3064
3135
  }
3065
3136
  return valid;
@@ -3596,8 +3667,8 @@ if (__root) {
3596
3667
  configurable: true, enumerable: true
3597
3668
  });
3598
3669
  }
3599
- // 2. Try injected data globals
3600
- if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
3670
+ // 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
3671
+ if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
3601
3672
  // 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
3602
3673
  var __bridge = window.FrontMcpBridge;
3603
3674
  if (__bridge && typeof __bridge.onToolResult === 'function') {
@@ -3849,7 +3920,7 @@ createRoot(document.getElementById('root'))
3849
3920
  return `${componentImport}
3850
3921
  import React from 'react';
3851
3922
  import { createRoot } from 'react-dom/client';
3852
- const output = window.__mcpToolOutput;
3923
+ const output = window.__mcpToolOutput || {};
3853
3924
  const props = ${propsCode};
3854
3925
  createRoot(document.getElementById('root'))
3855
3926
  .render(React.createElement(Component, props));`;
@@ -4222,6 +4293,86 @@ function wrapDetectedContent(value) {
4222
4293
  }
4223
4294
  }
4224
4295
 
4296
+ // libs/uipack/src/adapters/markdown.ts
4297
+ var SAFE_HREF = /^(https?:|mailto:|\/|#)/i;
4298
+ function emphasis(text) {
4299
+ return text.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
4300
+ }
4301
+ function inline(text) {
4302
+ const stash = [];
4303
+ const hold = (html2) => `\uE000${stash.push(html2) - 1}\uE000`;
4304
+ let out = escapeHtml(text.replace(/\uE000/g, "")).replace(
4305
+ /`([^`]+)`/g,
4306
+ (_m, code) => hold(`<code>${code}</code>`)
4307
+ );
4308
+ out = out.replace(
4309
+ /\[([^\]]+)\]\(([^)\s]+)\)/g,
4310
+ (_m, label, href) => SAFE_HREF.test(href) ? hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`) : label
4311
+ );
4312
+ out = emphasis(out);
4313
+ for (let i = 0; i < 2; i++) out = out.replace(/\uE000(\d+)\uE000/g, (_m, n) => stash[Number(n)]);
4314
+ return out;
4315
+ }
4316
+ function markdownToHtml(source) {
4317
+ const lines = source.replace(/\r\n?/g, "\n").split("\n");
4318
+ const blocks = [];
4319
+ let paragraph = [];
4320
+ let list;
4321
+ const flushParagraph = () => {
4322
+ if (paragraph.length > 0) blocks.push(`<p>${inline(paragraph.join(" "))}</p>`);
4323
+ paragraph = [];
4324
+ };
4325
+ const flushList = () => {
4326
+ if (list)
4327
+ blocks.push(`<${list.tag}>${list.items.map((item) => `<li>${inline(item)}</li>`).join("")}</${list.tag}>`);
4328
+ list = void 0;
4329
+ };
4330
+ for (let i = 0; i < lines.length; i++) {
4331
+ const line = lines[i];
4332
+ const fence = /^\s*(`{3,})/.exec(line);
4333
+ if (fence) {
4334
+ flushParagraph();
4335
+ flushList();
4336
+ const fenceLength = fence[1].length;
4337
+ const isClosing = (l) => {
4338
+ const m = /^\s*(`{3,})\s*$/.exec(l);
4339
+ return m !== null && m[1].length >= fenceLength;
4340
+ };
4341
+ const code = [];
4342
+ for (i++; i < lines.length && !isClosing(lines[i]); i++) code.push(lines[i]);
4343
+ blocks.push(`<pre><code>${escapeHtml(code.join("\n"))}
4344
+ </code></pre>`);
4345
+ continue;
4346
+ }
4347
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line);
4348
+ if (heading) {
4349
+ flushParagraph();
4350
+ flushList();
4351
+ blocks.push(`<h${heading[1].length}>${inline(heading[2].trim())}</h${heading[1].length}>`);
4352
+ continue;
4353
+ }
4354
+ const item = /^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);
4355
+ if (item) {
4356
+ flushParagraph();
4357
+ const tag = /\d/.test(item[1]) ? "ol" : "ul";
4358
+ if (list && list.tag !== tag) flushList();
4359
+ list ??= { tag, items: [] };
4360
+ list.items.push(item[2]);
4361
+ continue;
4362
+ }
4363
+ if (line.trim() === "") {
4364
+ flushParagraph();
4365
+ flushList();
4366
+ continue;
4367
+ }
4368
+ flushList();
4369
+ paragraph.push(line.trim());
4370
+ }
4371
+ flushParagraph();
4372
+ flushList();
4373
+ return blocks.join("\n");
4374
+ }
4375
+
4225
4376
  // libs/uipack/src/adapters/type-detector.ts
4226
4377
  function detectUIType(template) {
4227
4378
  if (template === null || template === void 0) {
@@ -4255,9 +4406,15 @@ function detectUIType(template) {
4255
4406
  }
4256
4407
 
4257
4408
  // libs/uipack/src/adapters/template-renderer.ts
4258
- function buildCspConfig(resolver) {
4409
+ function buildCspConfig(resolver, extra) {
4259
4410
  const cspResourceDomains = ["https://esm.sh"];
4260
4411
  const cspConnectDomains = ["https://esm.sh"];
4412
+ for (const domain of extra?.resourceDomains ?? []) {
4413
+ if (!cspResourceDomains.includes(domain)) cspResourceDomains.push(domain);
4414
+ }
4415
+ for (const domain of extra?.connectDomains ?? []) {
4416
+ if (!cspConnectDomains.includes(domain)) cspConnectDomains.push(domain);
4417
+ }
4261
4418
  if (resolver && "overrides" in resolver) {
4262
4419
  const overrides = resolver.overrides;
4263
4420
  if (overrides) {
@@ -4297,6 +4454,24 @@ function renderUnwrappedResult(rawResult, options) {
4297
4454
  }
4298
4455
  return rawResult;
4299
4456
  }
4457
+ var warnedComponentReferences = /* @__PURE__ */ new WeakMap();
4458
+ function warnComponentReference(template, toolName, logger) {
4459
+ const warned = warnedComponentReferences.get(template) ?? /* @__PURE__ */ new Set();
4460
+ if (warned.has(toolName)) return;
4461
+ warned.add(toolName);
4462
+ warnedComponentReferences.set(template, warned);
4463
+ logger.warn(
4464
+ `[FrontMCP] Tool "${toolName}": \`ui.template\` is a React component reference, which cannot be bundled (there is no source file to compile), so the widget would render an empty page. Use \`template: { file: './widget.tsx' }\` instead.`
4465
+ );
4466
+ }
4467
+ function isReactElement(value) {
4468
+ return typeof value === "object" && value !== null && "$$typeof" in value;
4469
+ }
4470
+ function isDefinitelyReactComponent(template) {
4471
+ if (typeof template !== "function") return false;
4472
+ const proto = template.prototype;
4473
+ return typeof proto?.render === "function" || template["$$typeof"] !== void 0;
4474
+ }
4300
4475
  function renderToolTemplate(options) {
4301
4476
  const { toolName, input, output, template, resolver, platformType, sizing } = options;
4302
4477
  const uiType = detectUIType(template);
@@ -4307,30 +4482,41 @@ function renderToolTemplate(options) {
4307
4482
  output,
4308
4483
  includeBridge: true,
4309
4484
  resolver,
4310
- sizing
4485
+ sizing,
4486
+ csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
4311
4487
  };
4312
4488
  let html2;
4313
4489
  let hash = "";
4314
4490
  let size = 0;
4315
4491
  if (typeof template === "object" && template !== null && "file" in template) {
4316
- const cspConfig = buildCspConfig(resolver);
4317
4492
  const result = renderComponent(
4318
4493
  { source: template, inlineReact: resourceMode === "inline" },
4319
- { ...shellConfig, csp: cspConfig }
4494
+ { ...shellConfig, csp: buildCspConfig(resolver, options.csp) }
4320
4495
  );
4321
4496
  html2 = result.html;
4322
4497
  hash = result.hash;
4323
4498
  size = result.size;
4324
4499
  } else if (typeof template === "function") {
4325
- if (uiType === "react") {
4500
+ const helpers = createTemplateHelpers();
4501
+ const ctx = { input: input ?? {}, output: output ?? {}, helpers };
4502
+ let rawResult;
4503
+ let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
4504
+ if (isHtmlBuilder) {
4505
+ try {
4506
+ rawResult = template(ctx);
4507
+ isHtmlBuilder = !isReactElement(rawResult);
4508
+ } catch (error) {
4509
+ if (uiType !== "react") throw error;
4510
+ isHtmlBuilder = false;
4511
+ }
4512
+ }
4513
+ if (!isHtmlBuilder) {
4514
+ warnComponentReference(template, toolName, options.logger ?? console);
4326
4515
  const shellResult = buildShell('<div id="root"></div>', shellConfig);
4327
4516
  html2 = shellResult.html;
4328
4517
  hash = shellResult.hash;
4329
4518
  size = shellResult.size;
4330
4519
  } else {
4331
- const helpers = createTemplateHelpers();
4332
- const ctx = { input, output, helpers };
4333
- const rawResult = template(ctx);
4334
4520
  const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
4335
4521
  if (wrapped) {
4336
4522
  html2 = wrapped;
@@ -4342,7 +4528,7 @@ function renderToolTemplate(options) {
4342
4528
  }
4343
4529
  }
4344
4530
  } else if (typeof template === "string") {
4345
- const shellResult = buildShell(template, shellConfig);
4531
+ const shellResult = buildShell(uiType === "markdown" ? markdownToHtml(template) : template, shellConfig);
4346
4532
  html2 = shellResult.html;
4347
4533
  hash = shellResult.hash;
4348
4534
  size = shellResult.size;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.8.6",
3
+ "version": "1.9.0",
4
4
  "description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -61,7 +61,7 @@
61
61
  "typescript": "^5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@frontmcp/lazy-zod": "1.8.6",
64
+ "@frontmcp/lazy-zod": "1.9.0",
65
65
  "esbuild": ">=0.27.0 <1"
66
66
  },
67
67
  "peerDependenciesMeta": {
package/shell/csp.d.ts CHANGED
@@ -28,10 +28,15 @@ export declare function buildCSPDirectives(csp?: CSPConfig): string[];
28
28
  export declare function buildCSPMetaTag(csp?: CSPConfig): string;
29
29
  /**
30
30
  * Validate CSP domain format.
31
+ *
32
+ * Accepts `https://` and `wss://` origins (a WebSocket API needs `wss://` in `connect-src`),
33
+ * their `https://*.` / `wss://*.` wildcard forms, and `http://` / `ws://` origins on a loopback
34
+ * host (`localhost`, `127.0.0.1`, `[::1]`) for local development. A value with a query or a
35
+ * fragment is rejected.
31
36
  */
32
37
  export declare function validateCSPDomain(domain: string): boolean;
33
38
  /**
34
- * Filter and warn about invalid CSP domains.
39
+ * Filter out invalid CSP domains, warning once per domain.
35
40
  */
36
41
  export declare function sanitizeCSPDomains(domains: string[] | undefined): string[];
37
42
  //# sourceMappingURL=csp.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../src/shell/csp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;GAEG;AACH,eAAO,MAAM,mBAAmB,sJAMtB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,sBAAsB,uPAQzB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,0BAA0B,4MAQ7B,CAAC;AAEX;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,CA+B5D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,MAAM,CAIvD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAYzD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAa1E"}
1
+ {"version":3,"file":"csp.d.ts","sourceRoot":"","sources":["../../src/shell/csp.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC;;GAEG;AACH,eAAO,MAAM,mBAAmB,sJAMtB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,sBAAsB,uPAQzB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,0BAA0B,4MAQ7B,CAAC;AAEX;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,MAAM,EAAE,CA8B5D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,MAAM,CAIvD;AAKD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAkBzD;AAKD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAgB1E"}