graphein-mcp 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/dist/chunk-N5KVZBDZ.js +338 -0
- package/dist/chunk-N5KVZBDZ.js.map +1 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +17 -0
- package/dist/server.js.map +1 -0
- package/package.json +69 -0
- package/resources/agent-guide.md +719 -0
- package/resources/chart-spec.schema.json +7828 -0
- package/resources/spec-reference.md +1382 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sachin Patney
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# graphein-mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server for
|
|
4
|
+
[**Graphein**](https://github.com/spatney/graphein) — the agent-first data-visualization
|
|
5
|
+
library. It wraps Graphein's whole agent feedback loop (**validate → repair → render →
|
|
6
|
+
critique**) into one tool call, and serves Graphein's **schema and guides as resources**,
|
|
7
|
+
so a model that has never seen the API can still build correct charts at runtime.
|
|
8
|
+
|
|
9
|
+
> Graphein's real moat isn't the charts — it's that the library is self-describing,
|
|
10
|
+
> self-validating, and self-correcting. This server delivers all of that to any MCP
|
|
11
|
+
> client, turning "the model doesn't know Graphein" from a dealbreaker into a non-issue.
|
|
12
|
+
|
|
13
|
+
## Run it
|
|
14
|
+
|
|
15
|
+
No install needed — point your MCP client at `npx graphein-mcp`:
|
|
16
|
+
|
|
17
|
+
```jsonc
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"graphein": { "command": "npx", "args": ["-y", "graphein-mcp"] }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That's the standard shape for Claude Desktop, Copilot, Cursor, and friends. The server
|
|
26
|
+
speaks MCP over **stdio**.
|
|
27
|
+
|
|
28
|
+
Or install it:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g graphein-mcp # then: graphein-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Tools
|
|
35
|
+
|
|
36
|
+
| Tool | What it does |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| **`render_chart`** | The one-call loop. Validates a `ChartSpec`, auto-repairs safe mistakes, renders a PNG, and returns the **image** plus a **vision-free critique** (render report + lint warnings + repairs applied). If the spec can't be made valid, returns structured errors with JSON-Patch fixes instead of an image. |
|
|
39
|
+
| **`validate_chart`** | Validate without rendering. Returns structural errors (each with a JSON-Patch `fix` when unambiguous, plus "did you mean" suggestions) and best-practice lint warnings. |
|
|
40
|
+
| **`repair_chart`** | Apply every safe, unambiguous fix and return the corrected spec, the patch ops applied, and whether it's now valid. |
|
|
41
|
+
| **`summarize_chart`** | Deterministic, plain-English description of what the data shows (doubles as alt-text; no LLM). |
|
|
42
|
+
|
|
43
|
+
`render_chart` accepts `spec` plus optional `width`, `height`, `dpr`, and `repair`
|
|
44
|
+
(default `true`). DOM-only visuals (`kpi`, `table`, `matrix`, slicers, `dashboard`)
|
|
45
|
+
validate but have no headless image — the tool says so rather than failing.
|
|
46
|
+
|
|
47
|
+
### Example result
|
|
48
|
+
|
|
49
|
+
Calling `render_chart` with a valid line spec returns an `image` block and a `text` block:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"ok": true,
|
|
54
|
+
"rendered": true,
|
|
55
|
+
"type": "line",
|
|
56
|
+
"pixelSize": { "width": 800, "height": 500 },
|
|
57
|
+
"summary": "Users rose 43% from 4200 to 6010 between 2024-01 and 2024-03.",
|
|
58
|
+
"marks": 3, "series": 1, "colors": 1,
|
|
59
|
+
"diagnostics": [],
|
|
60
|
+
"lint": [],
|
|
61
|
+
"repairsApplied": []
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Resources
|
|
66
|
+
|
|
67
|
+
The server delivers Graphein's API knowledge at runtime — read these instead of relying
|
|
68
|
+
on training data:
|
|
69
|
+
|
|
70
|
+
| URI | Contents |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `graphein://agent-guide` | Task-oriented guide: the workflow, choosing a chart type, the loop, recipes. **Read this first.** |
|
|
73
|
+
| `graphein://schema` | The machine-readable JSON Schema for every `ChartSpec` / `DashboardSpec` field. |
|
|
74
|
+
| `graphein://spec-reference` | The exhaustive field-by-field reference. |
|
|
75
|
+
|
|
76
|
+
## Prompt
|
|
77
|
+
|
|
78
|
+
`create_chart` — primes the workflow from a `goal` (and optional `data`): read the
|
|
79
|
+
guide, shape tidy data, emit a spec, render, and apply fixes instead of regenerating.
|
|
80
|
+
|
|
81
|
+
## Embed it
|
|
82
|
+
|
|
83
|
+
The server is also a library, so you can mount it on any transport:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { createServer } from 'graphein-mcp';
|
|
87
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
88
|
+
|
|
89
|
+
const server = createServer();
|
|
90
|
+
await server.connect(new StdioServerTransport());
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The individual tool handlers (`renderChartHandler`, `validateChartHandler`,
|
|
94
|
+
`repairChartHandler`, `summarizeChartHandler`) are exported as pure functions too.
|
|
95
|
+
|
|
96
|
+
## How it works
|
|
97
|
+
|
|
98
|
+
`graphein-mcp` is a thin wrapper over [`graphein`](https://github.com/spatney/graphein)
|
|
99
|
+
(validation, repair, lint, deterministic summaries) and
|
|
100
|
+
[`@graphein/node`](https://github.com/spatney/graphein/tree/main/packages/node) (headless
|
|
101
|
+
PNG rendering via `@napi-rs/canvas`). The core engine stays dependency-free; this package
|
|
102
|
+
owns the MCP plumbing.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// src/handlers.ts
|
|
2
|
+
import {
|
|
3
|
+
validateSpec,
|
|
4
|
+
repairSpec,
|
|
5
|
+
summarize
|
|
6
|
+
} from "graphein";
|
|
7
|
+
import { renderChart } from "@graphein/node";
|
|
8
|
+
var NO_HEADLESS_IMAGE = /* @__PURE__ */ new Set([
|
|
9
|
+
"kpi",
|
|
10
|
+
"table",
|
|
11
|
+
"matrix",
|
|
12
|
+
"dropdown",
|
|
13
|
+
"list",
|
|
14
|
+
"search",
|
|
15
|
+
"range",
|
|
16
|
+
"dateRange",
|
|
17
|
+
"dashboard"
|
|
18
|
+
]);
|
|
19
|
+
function text(value) {
|
|
20
|
+
return { type: "text", text: value };
|
|
21
|
+
}
|
|
22
|
+
function json(value) {
|
|
23
|
+
return text(JSON.stringify(value, null, 2));
|
|
24
|
+
}
|
|
25
|
+
function specType(spec) {
|
|
26
|
+
return typeof spec === "object" && spec !== null && "type" in spec ? String(spec.type) : "(missing)";
|
|
27
|
+
}
|
|
28
|
+
function tidyError(e) {
|
|
29
|
+
const out = { path: e.path, message: e.message };
|
|
30
|
+
if (e.rule) out.rule = e.rule;
|
|
31
|
+
if (e.severity) out.severity = e.severity;
|
|
32
|
+
if (e.fix) out.fix = e.fix;
|
|
33
|
+
if (e.suggestion) out.suggestion = e.suggestion;
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
function renderChartHandler(args) {
|
|
37
|
+
const { spec, width, height, dpr, repair = true } = args;
|
|
38
|
+
let working = spec;
|
|
39
|
+
let validation = validateSpec(working);
|
|
40
|
+
let repairs = [];
|
|
41
|
+
if (!validation.valid && repair) {
|
|
42
|
+
const repaired = repairSpec(working);
|
|
43
|
+
if (repaired.applied.length > 0) {
|
|
44
|
+
working = repaired.spec;
|
|
45
|
+
repairs = repaired.applied;
|
|
46
|
+
validation = validateSpec(working);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!validation.valid) {
|
|
50
|
+
return {
|
|
51
|
+
isError: true,
|
|
52
|
+
content: [
|
|
53
|
+
json({
|
|
54
|
+
ok: false,
|
|
55
|
+
rendered: false,
|
|
56
|
+
stage: "validate",
|
|
57
|
+
type: specType(working),
|
|
58
|
+
errors: validation.errors.map(tidyError),
|
|
59
|
+
lint: validation.warnings.map(tidyError),
|
|
60
|
+
repairsApplied: repairs,
|
|
61
|
+
hint: "Apply each error.fix JSON Patch (or the repair_chart tool), then call render_chart again. See the graphein://agent-guide and graphein://schema resources."
|
|
62
|
+
})
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const type = specType(working);
|
|
67
|
+
if (NO_HEADLESS_IMAGE.has(type)) {
|
|
68
|
+
return {
|
|
69
|
+
isError: false,
|
|
70
|
+
content: [
|
|
71
|
+
json({
|
|
72
|
+
ok: true,
|
|
73
|
+
rendered: false,
|
|
74
|
+
type,
|
|
75
|
+
reason: `'${type}' is a DOM-only visual with no headless image. The spec is valid; use it in a browser, or call summarize_chart for its text description.`,
|
|
76
|
+
summary: summarize(working) || void 0,
|
|
77
|
+
lint: validation.warnings.map(tidyError),
|
|
78
|
+
repairsApplied: repairs
|
|
79
|
+
})
|
|
80
|
+
]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const { png, report, width: pxW, height: pxH } = renderChart(working, {
|
|
85
|
+
width,
|
|
86
|
+
height,
|
|
87
|
+
dpr
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
isError: false,
|
|
91
|
+
content: [
|
|
92
|
+
{ type: "image", data: png.toString("base64"), mimeType: "image/png" },
|
|
93
|
+
json({
|
|
94
|
+
ok: report.ok,
|
|
95
|
+
rendered: true,
|
|
96
|
+
type,
|
|
97
|
+
pixelSize: { width: pxW, height: pxH },
|
|
98
|
+
summary: report.summary,
|
|
99
|
+
marks: report.markCount,
|
|
100
|
+
series: report.seriesCount,
|
|
101
|
+
colors: report.colorCount,
|
|
102
|
+
diagnostics: report.diagnostics,
|
|
103
|
+
lint: validation.warnings.map(tidyError),
|
|
104
|
+
repairsApplied: repairs
|
|
105
|
+
})
|
|
106
|
+
]
|
|
107
|
+
};
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return {
|
|
110
|
+
isError: true,
|
|
111
|
+
content: [
|
|
112
|
+
json({
|
|
113
|
+
ok: false,
|
|
114
|
+
rendered: false,
|
|
115
|
+
stage: "render",
|
|
116
|
+
type,
|
|
117
|
+
message: e instanceof Error ? e.message : String(e),
|
|
118
|
+
summary: summarize(working) || void 0,
|
|
119
|
+
repairsApplied: repairs
|
|
120
|
+
})
|
|
121
|
+
]
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function validateChartHandler(args) {
|
|
126
|
+
const result = validateSpec(args.spec);
|
|
127
|
+
return {
|
|
128
|
+
isError: false,
|
|
129
|
+
content: [
|
|
130
|
+
json({
|
|
131
|
+
valid: result.valid,
|
|
132
|
+
type: specType(args.spec),
|
|
133
|
+
errors: result.errors.map(tidyError),
|
|
134
|
+
warnings: result.warnings.map(tidyError)
|
|
135
|
+
})
|
|
136
|
+
]
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function repairChartHandler(args) {
|
|
140
|
+
const { spec, applied, remaining } = repairSpec(args.spec);
|
|
141
|
+
return {
|
|
142
|
+
isError: false,
|
|
143
|
+
content: [
|
|
144
|
+
json({
|
|
145
|
+
valid: remaining.length === 0,
|
|
146
|
+
applied,
|
|
147
|
+
remaining: remaining.map(tidyError),
|
|
148
|
+
spec
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function summarizeChartHandler(args) {
|
|
154
|
+
const result = validateSpec(args.spec);
|
|
155
|
+
if (!result.valid) {
|
|
156
|
+
return {
|
|
157
|
+
isError: true,
|
|
158
|
+
content: [
|
|
159
|
+
json({
|
|
160
|
+
summary: null,
|
|
161
|
+
reason: "Spec is invalid; fix it first (validate_chart / repair_chart).",
|
|
162
|
+
errors: result.errors.map(tidyError)
|
|
163
|
+
})
|
|
164
|
+
]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const summary = summarize(args.spec);
|
|
168
|
+
return {
|
|
169
|
+
isError: false,
|
|
170
|
+
content: [
|
|
171
|
+
text(summary || `(No narrative summary is available for a '${specType(args.spec)}' chart.)`)
|
|
172
|
+
]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/resources.ts
|
|
177
|
+
import { readFileSync } from "fs";
|
|
178
|
+
import { fileURLToPath } from "url";
|
|
179
|
+
var RESOURCES = [
|
|
180
|
+
{
|
|
181
|
+
name: "schema",
|
|
182
|
+
uri: "graphein://schema",
|
|
183
|
+
title: "Graphein ChartSpec JSON Schema",
|
|
184
|
+
description: "The machine-readable JSON Schema for every Graphein ChartSpec and DashboardSpec field \u2014 chart types, channels, transforms, annotations, and required properties. Generate or check a spec against this.",
|
|
185
|
+
mimeType: "application/json",
|
|
186
|
+
file: "chart-spec.schema.json"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "agent-guide",
|
|
190
|
+
uri: "graphein://agent-guide",
|
|
191
|
+
title: "Graphein Agent Guide",
|
|
192
|
+
description: "A task-oriented guide for producing correct Graphein charts: the one-rule workflow, choosing a chart type, encodings, transforms, the validate \u2192 repair \u2192 render \u2192 critique loop, and worked recipes. Read this first.",
|
|
193
|
+
mimeType: "text/markdown",
|
|
194
|
+
file: "agent-guide.md"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "spec-reference",
|
|
198
|
+
uri: "graphein://spec-reference",
|
|
199
|
+
title: "Graphein Spec Reference",
|
|
200
|
+
description: "The exhaustive field-by-field reference for every chart type, channel, transform, annotation, and modifier. Consult this for the precise shape of a specific field.",
|
|
201
|
+
mimeType: "text/markdown",
|
|
202
|
+
file: "spec-reference.md"
|
|
203
|
+
}
|
|
204
|
+
];
|
|
205
|
+
var cache = /* @__PURE__ */ new Map();
|
|
206
|
+
function readResourceFile(file) {
|
|
207
|
+
const hit = cache.get(file);
|
|
208
|
+
if (hit !== void 0) return hit;
|
|
209
|
+
const url = new URL(`../resources/${file}`, import.meta.url);
|
|
210
|
+
const text2 = readFileSync(fileURLToPath(url), "utf8");
|
|
211
|
+
cache.set(file, text2);
|
|
212
|
+
return text2;
|
|
213
|
+
}
|
|
214
|
+
function resourceByUri(uri) {
|
|
215
|
+
return RESOURCES.find((r) => r.uri === uri);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/create-server.ts
|
|
219
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
220
|
+
import { z } from "zod";
|
|
221
|
+
var VERSION = "0.3.0";
|
|
222
|
+
var SERVER_INSTRUCTIONS = `Graphein is an agent-first data-visualization library: you describe a chart as one JSON ChartSpec ({ type, data, encoding, ... }) and it renders. This server lets you build correct charts without prior knowledge of the API.
|
|
223
|
+
|
|
224
|
+
Workflow:
|
|
225
|
+
1. Read the graphein://agent-guide resource (and graphein://schema for exact fields) if you are unsure of the API.
|
|
226
|
+
2. Shape your data as a tidy array \u2014 one row per observation, one column per variable.
|
|
227
|
+
3. Emit a ChartSpec and call render_chart: it validates, auto-repairs safe mistakes, renders a PNG, and returns a vision-free critique (the render report + lint warnings). Read the critique to verify the chart looks right.
|
|
228
|
+
4. If a spec is invalid, render_chart (and validate_chart) return structured errors each with a JSON-Patch 'fix' \u2014 apply it (or call repair_chart) and retry instead of regenerating.
|
|
229
|
+
Use summarize_chart for deterministic alt-text. DOM-only visuals (kpi, table, matrix, slicers, dashboard) validate but produce no headless image.`;
|
|
230
|
+
var specSchema = z.record(z.string(), z.unknown()).describe(
|
|
231
|
+
'A Graphein ChartSpec or DashboardSpec object, e.g. { "type": "line", "data": [...], "encoding": {...} }. See the graphein://schema and graphein://agent-guide resources.'
|
|
232
|
+
);
|
|
233
|
+
function createServer() {
|
|
234
|
+
const server = new McpServer(
|
|
235
|
+
{ name: "graphein-mcp", version: VERSION },
|
|
236
|
+
{ instructions: SERVER_INSTRUCTIONS }
|
|
237
|
+
);
|
|
238
|
+
server.registerTool(
|
|
239
|
+
"render_chart",
|
|
240
|
+
{
|
|
241
|
+
title: "Render a Graphein chart",
|
|
242
|
+
description: "The one-call loop: validate a ChartSpec, auto-repair safe mistakes, render it to a PNG, and return the image plus a vision-free critique (render report, lint warnings, repairs applied). If the spec cannot be made valid, returns structured errors with JSON-Patch fixes instead of an image. DOM-only types (kpi, table, matrix, slicers, dashboard) validate but return no image.",
|
|
243
|
+
inputSchema: {
|
|
244
|
+
spec: specSchema,
|
|
245
|
+
width: z.number().int().positive().optional().describe("Logical width in CSS px (default 800)."),
|
|
246
|
+
height: z.number().int().positive().optional().describe("Logical height in CSS px (default 500)."),
|
|
247
|
+
dpr: z.number().positive().optional().describe("Device pixel ratio for crisp output (default 2)."),
|
|
248
|
+
repair: z.boolean().optional().describe("Auto-apply safe repairs before rendering when the spec is invalid (default true).")
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
async (args) => renderChartHandler(args)
|
|
252
|
+
);
|
|
253
|
+
server.registerTool(
|
|
254
|
+
"validate_chart",
|
|
255
|
+
{
|
|
256
|
+
title: "Validate a Graphein chart spec",
|
|
257
|
+
description: 'Validate a ChartSpec without rendering. Returns structural errors (each with a JSON-Patch `fix` when unambiguous, plus "did you mean" suggestions) and best-practice lint warnings. Fast feedback before rendering.',
|
|
258
|
+
inputSchema: { spec: specSchema }
|
|
259
|
+
},
|
|
260
|
+
async (args) => validateChartHandler(args)
|
|
261
|
+
);
|
|
262
|
+
server.registerTool(
|
|
263
|
+
"repair_chart",
|
|
264
|
+
{
|
|
265
|
+
title: "Repair a Graphein chart spec",
|
|
266
|
+
description: "Apply every safe, unambiguous fix Graphein proposes (misspelled chart type or enum, a temporal field typed as a category, \u2026) and return the corrected spec, the JSON Patch ops applied, and whether it is now valid. Turns a near-miss into a one-step correction.",
|
|
267
|
+
inputSchema: { spec: specSchema }
|
|
268
|
+
},
|
|
269
|
+
async (args) => repairChartHandler(args)
|
|
270
|
+
);
|
|
271
|
+
server.registerTool(
|
|
272
|
+
"summarize_chart",
|
|
273
|
+
{
|
|
274
|
+
title: "Summarize a Graphein chart",
|
|
275
|
+
description: `Return a deterministic, plain-English description of what the chart's data shows (e.g. "Users grew 46% over six months, peaking in June"). Doubles as alt-text; needs no LLM.`,
|
|
276
|
+
inputSchema: { spec: specSchema }
|
|
277
|
+
},
|
|
278
|
+
async (args) => summarizeChartHandler(args)
|
|
279
|
+
);
|
|
280
|
+
for (const r of RESOURCES) {
|
|
281
|
+
server.registerResource(
|
|
282
|
+
r.name,
|
|
283
|
+
r.uri,
|
|
284
|
+
{ title: r.title, description: r.description, mimeType: r.mimeType },
|
|
285
|
+
async (uri) => ({
|
|
286
|
+
contents: [{ uri: uri.href, mimeType: r.mimeType, text: readResourceFile(r.file) }]
|
|
287
|
+
})
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
server.registerPrompt(
|
|
291
|
+
"create_chart",
|
|
292
|
+
{
|
|
293
|
+
title: "Create a Graphein chart",
|
|
294
|
+
description: "Scaffold the workflow for building a validated Graphein chart from a goal (and optional data).",
|
|
295
|
+
argsSchema: {
|
|
296
|
+
goal: z.string().describe('What the chart should show, e.g. "monthly active users over the last year".'),
|
|
297
|
+
data: z.string().optional().describe("Optional: the data as a JSON array, or a description of the columns available.")
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
({ goal, data }) => ({
|
|
301
|
+
messages: [
|
|
302
|
+
{
|
|
303
|
+
role: "user",
|
|
304
|
+
content: {
|
|
305
|
+
type: "text",
|
|
306
|
+
text: `Build a Graphein chart for this goal:
|
|
307
|
+
|
|
308
|
+
${goal}
|
|
309
|
+
${data ? `
|
|
310
|
+
Data:
|
|
311
|
+
${data}
|
|
312
|
+
` : ""}
|
|
313
|
+
Steps:
|
|
314
|
+
1. If unsure of the API, read the graphein://agent-guide resource (and graphein://schema for exact fields).
|
|
315
|
+
2. Shape the data as a tidy array \u2014 one row per observation, one column per variable.
|
|
316
|
+
3. Choose a chart type and write a single ChartSpec ({ type, data, encoding, title }).
|
|
317
|
+
4. Call render_chart with the spec. Read the returned critique (render report + lint) to confirm it looks right.
|
|
318
|
+
5. If it reports errors, apply each error.fix patch (or call repair_chart) and render again \u2014 do not regenerate from scratch.`
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
return server;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export {
|
|
328
|
+
renderChartHandler,
|
|
329
|
+
validateChartHandler,
|
|
330
|
+
repairChartHandler,
|
|
331
|
+
summarizeChartHandler,
|
|
332
|
+
RESOURCES,
|
|
333
|
+
readResourceFile,
|
|
334
|
+
resourceByUri,
|
|
335
|
+
VERSION,
|
|
336
|
+
createServer
|
|
337
|
+
};
|
|
338
|
+
//# sourceMappingURL=chunk-N5KVZBDZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/handlers.ts","../src/resources.ts","../src/create-server.ts"],"sourcesContent":["/**\r\n * Pure tool handlers — the generate → validate → repair → render → critique loop\r\n * exposed as plain functions so they can be unit-tested without a transport. Each\r\n * returns MCP-shaped content (`text` and/or `image` blocks). `createServer` wires\r\n * these into the `McpServer`.\r\n *\r\n * The handlers wrap `graphein`'s self-validating / self-repairing / self-explaining\r\n * core (`validateSpec`, `repairSpec`, `summarize`) and `@graphein/node`'s headless\r\n * `renderChart`, so an agent gets the chart **plus** a vision-free critique in one\r\n * call — and a one-step repair when its spec is slightly wrong.\r\n */\r\nimport {\r\n validateSpec,\r\n repairSpec,\r\n summarize,\r\n type ChartSpec,\r\n type ValidationError,\r\n} from 'graphein';\r\nimport { renderChart } from '@graphein/node';\r\nimport type { JsonPatchOp } from './types.js';\r\n\r\n/** A subset of MCP content blocks the handlers emit. */\r\nexport type McpContent =\r\n | { type: 'text'; text: string }\r\n | { type: 'image'; data: string; mimeType: string };\r\n\r\n/**\r\n * The shape every handler returns — assignable to the SDK's `CallToolResult`\r\n * (which is an open/passthrough type, hence the index signature).\r\n */\r\nexport interface ToolResult {\r\n content: McpContent[];\r\n isError?: boolean;\r\n [key: string]: unknown;\r\n}\r\n\r\n/** Options accepted by {@link renderChartHandler}. */\r\nexport interface RenderArgs {\r\n spec: unknown;\r\n width?: number;\r\n height?: number;\r\n dpr?: number;\r\n /** Auto-apply safe repairs before rendering when the spec is invalid. Default true. */\r\n repair?: boolean;\r\n}\r\n\r\n/**\r\n * Visuals that have no canvas form (they render as DOM in the browser) and so\r\n * cannot be rasterized headless. Mirrors `DOM_ONLY_TYPES` in core's\r\n * `runtime/headless.ts`; kept local so a friendly message is returned *before*\r\n * attempting a render. A try/catch around `renderChart` is the backstop.\r\n */\r\nconst NO_HEADLESS_IMAGE: ReadonlySet<string> = new Set([\r\n 'kpi',\r\n 'table',\r\n 'matrix',\r\n 'dropdown',\r\n 'list',\r\n 'search',\r\n 'range',\r\n 'dateRange',\r\n 'dashboard',\r\n]);\r\n\r\nfunction text(value: string): McpContent {\r\n return { type: 'text', text: value };\r\n}\r\n\r\nfunction json(value: unknown): McpContent {\r\n return text(JSON.stringify(value, null, 2));\r\n}\r\n\r\nfunction specType(spec: unknown): string {\r\n return typeof spec === 'object' && spec !== null && 'type' in spec\r\n ? String((spec as { type: unknown }).type)\r\n : '(missing)';\r\n}\r\n\r\n/** Slim a validation error for an agent payload (drops nothing useful). */\r\nfunction tidyError(e: ValidationError) {\r\n const out: Record<string, unknown> = { path: e.path, message: e.message };\r\n if (e.rule) out.rule = e.rule;\r\n if (e.severity) out.severity = e.severity;\r\n if (e.fix) out.fix = e.fix;\r\n if (e.suggestion) out.suggestion = e.suggestion;\r\n return out;\r\n}\r\n\r\n/**\r\n * **The flagship.** Validate a spec, auto-repair it if it's safely fixable, render\r\n * it to a PNG headless, and return the image alongside a machine-readable critique\r\n * (the render report + lint warnings + any repairs applied). When the spec can't be\r\n * made valid, returns the structured errors and JSON-Patch fixes instead of an image\r\n * so the agent corrects in one step rather than regenerating.\r\n */\r\nexport function renderChartHandler(args: RenderArgs): ToolResult {\r\n const { spec, width, height, dpr, repair = true } = args;\r\n\r\n let working: unknown = spec;\r\n let validation = validateSpec(working);\r\n let repairs: JsonPatchOp[] = [];\r\n\r\n if (!validation.valid && repair) {\r\n const repaired = repairSpec(working);\r\n if (repaired.applied.length > 0) {\r\n working = repaired.spec;\r\n repairs = repaired.applied;\r\n validation = validateSpec(working);\r\n }\r\n }\r\n\r\n if (!validation.valid) {\r\n return {\r\n isError: true,\r\n content: [\r\n json({\r\n ok: false,\r\n rendered: false,\r\n stage: 'validate',\r\n type: specType(working),\r\n errors: validation.errors.map(tidyError),\r\n lint: validation.warnings.map(tidyError),\r\n repairsApplied: repairs,\r\n hint: 'Apply each error.fix JSON Patch (or the repair_chart tool), then call render_chart again. See the graphein://agent-guide and graphein://schema resources.',\r\n }),\r\n ],\r\n };\r\n }\r\n\r\n const type = specType(working);\r\n\r\n if (NO_HEADLESS_IMAGE.has(type)) {\r\n return {\r\n isError: false,\r\n content: [\r\n json({\r\n ok: true,\r\n rendered: false,\r\n type,\r\n reason: `'${type}' is a DOM-only visual with no headless image. The spec is valid; use it in a browser, or call summarize_chart for its text description.`,\r\n summary: summarize(working as ChartSpec) || undefined,\r\n lint: validation.warnings.map(tidyError),\r\n repairsApplied: repairs,\r\n }),\r\n ],\r\n };\r\n }\r\n\r\n try {\r\n const { png, report, width: pxW, height: pxH } = renderChart(working as ChartSpec, {\r\n width,\r\n height,\r\n dpr,\r\n });\r\n return {\r\n isError: false,\r\n content: [\r\n { type: 'image', data: png.toString('base64'), mimeType: 'image/png' },\r\n json({\r\n ok: report.ok,\r\n rendered: true,\r\n type,\r\n pixelSize: { width: pxW, height: pxH },\r\n summary: report.summary,\r\n marks: report.markCount,\r\n series: report.seriesCount,\r\n colors: report.colorCount,\r\n diagnostics: report.diagnostics,\r\n lint: validation.warnings.map(tidyError),\r\n repairsApplied: repairs,\r\n }),\r\n ],\r\n };\r\n } catch (e) {\r\n return {\r\n isError: true,\r\n content: [\r\n json({\r\n ok: false,\r\n rendered: false,\r\n stage: 'render',\r\n type,\r\n message: e instanceof Error ? e.message : String(e),\r\n summary: summarize(working as ChartSpec) || undefined,\r\n repairsApplied: repairs,\r\n }),\r\n ],\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Validate a spec without rendering — fast structural + best-practice feedback.\r\n * Returns errors (each with a JSON-Patch `fix` when unambiguous and \"did you mean\"\r\n * suggestions) and lint `warnings`.\r\n */\r\nexport function validateChartHandler(args: { spec: unknown }): ToolResult {\r\n const result = validateSpec(args.spec);\r\n return {\r\n isError: false,\r\n content: [\r\n json({\r\n valid: result.valid,\r\n type: specType(args.spec),\r\n errors: result.errors.map(tidyError),\r\n warnings: result.warnings.map(tidyError),\r\n }),\r\n ],\r\n };\r\n}\r\n\r\n/**\r\n * Apply every safe, unambiguous repair Graphein proposes and return the corrected\r\n * spec, the JSON Patch operations applied, and whether it is now valid.\r\n */\r\nexport function repairChartHandler(args: { spec: unknown }): ToolResult {\r\n const { spec, applied, remaining } = repairSpec(args.spec);\r\n return {\r\n isError: false,\r\n content: [\r\n json({\r\n valid: remaining.length === 0,\r\n applied,\r\n remaining: remaining.map(tidyError),\r\n spec,\r\n }),\r\n ],\r\n };\r\n}\r\n\r\n/**\r\n * Return a deterministic, plain-English summary of what the chart's data shows —\r\n * doubles as alt-text, needs no LLM.\r\n */\r\nexport function summarizeChartHandler(args: { spec: unknown }): ToolResult {\r\n const result = validateSpec(args.spec);\r\n if (!result.valid) {\r\n return {\r\n isError: true,\r\n content: [\r\n json({\r\n summary: null,\r\n reason: 'Spec is invalid; fix it first (validate_chart / repair_chart).',\r\n errors: result.errors.map(tidyError),\r\n }),\r\n ],\r\n };\r\n }\r\n const summary = summarize(args.spec as ChartSpec);\r\n return {\r\n isError: false,\r\n content: [\r\n text(summary || `(No narrative summary is available for a '${specType(args.spec)}' chart.)`),\r\n ],\r\n };\r\n}\r\n","/**\r\n * The agent-facing knowledge Graphein serves as MCP **resources** — the schema\r\n * and the prose guides — so a model that has never seen Graphein's API can read\r\n * the contract at runtime instead of relying on training data. This is the core\r\n * of the MCP server's \"neutralize the training-data gap\" purpose.\r\n *\r\n * The files live in `../resources/` (committed copies of the repo's `docs/`, kept\r\n * in sync by `scripts/sync-resources.mjs`). They are read lazily and cached so the\r\n * server pays nothing until an agent actually asks for one.\r\n */\r\nimport { readFileSync } from 'node:fs';\r\nimport { fileURLToPath } from 'node:url';\r\n\r\n/** Metadata describing one served resource. */\r\nexport interface GrapheinResource {\r\n /** Short registration name. */\r\n name: string;\r\n /** Stable `graphein://…` URI an agent reads. */\r\n uri: string;\r\n /** Human title. */\r\n title: string;\r\n /** What the resource is and when to read it. */\r\n description: string;\r\n /** IANA media type. */\r\n mimeType: string;\r\n /** File under `resources/`. */\r\n file: string;\r\n}\r\n\r\n/** Every resource the server exposes, in the order they should be advertised. */\r\nexport const RESOURCES: GrapheinResource[] = [\r\n {\r\n name: 'schema',\r\n uri: 'graphein://schema',\r\n title: 'Graphein ChartSpec JSON Schema',\r\n description:\r\n 'The machine-readable JSON Schema for every Graphein ChartSpec and DashboardSpec field — chart types, channels, transforms, annotations, and required properties. Generate or check a spec against this.',\r\n mimeType: 'application/json',\r\n file: 'chart-spec.schema.json',\r\n },\r\n {\r\n name: 'agent-guide',\r\n uri: 'graphein://agent-guide',\r\n title: 'Graphein Agent Guide',\r\n description:\r\n 'A task-oriented guide for producing correct Graphein charts: the one-rule workflow, choosing a chart type, encodings, transforms, the validate → repair → render → critique loop, and worked recipes. Read this first.',\r\n mimeType: 'text/markdown',\r\n file: 'agent-guide.md',\r\n },\r\n {\r\n name: 'spec-reference',\r\n uri: 'graphein://spec-reference',\r\n title: 'Graphein Spec Reference',\r\n description:\r\n 'The exhaustive field-by-field reference for every chart type, channel, transform, annotation, and modifier. Consult this for the precise shape of a specific field.',\r\n mimeType: 'text/markdown',\r\n file: 'spec-reference.md',\r\n },\r\n];\r\n\r\nconst cache = new Map<string, string>();\r\n\r\n/**\r\n * Read a bundled resource file's text, cached. Resolved relative to this module\r\n * so it works both from `dist/` (published) and `src/` (tests) — the committed\r\n * `resources/` dir always sits one level up from either.\r\n */\r\nexport function readResourceFile(file: string): string {\r\n const hit = cache.get(file);\r\n if (hit !== undefined) return hit;\r\n const url = new URL(`../resources/${file}`, import.meta.url);\r\n const text = readFileSync(fileURLToPath(url), 'utf8');\r\n cache.set(file, text);\r\n return text;\r\n}\r\n\r\n/** Look up a resource by its `graphein://…` URI. */\r\nexport function resourceByUri(uri: string): GrapheinResource | undefined {\r\n return RESOURCES.find((r) => r.uri === uri);\r\n}\r\n","/**\r\n * `createServer()` — builds the Graphein MCP server: the generate → validate →\r\n * repair → render → critique loop as four tools, the schema + guides as resources,\r\n * and a `create_chart` prompt that teaches the workflow. Split from the stdio\r\n * entry point (`server.ts`) so it can be driven over any transport — including the\r\n * in-memory transport in tests.\r\n */\r\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\r\nimport { z } from 'zod';\r\nimport {\r\n renderChartHandler,\r\n validateChartHandler,\r\n repairChartHandler,\r\n summarizeChartHandler,\r\n} from './handlers.js';\r\nimport { RESOURCES, readResourceFile } from './resources.js';\r\n\r\n/** Package version, surfaced as the MCP server version. */\r\nexport const VERSION = '0.3.0';\r\n\r\nconst SERVER_INSTRUCTIONS = `Graphein is an agent-first data-visualization library: you describe a chart as one JSON ChartSpec ({ type, data, encoding, ... }) and it renders. This server lets you build correct charts without prior knowledge of the API.\r\n\r\nWorkflow:\r\n1. Read the graphein://agent-guide resource (and graphein://schema for exact fields) if you are unsure of the API.\r\n2. Shape your data as a tidy array — one row per observation, one column per variable.\r\n3. Emit a ChartSpec and call render_chart: it validates, auto-repairs safe mistakes, renders a PNG, and returns a vision-free critique (the render report + lint warnings). Read the critique to verify the chart looks right.\r\n4. If a spec is invalid, render_chart (and validate_chart) return structured errors each with a JSON-Patch 'fix' — apply it (or call repair_chart) and retry instead of regenerating.\r\nUse summarize_chart for deterministic alt-text. DOM-only visuals (kpi, table, matrix, slicers, dashboard) validate but produce no headless image.`;\r\n\r\n/** A permissive object schema for a Graphein spec — validateSpec does the real checking. */\r\nconst specSchema = z\r\n .record(z.string(), z.unknown())\r\n .describe(\r\n 'A Graphein ChartSpec or DashboardSpec object, e.g. { \"type\": \"line\", \"data\": [...], \"encoding\": {...} }. See the graphein://schema and graphein://agent-guide resources.',\r\n );\r\n\r\n/**\r\n * Build a fully-configured Graphein MCP server. The caller connects it to a\r\n * transport (`server.connect(transport)`).\r\n */\r\nexport function createServer(): McpServer {\r\n const server = new McpServer(\r\n { name: 'graphein-mcp', version: VERSION },\r\n { instructions: SERVER_INSTRUCTIONS },\r\n );\r\n\r\n // --- Tools: the runtime loop -------------------------------------------------\r\n\r\n server.registerTool(\r\n 'render_chart',\r\n {\r\n title: 'Render a Graphein chart',\r\n description:\r\n 'The one-call loop: validate a ChartSpec, auto-repair safe mistakes, render it to a PNG, and return the image plus a vision-free critique (render report, lint warnings, repairs applied). If the spec cannot be made valid, returns structured errors with JSON-Patch fixes instead of an image. DOM-only types (kpi, table, matrix, slicers, dashboard) validate but return no image.',\r\n inputSchema: {\r\n spec: specSchema,\r\n width: z.number().int().positive().optional().describe('Logical width in CSS px (default 800).'),\r\n height: z.number().int().positive().optional().describe('Logical height in CSS px (default 500).'),\r\n dpr: z.number().positive().optional().describe('Device pixel ratio for crisp output (default 2).'),\r\n repair: z\r\n .boolean()\r\n .optional()\r\n .describe('Auto-apply safe repairs before rendering when the spec is invalid (default true).'),\r\n },\r\n },\r\n async (args) => renderChartHandler(args),\r\n );\r\n\r\n server.registerTool(\r\n 'validate_chart',\r\n {\r\n title: 'Validate a Graphein chart spec',\r\n description:\r\n 'Validate a ChartSpec without rendering. Returns structural errors (each with a JSON-Patch `fix` when unambiguous, plus \"did you mean\" suggestions) and best-practice lint warnings. Fast feedback before rendering.',\r\n inputSchema: { spec: specSchema },\r\n },\r\n async (args) => validateChartHandler(args),\r\n );\r\n\r\n server.registerTool(\r\n 'repair_chart',\r\n {\r\n title: 'Repair a Graphein chart spec',\r\n description:\r\n 'Apply every safe, unambiguous fix Graphein proposes (misspelled chart type or enum, a temporal field typed as a category, …) and return the corrected spec, the JSON Patch ops applied, and whether it is now valid. Turns a near-miss into a one-step correction.',\r\n inputSchema: { spec: specSchema },\r\n },\r\n async (args) => repairChartHandler(args),\r\n );\r\n\r\n server.registerTool(\r\n 'summarize_chart',\r\n {\r\n title: 'Summarize a Graphein chart',\r\n description:\r\n 'Return a deterministic, plain-English description of what the chart\\'s data shows (e.g. \"Users grew 46% over six months, peaking in June\"). Doubles as alt-text; needs no LLM.',\r\n inputSchema: { spec: specSchema },\r\n },\r\n async (args) => summarizeChartHandler(args),\r\n );\r\n\r\n // --- Resources: deliver the API knowledge at runtime -------------------------\r\n\r\n for (const r of RESOURCES) {\r\n server.registerResource(\r\n r.name,\r\n r.uri,\r\n { title: r.title, description: r.description, mimeType: r.mimeType },\r\n async (uri) => ({\r\n contents: [{ uri: uri.href, mimeType: r.mimeType, text: readResourceFile(r.file) }],\r\n }),\r\n );\r\n }\r\n\r\n // --- Prompt: teach the workflow ---------------------------------------------\r\n\r\n server.registerPrompt(\r\n 'create_chart',\r\n {\r\n title: 'Create a Graphein chart',\r\n description:\r\n 'Scaffold the workflow for building a validated Graphein chart from a goal (and optional data).',\r\n argsSchema: {\r\n goal: z.string().describe('What the chart should show, e.g. \"monthly active users over the last year\".'),\r\n data: z\r\n .string()\r\n .optional()\r\n .describe('Optional: the data as a JSON array, or a description of the columns available.'),\r\n },\r\n },\r\n ({ goal, data }) => ({\r\n messages: [\r\n {\r\n role: 'user',\r\n content: {\r\n type: 'text',\r\n text: `Build a Graphein chart for this goal:\\n\\n${goal}\\n${\r\n data ? `\\nData:\\n${data}\\n` : ''\r\n }\\nSteps:\\n1. If unsure of the API, read the graphein://agent-guide resource (and graphein://schema for exact fields).\\n2. Shape the data as a tidy array — one row per observation, one column per variable.\\n3. Choose a chart type and write a single ChartSpec ({ type, data, encoding, title }).\\n4. Call render_chart with the spec. Read the returned critique (render report + lint) to confirm it looks right.\\n5. If it reports errors, apply each error.fix patch (or call repair_chart) and render again — do not regenerate from scratch.`,\r\n },\r\n },\r\n ],\r\n }),\r\n );\r\n\r\n return server;\r\n}\r\n"],"mappings":"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","names":["text"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
export { AnySpec, ChartSpec, DashboardSpec, JsonPatchOp, RenderReport, ValidationError, ValidationResult } from 'graphein';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `createServer()` — builds the Graphein MCP server: the generate → validate →
|
|
6
|
+
* repair → render → critique loop as four tools, the schema + guides as resources,
|
|
7
|
+
* and a `create_chart` prompt that teaches the workflow. Split from the stdio
|
|
8
|
+
* entry point (`server.ts`) so it can be driven over any transport — including the
|
|
9
|
+
* in-memory transport in tests.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Package version, surfaced as the MCP server version. */
|
|
13
|
+
declare const VERSION = "0.3.0";
|
|
14
|
+
/**
|
|
15
|
+
* Build a fully-configured Graphein MCP server. The caller connects it to a
|
|
16
|
+
* transport (`server.connect(transport)`).
|
|
17
|
+
*/
|
|
18
|
+
declare function createServer(): McpServer;
|
|
19
|
+
|
|
20
|
+
/** A subset of MCP content blocks the handlers emit. */
|
|
21
|
+
type McpContent = {
|
|
22
|
+
type: 'text';
|
|
23
|
+
text: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'image';
|
|
26
|
+
data: string;
|
|
27
|
+
mimeType: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The shape every handler returns — assignable to the SDK's `CallToolResult`
|
|
31
|
+
* (which is an open/passthrough type, hence the index signature).
|
|
32
|
+
*/
|
|
33
|
+
interface ToolResult {
|
|
34
|
+
content: McpContent[];
|
|
35
|
+
isError?: boolean;
|
|
36
|
+
[key: string]: unknown;
|
|
37
|
+
}
|
|
38
|
+
/** Options accepted by {@link renderChartHandler}. */
|
|
39
|
+
interface RenderArgs {
|
|
40
|
+
spec: unknown;
|
|
41
|
+
width?: number;
|
|
42
|
+
height?: number;
|
|
43
|
+
dpr?: number;
|
|
44
|
+
/** Auto-apply safe repairs before rendering when the spec is invalid. Default true. */
|
|
45
|
+
repair?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* **The flagship.** Validate a spec, auto-repair it if it's safely fixable, render
|
|
49
|
+
* it to a PNG headless, and return the image alongside a machine-readable critique
|
|
50
|
+
* (the render report + lint warnings + any repairs applied). When the spec can't be
|
|
51
|
+
* made valid, returns the structured errors and JSON-Patch fixes instead of an image
|
|
52
|
+
* so the agent corrects in one step rather than regenerating.
|
|
53
|
+
*/
|
|
54
|
+
declare function renderChartHandler(args: RenderArgs): ToolResult;
|
|
55
|
+
/**
|
|
56
|
+
* Validate a spec without rendering — fast structural + best-practice feedback.
|
|
57
|
+
* Returns errors (each with a JSON-Patch `fix` when unambiguous and "did you mean"
|
|
58
|
+
* suggestions) and lint `warnings`.
|
|
59
|
+
*/
|
|
60
|
+
declare function validateChartHandler(args: {
|
|
61
|
+
spec: unknown;
|
|
62
|
+
}): ToolResult;
|
|
63
|
+
/**
|
|
64
|
+
* Apply every safe, unambiguous repair Graphein proposes and return the corrected
|
|
65
|
+
* spec, the JSON Patch operations applied, and whether it is now valid.
|
|
66
|
+
*/
|
|
67
|
+
declare function repairChartHandler(args: {
|
|
68
|
+
spec: unknown;
|
|
69
|
+
}): ToolResult;
|
|
70
|
+
/**
|
|
71
|
+
* Return a deterministic, plain-English summary of what the chart's data shows —
|
|
72
|
+
* doubles as alt-text, needs no LLM.
|
|
73
|
+
*/
|
|
74
|
+
declare function summarizeChartHandler(args: {
|
|
75
|
+
spec: unknown;
|
|
76
|
+
}): ToolResult;
|
|
77
|
+
|
|
78
|
+
/** Metadata describing one served resource. */
|
|
79
|
+
interface GrapheinResource {
|
|
80
|
+
/** Short registration name. */
|
|
81
|
+
name: string;
|
|
82
|
+
/** Stable `graphein://…` URI an agent reads. */
|
|
83
|
+
uri: string;
|
|
84
|
+
/** Human title. */
|
|
85
|
+
title: string;
|
|
86
|
+
/** What the resource is and when to read it. */
|
|
87
|
+
description: string;
|
|
88
|
+
/** IANA media type. */
|
|
89
|
+
mimeType: string;
|
|
90
|
+
/** File under `resources/`. */
|
|
91
|
+
file: string;
|
|
92
|
+
}
|
|
93
|
+
/** Every resource the server exposes, in the order they should be advertised. */
|
|
94
|
+
declare const RESOURCES: GrapheinResource[];
|
|
95
|
+
/**
|
|
96
|
+
* Read a bundled resource file's text, cached. Resolved relative to this module
|
|
97
|
+
* so it works both from `dist/` (published) and `src/` (tests) — the committed
|
|
98
|
+
* `resources/` dir always sits one level up from either.
|
|
99
|
+
*/
|
|
100
|
+
declare function readResourceFile(file: string): string;
|
|
101
|
+
/** Look up a resource by its `graphein://…` URI. */
|
|
102
|
+
declare function resourceByUri(uri: string): GrapheinResource | undefined;
|
|
103
|
+
|
|
104
|
+
export { type GrapheinResource, type McpContent, RESOURCES, type RenderArgs, type ToolResult, VERSION, createServer, readResourceFile, renderChartHandler, repairChartHandler, resourceByUri, summarizeChartHandler, validateChartHandler };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RESOURCES,
|
|
3
|
+
VERSION,
|
|
4
|
+
createServer,
|
|
5
|
+
readResourceFile,
|
|
6
|
+
renderChartHandler,
|
|
7
|
+
repairChartHandler,
|
|
8
|
+
resourceByUri,
|
|
9
|
+
summarizeChartHandler,
|
|
10
|
+
validateChartHandler
|
|
11
|
+
} from "./chunk-N5KVZBDZ.js";
|
|
12
|
+
export {
|
|
13
|
+
RESOURCES,
|
|
14
|
+
VERSION,
|
|
15
|
+
createServer,
|
|
16
|
+
readResourceFile,
|
|
17
|
+
renderChartHandler,
|
|
18
|
+
repairChartHandler,
|
|
19
|
+
resourceByUri,
|
|
20
|
+
summarizeChartHandler,
|
|
21
|
+
validateChartHandler
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createServer
|
|
4
|
+
} from "./chunk-N5KVZBDZ.js";
|
|
5
|
+
|
|
6
|
+
// src/server.ts
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
const server = createServer();
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
}
|
|
13
|
+
main().catch((err) => {
|
|
14
|
+
console.error("[graphein-mcp] fatal:", err);
|
|
15
|
+
process.exitCode = 1;
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=server.js.map
|