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.
- package/CLAUDE.md +3 -2
- package/README.md +46 -7
- package/ai/behaviorContracts.cjs +19 -9
- package/ai/cli.js +15 -0
- package/ai/dist/mcp-server.js +583 -66
- package/ai/schema.json +1 -1
- package/ai/system-prompt.md +3 -3
- package/dist/components/AccessibleNavTree.d.ts +2 -1
- package/dist/components/Annotation.d.ts +1 -1
- package/dist/components/CategoryColors.d.ts +1 -1
- package/dist/components/ChartGrid.d.ts +1 -1
- package/dist/components/ContextLayout.d.ts +1 -1
- package/dist/components/DataSummaryContext.d.ts +1 -1
- package/dist/components/DetailsPanel.d.ts +1 -1
- package/dist/components/Legend.d.ts +3 -2
- package/dist/components/LinkedCharts.d.ts +1 -1
- package/dist/components/ThemeProvider.d.ts +1 -1
- package/dist/components/Tooltip/FlippingTooltip.d.ts +1 -1
- package/dist/components/Tooltip/Tooltip.d.ts +2 -2
- package/dist/components/ai/qualityScorecard.d.ts +11 -0
- package/dist/components/charts/geo/ChoroplethMap.d.ts +2 -1
- package/dist/components/charts/network/CirclePack.d.ts +2 -1
- package/dist/components/charts/network/OrbitDiagram.d.ts +1 -1
- package/dist/components/charts/network/TreeDiagram.d.ts +2 -1
- package/dist/components/charts/network/Treemap.d.ts +2 -1
- package/dist/components/charts/realtime/RealtimeHistogram.d.ts +1 -1
- package/dist/components/charts/shared/ChartError.d.ts +2 -1
- package/dist/components/charts/shared/withChartWrapper.d.ts +1 -1
- package/dist/components/charts/xy/MinimapChart.d.ts +2 -1
- package/dist/components/charts/xy/ScatterplotMatrix.d.ts +2 -1
- package/dist/components/semiotic-server.d.ts +2 -1
- package/dist/components/server/renderEvidence.d.ts +92 -0
- package/dist/components/server/renderToStaticSVG.d.ts +14 -1
- package/dist/components/stream/AccessibleDataTable.d.ts +5 -5
- package/dist/components/stream/FocusRing.d.ts +2 -1
- package/dist/components/stream/MarginalGraphics.d.ts +2 -1
- package/dist/components/stream/NetworkCanvasHitTester.d.ts +3 -2
- package/dist/components/stream/NetworkPipelineStore.d.ts +46 -1
- package/dist/components/stream/NetworkSVGOverlay.d.ts +2 -1
- package/dist/components/stream/OrdinalBrushOverlay.d.ts +19 -1
- package/dist/components/stream/OrdinalSVGOverlay.d.ts +2 -2
- package/dist/components/stream/PipelineStore.d.ts +6 -0
- package/dist/components/stream/SVGOverlay.d.ts +3 -2
- package/dist/components/stream/StalenessBadge.d.ts +19 -0
- package/dist/components/stream/XYBrushOverlay.d.ts +21 -1
- package/dist/components/stream/quadtreeHitTest.d.ts +7 -1
- package/dist/geo.min.js +1 -1
- package/dist/geo.module.min.js +1 -1
- package/dist/network.min.js +1 -1
- package/dist/network.module.min.js +1 -1
- package/dist/ordinal.min.js +1 -1
- package/dist/ordinal.module.min.js +1 -1
- package/dist/realtime.min.js +1 -1
- package/dist/realtime.module.min.js +1 -1
- package/dist/semiotic-ai.min.js +1 -1
- package/dist/semiotic-ai.module.min.js +1 -1
- package/dist/semiotic-server.d.ts +2 -1
- package/dist/semiotic-themes.min.js +1 -1
- package/dist/semiotic-themes.module.min.js +1 -1
- package/dist/semiotic-utils.min.js +1 -1
- package/dist/semiotic-utils.module.min.js +1 -1
- package/dist/semiotic-value.min.js +1 -1
- package/dist/semiotic-value.module.min.js +1 -1
- package/dist/semiotic.min.js +1 -1
- package/dist/semiotic.module.min.js +1 -1
- package/dist/server.min.js +1 -1
- package/dist/server.module.min.js +1 -1
- package/dist/xy.min.js +1 -1
- package/dist/xy.module.min.js +1 -1
- package/package.json +2 -2
package/ai/dist/mcp-server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 && !
|
|
7170
|
+
if (intent && !VALID_INTENTS2.includes(intent)) {
|
|
7167
7171
|
return {
|
|
7168
7172
|
ok: false,
|
|
7169
|
-
error: `Unknown intent "${intent}". Expected one of: ${
|
|
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 ('${
|
|
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
|
|
7457
|
-
if (
|
|
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
|
-
} =
|
|
32452
|
+
} = import_componentMetadata.default;
|
|
32441
32453
|
var {
|
|
32442
32454
|
formatSuggestionReport,
|
|
32443
|
-
suggestCharts
|
|
32444
|
-
|
|
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
|
-
} =
|
|
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
|
+
"&": "&",
|
|
32693
|
+
"<": "<",
|
|
32694
|
+
">": ">",
|
|
32695
|
+
'"': """,
|
|
32696
|
+
"'": "'"
|
|
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
|
-
|
|
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: [
|
|
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: [
|
|
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
|
|
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 { ${
|
|
32740
|
-
`<ThemeProvider theme={
|
|
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
|
-
`
|
|
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(
|
|
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
|
-
"
|
|
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.
|
|
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
|
|
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,
|
|
33393
|
-
res.setHeader(
|
|
33394
|
-
|
|
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
|
|
33401
|
-
|
|
33402
|
-
|
|
33403
|
-
|
|
33404
|
-
|
|
33405
|
-
|
|
33406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33416
|
-
|
|
33417
|
-
|
|
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
|
-
}
|
|
33420
|
-
|
|
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();
|