semiotic 3.1.1 → 3.1.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.
@@ -19,6 +19,9 @@
19
19
  * }
20
20
  * }
21
21
  * }
22
+ *
23
+ * HTTP mode (for remote inspectors / web clients):
24
+ * npx semiotic-mcp --http --port 3001
22
25
  */
23
26
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
24
27
  if (k2 === undefined) k2 = k;
@@ -56,27 +59,25 @@ var __importStar = (this && this.__importStar) || (function () {
56
59
  Object.defineProperty(exports, "__esModule", { value: true });
57
60
  const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
58
61
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
62
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
63
+ const zod_1 = require("zod");
59
64
  const fs = __importStar(require("fs"));
60
65
  const path = __importStar(require("path"));
66
+ const http = __importStar(require("http"));
61
67
  const renderHOCToSVG_1 = require("./renderHOCToSVG");
62
68
  const componentRegistry_1 = require("./componentRegistry");
63
69
  const ai_1 = require("semiotic/ai");
64
70
  // Load schema.json for version info
65
71
  const schemaPath = path.resolve(__dirname, "../schema.json");
66
72
  const schema = JSON.parse(fs.readFileSync(schemaPath, "utf-8"));
67
- // Build MCP server
68
- const server = new mcp_js_1.McpServer({
69
- name: "semiotic",
70
- version: schema.version || "3.0.0",
71
- });
72
73
  // Build component name → schema lookup from schema.json
73
74
  const schemaByComponent = {};
74
75
  for (const tool of schema.tools) {
75
76
  schemaByComponent[tool.function.name] = tool.function;
76
77
  }
77
- // ── getSchema tool ──────────────────────────────────────────────────────
78
- // Returns the prop schema for a specific component, or lists all components.
79
- server.tool("getSchema", `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.`, {}, async (args) => {
78
+ const componentNames = Object.keys(componentRegistry_1.COMPONENT_REGISTRY).sort();
79
+ const REPO = "nteract/semiotic";
80
+ async function getSchemaHandler(args) {
80
81
  const component = args.component;
81
82
  if (!component) {
82
83
  const all = Object.keys(schemaByComponent).sort();
@@ -94,14 +95,12 @@ server.tool("getSchema", `Return the prop schema for a Semiotic chart component.
94
95
  isError: true,
95
96
  };
96
97
  }
97
- const renderable = componentRegistry_1.COMPONENT_REGISTRY[component] ? "This component can be rendered to SVG via renderChart." : "This component requires a browser environment and cannot be rendered via renderChart.";
98
+ const renderableNote = componentRegistry_1.COMPONENT_REGISTRY[component] ? "This component can be rendered to SVG via renderChart." : "This component requires a browser environment and cannot be rendered via renderChart.";
98
99
  return {
99
- content: [{ type: "text", text: `${renderable}\n\n${JSON.stringify(entry, null, 2)}` }],
100
+ content: [{ type: "text", text: `${renderableNote}\n\n${JSON.stringify(entry, null, 2)}` }],
100
101
  };
101
- });
102
- // ── suggestChart tool ───────────────────────────────────────────────────
103
- // Analyzes a data sample and recommends appropriate chart types.
104
- server.tool("suggestChart", "Recommend Semiotic chart types for a given data sample. Pass { data: [...] } with 1-5 sample objects. Optionally pass { intent: 'comparison' | 'trend' | 'distribution' | 'relationship' | 'composition' | 'geographic' | 'network' | 'hierarchy' } to narrow suggestions. Returns ranked recommendations with example props.", {}, async (args) => {
102
+ }
103
+ async function suggestChartHandler(args) {
105
104
  const data = args.data;
106
105
  const intent = args.intent;
107
106
  if (!data || !Array.isArray(data) || data.length === 0) {
@@ -135,8 +134,6 @@ server.tool("suggestChart", "Recommend Semiotic chart types for a given data sam
135
134
  numericFields.push(key);
136
135
  }
137
136
  else if (typeof first === "string") {
138
- // Check for dates — require ISO-like pattern (YYYY-MM or YYYY/MM or YYYY-MM-DD, etc.)
139
- // to avoid false positives on 4-digit IDs like "1234"
140
137
  if (/^\d{4}[-/]\d{2}/.test(first) && !isNaN(Date.parse(first))) {
141
138
  dateFields.push(key);
142
139
  }
@@ -144,20 +141,17 @@ server.tool("suggestChart", "Recommend Semiotic chart types for a given data sam
144
141
  stringFields.push(key);
145
142
  }
146
143
  }
147
- // Detect geo fields
148
144
  const kl = key.toLowerCase();
149
145
  if (kl === "lat" || kl === "latitude")
150
146
  geoFields.lat = key;
151
147
  if (kl === "lon" || kl === "lng" || kl === "longitude")
152
148
  geoFields.lon = key;
153
- // Detect network fields
154
149
  if (kl === "source" || kl === "from")
155
150
  networkFields.source = key;
156
151
  if (kl === "target" || kl === "to")
157
152
  networkFields.target = key;
158
153
  if (kl === "value" || kl === "weight" || kl === "amount")
159
154
  networkFields.value = key;
160
- // Detect hierarchy fields
161
155
  if (kl === "children" || kl === "values")
162
156
  hierarchyFields.children = key;
163
157
  if (kl === "parent")
@@ -252,11 +246,11 @@ server.tool("suggestChart", "Recommend Semiotic chart types for a given data sam
252
246
  props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"`, stackBy: `"${stringFields[1]}"` },
253
247
  });
254
248
  }
255
- if (data.length >= 10 && (!intent || intent === "distribution")) {
249
+ if (!intent || intent === "distribution") {
256
250
  suggestions.push({
257
251
  component: "Histogram",
258
252
  confidence: "medium",
259
- reason: `${data.length}+ data points — histogram shows value distribution`,
253
+ reason: `Numeric distribution of ${valField} — histogram shows value spread`,
260
254
  props: { data: "data", categoryAccessor: `"${catField}"`, valueAccessor: `"${valField}"` },
261
255
  });
262
256
  }
@@ -314,22 +308,10 @@ server.tool("suggestChart", "Recommend Semiotic chart types for a given data sam
314
308
  return {
315
309
  content: [{ type: "text", text: lines.join("\n\n") }],
316
310
  };
317
- });
318
- // ── renderChart tool ─────────────────────────────────────────────────────
319
- // Generic tool that renders any Semiotic HOC chart to static SVG.
320
- // Accepts { component, props } — the single entry point for all chart rendering.
321
- const componentNames = Object.keys(componentRegistry_1.COMPONENT_REGISTRY).sort();
322
- server.tool("renderChart", `Render any Semiotic chart to static SVG. Pass { component: '<name>', props: { ... } }. Returns SVG string or validation errors. Available components: ${componentNames.join(", ")}.`, {}, async (args) => {
311
+ }
312
+ async function renderChartHandler(args) {
323
313
  const component = args.component;
324
- let props;
325
- if (args.props) {
326
- props = args.props;
327
- }
328
- else {
329
- // Flatten shape: { component, data, ... } — strip component before forwarding
330
- const { component: _, ...rest } = args;
331
- props = rest;
332
- }
314
+ const props = args.props ?? {};
333
315
  if (!component) {
334
316
  return {
335
317
  content: [{ type: "text", text: `Missing 'component' field. Provide { component: '<name>', props: { ... } }. Available: ${componentNames.join(", ")}` }],
@@ -352,20 +334,10 @@ server.tool("renderChart", `Render any Semiotic chart to static SVG. Pass { comp
352
334
  return {
353
335
  content: [{ type: "text", text: result.svg }],
354
336
  };
355
- });
356
- // ── diagnoseConfig tool ──────────────────────────────────────────────────
357
- // Anti-pattern detector: checks for common failure modes and returns
358
- // actionable fix instructions.
359
- server.tool("diagnoseConfig", "Diagnose a Semiotic chart configuration for common problems (empty data, bad dimensions, missing accessors, wrong data shape, etc). Pass { component: 'LineChart', props: { ... } }. Returns structured diagnoses with fix instructions.", {}, async (args) => {
337
+ }
338
+ async function diagnoseConfigHandler(args) {
360
339
  const component = args.component;
361
- let props;
362
- if (args.props) {
363
- props = args.props;
364
- }
365
- else {
366
- const { component: _, ...rest } = args;
367
- props = rest;
368
- }
340
+ const props = args.props ?? {};
369
341
  if (!component) {
370
342
  return {
371
343
  content: [{ type: "text", text: "Missing 'component' field. Provide { component: 'LineChart', props: { ... } }." }],
@@ -374,13 +346,13 @@ server.tool("diagnoseConfig", "Diagnose a Semiotic chart configuration for commo
374
346
  }
375
347
  const result = (0, ai_1.diagnoseConfig)(component, props);
376
348
  if (result.ok) {
377
- const warnings = result.diagnoses.filter(d => d.severity === "warning");
349
+ const warnings = result.diagnoses.filter((d) => d.severity === "warning");
378
350
  const msg = warnings.length > 0
379
- ? `Configuration looks good with ${warnings.length} warning(s):\n${warnings.map(w => `⚠ [${w.code}] ${w.message}\n Fix: ${w.fix}`).join("\n")}`
351
+ ? `Configuration looks good with ${warnings.length} warning(s):\n${warnings.map((w) => `⚠ [${w.code}] ${w.message}\n Fix: ${w.fix}`).join("\n")}`
380
352
  : `✓ Configuration looks good — no issues detected.`;
381
353
  return { content: [{ type: "text", text: msg }] };
382
354
  }
383
- const lines = result.diagnoses.map(d => {
355
+ const lines = result.diagnoses.map((d) => {
384
356
  const icon = d.severity === "error" ? "✗" : "⚠";
385
357
  const fixLine = d.fix ? `\n Fix: ${d.fix}` : "";
386
358
  return `${icon} [${d.code}] ${d.message}${fixLine}`;
@@ -389,12 +361,8 @@ server.tool("diagnoseConfig", "Diagnose a Semiotic chart configuration for commo
389
361
  content: [{ type: "text", text: lines.join("\n") }],
390
362
  isError: true,
391
363
  };
392
- });
393
- // ── reportIssue tool ─────────────────────────────────────────────────────
394
- // Generates a pre-filled GitHub issue URL for bug reports or feature requests.
395
- // The user (or AI agent) can open the URL to submit — no auth needed.
396
- const REPO = "nteract/semiotic";
397
- server.tool("reportIssue", "Generate a GitHub issue URL for Semiotic bug reports or feature requests. Pass { title, body, labels? }. Returns a URL the user can open to submit. For rendering bugs, include the component name, props summary, and any diagnoseConfig output in the body.", {}, async (args) => {
364
+ }
365
+ async function reportIssueHandler(args) {
398
366
  const title = args.title;
399
367
  const body = args.body;
400
368
  const labels = args.labels;
@@ -416,11 +384,100 @@ server.tool("reportIssue", "Generate a GitHub issue URL for Semiotic bug reports
416
384
  return {
417
385
  content: [{ type: "text", text: `Open this URL to submit the issue:\n\n${url}` }],
418
386
  };
419
- });
420
- // Start the server
387
+ }
388
+ // ── Server factory ───────────────────────────────────────────────────────
389
+ // Creates a fresh McpServer with all tools registered.
390
+ // HTTP mode needs one instance per session (McpServer can only connect to one transport).
391
+ // Stdio mode uses a single instance.
392
+ function createServer() {
393
+ const srv = new mcp_js_1.McpServer({
394
+ name: "semiotic",
395
+ version: schema.version || "3.0.0",
396
+ });
397
+ srv.tool("getSchema", `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.`, { component: zod_1.z.string().optional().describe("Component name, e.g. 'LineChart'. Omit to list all.") }, getSchemaHandler);
398
+ srv.tool("suggestChart", "Recommend Semiotic chart types for a given data sample. Pass { data: [...] } with 1-5 sample objects. Optionally pass intent to narrow suggestions. Returns ranked recommendations with example props.", {
399
+ data: zod_1.z.array(zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())).min(1).max(5).describe("1-5 sample data objects"),
400
+ intent: zod_1.z.enum(["comparison", "trend", "distribution", "relationship", "composition", "geographic", "network", "hierarchy"]).optional().describe("Visualization intent to narrow suggestions"),
401
+ }, suggestChartHandler);
402
+ srv.tool("renderChart", `Render a Semiotic chart to static SVG. Returns SVG string or validation errors. Available components: ${componentNames.join(", ")}.`, {
403
+ component: zod_1.z.string().describe("Chart component name, e.g. 'LineChart', 'BarChart'"),
404
+ props: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x' }."),
405
+ }, renderChartHandler);
406
+ srv.tool("diagnoseConfig", "Diagnose a Semiotic chart configuration for common problems (empty data, bad dimensions, missing accessors, wrong data shape, etc). Returns a human-readable diagnostic report with actionable fixes.", {
407
+ component: zod_1.z.string().describe("Chart component name, e.g. 'LineChart'"),
408
+ props: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional().describe("Chart props object, e.g. { data: [...], xAccessor: 'x' }."),
409
+ }, diagnoseConfigHandler);
410
+ srv.tool("reportIssue", "Generate a GitHub issue URL for Semiotic bug reports or feature requests. Returns a URL the user can open to submit. For rendering bugs, include the component name, props summary, and any diagnoseConfig output in the body.", {
411
+ title: zod_1.z.string().describe("Issue title, e.g. 'Bug: BarChart tooltip shows undefined'"),
412
+ body: zod_1.z.string().optional().describe("Issue body with details, reproduction steps, diagnoseConfig output"),
413
+ labels: zod_1.z.union([zod_1.z.array(zod_1.z.string()), zod_1.z.string()]).optional().describe("GitHub labels, e.g. ['bug'] or 'bug'"),
414
+ }, reportIssueHandler);
415
+ return srv;
416
+ }
417
+ // ── Startup ──────────────────────────────────────────────────────────────
418
+ const cliArgs = process.argv.slice(2);
419
+ const httpMode = cliArgs.includes("--http");
420
+ const portFlagIndex = cliArgs.indexOf("--port");
421
+ const parsedPort = portFlagIndex !== -1 && cliArgs[portFlagIndex + 1] != null
422
+ ? parseInt(cliArgs[portFlagIndex + 1], 10)
423
+ : NaN;
424
+ const port = Number.isFinite(parsedPort) ? parsedPort : 3001;
421
425
  async function main() {
422
- const transport = new stdio_js_1.StdioServerTransport();
423
- await server.connect(transport);
426
+ if (httpMode) {
427
+ // HTTP mode — session-based, one server+transport per session
428
+ const sessions = new Map();
429
+ const httpServer = http.createServer(async (req, res) => {
430
+ // CORS headers for browser-based inspectors
431
+ res.setHeader("Access-Control-Allow-Origin", "*");
432
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
433
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, mcp-session-id");
434
+ res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
435
+ if (req.method === "OPTIONS") {
436
+ res.writeHead(204);
437
+ res.end();
438
+ return;
439
+ }
440
+ const sessionId = req.headers["mcp-session-id"];
441
+ if (sessionId && sessions.has(sessionId)) {
442
+ // Existing session — route to its transport
443
+ const session = sessions.get(sessionId);
444
+ await session.transport.handleRequest(req, res);
445
+ }
446
+ else if (!sessionId) {
447
+ // New session — create server + transport
448
+ const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
449
+ sessionIdGenerator: () => `semiotic-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
450
+ });
451
+ const srv = createServer();
452
+ await srv.connect(transport);
453
+ transport.onclose = () => {
454
+ const sid = transport.sessionId;
455
+ if (sid)
456
+ sessions.delete(sid);
457
+ };
458
+ await transport.handleRequest(req, res);
459
+ const sid = transport.sessionId;
460
+ if (sid) {
461
+ sessions.set(sid, { server: srv, transport });
462
+ }
463
+ }
464
+ else {
465
+ // Session ID provided but not found — stale session
466
+ res.writeHead(400, { "Content-Type": "application/json" });
467
+ res.end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "Unknown session. Send a request without mcp-session-id to start a new session." }, id: null }));
468
+ }
469
+ });
470
+ httpServer.listen(port, () => {
471
+ console.error(`Semiotic MCP server (HTTP) listening on http://localhost:${port}`);
472
+ console.error("Tools: getSchema, suggestChart, renderChart, diagnoseConfig, reportIssue");
473
+ });
474
+ }
475
+ else {
476
+ // Default: stdio mode for Claude Desktop, Claude Code, Cursor, etc.
477
+ const srv = createServer();
478
+ const transport = new stdio_js_1.StdioServerTransport();
479
+ await srv.connect(transport);
480
+ }
424
481
  }
425
482
  main().catch((err) => {
426
483
  console.error("MCP server error:", err);
@@ -53,12 +53,14 @@ function renderHOCToSVG(componentName, props) {
53
53
  error: `Unknown component "${componentName}". Available: ${Object.keys(componentRegistry_1.COMPONENT_REGISTRY).join(", ")}`,
54
54
  };
55
55
  }
56
- // Validate props
56
+ // Validate props (skip for components not in the validation map, e.g. geo)
57
57
  const validation = (0, ai_1.validateProps)(componentName, props);
58
- if (!validation.valid) {
58
+ const errors = validation.errors ?? [];
59
+ const isUnknownComponentOnly = errors.length === 1 && errors[0].startsWith("Unknown component");
60
+ if (!validation.valid && !isUnknownComponentOnly) {
59
61
  return {
60
62
  svg: null,
61
- error: `Validation errors:\n${validation.errors.join("\n")}`,
63
+ error: `Validation errors:\n${errors.join("\n")}`,
62
64
  };
63
65
  }
64
66
  // Disable hover (not useful in static SVG)
package/ai/schema.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "name": "semiotic",
4
- "version": "3.1.1",
4
+ "version": "3.1.2",
5
5
  "description": "React data visualization library for charts, networks, and beyond",
6
6
  "tools": [
7
7
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semiotic",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "description": "React data visualization library with built-in MCP server for AI-assisted chart generation",
5
5
  "main": "dist/semiotic.min.js",
6
6
  "module": "dist/semiotic.module.min.js",