semiotic 3.7.0 → 3.7.2

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 (70) hide show
  1. package/CLAUDE.md +3 -2
  2. package/README.md +46 -7
  3. package/ai/behaviorContracts.cjs +19 -9
  4. package/ai/cli.js +15 -0
  5. package/ai/dist/mcp-server.js +583 -66
  6. package/ai/schema.json +1 -1
  7. package/ai/system-prompt.md +3 -3
  8. package/dist/components/AccessibleNavTree.d.ts +2 -1
  9. package/dist/components/Annotation.d.ts +1 -1
  10. package/dist/components/CategoryColors.d.ts +1 -1
  11. package/dist/components/ChartGrid.d.ts +1 -1
  12. package/dist/components/ContextLayout.d.ts +1 -1
  13. package/dist/components/DataSummaryContext.d.ts +1 -1
  14. package/dist/components/DetailsPanel.d.ts +1 -1
  15. package/dist/components/Legend.d.ts +3 -2
  16. package/dist/components/LinkedCharts.d.ts +1 -1
  17. package/dist/components/ThemeProvider.d.ts +1 -1
  18. package/dist/components/Tooltip/FlippingTooltip.d.ts +1 -1
  19. package/dist/components/Tooltip/Tooltip.d.ts +2 -2
  20. package/dist/components/ai/qualityScorecard.d.ts +11 -0
  21. package/dist/components/charts/geo/ChoroplethMap.d.ts +2 -1
  22. package/dist/components/charts/network/CirclePack.d.ts +2 -1
  23. package/dist/components/charts/network/OrbitDiagram.d.ts +1 -1
  24. package/dist/components/charts/network/TreeDiagram.d.ts +2 -1
  25. package/dist/components/charts/network/Treemap.d.ts +2 -1
  26. package/dist/components/charts/realtime/RealtimeHistogram.d.ts +1 -1
  27. package/dist/components/charts/shared/ChartError.d.ts +2 -1
  28. package/dist/components/charts/shared/withChartWrapper.d.ts +1 -1
  29. package/dist/components/charts/xy/MinimapChart.d.ts +2 -1
  30. package/dist/components/charts/xy/ScatterplotMatrix.d.ts +2 -1
  31. package/dist/components/semiotic-server.d.ts +2 -1
  32. package/dist/components/server/renderEvidence.d.ts +92 -0
  33. package/dist/components/server/renderToStaticSVG.d.ts +14 -1
  34. package/dist/components/stream/AccessibleDataTable.d.ts +5 -5
  35. package/dist/components/stream/FocusRing.d.ts +2 -1
  36. package/dist/components/stream/MarginalGraphics.d.ts +2 -1
  37. package/dist/components/stream/NetworkCanvasHitTester.d.ts +3 -2
  38. package/dist/components/stream/NetworkPipelineStore.d.ts +46 -1
  39. package/dist/components/stream/NetworkSVGOverlay.d.ts +2 -1
  40. package/dist/components/stream/OrdinalBrushOverlay.d.ts +19 -1
  41. package/dist/components/stream/OrdinalSVGOverlay.d.ts +2 -2
  42. package/dist/components/stream/PipelineStore.d.ts +6 -0
  43. package/dist/components/stream/SVGOverlay.d.ts +3 -2
  44. package/dist/components/stream/StalenessBadge.d.ts +19 -0
  45. package/dist/components/stream/XYBrushOverlay.d.ts +21 -1
  46. package/dist/components/stream/quadtreeHitTest.d.ts +7 -1
  47. package/dist/geo.min.js +1 -1
  48. package/dist/geo.module.min.js +1 -1
  49. package/dist/network.min.js +1 -1
  50. package/dist/network.module.min.js +1 -1
  51. package/dist/ordinal.min.js +1 -1
  52. package/dist/ordinal.module.min.js +1 -1
  53. package/dist/realtime.min.js +1 -1
  54. package/dist/realtime.module.min.js +1 -1
  55. package/dist/semiotic-ai.min.js +1 -1
  56. package/dist/semiotic-ai.module.min.js +1 -1
  57. package/dist/semiotic-server.d.ts +2 -1
  58. package/dist/semiotic-themes.min.js +1 -1
  59. package/dist/semiotic-themes.module.min.js +1 -1
  60. package/dist/semiotic-utils.min.js +1 -1
  61. package/dist/semiotic-utils.module.min.js +1 -1
  62. package/dist/semiotic-value.min.js +1 -1
  63. package/dist/semiotic-value.module.min.js +1 -1
  64. package/dist/semiotic.min.js +1 -1
  65. package/dist/semiotic.module.min.js +1 -1
  66. package/dist/server.min.js +1 -1
  67. package/dist/server.module.min.js +1 -1
  68. package/dist/xy.min.js +1 -1
  69. package/dist/xy.module.min.js +1 -1
  70. package/package.json +2 -2
@@ -7,7 +7,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ try {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ } catch (e) {
13
+ throw mod = 0, e;
14
+ }
11
15
  };
12
16
  var __export = (target, all) => {
13
17
  for (var name in all)
@@ -7030,7 +7034,7 @@ var require_chartSuggestions = __commonJS({
7030
7034
  "ai/chartSuggestions.cjs"(exports2, module2) {
7031
7035
  "use strict";
7032
7036
  var path2 = require("path");
7033
- var VALID_INTENTS = [
7037
+ var VALID_INTENTS2 = [
7034
7038
  "comparison",
7035
7039
  "trend",
7036
7040
  "distribution",
@@ -7163,10 +7167,10 @@ var require_chartSuggestions = __commonJS({
7163
7167
  const data = args.data;
7164
7168
  const intent = args.intent;
7165
7169
  const capabilities = args.capabilities;
7166
- if (intent && !VALID_INTENTS.includes(intent)) {
7170
+ if (intent && !VALID_INTENTS2.includes(intent)) {
7167
7171
  return {
7168
7172
  ok: false,
7169
- error: `Unknown intent "${intent}". Expected one of: ${VALID_INTENTS.join(", ")}.`
7173
+ error: `Unknown intent "${intent}". Expected one of: ${VALID_INTENTS2.join(", ")}.`
7170
7174
  };
7171
7175
  }
7172
7176
  if (capabilities) {
@@ -7384,7 +7388,7 @@ ${result.filteredOut.map((s) => `- ${s.component}: ${s.reason}`).join("\n")}
7384
7388
 
7385
7389
  Relax the capability constraints, or use getSchema to browse alternatives.` : `
7386
7390
 
7387
- Try providing intent ('${VALID_INTENTS.join("', '")}') to narrow recommendations, or use getSchema to browse available components.`;
7391
+ Try providing intent ('${VALID_INTENTS2.join("', '")}') to narrow recommendations, or use getSchema to browse available components.`;
7388
7392
  return `Could not confidently recommend a chart type.
7389
7393
 
7390
7394
  ${result.fieldSummary}${tail}`;
@@ -7423,7 +7427,7 @@ For accessibility, use \`colorScheme={COLOR_BLIND_SAFE_CATEGORICAL}\` (import fr
7423
7427
  return Object.entries(capabilities).filter(([, v]) => v != null).map(([k, v]) => `${k}=${v}`).join(", ");
7424
7428
  }
7425
7429
  module2.exports = {
7426
- VALID_INTENTS,
7430
+ VALID_INTENTS: VALID_INTENTS2,
7427
7431
  VALID_CAPABILITY_KEYS,
7428
7432
  formatSuggestionReport: formatSuggestionReport2,
7429
7433
  suggestCharts: suggestCharts2,
@@ -7453,8 +7457,8 @@ var require_behaviorContracts = __commonJS({
7453
7457
  const schema2 = JSON.parse(fs2.readFileSync(schemaPath2, "utf8"));
7454
7458
  const out = /* @__PURE__ */ new Set();
7455
7459
  for (const tool of schema2.tools || []) {
7456
- const required2 = tool.function?.parameters?.required || [];
7457
- if (required2.includes("data")) out.add(tool.function.name);
7460
+ const properties = tool.function?.parameters?.properties || {};
7461
+ if ("data" in properties) out.add(tool.function.name);
7458
7462
  }
7459
7463
  if (out.size > 0) return out;
7460
7464
  } catch {
@@ -7520,6 +7524,10 @@ var require_behaviorContracts = __commonJS({
7520
7524
  "Scatterplot",
7521
7525
  "BubbleChart",
7522
7526
  "ConnectedScatterplot",
7527
+ "CandlestickChart",
7528
+ "MultiAxisLineChart",
7529
+ "QuadrantChart",
7530
+ "DifferenceChart",
7523
7531
  "BarChart",
7524
7532
  "StackedBarChart",
7525
7533
  "GroupedBarChart",
@@ -32433,22 +32441,27 @@ ${errors.join("\n")}`
32433
32441
  }
32434
32442
 
32435
32443
  // ai/mcp-server.ts
32444
+ var import_server2 = require("semiotic/server");
32436
32445
  var import_ai3 = require("semiotic/ai");
32446
+ var import_componentMetadata = __toESM(require_componentMetadata());
32447
+ var import_chartSuggestions = __toESM(require_chartSuggestions());
32448
+ var import_behaviorContracts = __toESM(require_behaviorContracts());
32437
32449
  var {
32438
32450
  componentIndexFromSchema,
32439
32451
  metadataForComponent
32440
- } = require_componentMetadata();
32452
+ } = import_componentMetadata.default;
32441
32453
  var {
32442
32454
  formatSuggestionReport,
32443
- suggestCharts
32444
- } = require_chartSuggestions();
32455
+ suggestCharts,
32456
+ VALID_INTENTS
32457
+ } = import_chartSuggestions.default;
32445
32458
  var {
32446
32459
  BEHAVIOR_CONTRACTS,
32447
32460
  behaviorContractsFor,
32448
32461
  dataRequiredForUsageMode,
32449
32462
  formatDoctorBehaviorContracts,
32450
32463
  normalizeUsageMode
32451
- } = require_behaviorContracts();
32464
+ } = import_behaviorContracts.default;
32452
32465
  var schemaPath = path.resolve(__dirname, "../schema.json");
32453
32466
  var schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
32454
32467
  var schemaByComponent = {};
@@ -32458,6 +32471,8 @@ for (const tool of schema.tools) {
32458
32471
  var allComponentNames = Object.keys(schemaByComponent).sort();
32459
32472
  var componentNames = Object.keys(COMPONENT_REGISTRY).sort();
32460
32473
  var REPO = "nteract/semiotic";
32474
+ var SEMIOTIC_CHART_WIDGET_URI = "ui://semiotic/chart-widget.html";
32475
+ var MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
32461
32476
  function aiFilePath(fileName) {
32462
32477
  return path.resolve(__dirname, "..", fileName);
32463
32478
  }
@@ -32476,6 +32491,30 @@ function textResource(uri, mimeType, text) {
32476
32491
  }]
32477
32492
  };
32478
32493
  }
32494
+ function appResource(uri, text) {
32495
+ return {
32496
+ contents: [{
32497
+ uri: uri.href,
32498
+ mimeType: MCP_APP_MIME_TYPE,
32499
+ text,
32500
+ _meta: {
32501
+ ui: {
32502
+ prefersBorder: true,
32503
+ csp: {
32504
+ connectDomains: [],
32505
+ resourceDomains: []
32506
+ }
32507
+ },
32508
+ "openai/widgetDescription": "Interactive Semiotic chart preview rendered by the semiotic-mcp server.",
32509
+ "openai/widgetPrefersBorder": true,
32510
+ "openai/widgetCSP": {
32511
+ connect_domains: [],
32512
+ resource_domains: []
32513
+ }
32514
+ }
32515
+ }]
32516
+ };
32517
+ }
32479
32518
  function promptMessage(text) {
32480
32519
  return {
32481
32520
  messages: [{
@@ -32487,6 +32526,279 @@ function promptMessage(text) {
32487
32526
  }]
32488
32527
  };
32489
32528
  }
32529
+ function stripUnsafeSvg(svg) {
32530
+ return svg.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\s(href|xlink:href)\s*=\s*(["'])\s*javascript:[^"']*\2/gi, "");
32531
+ }
32532
+ function parseRenderEvidence(result) {
32533
+ const evidenceText = result.content.find((block) => block.text.startsWith("Render evidence:\n"))?.text;
32534
+ if (!evidenceText) return null;
32535
+ try {
32536
+ return JSON.parse(evidenceText.replace(/^Render evidence:\n/, ""));
32537
+ } catch {
32538
+ return null;
32539
+ }
32540
+ }
32541
+ function chartTitleFromProps(component, props) {
32542
+ return typeof props.title === "string" && props.title.trim() ? props.title.trim() : component;
32543
+ }
32544
+ function chartDatumCount(props) {
32545
+ if (Array.isArray(props.data)) return props.data.length;
32546
+ if (Array.isArray(props.nodes)) return props.nodes.length;
32547
+ if (Array.isArray(props.edges)) return props.edges.length;
32548
+ if (Array.isArray(props.links)) return props.links.length;
32549
+ return null;
32550
+ }
32551
+ function renderSemioticChartWidgetHTML() {
32552
+ return `
32553
+ <!doctype html>
32554
+ <html lang="en">
32555
+ <head>
32556
+ <meta charset="utf-8" />
32557
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
32558
+ <style>
32559
+ :root {
32560
+ color-scheme: light dark;
32561
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
32562
+ --bg: Canvas;
32563
+ --fg: CanvasText;
32564
+ --muted: color-mix(in srgb, CanvasText 62%, Canvas 38%);
32565
+ --border: color-mix(in srgb, CanvasText 16%, Canvas 84%);
32566
+ --panel: color-mix(in srgb, Canvas 94%, CanvasText 6%);
32567
+ --accent: #2f6fed;
32568
+ }
32569
+ * { box-sizing: border-box; }
32570
+ body { margin: 0; background: var(--bg); color: var(--fg); }
32571
+ main { display: grid; gap: 10px; padding: 12px; min-height: 100vh; }
32572
+ header { display: flex; align-items: start; justify-content: space-between; gap: 10px; }
32573
+ h1 { font-size: 16px; line-height: 1.25; margin: 0; font-weight: 650; }
32574
+ .summary { margin-top: 3px; color: var(--muted); font-size: 12px; line-height: 1.35; }
32575
+ .toolbar { display: flex; align-items: center; justify-content: flex-end; gap: 6px; flex-wrap: wrap; }
32576
+ button {
32577
+ border: 1px solid var(--border);
32578
+ background: var(--panel);
32579
+ color: var(--fg);
32580
+ border-radius: 6px;
32581
+ font: inherit;
32582
+ font-size: 12px;
32583
+ padding: 6px 8px;
32584
+ cursor: pointer;
32585
+ }
32586
+ button[aria-pressed="true"] {
32587
+ border-color: var(--accent);
32588
+ color: var(--accent);
32589
+ }
32590
+ label { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 12px; }
32591
+ input[type="range"] { width: 92px; }
32592
+ .chart-shell {
32593
+ overflow: auto;
32594
+ border: 1px solid var(--border);
32595
+ border-radius: 8px;
32596
+ min-height: 260px;
32597
+ background: white;
32598
+ }
32599
+ .chart-shell.fit svg { width: 100%; height: auto; }
32600
+ .chart {
32601
+ min-width: 360px;
32602
+ padding: 10px;
32603
+ transform-origin: top left;
32604
+ }
32605
+ .chart svg { display: block; max-width: none; }
32606
+ .empty {
32607
+ min-height: 240px;
32608
+ display: grid;
32609
+ place-items: center;
32610
+ color: var(--muted);
32611
+ text-align: center;
32612
+ padding: 24px;
32613
+ }
32614
+ .drawer {
32615
+ display: none;
32616
+ border: 1px solid var(--border);
32617
+ border-radius: 8px;
32618
+ overflow: auto;
32619
+ max-height: 220px;
32620
+ }
32621
+ .drawer.open { display: block; }
32622
+ pre {
32623
+ margin: 0;
32624
+ padding: 10px;
32625
+ font-size: 12px;
32626
+ white-space: pre-wrap;
32627
+ overflow-wrap: anywhere;
32628
+ }
32629
+ table { width: 100%; border-collapse: collapse; font-size: 12px; }
32630
+ th, td { border-bottom: 1px solid var(--border); padding: 6px 8px; text-align: left; vertical-align: top; }
32631
+ th { position: sticky; top: 0; background: var(--panel); }
32632
+ .hover {
32633
+ position: fixed;
32634
+ pointer-events: none;
32635
+ z-index: 10;
32636
+ max-width: 280px;
32637
+ padding: 6px 8px;
32638
+ border-radius: 6px;
32639
+ border: 1px solid var(--border);
32640
+ background: var(--bg);
32641
+ color: var(--fg);
32642
+ box-shadow: 0 8px 24px rgb(0 0 0 / 18%);
32643
+ font-size: 12px;
32644
+ display: none;
32645
+ }
32646
+ @media (max-width: 520px) {
32647
+ main { padding: 10px; }
32648
+ header { display: grid; }
32649
+ .toolbar { justify-content: start; }
32650
+ .chart { min-width: 300px; }
32651
+ }
32652
+ </style>
32653
+ </head>
32654
+ <body>
32655
+ <main>
32656
+ <header>
32657
+ <div>
32658
+ <h1 id="title">Semiotic chart</h1>
32659
+ <div class="summary" id="summary">Waiting for a tool result...</div>
32660
+ </div>
32661
+ <div class="toolbar" aria-label="Chart controls">
32662
+ <button id="fit" type="button" aria-pressed="true">Fit</button>
32663
+ <button id="data" type="button" aria-pressed="false">Data</button>
32664
+ <button id="evidence" type="button" aria-pressed="false">Evidence</button>
32665
+ <label>Zoom <input id="zoom" type="range" min="60" max="180" value="100" /></label>
32666
+ </div>
32667
+ </header>
32668
+ <section id="chartShell" class="chart-shell fit" aria-label="Rendered Semiotic chart">
32669
+ <div id="chart" class="chart"><div class="empty">Ask ChatGPT to render a Semiotic chart.</div></div>
32670
+ </section>
32671
+ <section id="dataDrawer" class="drawer" aria-label="Chart data"></section>
32672
+ <section id="evidenceDrawer" class="drawer" aria-label="Render evidence"><pre id="evidenceText">{}</pre></section>
32673
+ </main>
32674
+ <div id="hover" class="hover" role="status" aria-live="polite"></div>
32675
+ <script>
32676
+ const state = { output: null, meta: null };
32677
+ const titleEl = document.getElementById("title");
32678
+ const summaryEl = document.getElementById("summary");
32679
+ const chartEl = document.getElementById("chart");
32680
+ const chartShell = document.getElementById("chartShell");
32681
+ const dataDrawer = document.getElementById("dataDrawer");
32682
+ const evidenceDrawer = document.getElementById("evidenceDrawer");
32683
+ const evidenceText = document.getElementById("evidenceText");
32684
+ const hover = document.getElementById("hover");
32685
+ const fitButton = document.getElementById("fit");
32686
+ const dataButton = document.getElementById("data");
32687
+ const evidenceButton = document.getElementById("evidence");
32688
+ const zoom = document.getElementById("zoom");
32689
+
32690
+ function html(value) {
32691
+ return String(value ?? "").replace(/[&<>"']/g, (char) => ({
32692
+ "&": "&amp;",
32693
+ "<": "&lt;",
32694
+ ">": "&gt;",
32695
+ '"': "&quot;",
32696
+ "'": "&#39;"
32697
+ })[char]);
32698
+ }
32699
+
32700
+ function currentPayload() {
32701
+ const openai = window.openai || {};
32702
+ const output = state.output || openai.toolOutput || null;
32703
+ const meta = state.meta || openai.toolResultMetadata || openai.toolResponseMetadata || openai._meta || null;
32704
+ return { output, meta };
32705
+ }
32706
+
32707
+ function sampleRows(meta) {
32708
+ const props = meta?.props || {};
32709
+ if (Array.isArray(props.data)) return props.data.slice(0, 50);
32710
+ if (Array.isArray(props.nodes)) return props.nodes.slice(0, 50);
32711
+ if (Array.isArray(props.edges)) return props.edges.slice(0, 50);
32712
+ if (Array.isArray(props.links)) return props.links.slice(0, 50);
32713
+ return [];
32714
+ }
32715
+
32716
+ function renderTable(rows) {
32717
+ if (!rows.length) return '<pre>No row data was provided in the widget metadata.</pre>';
32718
+ const columns = Array.from(rows.reduce((set, row) => {
32719
+ Object.keys(row || {}).forEach((key) => set.add(key));
32720
+ return set;
32721
+ }, new Set()));
32722
+ return '<table><thead><tr>' + columns.map((col) => '<th>' + html(col) + '</th>').join('') +
32723
+ '</tr></thead><tbody>' + rows.map((row) => '<tr>' + columns.map((col) => '<td>' + html(row?.[col]) + '</td>').join('') + '</tr>').join('') + '</tbody></table>';
32724
+ }
32725
+
32726
+ function render(output, meta) {
32727
+ const payload = output || {};
32728
+ const hidden = meta || {};
32729
+ titleEl.textContent = payload.title || payload.component || "Semiotic chart";
32730
+ summaryEl.textContent = payload.summary || "Rendered by semiotic-mcp.";
32731
+ const svg = hidden.svg || payload.svg;
32732
+ if (svg) {
32733
+ chartEl.innerHTML = svg;
32734
+ } else {
32735
+ chartEl.innerHTML = '<div class="empty">No SVG payload received. The model-visible chart summary is still available above.</div>';
32736
+ }
32737
+ const rows = sampleRows(hidden);
32738
+ dataDrawer.innerHTML = renderTable(rows);
32739
+ evidenceText.textContent = JSON.stringify(payload.evidence || hidden.evidence || {}, null, 2);
32740
+ }
32741
+
32742
+ function rerenderFromGlobals() {
32743
+ const payload = currentPayload();
32744
+ render(payload.output, payload.meta);
32745
+ }
32746
+
32747
+ fitButton.addEventListener("click", () => {
32748
+ const enabled = !chartShell.classList.contains("fit");
32749
+ chartShell.classList.toggle("fit", enabled);
32750
+ fitButton.setAttribute("aria-pressed", String(enabled));
32751
+ });
32752
+ dataButton.addEventListener("click", () => {
32753
+ const open = !dataDrawer.classList.contains("open");
32754
+ dataDrawer.classList.toggle("open", open);
32755
+ dataButton.setAttribute("aria-pressed", String(open));
32756
+ });
32757
+ evidenceButton.addEventListener("click", () => {
32758
+ const open = !evidenceDrawer.classList.contains("open");
32759
+ evidenceDrawer.classList.toggle("open", open);
32760
+ evidenceButton.setAttribute("aria-pressed", String(open));
32761
+ });
32762
+ zoom.addEventListener("input", () => {
32763
+ chartEl.style.transform = 'scale(' + Number(zoom.value) / 100 + ')';
32764
+ chartEl.style.width = (10000 / Number(zoom.value)) + '%';
32765
+ });
32766
+ chartEl.addEventListener("mousemove", (event) => {
32767
+ const target = event.target;
32768
+ if (!(target instanceof Element) || target === chartEl) {
32769
+ hover.style.display = "none";
32770
+ return;
32771
+ }
32772
+ const label = target.getAttribute("aria-label") || target.textContent?.trim() || target.tagName.toLowerCase();
32773
+ hover.textContent = label.slice(0, 180);
32774
+ hover.style.left = Math.min(event.clientX + 12, window.innerWidth - 300) + "px";
32775
+ hover.style.top = Math.min(event.clientY + 12, window.innerHeight - 70) + "px";
32776
+ hover.style.display = "block";
32777
+ });
32778
+ chartEl.addEventListener("mouseleave", () => {
32779
+ hover.style.display = "none";
32780
+ });
32781
+ window.addEventListener("message", (event) => {
32782
+ if (event.source !== window.parent) return;
32783
+ const message = event.data;
32784
+ if (!message || message.jsonrpc !== "2.0") return;
32785
+ if (message.method === "ui/notifications/tool-result") {
32786
+ state.output = message.params?.structuredContent || null;
32787
+ state.meta = message.params?._meta || null;
32788
+ render(state.output, state.meta);
32789
+ }
32790
+ }, { passive: true });
32791
+ window.addEventListener("openai:set_globals", (event) => {
32792
+ const globals = event.detail?.globals || {};
32793
+ state.output = globals.toolOutput || state.output;
32794
+ state.meta = globals.toolResultMetadata || globals.toolResponseMetadata || globals._meta || state.meta;
32795
+ rerenderFromGlobals();
32796
+ }, { passive: true });
32797
+ rerenderFromGlobals();
32798
+ </script>
32799
+ </body>
32800
+ </html>`.trim();
32801
+ }
32490
32802
  async function getSchemaHandler(args) {
32491
32803
  const component = args.component;
32492
32804
  if (!component) {
@@ -32524,8 +32836,23 @@ ${JSON.stringify(contracts, null, 2)}` : "";
32524
32836
  ${JSON.stringify(entry, null, 2)}${contractText}` }]
32525
32837
  };
32526
32838
  }
32839
+ var SUGGEST_INTENT_ALIASES = {
32840
+ "compare-series": "comparison",
32841
+ "compare-categories": "comparison",
32842
+ "rank": "comparison",
32843
+ "part-to-whole": "composition",
32844
+ "composition-over-time": "composition",
32845
+ "correlation": "relationship",
32846
+ "flow": "network",
32847
+ "geo": "geographic",
32848
+ "outlier-detection": "distribution",
32849
+ "change-detection": "trend"
32850
+ };
32527
32851
  async function suggestChartHandler(args) {
32528
- const result = suggestCharts(args);
32852
+ let intent = args.intent;
32853
+ if (intent && SUGGEST_INTENT_ALIASES[intent]) intent = SUGGEST_INTENT_ALIASES[intent];
32854
+ if (intent && !VALID_INTENTS.includes(intent)) intent = void 0;
32855
+ const result = suggestCharts({ ...args, intent });
32529
32856
  const content = [{ type: "text", text: formatSuggestionReport(result) }];
32530
32857
  if (!result.ok) {
32531
32858
  return { content, isError: true, structuredContent: result };
@@ -32563,6 +32890,21 @@ async function renderChartHandler(args) {
32563
32890
  };
32564
32891
  }
32565
32892
  let svg = result.svg;
32893
+ let evidenceBlock = null;
32894
+ try {
32895
+ const { svg: evidenceSvg, evidence } = (0, import_server2.renderChartWithEvidence)(component, props);
32896
+ svg = evidenceSvg;
32897
+ evidenceBlock = {
32898
+ type: "text",
32899
+ text: `Render evidence:
32900
+ ${JSON.stringify(evidence, null, 2)}`
32901
+ };
32902
+ } catch {
32903
+ evidenceBlock = {
32904
+ type: "text",
32905
+ text: `Render evidence: unavailable for ${component} (no server render config). The SVG above is the validated React render; mark-count / domain evidence is only produced for components with a server render path.`
32906
+ };
32907
+ }
32566
32908
  if (theme && Object.keys(theme).length > 0) {
32567
32909
  const validVars = Object.entries(theme).filter(([k]) => k.startsWith("--semiotic-")).map(([k, v]) => `${k}: ${v}`).join("; ");
32568
32910
  if (validVars) {
@@ -32576,7 +32918,10 @@ async function renderChartHandler(args) {
32576
32918
  const pngBuffer = await sharpFn(Buffer.from(svg)).png().toBuffer();
32577
32919
  const base643 = pngBuffer.toString("base64");
32578
32920
  return {
32579
- content: [{ type: "text", text: `data:image/png;base64,${base643}` }]
32921
+ content: [
32922
+ { type: "text", text: `data:image/png;base64,${base643}` },
32923
+ ...evidenceBlock ? [evidenceBlock] : []
32924
+ ]
32580
32925
  };
32581
32926
  } catch (err) {
32582
32927
  if (err.code === "MODULE_NOT_FOUND" || err.code === "ERR_MODULE_NOT_FOUND") {
@@ -32599,7 +32944,52 @@ ${svg}` }],
32599
32944
  }
32600
32945
  }
32601
32946
  return {
32602
- content: [{ type: "text", text: svg }]
32947
+ content: [
32948
+ { type: "text", text: svg },
32949
+ ...evidenceBlock ? [evidenceBlock] : []
32950
+ ]
32951
+ };
32952
+ }
32953
+ async function renderInteractiveChartHandler(args) {
32954
+ const component = args.component;
32955
+ const props = args.props ?? {};
32956
+ const rendered = await renderChartHandler({
32957
+ component,
32958
+ props,
32959
+ theme: args.theme,
32960
+ format: "svg"
32961
+ });
32962
+ if (rendered.isError) return rendered;
32963
+ const svg = stripUnsafeSvg(rendered.content[0]?.text ?? "");
32964
+ const evidence = parseRenderEvidence(rendered);
32965
+ const title = chartTitleFromProps(component || "Semiotic chart", props);
32966
+ const datumCount = chartDatumCount(props);
32967
+ const summary = [
32968
+ `Rendered ${title} with ${component}.`,
32969
+ datumCount == null ? "No row count was inferred from props." : `${datumCount} input row${datumCount === 1 ? "" : "s"} available in the widget data drawer.`,
32970
+ "Use the widget controls to zoom, fit width, inspect data, and inspect render evidence."
32971
+ ].join(" ");
32972
+ return {
32973
+ content: [{
32974
+ type: "text",
32975
+ text: `Rendered ${title} (${component}) as an interactive ChatGPT Apps widget.`
32976
+ }],
32977
+ structuredContent: {
32978
+ component: component ?? "SemioticChart",
32979
+ title,
32980
+ summary,
32981
+ datumCount,
32982
+ evidence
32983
+ },
32984
+ _meta: {
32985
+ component,
32986
+ title,
32987
+ props,
32988
+ theme: args.theme ?? null,
32989
+ svg,
32990
+ evidence,
32991
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
32992
+ }
32603
32993
  };
32604
32994
  }
32605
32995
  function filterUsageModeDiagnoses(component, usageMode, diagnoses) {
@@ -32687,23 +33077,26 @@ async function reportIssueHandler(args) {
32687
33077
  ${url2}` }]
32688
33078
  };
32689
33079
  }
32690
- var THEME_PRESET_NAMES = [
32691
- "light",
32692
- "dark",
32693
- "high-contrast",
32694
- "pastels",
32695
- "pastels-dark",
32696
- "bi-tool",
32697
- "bi-tool-dark",
32698
- "italian",
32699
- "italian-dark",
32700
- "tufte",
32701
- "tufte-dark",
32702
- "journalist",
32703
- "journalist-dark",
32704
- "playful",
32705
- "playful-dark"
32706
- ];
33080
+ var THEME_PRESETS = {
33081
+ "light": "LIGHT_THEME",
33082
+ "dark": "DARK_THEME",
33083
+ "high-contrast": "HIGH_CONTRAST_THEME",
33084
+ "pastels": "PASTELS_LIGHT",
33085
+ "pastels-dark": "PASTELS_DARK",
33086
+ "bi-tool": "BI_TOOL_LIGHT",
33087
+ "bi-tool-dark": "BI_TOOL_DARK",
33088
+ "italian": "ITALIAN_LIGHT",
33089
+ "italian-dark": "ITALIAN_DARK",
33090
+ "tufte": "TUFTE_LIGHT",
33091
+ "tufte-dark": "TUFTE_DARK",
33092
+ "journalist": "JOURNALIST_LIGHT",
33093
+ "journalist-dark": "JOURNALIST_DARK",
33094
+ "playful": "PLAYFUL_LIGHT",
33095
+ "playful-dark": "PLAYFUL_DARK",
33096
+ "carbon": "CARBON_LIGHT",
33097
+ "carbon-dark": "CARBON_DARK"
33098
+ };
33099
+ var THEME_PRESET_NAMES = Object.keys(THEME_PRESETS);
32707
33100
  async function applyThemeHandler(args) {
32708
33101
  const name = args.name;
32709
33102
  if (!name) {
@@ -32723,6 +33116,7 @@ Dark-mode presets: ${THEME_PRESET_NAMES.filter((n) => n.includes("dark")).join("
32723
33116
  isError: true
32724
33117
  };
32725
33118
  }
33119
+ const exportName = THEME_PRESETS[name];
32726
33120
  const usage = [
32727
33121
  `## Theme: "${name}"`,
32728
33122
  "",
@@ -32736,24 +33130,23 @@ Dark-mode presets: ${THEME_PRESET_NAMES.filter((n) => n.includes("dark")).join("
32736
33130
  "",
32737
33131
  "### Option 2: Import the theme object",
32738
33132
  "```jsx",
32739
- `import { ${name.replace(/-./g, (c) => c[1].toUpperCase()).replace(/^./, (c) => c.toUpperCase()).replace(/Dark$/, "_DARK").replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()} } from "semiotic/themes"`,
32740
- `<ThemeProvider theme={themeObject}>`,
33133
+ `import { ${exportName} } from "semiotic/themes"`,
33134
+ `<ThemeProvider theme={${exportName}}>`,
32741
33135
  ` <BarChart ... />`,
32742
33136
  `</ThemeProvider>`,
32743
33137
  "```",
32744
33138
  "",
32745
33139
  "### Option 3: CSS custom properties (no React required)",
32746
33140
  "```jsx",
32747
- `import { themeToCSS } from "semiotic/themes"`,
32748
- `import { ${name.replace(/-./g, (c) => c[1].toUpperCase()).replace(/^./, (c) => c.toUpperCase()).replace(/Dark$/, "_DARK").replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase()} } from "semiotic/themes"`,
32749
- `const css = themeToCSS(themeObject, ".my-charts")`,
33141
+ `import { themeToCSS, ${exportName} } from "semiotic/themes"`,
33142
+ `const css = themeToCSS(${exportName}, ".my-charts")`,
32750
33143
  "// Outputs CSS custom properties string for embedding in a stylesheet",
32751
33144
  "```",
32752
33145
  "",
32753
33146
  "### Option 4: Design tokens JSON",
32754
33147
  "```jsx",
32755
- `import { themeToTokens } from "semiotic/themes"`,
32756
- `const tokens = themeToTokens(themeObject)`,
33148
+ `import { themeToTokens, ${exportName} } from "semiotic/themes"`,
33149
+ `const tokens = themeToTokens(${exportName})`,
32757
33150
  "// Style Dictionary / DTCG-compatible token format",
32758
33151
  "```",
32759
33152
  "",
@@ -33044,6 +33437,12 @@ async function groundChartHandler(args) {
33044
33437
  structuredContent: grounding
33045
33438
  };
33046
33439
  }
33440
+ var READ_ONLY_TOOL_ANNOTATIONS = {
33441
+ readOnlyHint: true,
33442
+ destructiveHint: false,
33443
+ idempotentHint: true,
33444
+ openWorldHint: false
33445
+ };
33047
33446
  function createServer2() {
33048
33447
  const srv = new McpServer({
33049
33448
  name: "semiotic",
@@ -33102,6 +33501,27 @@ function createServer2() {
33102
33501
  },
33103
33502
  (uri) => textResource(uri, "text/markdown", readAIFile("examples.md"))
33104
33503
  );
33504
+ srv.registerResource(
33505
+ "semiotic-chatgpt-chart-widget",
33506
+ SEMIOTIC_CHART_WIDGET_URI,
33507
+ {
33508
+ title: "Semiotic ChatGPT Chart Widget",
33509
+ description: "MCP Apps widget template for interactive Semiotic chart previews inside ChatGPT.",
33510
+ mimeType: MCP_APP_MIME_TYPE,
33511
+ _meta: {
33512
+ ui: {
33513
+ prefersBorder: true,
33514
+ csp: {
33515
+ connectDomains: [],
33516
+ resourceDomains: []
33517
+ }
33518
+ },
33519
+ "openai/widgetDescription": "Interactive Semiotic chart preview rendered by the semiotic-mcp server.",
33520
+ "openai/widgetPrefersBorder": true
33521
+ }
33522
+ },
33523
+ (uri) => appResource(uri, renderSemioticChartWidgetHTML())
33524
+ );
33105
33525
  srv.registerPrompt(
33106
33526
  "build-semiotic-chart",
33107
33527
  {
@@ -33167,14 +33587,15 @@ function createServer2() {
33167
33587
  "getSchema",
33168
33588
  `Return the prop schema for a Semiotic chart component. Pass { component: '<name>' } to get its props, or omit component to list all available components. Components marked [renderable] can be passed to renderChart for static SVG output.`,
33169
33589
  { component: external_exports3.string().optional().describe("Component name, e.g. 'LineChart'. Omit to list all.") },
33590
+ READ_ONLY_TOOL_ANNOTATIONS,
33170
33591
  getSchemaHandler
33171
33592
  );
33172
33593
  srv.tool(
33173
33594
  "suggestChart",
33174
- "Recommend Semiotic chart types for a given data sample. Pass { data: [...] } with 1-5 sample objects. Optionally pass intent to narrow suggestions, or capabilities to require/forbid features (push API, linked hover, SSR, selection, legend). Returns ranked recommendations with example props; charts that don't satisfy the capability constraints are dropped.",
33595
+ "Lightweight heuristic chart recommender for a small data sample (1-5 rows) with capability filtering (push API, linked hover, SSR, selection, legend). Returns ranked recommendations with example props. For richer capability-descriptor ranking (scores, reasons, caveats) and the full 13-intent taxonomy, prefer `suggestCharts` (plural).",
33175
33596
  {
33176
33597
  data: external_exports3.array(external_exports3.record(external_exports3.string(), external_exports3.unknown())).min(1).max(5).describe("1-5 sample data objects"),
33177
- intent: external_exports3.enum(["comparison", "trend", "distribution", "relationship", "composition", "geographic", "network", "hierarchy"]).optional().describe("Visualization intent to narrow suggestions"),
33598
+ intent: external_exports3.string().optional().describe("Visualization intent. Accepts this engine's intents (comparison, trend, distribution, relationship, composition, geographic, network, hierarchy) AND the richer suggestCharts taxonomy (compare-categories, part-to-whole, correlation, flow, geo, rank, \u2026), which is translated automatically; an unrecognized intent is ignored rather than rejected."),
33178
33599
  capabilities: external_exports3.object({
33179
33600
  push: external_exports3.boolean().optional().describe("Require ref-based push API (live streaming via ref.current.push())"),
33180
33601
  linkedHover: external_exports3.boolean().optional().describe("Require cross-chart linked hover support"),
@@ -33187,19 +33608,53 @@ function createServer2() {
33187
33608
  // validation from being unreachable from MCP callers.
33188
33609
  }).strict().optional().describe("Capability constraints \u2014 set a key to true to require, false to forbid. Unset keys are ignored.")
33189
33610
  },
33611
+ READ_ONLY_TOOL_ANNOTATIONS,
33190
33612
  suggestChartHandler
33191
33613
  );
33192
33614
  srv.tool(
33193
33615
  "renderChart",
33194
- `Render a Semiotic chart to static SVG or PNG. This is a static snapshot path: props must include data immediately, and ref/push-mode charts cannot be rendered through this tool. Returns SVG string (default) or Base64-encoded PNG image. Optionally pass theme CSS custom properties (--semiotic-bg, --semiotic-text, etc.) to style the output. PNG requires the 'sharp' package to be installed. Available components: ${componentNames.join(", ")}.`,
33616
+ `Render a Semiotic chart to static SVG or PNG. This is a static snapshot path: props must include data immediately, and ref/push-mode charts cannot be rendered through this tool. Returns SVG string (default) or Base64-encoded PNG image, plus a "Render evidence" JSON block (mark counts by type, resolved axis domains, empty flag, annotation count, accessible name) \u2014 read the evidence instead of parsing the SVG to verify the chart actually rendered data marks. Optionally pass theme CSS custom properties (--semiotic-bg, --semiotic-text, etc.) to style the output. PNG requires the 'sharp' package to be installed. Available components: ${componentNames.join(", ")}.`,
33195
33617
  {
33196
33618
  component: external_exports3.string().describe("Chart component name, e.g. 'LineChart', 'BarChart'"),
33197
33619
  props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x' }."),
33198
33620
  theme: external_exports3.record(external_exports3.string(), external_exports3.string()).optional().describe("CSS custom properties for theming, e.g. { '--semiotic-bg': '#1a1a2e', '--semiotic-text': '#ededed' }. Only --semiotic-* variables are applied."),
33199
33621
  format: external_exports3.enum(["svg", "png"]).optional().describe("Output format: 'svg' (default) returns SVG markup, 'png' returns a Base64-encoded PNG image. PNG requires the 'sharp' package.")
33200
33622
  },
33623
+ READ_ONLY_TOOL_ANNOTATIONS,
33201
33624
  renderChartHandler
33202
33625
  );
33626
+ srv.registerTool(
33627
+ "renderInteractiveChart",
33628
+ {
33629
+ title: "Render interactive Semiotic chart",
33630
+ description: `Render a static-data Semiotic chart as a ChatGPT Apps widget. Use this after suggestCharts/getSchema/diagnoseConfig when the user wants to see an interactive chart inside ChatGPT. The server renders Semiotic to SVG and the widget adds fit, zoom, data, hover, and render-evidence controls. Available components: ${componentNames.join(", ")}.`,
33631
+ inputSchema: {
33632
+ component: external_exports3.string().describe("Renderable chart component name, e.g. 'LineChart', 'BarChart', 'GaugeChart'."),
33633
+ props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Static Semiotic chart props, including data/accessors where required."),
33634
+ theme: external_exports3.record(external_exports3.string(), external_exports3.string()).optional().describe("CSS custom properties such as { '--semiotic-bg': '#fff', '--semiotic-text': '#111' }. Only --semiotic-* variables are applied.")
33635
+ },
33636
+ outputSchema: {
33637
+ component: external_exports3.string(),
33638
+ title: external_exports3.string(),
33639
+ summary: external_exports3.string(),
33640
+ datumCount: external_exports3.number().nullable(),
33641
+ evidence: external_exports3.record(external_exports3.string(), external_exports3.unknown()).nullable()
33642
+ },
33643
+ annotations: {
33644
+ readOnlyHint: true,
33645
+ destructiveHint: false,
33646
+ idempotentHint: true,
33647
+ openWorldHint: false
33648
+ },
33649
+ _meta: {
33650
+ ui: { resourceUri: SEMIOTIC_CHART_WIDGET_URI },
33651
+ "openai/outputTemplate": SEMIOTIC_CHART_WIDGET_URI,
33652
+ "openai/toolInvocation/invoking": "Rendering Semiotic chart...",
33653
+ "openai/toolInvocation/invoked": "Rendered Semiotic chart."
33654
+ }
33655
+ },
33656
+ renderInteractiveChartHandler
33657
+ );
33203
33658
  srv.tool(
33204
33659
  "diagnoseConfig",
33205
33660
  "Diagnose a Semiotic chart configuration for common problems (empty data, bad dimensions, missing accessors, wrong data shape, color contrast issues, etc). Pass usageMode='push' for ref-based React HOCs that intentionally omit data; omit usageMode or pass 'static' for renderChart/MCP/server configs where data is required. Checks WCAG color contrast ratios and suggests COLOR_BLIND_SAFE_CATEGORICAL for accessibility. Returns a human-readable diagnostic report with actionable fixes.",
@@ -33208,6 +33663,7 @@ function createServer2() {
33208
33663
  props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x' }."),
33209
33664
  usageMode: external_exports3.enum(["static", "push", "renderChart", "server"]).optional().describe("Validation mode. Use 'push' for ref-based React HOCs that omit data; use 'static' or omit for renderChart/MCP/static data configs.")
33210
33665
  },
33666
+ READ_ONLY_TOOL_ANNOTATIONS,
33211
33667
  diagnoseConfigHandler
33212
33668
  );
33213
33669
  srv.tool(
@@ -33220,6 +33676,7 @@ function createServer2() {
33220
33676
  describe: external_exports3.boolean().optional().describe("True if ChartContainer's describe option (auto-generated L1\u2013L3 description via describeChart) is enabled \u2014 passes the 'features described' heuristic."),
33221
33677
  navigable: external_exports3.boolean().optional().describe("True if ChartContainer's navigable option (structured navigation tree via buildNavigationTree) is enabled \u2014 passes the 'navigable structure' heuristic.")
33222
33678
  },
33679
+ READ_ONLY_TOOL_ANNOTATIONS,
33223
33680
  auditAccessibilityHandler
33224
33681
  );
33225
33682
  srv.tool(
@@ -33230,6 +33687,7 @@ function createServer2() {
33230
33687
  body: external_exports3.string().optional().describe("Issue body with details, reproduction steps, diagnoseConfig output"),
33231
33688
  labels: external_exports3.union([external_exports3.array(external_exports3.string()), external_exports3.string()]).optional().describe("GitHub labels, e.g. ['bug'] or 'bug'")
33232
33689
  },
33690
+ READ_ONLY_TOOL_ANNOTATIONS,
33233
33691
  reportIssueHandler
33234
33692
  );
33235
33693
  srv.tool(
@@ -33238,6 +33696,7 @@ function createServer2() {
33238
33696
  {
33239
33697
  name: external_exports3.string().optional().describe("Theme preset name, e.g. 'tufte', 'pastels-dark', 'bi-tool'. Omit to list all available themes.")
33240
33698
  },
33699
+ READ_ONLY_TOOL_ANNOTATIONS,
33241
33700
  applyThemeHandler
33242
33701
  );
33243
33702
  srv.tool(
@@ -33248,6 +33707,7 @@ function createServer2() {
33248
33707
  props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).describe("The full chart props including data"),
33249
33708
  query: external_exports3.string().optional().describe("A natural language question about the chart data")
33250
33709
  },
33710
+ READ_ONLY_TOOL_ANNOTATIONS,
33251
33711
  interrogateChartHandler
33252
33712
  );
33253
33713
  srv.tool(
@@ -33257,6 +33717,7 @@ function createServer2() {
33257
33717
  component: external_exports3.string().describe("Chart component name, e.g. 'LineChart'"),
33258
33718
  props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).describe("The full chart props including data")
33259
33719
  },
33720
+ READ_ONLY_TOOL_ANNOTATIONS,
33260
33721
  groundChartHandler
33261
33722
  );
33262
33723
  srv.tool(
@@ -33277,6 +33738,7 @@ function createServer2() {
33277
33738
  intent: external_exports3.union([external_exports3.string(), external_exports3.array(external_exports3.string())]).optional().describe("Ranking intent."),
33278
33739
  maxResults: external_exports3.number().int().min(1).max(20).optional()
33279
33740
  },
33741
+ READ_ONLY_TOOL_ANNOTATIONS,
33280
33742
  suggestStreamChartsHandler
33281
33743
  );
33282
33744
  srv.tool(
@@ -33288,6 +33750,7 @@ function createServer2() {
33288
33750
  maxPanels: external_exports3.number().int().min(1).max(12).optional().describe("Maximum panels (default 6)."),
33289
33751
  diversifyByFamily: external_exports3.boolean().optional().describe("Prefer not to repeat chart families across panels (default true).")
33290
33752
  },
33753
+ READ_ONLY_TOOL_ANNOTATIONS,
33291
33754
  suggestDashboardHandler
33292
33755
  );
33293
33756
  srv.tool(
@@ -33312,6 +33775,7 @@ function createServer2() {
33312
33775
  intent: external_exports3.union([external_exports3.string(), external_exports3.array(external_exports3.string())]).optional(),
33313
33776
  maxResults: external_exports3.number().int().min(1).max(20).optional()
33314
33777
  },
33778
+ READ_ONLY_TOOL_ANNOTATIONS,
33315
33779
  suggestStretchChartsHandler
33316
33780
  );
33317
33781
  srv.tool(
@@ -33323,6 +33787,7 @@ function createServer2() {
33323
33787
  intent: external_exports3.union([external_exports3.string(), external_exports3.array(external_exports3.string())]).optional().describe("User intent \u2014 informs ranking of alternatives when the chart doesn't fit."),
33324
33788
  maxAlternatives: external_exports3.number().int().min(1).max(10).optional().describe("Cap on alternatives returned (default 3).")
33325
33789
  },
33790
+ READ_ONLY_TOOL_ANNOTATIONS,
33326
33791
  repairChartConfigHandler
33327
33792
  );
33328
33793
  srv.tool(
@@ -33349,6 +33814,7 @@ function createServer2() {
33349
33814
  receptionModality: external_exports3.enum(["visual", "screen-reader", "sonified", "agent"]).optional().describe("Reception channel \u2014 see suggestCharts.")
33350
33815
  }).optional().describe("Audience profile \u2014 familiarity, adoption targets, exposure level, and reception modality.")
33351
33816
  },
33817
+ READ_ONLY_TOOL_ANNOTATIONS,
33352
33818
  proposeChartVariantsHandler
33353
33819
  );
33354
33820
  srv.tool(
@@ -33375,6 +33841,7 @@ function createServer2() {
33375
33841
  receptionModality: external_exports3.enum(["visual", "screen-reader", "sonified", "agent"]).optional().describe("Reception channel. A non-visual value down-ranks charts the audience can't receive in that channel (e.g. a many-slice pie for a screen reader) and adds receivability caveats.")
33376
33842
  }).optional().describe("Audience profile \u2014 familiarity, adoption targets, exposure level, and reception modality.")
33377
33843
  },
33844
+ READ_ONLY_TOOL_ANNOTATIONS,
33378
33845
  suggestChartsHandler
33379
33846
  );
33380
33847
  return srv;
@@ -33386,45 +33853,95 @@ var parsedPort = portFlagIndex !== -1 && cliArgs[portFlagIndex + 1] != null ? pa
33386
33853
  var port = Number.isFinite(parsedPort) ? parsedPort : 3001;
33387
33854
  async function main() {
33388
33855
  if (httpMode) {
33389
- const sessions = /* @__PURE__ */ new Map();
33856
+ const allowedHosts = (process.env.MCP_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
33857
+ const healthBody = () => JSON.stringify({
33858
+ status: "ok",
33859
+ name: "semiotic-mcp",
33860
+ version: schema.version || "3.0.0",
33861
+ transport: "streamable-http",
33862
+ mode: "stateless"
33863
+ });
33390
33864
  const httpServer = http.createServer(async (req, res) => {
33391
33865
  res.setHeader("Access-Control-Allow-Origin", "*");
33392
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
33393
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
33394
- res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
33866
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
33867
+ res.setHeader(
33868
+ "Access-Control-Allow-Headers",
33869
+ "Content-Type, Accept, Authorization, mcp-session-id, MCP-Protocol-Version, Last-Event-ID"
33870
+ );
33871
+ res.setHeader("Access-Control-Expose-Headers", "MCP-Protocol-Version");
33395
33872
  if (req.method === "OPTIONS") {
33396
33873
  res.writeHead(204);
33397
33874
  res.end();
33398
33875
  return;
33399
33876
  }
33400
- const sessionId = req.headers["mcp-session-id"];
33401
- if (sessionId && sessions.has(sessionId)) {
33402
- const session = sessions.get(sessionId);
33403
- await session.transport.handleRequest(req, res);
33404
- } else if (!sessionId) {
33405
- const transport = new StreamableHTTPServerTransport({
33406
- sessionIdGenerator: () => `semiotic-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
33877
+ const pathname = (() => {
33878
+ try {
33879
+ return new URL(req.url || "/", "http://localhost").pathname;
33880
+ } catch {
33881
+ return "/";
33882
+ }
33883
+ })();
33884
+ if (allowedHosts.length > 0) {
33885
+ const rawHost = String(req.headers.host || "").trim().toLowerCase();
33886
+ const normalizedHost = rawHost.startsWith("[") ? rawHost.replace(/^\[([^\]]+)\](?::\d+)?$/, "$1") : rawHost.split(":")[0];
33887
+ if (!allowedHosts.includes(rawHost) && !allowedHosts.includes(normalizedHost)) {
33888
+ res.writeHead(403, { "Content-Type": "application/json" });
33889
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Forbidden host" }, id: null }));
33890
+ return;
33891
+ }
33892
+ }
33893
+ if (req.method === "GET" && (pathname === "/healthz" || pathname === "/health")) {
33894
+ res.writeHead(200, { "Content-Type": "application/json" });
33895
+ res.end(healthBody());
33896
+ return;
33897
+ }
33898
+ if (pathname !== "/" && pathname !== "/mcp") {
33899
+ res.writeHead(404, { "Content-Type": "application/json" });
33900
+ res.end(JSON.stringify({ error: "Not found" }));
33901
+ return;
33902
+ }
33903
+ if (req.method === "GET") {
33904
+ res.writeHead(200, { "Content-Type": "application/json" });
33905
+ res.end(healthBody());
33906
+ return;
33907
+ }
33908
+ if (req.method !== "POST") {
33909
+ res.writeHead(405, { "Content-Type": "application/json" });
33910
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Method not allowed" }, id: null }));
33911
+ return;
33912
+ }
33913
+ const srv = createServer2();
33914
+ const transport = new StreamableHTTPServerTransport({
33915
+ sessionIdGenerator: void 0,
33916
+ enableJsonResponse: true
33917
+ });
33918
+ let torndown = false;
33919
+ const teardown = () => {
33920
+ if (torndown) return;
33921
+ torndown = true;
33922
+ Promise.resolve(transport.close()).catch(() => {
33923
+ });
33924
+ Promise.resolve(srv.close()).catch(() => {
33407
33925
  });
33408
- const srv = createServer2();
33926
+ };
33927
+ res.on("close", teardown);
33928
+ try {
33409
33929
  await srv.connect(transport);
33410
- transport.onclose = () => {
33411
- const sid2 = transport.sessionId;
33412
- if (sid2) sessions.delete(sid2);
33413
- };
33414
33930
  await transport.handleRequest(req, res);
33415
- const sid = transport.sessionId;
33416
- if (sid) {
33417
- sessions.set(sid, { server: srv, transport });
33931
+ } catch (err) {
33932
+ console.error("Request handling error:", err);
33933
+ if (!res.headersSent) {
33934
+ res.writeHead(500, { "Content-Type": "application/json" });
33935
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }));
33418
33936
  }
33419
- } else {
33420
- res.writeHead(400, { "Content-Type": "application/json" });
33421
- res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Unknown session. Send a request without mcp-session-id to start a new session." }, id: null }));
33937
+ } finally {
33938
+ teardown();
33422
33939
  }
33423
33940
  });
33424
33941
  httpServer.listen(port, () => {
33425
33942
  console.error(`Semiotic MCP server (HTTP) listening on http://localhost:${port}`);
33426
- console.error("Tools: getSchema, suggestChart, suggestCharts, proposeChartVariants, suggestStreamCharts, suggestDashboard, suggestStretchCharts, repairChartConfig, renderChart, interrogateChart, groundChart, diagnoseConfig, auditAccessibility, reportIssue, applyTheme");
33427
- console.error("Resources: semiotic://schema, semiotic://components, semiotic://behavior-contracts, semiotic://system-prompt, semiotic://examples");
33943
+ console.error("Tools: getSchema, suggestChart, suggestCharts, proposeChartVariants, suggestStreamCharts, suggestDashboard, suggestStretchCharts, repairChartConfig, renderChart, renderInteractiveChart, interrogateChart, groundChart, diagnoseConfig, auditAccessibility, reportIssue, applyTheme");
33944
+ console.error("Resources: semiotic://schema, semiotic://components, semiotic://behavior-contracts, semiotic://system-prompt, semiotic://examples, ui://semiotic/chart-widget.html");
33428
33945
  });
33429
33946
  } else {
33430
33947
  const srv = createServer2();