semiotic 3.7.1 → 3.7.3
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/README.md +18 -1
- package/ai/behaviorContracts.cjs +19 -9
- package/ai/cli.js +15 -0
- package/ai/dist/mcp-server.js +175 -61
- package/ai/schema.json +1 -1
- package/dist/components/stream/NetworkCanvasHitTester.d.ts +3 -2
- package/dist/components/stream/NetworkPipelineStore.d.ts +46 -1
- package/dist/components/stream/PipelineStore.d.ts +6 -0
- package/dist/components/stream/StalenessBadge.d.ts +19 -0
- 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-utils.min.js +1 -1
- package/dist/semiotic-utils.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 +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,17 @@ Simple charts in 5 lines. Network graphs, streaming data, and coordinated
|
|
|
12
12
|
dashboards when you need them. Structured schemas and an MCP server so
|
|
13
13
|
AI coding assistants generate correct chart code on the first try.
|
|
14
14
|
|
|
15
|
+
## What's New in 3.7.3
|
|
16
|
+
|
|
17
|
+
3.7.3 is a hosted ChatGPT Apps patch release:
|
|
18
|
+
|
|
19
|
+
- `semiotic-mcp --http` can now serve the OpenAI Apps domain verification challenge from
|
|
20
|
+
`/.well-known/openai-apps-challenge` when `OPENAI_APPS_CHALLENGE_TOKEN` is configured.
|
|
21
|
+
- The Cloud Run wrapper docs now include the exact Challenge Base URL, token environment variable,
|
|
22
|
+
and `curl` check for ChatGPT Apps verification.
|
|
23
|
+
- MCP HTTP tests now cover the Apps challenge route while preserving the unauthenticated OAuth
|
|
24
|
+
discovery 404 behavior.
|
|
25
|
+
|
|
15
26
|
```jsx
|
|
16
27
|
import { LineChart } from "semiotic/xy"
|
|
17
28
|
|
|
@@ -411,10 +422,16 @@ Add to your MCP client config (e.g. `claude_desktop_config.json` for Claude Desk
|
|
|
411
422
|
}
|
|
412
423
|
```
|
|
413
424
|
|
|
414
|
-
No API keys or authentication required. The server runs locally via stdio. HTTP mode is also available for inspectors, web clients, and ChatGPT Apps SDK experiments: `npx semiotic-mcp --http --port 3001`.
|
|
425
|
+
No API keys or authentication required. The server runs locally via stdio. HTTP mode is also available for inspectors, web clients, and ChatGPT Apps SDK experiments: `npx semiotic-mcp --http --port 3001`. Since 3.7.2, HTTP mode is stateless: each request gets a fresh read-only MCP server + transport, so it can autoscale on serverless hosts without sticky sessions.
|
|
415
426
|
|
|
416
427
|
For ChatGPT developer mode, expose the HTTP endpoint over HTTPS with a tunnel and create a connector that points at `https://<your-tunnel>/mcp`. The experimental Apps SDK surface is `renderInteractiveChart`, which returns a `text/html;profile=mcp-app` widget template plus a hidden SVG payload rendered by Semiotic on the MCP server.
|
|
417
428
|
|
|
429
|
+
For a hosted deployment, see `deploy/cloud-run`. The wrapper runs the published `semiotic-mcp`
|
|
430
|
+
binary, exposes `/mcp` plus health endpoints, and supports `MCP_ALLOWED_HOSTS` for production
|
|
431
|
+
host-header allowlisting. For ChatGPT Apps domain verification, set
|
|
432
|
+
`OPENAI_APPS_CHALLENGE_TOKEN` so HTTP mode serves the raw token from
|
|
433
|
+
`/.well-known/openai-apps-challenge`.
|
|
434
|
+
|
|
418
435
|
### Tools
|
|
419
436
|
|
|
420
437
|
| Tool | Description |
|
package/ai/behaviorContracts.cjs
CHANGED
|
@@ -15,13 +15,17 @@ const DOC_MARKER_START = "<!-- semiotic-behavior-contracts:start -->"
|
|
|
15
15
|
const DOC_MARKER_END = "<!-- semiotic-behavior-contracts:end -->"
|
|
16
16
|
|
|
17
17
|
// Components whose static config requires `data` are derived from
|
|
18
|
-
// `ai/schema.json` rather than maintained as a hand-curated list.
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
18
|
+
// `ai/schema.json` rather than maintained as a hand-curated list.
|
|
19
|
+
//
|
|
20
|
+
// A component needs data in STATIC usage if its schema declares a `data` input
|
|
21
|
+
// prop — NOT merely if `data` is in the `required` array. `required` lists the
|
|
22
|
+
// semantic accessors a chart needs (highAccessor, subcategoryAccessor, series,
|
|
23
|
+
// …), and several data-driven charts don't put `data` itself there:
|
|
24
|
+
// CandlestickChart, MultiAxisLineChart, QuadrantChart, DifferenceChart,
|
|
25
|
+
// LikertChart, and SwimlaneChart. Keying off `required.includes("data")` missed
|
|
26
|
+
// exactly those — they'd render blank with no data yet passed --doctor / MCP
|
|
27
|
+
// diagnoseConfig as "OK" in static mode. Keying off the presence of a `data`
|
|
28
|
+
// property catches them (and still includes the charts that DO list `data`).
|
|
25
29
|
//
|
|
26
30
|
// `STATIC_DATA_COMPONENTS` stays exported as a Set for test/legacy callers
|
|
27
31
|
// that probe the surface, and is rebuilt from disk at module load time.
|
|
@@ -41,8 +45,10 @@ function loadStaticDataComponentsFromSchema() {
|
|
|
41
45
|
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"))
|
|
42
46
|
const out = new Set()
|
|
43
47
|
for (const tool of schema.tools || []) {
|
|
44
|
-
const
|
|
45
|
-
if
|
|
48
|
+
const properties = tool.function?.parameters?.properties || {}
|
|
49
|
+
// Data-driven if the schema declares a `data` input prop, regardless of
|
|
50
|
+
// whether `data` appears in `required` (see note above).
|
|
51
|
+
if ("data" in properties) out.add(tool.function.name)
|
|
46
52
|
}
|
|
47
53
|
if (out.size > 0) return out
|
|
48
54
|
} catch {
|
|
@@ -114,6 +120,10 @@ const PUSH_MODE_COMPONENTS = [
|
|
|
114
120
|
"Scatterplot",
|
|
115
121
|
"BubbleChart",
|
|
116
122
|
"ConnectedScatterplot",
|
|
123
|
+
"CandlestickChart",
|
|
124
|
+
"MultiAxisLineChart",
|
|
125
|
+
"QuadrantChart",
|
|
126
|
+
"DifferenceChart",
|
|
117
127
|
"BarChart",
|
|
118
128
|
"StackedBarChart",
|
|
119
129
|
"GroupedBarChart",
|
package/ai/cli.js
CHANGED
|
@@ -163,6 +163,21 @@ function validatePropsWithSchema(componentName, props, usageMode = "static") {
|
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
// Array-shape charts that declare a `data` schema prop need it in static
|
|
167
|
+
// usage even when "data" isn't in `required` (those lists hold semantic
|
|
168
|
+
// accessors). Without this, --doctor passed dataless static CandlestickChart /
|
|
169
|
+
// MultiAxisLineChart / QuadrantChart / DifferenceChart / SwimlaneChart /
|
|
170
|
+
// LikertChart configs that render blank. dataRequiredForUsageMode is true for
|
|
171
|
+
// them in static and false in push, mirroring the MCP diagnoseConfig path.
|
|
172
|
+
if (
|
|
173
|
+
"data" in properties &&
|
|
174
|
+
!required.includes("data") &&
|
|
175
|
+
dataRequiredForUsageMode(component.name, usageMode) &&
|
|
176
|
+
(props.data === undefined || props.data === null)
|
|
177
|
+
) {
|
|
178
|
+
errors.push(`"data" is required for ${component.name}.`)
|
|
179
|
+
}
|
|
180
|
+
|
|
166
181
|
for (const [propName, value] of Object.entries(props)) {
|
|
167
182
|
if (value === undefined || value === null) continue
|
|
168
183
|
const propSchema = properties[propName]
|
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",
|
|
@@ -32435,21 +32443,25 @@ ${errors.join("\n")}`
|
|
|
32435
32443
|
// ai/mcp-server.ts
|
|
32436
32444
|
var import_server2 = require("semiotic/server");
|
|
32437
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());
|
|
32438
32449
|
var {
|
|
32439
32450
|
componentIndexFromSchema,
|
|
32440
32451
|
metadataForComponent
|
|
32441
|
-
} =
|
|
32452
|
+
} = import_componentMetadata.default;
|
|
32442
32453
|
var {
|
|
32443
32454
|
formatSuggestionReport,
|
|
32444
|
-
suggestCharts
|
|
32445
|
-
|
|
32455
|
+
suggestCharts,
|
|
32456
|
+
VALID_INTENTS
|
|
32457
|
+
} = import_chartSuggestions.default;
|
|
32446
32458
|
var {
|
|
32447
32459
|
BEHAVIOR_CONTRACTS,
|
|
32448
32460
|
behaviorContractsFor,
|
|
32449
32461
|
dataRequiredForUsageMode,
|
|
32450
32462
|
formatDoctorBehaviorContracts,
|
|
32451
32463
|
normalizeUsageMode
|
|
32452
|
-
} =
|
|
32464
|
+
} = import_behaviorContracts.default;
|
|
32453
32465
|
var schemaPath = path.resolve(__dirname, "../schema.json");
|
|
32454
32466
|
var schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
|
|
32455
32467
|
var schemaByComponent = {};
|
|
@@ -32824,8 +32836,23 @@ ${JSON.stringify(contracts, null, 2)}` : "";
|
|
|
32824
32836
|
${JSON.stringify(entry, null, 2)}${contractText}` }]
|
|
32825
32837
|
};
|
|
32826
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
|
+
};
|
|
32827
32851
|
async function suggestChartHandler(args) {
|
|
32828
|
-
|
|
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 });
|
|
32829
32856
|
const content = [{ type: "text", text: formatSuggestionReport(result) }];
|
|
32830
32857
|
if (!result.ok) {
|
|
32831
32858
|
return { content, isError: true, structuredContent: result };
|
|
@@ -32873,6 +32900,10 @@ async function renderChartHandler(args) {
|
|
|
32873
32900
|
${JSON.stringify(evidence, null, 2)}`
|
|
32874
32901
|
};
|
|
32875
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
|
+
};
|
|
32876
32907
|
}
|
|
32877
32908
|
if (theme && Object.keys(theme).length > 0) {
|
|
32878
32909
|
const validVars = Object.entries(theme).filter(([k]) => k.startsWith("--semiotic-")).map(([k, v]) => `${k}: ${v}`).join("; ");
|
|
@@ -33046,23 +33077,26 @@ async function reportIssueHandler(args) {
|
|
|
33046
33077
|
${url2}` }]
|
|
33047
33078
|
};
|
|
33048
33079
|
}
|
|
33049
|
-
var
|
|
33050
|
-
"light",
|
|
33051
|
-
"dark",
|
|
33052
|
-
"high-contrast",
|
|
33053
|
-
"pastels",
|
|
33054
|
-
"pastels-dark",
|
|
33055
|
-
"bi-tool",
|
|
33056
|
-
"bi-tool-dark",
|
|
33057
|
-
"italian",
|
|
33058
|
-
"italian-dark",
|
|
33059
|
-
"tufte",
|
|
33060
|
-
"tufte-dark",
|
|
33061
|
-
"journalist",
|
|
33062
|
-
"journalist-dark",
|
|
33063
|
-
"playful",
|
|
33064
|
-
"playful-dark"
|
|
33065
|
-
|
|
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);
|
|
33066
33100
|
async function applyThemeHandler(args) {
|
|
33067
33101
|
const name = args.name;
|
|
33068
33102
|
if (!name) {
|
|
@@ -33082,6 +33116,7 @@ Dark-mode presets: ${THEME_PRESET_NAMES.filter((n) => n.includes("dark")).join("
|
|
|
33082
33116
|
isError: true
|
|
33083
33117
|
};
|
|
33084
33118
|
}
|
|
33119
|
+
const exportName = THEME_PRESETS[name];
|
|
33085
33120
|
const usage = [
|
|
33086
33121
|
`## Theme: "${name}"`,
|
|
33087
33122
|
"",
|
|
@@ -33095,24 +33130,23 @@ Dark-mode presets: ${THEME_PRESET_NAMES.filter((n) => n.includes("dark")).join("
|
|
|
33095
33130
|
"",
|
|
33096
33131
|
"### Option 2: Import the theme object",
|
|
33097
33132
|
"```jsx",
|
|
33098
|
-
`import { ${
|
|
33099
|
-
`<ThemeProvider theme={
|
|
33133
|
+
`import { ${exportName} } from "semiotic/themes"`,
|
|
33134
|
+
`<ThemeProvider theme={${exportName}}>`,
|
|
33100
33135
|
` <BarChart ... />`,
|
|
33101
33136
|
`</ThemeProvider>`,
|
|
33102
33137
|
"```",
|
|
33103
33138
|
"",
|
|
33104
33139
|
"### Option 3: CSS custom properties (no React required)",
|
|
33105
33140
|
"```jsx",
|
|
33106
|
-
`import { themeToCSS } from "semiotic/themes"`,
|
|
33107
|
-
`
|
|
33108
|
-
`const css = themeToCSS(themeObject, ".my-charts")`,
|
|
33141
|
+
`import { themeToCSS, ${exportName} } from "semiotic/themes"`,
|
|
33142
|
+
`const css = themeToCSS(${exportName}, ".my-charts")`,
|
|
33109
33143
|
"// Outputs CSS custom properties string for embedding in a stylesheet",
|
|
33110
33144
|
"```",
|
|
33111
33145
|
"",
|
|
33112
33146
|
"### Option 4: Design tokens JSON",
|
|
33113
33147
|
"```jsx",
|
|
33114
|
-
`import { themeToTokens } from "semiotic/themes"`,
|
|
33115
|
-
`const tokens = themeToTokens(
|
|
33148
|
+
`import { themeToTokens, ${exportName} } from "semiotic/themes"`,
|
|
33149
|
+
`const tokens = themeToTokens(${exportName})`,
|
|
33116
33150
|
"// Style Dictionary / DTCG-compatible token format",
|
|
33117
33151
|
"```",
|
|
33118
33152
|
"",
|
|
@@ -33403,6 +33437,12 @@ async function groundChartHandler(args) {
|
|
|
33403
33437
|
structuredContent: grounding
|
|
33404
33438
|
};
|
|
33405
33439
|
}
|
|
33440
|
+
var READ_ONLY_TOOL_ANNOTATIONS = {
|
|
33441
|
+
readOnlyHint: true,
|
|
33442
|
+
destructiveHint: false,
|
|
33443
|
+
idempotentHint: true,
|
|
33444
|
+
openWorldHint: false
|
|
33445
|
+
};
|
|
33406
33446
|
function createServer2() {
|
|
33407
33447
|
const srv = new McpServer({
|
|
33408
33448
|
name: "semiotic",
|
|
@@ -33547,14 +33587,15 @@ function createServer2() {
|
|
|
33547
33587
|
"getSchema",
|
|
33548
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.`,
|
|
33549
33589
|
{ component: external_exports3.string().optional().describe("Component name, e.g. 'LineChart'. Omit to list all.") },
|
|
33590
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33550
33591
|
getSchemaHandler
|
|
33551
33592
|
);
|
|
33552
33593
|
srv.tool(
|
|
33553
33594
|
"suggestChart",
|
|
33554
|
-
"
|
|
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).",
|
|
33555
33596
|
{
|
|
33556
33597
|
data: external_exports3.array(external_exports3.record(external_exports3.string(), external_exports3.unknown())).min(1).max(5).describe("1-5 sample data objects"),
|
|
33557
|
-
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."),
|
|
33558
33599
|
capabilities: external_exports3.object({
|
|
33559
33600
|
push: external_exports3.boolean().optional().describe("Require ref-based push API (live streaming via ref.current.push())"),
|
|
33560
33601
|
linkedHover: external_exports3.boolean().optional().describe("Require cross-chart linked hover support"),
|
|
@@ -33567,6 +33608,7 @@ function createServer2() {
|
|
|
33567
33608
|
// validation from being unreachable from MCP callers.
|
|
33568
33609
|
}).strict().optional().describe("Capability constraints \u2014 set a key to true to require, false to forbid. Unset keys are ignored.")
|
|
33569
33610
|
},
|
|
33611
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33570
33612
|
suggestChartHandler
|
|
33571
33613
|
);
|
|
33572
33614
|
srv.tool(
|
|
@@ -33578,6 +33620,7 @@ function createServer2() {
|
|
|
33578
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."),
|
|
33579
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.")
|
|
33580
33622
|
},
|
|
33623
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33581
33624
|
renderChartHandler
|
|
33582
33625
|
);
|
|
33583
33626
|
srv.registerTool(
|
|
@@ -33620,6 +33663,7 @@ function createServer2() {
|
|
|
33620
33663
|
props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x' }."),
|
|
33621
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.")
|
|
33622
33665
|
},
|
|
33666
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33623
33667
|
diagnoseConfigHandler
|
|
33624
33668
|
);
|
|
33625
33669
|
srv.tool(
|
|
@@ -33632,6 +33676,7 @@ function createServer2() {
|
|
|
33632
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."),
|
|
33633
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.")
|
|
33634
33678
|
},
|
|
33679
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33635
33680
|
auditAccessibilityHandler
|
|
33636
33681
|
);
|
|
33637
33682
|
srv.tool(
|
|
@@ -33642,6 +33687,7 @@ function createServer2() {
|
|
|
33642
33687
|
body: external_exports3.string().optional().describe("Issue body with details, reproduction steps, diagnoseConfig output"),
|
|
33643
33688
|
labels: external_exports3.union([external_exports3.array(external_exports3.string()), external_exports3.string()]).optional().describe("GitHub labels, e.g. ['bug'] or 'bug'")
|
|
33644
33689
|
},
|
|
33690
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33645
33691
|
reportIssueHandler
|
|
33646
33692
|
);
|
|
33647
33693
|
srv.tool(
|
|
@@ -33650,6 +33696,7 @@ function createServer2() {
|
|
|
33650
33696
|
{
|
|
33651
33697
|
name: external_exports3.string().optional().describe("Theme preset name, e.g. 'tufte', 'pastels-dark', 'bi-tool'. Omit to list all available themes.")
|
|
33652
33698
|
},
|
|
33699
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33653
33700
|
applyThemeHandler
|
|
33654
33701
|
);
|
|
33655
33702
|
srv.tool(
|
|
@@ -33660,6 +33707,7 @@ function createServer2() {
|
|
|
33660
33707
|
props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).describe("The full chart props including data"),
|
|
33661
33708
|
query: external_exports3.string().optional().describe("A natural language question about the chart data")
|
|
33662
33709
|
},
|
|
33710
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33663
33711
|
interrogateChartHandler
|
|
33664
33712
|
);
|
|
33665
33713
|
srv.tool(
|
|
@@ -33669,6 +33717,7 @@ function createServer2() {
|
|
|
33669
33717
|
component: external_exports3.string().describe("Chart component name, e.g. 'LineChart'"),
|
|
33670
33718
|
props: external_exports3.record(external_exports3.string(), external_exports3.unknown()).describe("The full chart props including data")
|
|
33671
33719
|
},
|
|
33720
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33672
33721
|
groundChartHandler
|
|
33673
33722
|
);
|
|
33674
33723
|
srv.tool(
|
|
@@ -33689,6 +33738,7 @@ function createServer2() {
|
|
|
33689
33738
|
intent: external_exports3.union([external_exports3.string(), external_exports3.array(external_exports3.string())]).optional().describe("Ranking intent."),
|
|
33690
33739
|
maxResults: external_exports3.number().int().min(1).max(20).optional()
|
|
33691
33740
|
},
|
|
33741
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33692
33742
|
suggestStreamChartsHandler
|
|
33693
33743
|
);
|
|
33694
33744
|
srv.tool(
|
|
@@ -33700,6 +33750,7 @@ function createServer2() {
|
|
|
33700
33750
|
maxPanels: external_exports3.number().int().min(1).max(12).optional().describe("Maximum panels (default 6)."),
|
|
33701
33751
|
diversifyByFamily: external_exports3.boolean().optional().describe("Prefer not to repeat chart families across panels (default true).")
|
|
33702
33752
|
},
|
|
33753
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33703
33754
|
suggestDashboardHandler
|
|
33704
33755
|
);
|
|
33705
33756
|
srv.tool(
|
|
@@ -33724,6 +33775,7 @@ function createServer2() {
|
|
|
33724
33775
|
intent: external_exports3.union([external_exports3.string(), external_exports3.array(external_exports3.string())]).optional(),
|
|
33725
33776
|
maxResults: external_exports3.number().int().min(1).max(20).optional()
|
|
33726
33777
|
},
|
|
33778
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33727
33779
|
suggestStretchChartsHandler
|
|
33728
33780
|
);
|
|
33729
33781
|
srv.tool(
|
|
@@ -33735,6 +33787,7 @@ function createServer2() {
|
|
|
33735
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."),
|
|
33736
33788
|
maxAlternatives: external_exports3.number().int().min(1).max(10).optional().describe("Cap on alternatives returned (default 3).")
|
|
33737
33789
|
},
|
|
33790
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33738
33791
|
repairChartConfigHandler
|
|
33739
33792
|
);
|
|
33740
33793
|
srv.tool(
|
|
@@ -33761,6 +33814,7 @@ function createServer2() {
|
|
|
33761
33814
|
receptionModality: external_exports3.enum(["visual", "screen-reader", "sonified", "agent"]).optional().describe("Reception channel \u2014 see suggestCharts.")
|
|
33762
33815
|
}).optional().describe("Audience profile \u2014 familiarity, adoption targets, exposure level, and reception modality.")
|
|
33763
33816
|
},
|
|
33817
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33764
33818
|
proposeChartVariantsHandler
|
|
33765
33819
|
);
|
|
33766
33820
|
srv.tool(
|
|
@@ -33787,6 +33841,7 @@ function createServer2() {
|
|
|
33787
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.")
|
|
33788
33842
|
}).optional().describe("Audience profile \u2014 familiarity, adoption targets, exposure level, and reception modality.")
|
|
33789
33843
|
},
|
|
33844
|
+
READ_ONLY_TOOL_ANNOTATIONS,
|
|
33790
33845
|
suggestChartsHandler
|
|
33791
33846
|
);
|
|
33792
33847
|
return srv;
|
|
@@ -33798,39 +33853,98 @@ var parsedPort = portFlagIndex !== -1 && cliArgs[portFlagIndex + 1] != null ? pa
|
|
|
33798
33853
|
var port = Number.isFinite(parsedPort) ? parsedPort : 3001;
|
|
33799
33854
|
async function main() {
|
|
33800
33855
|
if (httpMode) {
|
|
33801
|
-
const
|
|
33856
|
+
const allowedHosts = (process.env.MCP_ALLOWED_HOSTS || "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
|
|
33857
|
+
const openaiAppsChallengeToken = (process.env.OPENAI_APPS_CHALLENGE_TOKEN || "").trim();
|
|
33858
|
+
const healthBody = () => JSON.stringify({
|
|
33859
|
+
status: "ok",
|
|
33860
|
+
name: "semiotic-mcp",
|
|
33861
|
+
version: schema.version || "3.0.0",
|
|
33862
|
+
transport: "streamable-http",
|
|
33863
|
+
mode: "stateless"
|
|
33864
|
+
});
|
|
33802
33865
|
const httpServer = http.createServer(async (req, res) => {
|
|
33803
33866
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
33804
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST,
|
|
33805
|
-
res.setHeader(
|
|
33806
|
-
|
|
33867
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
33868
|
+
res.setHeader(
|
|
33869
|
+
"Access-Control-Allow-Headers",
|
|
33870
|
+
"Content-Type, Accept, Authorization, mcp-session-id, MCP-Protocol-Version, Last-Event-ID"
|
|
33871
|
+
);
|
|
33872
|
+
res.setHeader("Access-Control-Expose-Headers", "MCP-Protocol-Version");
|
|
33807
33873
|
if (req.method === "OPTIONS") {
|
|
33808
33874
|
res.writeHead(204);
|
|
33809
33875
|
res.end();
|
|
33810
33876
|
return;
|
|
33811
33877
|
}
|
|
33812
|
-
const
|
|
33813
|
-
|
|
33814
|
-
|
|
33815
|
-
|
|
33816
|
-
|
|
33817
|
-
|
|
33818
|
-
|
|
33878
|
+
const pathname = (() => {
|
|
33879
|
+
try {
|
|
33880
|
+
return new URL(req.url || "/", "http://localhost").pathname;
|
|
33881
|
+
} catch {
|
|
33882
|
+
return "/";
|
|
33883
|
+
}
|
|
33884
|
+
})();
|
|
33885
|
+
if (allowedHosts.length > 0) {
|
|
33886
|
+
const rawHost = String(req.headers.host || "").trim().toLowerCase();
|
|
33887
|
+
const normalizedHost = rawHost.startsWith("[") ? rawHost.replace(/^\[([^\]]+)\](?::\d+)?$/, "$1") : rawHost.split(":")[0];
|
|
33888
|
+
if (!allowedHosts.includes(rawHost) && !allowedHosts.includes(normalizedHost)) {
|
|
33889
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
33890
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Forbidden host" }, id: null }));
|
|
33891
|
+
return;
|
|
33892
|
+
}
|
|
33893
|
+
}
|
|
33894
|
+
if (req.method === "GET" && (pathname === "/healthz" || pathname === "/health")) {
|
|
33895
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
33896
|
+
res.end(healthBody());
|
|
33897
|
+
return;
|
|
33898
|
+
}
|
|
33899
|
+
if (req.method === "GET" && pathname === "/.well-known/openai-apps-challenge" && openaiAppsChallengeToken) {
|
|
33900
|
+
res.writeHead(200, {
|
|
33901
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
33902
|
+
"Cache-Control": "no-store"
|
|
33819
33903
|
});
|
|
33820
|
-
|
|
33904
|
+
res.end(openaiAppsChallengeToken);
|
|
33905
|
+
return;
|
|
33906
|
+
}
|
|
33907
|
+
if (pathname !== "/" && pathname !== "/mcp") {
|
|
33908
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
33909
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
33910
|
+
return;
|
|
33911
|
+
}
|
|
33912
|
+
if (req.method === "GET") {
|
|
33913
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
33914
|
+
res.end(healthBody());
|
|
33915
|
+
return;
|
|
33916
|
+
}
|
|
33917
|
+
if (req.method !== "POST") {
|
|
33918
|
+
res.writeHead(405, { "Content-Type": "application/json" });
|
|
33919
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32e3, message: "Method not allowed" }, id: null }));
|
|
33920
|
+
return;
|
|
33921
|
+
}
|
|
33922
|
+
const srv = createServer2();
|
|
33923
|
+
const transport = new StreamableHTTPServerTransport({
|
|
33924
|
+
sessionIdGenerator: void 0,
|
|
33925
|
+
enableJsonResponse: true
|
|
33926
|
+
});
|
|
33927
|
+
let torndown = false;
|
|
33928
|
+
const teardown = () => {
|
|
33929
|
+
if (torndown) return;
|
|
33930
|
+
torndown = true;
|
|
33931
|
+
Promise.resolve(transport.close()).catch(() => {
|
|
33932
|
+
});
|
|
33933
|
+
Promise.resolve(srv.close()).catch(() => {
|
|
33934
|
+
});
|
|
33935
|
+
};
|
|
33936
|
+
res.on("close", teardown);
|
|
33937
|
+
try {
|
|
33821
33938
|
await srv.connect(transport);
|
|
33822
|
-
transport.onclose = () => {
|
|
33823
|
-
const sid2 = transport.sessionId;
|
|
33824
|
-
if (sid2) sessions.delete(sid2);
|
|
33825
|
-
};
|
|
33826
33939
|
await transport.handleRequest(req, res);
|
|
33827
|
-
|
|
33828
|
-
|
|
33829
|
-
|
|
33940
|
+
} catch (err) {
|
|
33941
|
+
console.error("Request handling error:", err);
|
|
33942
|
+
if (!res.headersSent) {
|
|
33943
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
33944
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32603, message: "Internal server error" }, id: null }));
|
|
33830
33945
|
}
|
|
33831
|
-
}
|
|
33832
|
-
|
|
33833
|
-
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 }));
|
|
33946
|
+
} finally {
|
|
33947
|
+
teardown();
|
|
33834
33948
|
}
|
|
33835
33949
|
});
|
|
33836
33950
|
httpServer.listen(port, () => {
|
package/ai/schema.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { NetworkSceneNode, NetworkSceneEdge } from "./networkTypes";
|
|
1
|
+
import type { NetworkSceneNode, NetworkSceneEdge, NetworkCircleNode } from "./networkTypes";
|
|
2
|
+
import type { Quadtree } from "d3-quadtree";
|
|
2
3
|
export interface NetworkHitResult {
|
|
3
4
|
type: "node" | "edge";
|
|
4
5
|
datum: any;
|
|
@@ -11,4 +12,4 @@ export interface NetworkHitResult {
|
|
|
11
12
|
*
|
|
12
13
|
* Checks nodes first (they're on top), then edges.
|
|
13
14
|
*/
|
|
14
|
-
export declare function findNearestNetworkNode(sceneNodes: NetworkSceneNode[], sceneEdges: NetworkSceneEdge[], px: number, py: number, maxDistance?: number): NetworkHitResult | null;
|
|
15
|
+
export declare function findNearestNetworkNode(sceneNodes: NetworkSceneNode[], sceneEdges: NetworkSceneEdge[], px: number, py: number, maxDistance?: number, nodeQuadtree?: Quadtree<NetworkCircleNode> | null, maxNodeRadius?: number): NetworkHitResult | null;
|