ztd-charts-mcp 0.2.0 → 0.2.1
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.
|
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { getChartInfo, getChartSchema, isHttpUrl, listCharts, loadRows, reactSnippet, render, specSnippet, suggest, validate } from "ztd-charts-cli";
|
|
5
5
|
import { GROUPS, LEVELS, parseCSV, normalizeData, specLocale } from "ztd-charts/spec";
|
|
6
6
|
var SERVER_NAME = "ztd-charts";
|
|
7
|
-
var SERVER_VERSION = "0.2.
|
|
7
|
+
var SERVER_VERSION = "0.2.1";
|
|
8
8
|
var GROUP_IDS = GROUPS.map((g) => g.id);
|
|
9
9
|
var LEVEL_IDS = LEVELS.map((l) => l.id);
|
|
10
10
|
var text = (t) => ({ type: "text", text: t });
|
|
@@ -119,7 +119,7 @@ ${specSnippet(s)}`));
|
|
|
119
119
|
else if (csv) data = parseCSV(csv);
|
|
120
120
|
else if (url) {
|
|
121
121
|
if (!isHttpUrl(url)) return { isError: true, content: [text("url must start with http:// or https://. For a local file, pass its text as csv or its rows as rows.")] };
|
|
122
|
-
data = await loadRows(url);
|
|
122
|
+
data = await loadRows(url, locale);
|
|
123
123
|
} else return { isError: true, content: [text("Pass one of csv, rows or url.")] };
|
|
124
124
|
} catch (e) {
|
|
125
125
|
return { isError: true, content: [text(`Couldn't read the data: ${e.message}`)] };
|
|
@@ -149,4 +149,4 @@ export {
|
|
|
149
149
|
SERVER_VERSION,
|
|
150
150
|
createServer
|
|
151
151
|
};
|
|
152
|
-
//# sourceMappingURL=chunk-
|
|
152
|
+
//# sourceMappingURL=chunk-5RVK2R4T.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { getChartInfo, getChartSchema, isHttpUrl, listCharts, loadRows, reactSnippet, render, specSnippet, suggest, validate, type ChartSpec } from \"ztd-charts-cli\";\nimport { GROUPS, LEVELS, parseCSV, normalizeData, specLocale, type ChartGroup, type ChartLevel } from \"ztd-charts/spec\";\n\nexport const SERVER_NAME = \"ztd-charts\";\nexport const SERVER_VERSION = \"0.2.0\";\n\nconst GROUP_IDS = GROUPS.map((g) => g.id) as [ChartGroup, ...ChartGroup[]];\nconst LEVEL_IDS = LEVELS.map((l) => l.id) as [ChartLevel, ...ChartLevel[]];\n\nconst text = (t: string) => ({ type: \"text\" as const, text: t });\nconst json = (v: unknown) => text(JSON.stringify(v, null, 2));\n\n/** 데이터가 큰 스펙은 앞부분만 보여줘요 (응답 크기 관리) */\nfunction trimSpec(spec: ChartSpec, rows = 20): ChartSpec {\n if (!Array.isArray(spec.data) || spec.data.length <= rows) return spec;\n return { ...spec, data: spec.data.slice(0, rows) };\n}\n\nexport function createServer(): McpServer {\n const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {}, resources: {} }, instructions: 'ztd-charts: 33 animated React chart types driven by a JSON spec { type, data, encoding, options, card }. Typical flow: suggest_chart (or list_charts) -> get_chart_schema -> write a spec -> validate_spec -> render_chart to preview -> use the returned React snippet. Built-in chart text (tooltips, screen reader text, badges) and number formats are English by default. For Korean, pass locale \"ko\" to render_chart or set options.locale in the spec, and write card text in Korean. validate_spec and suggest_chart take locale too.' });\n\n server.registerTool(\n \"list_charts\",\n {\n title: \"List chart types\",\n description: \"List the 33 chart types with name, level (basic, intermediate, advanced), group, one-line summary, when to use or avoid, and encoding channels. Filter by group or level.\",\n inputSchema: { group: z.enum(GROUP_IDS).optional().describe(\"Chart group\"), level: z.enum(LEVEL_IDS).optional().describe(\"Difficulty level\") },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n async ({ group, level }) => {\n const list = listCharts({ group, level });\n return { content: [json({ count: list.length, charts: list })] };\n },\n );\n\n server.registerTool(\n \"get_chart_schema\",\n {\n title: \"Get chart schema\",\n description: \"Get the JSON Schema for one chart type (encoding channels, options with types/defaults), plus a complete example spec and a React snippet. Omit type for the full schema of all 33 charts (about 200 KB, so prefer asking for one type).\",\n inputSchema: { type: z.string().optional().describe('Chart type, e.g. \"pie\", \"line\", \"sankey\". Aliases like \"scatter\", \"violin\" work.') },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n async ({ type }) => {\n if (!type) return { content: [json(getChartSchema())] };\n try {\n const info = getChartInfo(type);\n return { content: [json({ type: info.type, name: info.name, summary: info.summary, useWhen: info.useWhen, avoidWhen: info.avoidWhen, encoding: info.encoding, options: info.options, example: trimSpec(info.example), schema: info.schema }), text(info.react)] };\n } catch (e) {\n return { isError: true, content: [text((e as Error).message)] };\n }\n },\n );\n\n server.registerTool(\n \"validate_spec\",\n {\n title: \"Validate chart spec\",\n description: \"Validate a chart spec against the registry and its inline data: unknown type (with did-you-mean), missing/unknown encoding channels, fields missing from data, non-numeric value fields, option types and enums. Returns { valid, errors[], warnings[] } with JSON paths and messages. Set locale for the language of the messages.\",\n inputSchema: {\n spec: z.record(z.string(), z.unknown()).describe(\"{ type, data, encoding, options?, card?, theme? }\"),\n locale: z.string().optional().describe('Locale for the error and warning messages. Default \"en-US\", or the spec\\'s options.locale when set. \"ko\" or \"ko-KR\" gives Korean messages'),\n },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n async ({ spec, locale }) => {\n // render_chart와 같은 로캘(locale, 그다음 스펙 options.locale)로 검사해요. 둘 다 없으면 영어\n const r = validate(spec, specLocale(spec as unknown as ChartSpec, locale));\n return { content: [json(r)] };\n },\n );\n\n server.registerTool(\n \"render_chart\",\n {\n title: \"Render chart\",\n description: \"Server-render a chart spec to PNG (returned as an image) and/or SVG text, with the card frame (number, title, legend, takeaway, level badge) when spec.card is set. Also returns ready-to-paste React code. data may be inline rows, { csv }, or { url } (fetched). Set locale for the language of built-in chart text and number formats.\",\n inputSchema: {\n spec: z.record(z.string(), z.unknown()).describe(\"Chart spec\"),\n format: z.enum([\"png\", \"svg\", \"both\"]).optional().describe(\"Default png\"),\n width: z.number().int().min(200).max(2400).optional().describe(\"Pixel width, default 720\"),\n theme: z.enum([\"dark\", \"light\"]).optional().describe(\"Default dark\"),\n scale: z.number().min(1).max(4).optional().describe(\"PNG pixel ratio, default 2\"),\n locale: z.string().optional().describe('Locale for built-in chart text and number formats. Default \"en-US\" (1.2K). \"ko\" or \"ko-KR\" gives Korean text and 1.2만 style numbers'),\n },\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n async ({ spec, format = \"png\", width, theme, scale, locale }) => {\n const s = spec as unknown as ChartSpec;\n // 그릴 때와 같은 로캘(locale, 그다음 스펙 options.locale)로 검사해요\n const v = validate(s, specLocale(s, locale));\n if (!v.valid) return { isError: true, content: [text(\"Invalid spec. Fix the errors below (the same result validate_spec returns).\"), json(v)] };\n try {\n const res = await render(s, { format: format === \"svg\" ? \"svg\" : \"png\", width, theme, scale, locale });\n const content: ({ type: \"text\"; text: string } | { type: \"image\"; data: string; mimeType: string })[] = [];\n if (format !== \"svg\" && res.png) content.push({ type: \"image\", data: res.png.toString(\"base64\"), mimeType: \"image/png\" });\n if ((format === \"svg\" || format === \"both\") && res.svg) content.push(text(res.svg));\n content.push(text(`Size ${res.width}×${res.height}px${v.warnings.length ? `\\nWarnings: ${v.warnings.map((w) => w.message).join(\" / \")}` : \"\"}`));\n content.push(text(`// React (components)\\n${reactSnippet(s)}\\n// React (spec as is)\\n${specSnippet(s)}`));\n return { content };\n } catch (e) {\n return { isError: true, content: [text(`Render failed: ${(e as Error).message}`)] };\n }\n },\n );\n\n server.registerTool(\n \"suggest_chart\",\n {\n title: \"Suggest charts for data\",\n description: \"Profile tabular data (column kinds, cardinality, hierarchy, source/target pairs, dates) and recommend chart types ranked by fit, each with a reason and a ready spec. Pass csv text, rows (array of objects), or a url to CSV/JSON (Google Sheets CSV export, REST). Set locale for the language of the reasons and the suggested specs.\",\n inputSchema: {\n csv: z.string().optional().describe(\"CSV text with header row\"),\n rows: z.array(z.record(z.string(), z.unknown())).optional().describe(\"Array of row objects\"),\n url: z.string().optional().describe(\"http(s) URL returning CSV or JSON\"),\n limit: z.number().int().min(1).max(10).optional().describe(\"Default 5\"),\n locale: z.string().optional().describe('Locale for chart names, reasons and card text in the suggestions. Default \"en-US\". \"ko\" or \"ko-KR\" gives Korean text and sets options.locale \"ko-KR\" in each spec'),\n },\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n async ({ csv, rows, url, limit, locale }) => {\n let data: Record<string, unknown>[];\n try {\n if (rows) data = normalizeData(rows);\n else if (csv) data = parseCSV(csv);\n else if (url) {\n // http(s)만 받아요. loadRows는 파일 경로도 읽어서, 그대로 두면 서버가 도는 컴퓨터의 파일을 돌려줄 수 있어요\n if (!isHttpUrl(url)) return { isError: true, content: [text(\"url must start with http:// or https://. For a local file, pass its text as csv or its rows as rows.\")] };\n data = await loadRows(url);\n } else return { isError: true, content: [text(\"Pass one of csv, rows or url.\")] };\n } catch (e) {\n return { isError: true, content: [text(`Couldn't read the data: ${(e as Error).message}`)] };\n }\n const { profile, suggestions } = suggest(data, { limit: limit ?? 5, locale });\n return {\n content: [\n json({\n profile,\n suggestions: suggestions.map((s) => ({ type: s.type, name: s.name, score: s.score, reason: s.reason, spec: trimSpec(s.spec, 8), note: s.spec.data && Array.isArray(s.spec.data) && s.spec.data.length > 8 ? `spec.data shows only the first 8 rows. Put all ${s.spec.data.length} rows in the real spec.` : undefined })),\n }),\n ],\n };\n },\n );\n\n server.registerResource(\n \"chart-spec-schema\",\n \"ztd-charts://schema/chart-spec.json\",\n { title: \"ztd charts spec JSON Schema\", description: \"JSON Schema (draft 2020-12) for all chart specs\", mimeType: \"application/json\" },\n async (uri) => ({ contents: [{ uri: uri.href, mimeType: \"application/json\", text: JSON.stringify(getChartSchema(), null, 2) }] }),\n );\n\n return server;\n}\n"],"mappings":";AAAA,SAAS,iBAAiB;AAC1B,SAAS,SAAS;AAClB,SAAS,cAAc,gBAAgB,WAAW,YAAY,UAAU,cAAc,QAAQ,aAAa,SAAS,gBAAgC;AACpJ,SAAS,QAAQ,QAAQ,UAAU,eAAe,kBAAoD;AAE/F,IAAM,cAAc;AACpB,IAAM,iBAAiB;AAE9B,IAAM,YAAY,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE;AACxC,IAAM,YAAY,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE;AAExC,IAAM,OAAO,CAAC,OAAe,EAAE,MAAM,QAAiB,MAAM,EAAE;AAC9D,IAAM,OAAO,CAAC,MAAe,KAAK,KAAK,UAAU,GAAG,MAAM,CAAC,CAAC;AAG5D,SAAS,SAAS,MAAiB,OAAO,IAAe;AACvD,MAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,KAAK,KAAK,KAAK,UAAU,KAAM,QAAO;AAClE,SAAO,EAAE,GAAG,MAAM,MAAM,KAAK,KAAK,MAAM,GAAG,IAAI,EAAE;AACnD;AAEO,SAAS,eAA0B;AACxC,QAAM,SAAS,IAAI,UAAU,EAAE,MAAM,aAAa,SAAS,eAAe,GAAG,EAAE,cAAc,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,EAAE,GAAG,cAAc,ihBAAihB,CAAC;AAE3pB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,SAAS,aAAa,GAAG,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,SAAS,kBAAkB,EAAE;AAAA,MAC7I,aAAa,EAAE,cAAc,MAAM,gBAAgB,KAAK;AAAA,IAC1D;AAAA,IACA,OAAO,EAAE,OAAO,MAAM,MAAM;AAC1B,YAAM,OAAO,WAAW,EAAE,OAAO,MAAM,CAAC;AACxC,aAAO,EAAE,SAAS,CAAC,KAAK,EAAE,OAAO,KAAK,QAAQ,QAAQ,KAAK,CAAC,CAAC,EAAE;AAAA,IACjE;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,kFAAkF,EAAE;AAAA,MACxI,aAAa,EAAE,cAAc,MAAM,gBAAgB,KAAK;AAAA,IAC1D;AAAA,IACA,OAAO,EAAE,KAAK,MAAM;AAClB,UAAI,CAAC,KAAM,QAAO,EAAE,SAAS,CAAC,KAAK,eAAe,CAAC,CAAC,EAAE;AACtD,UAAI;AACF,cAAM,OAAO,aAAa,IAAI;AAC9B,eAAO,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,MAAM,KAAK,MAAM,SAAS,KAAK,SAAS,SAAS,KAAK,SAAS,WAAW,KAAK,WAAW,UAAU,KAAK,UAAU,SAAS,KAAK,SAAS,SAAS,SAAS,KAAK,OAAO,GAAG,QAAQ,KAAK,OAAO,CAAC,GAAG,KAAK,KAAK,KAAK,CAAC,EAAE;AAAA,MAClQ,SAAS,GAAG;AACV,eAAO,EAAE,SAAS,MAAM,SAAS,CAAC,KAAM,EAAY,OAAO,CAAC,EAAE;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa;AAAA,QACX,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,mDAAmD;AAAA,QACpG,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,0IAA2I;AAAA,MACpL;AAAA,MACA,aAAa,EAAE,cAAc,MAAM,gBAAgB,KAAK;AAAA,IAC1D;AAAA,IACA,OAAO,EAAE,MAAM,OAAO,MAAM;AAE1B,YAAM,IAAI,SAAS,MAAM,WAAW,MAA8B,MAAM,CAAC;AACzE,aAAO,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa;AAAA,QACX,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,YAAY;AAAA,QAC7D,QAAQ,EAAE,KAAK,CAAC,OAAO,OAAO,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS,aAAa;AAAA,QACxE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,EAAE,IAAI,IAAI,EAAE,SAAS,EAAE,SAAS,0BAA0B;AAAA,QACzF,OAAO,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,cAAc;AAAA,QACnE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,SAAS,4BAA4B;AAAA,QAChF,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,0IAAqI;AAAA,MAC9K;AAAA,MACA,aAAa,EAAE,cAAc,MAAM,eAAe,KAAK;AAAA,IACzD;AAAA,IACA,OAAO,EAAE,MAAM,SAAS,OAAO,OAAO,OAAO,OAAO,OAAO,MAAM;AAC/D,YAAM,IAAI;AAEV,YAAM,IAAI,SAAS,GAAG,WAAW,GAAG,MAAM,CAAC;AAC3C,UAAI,CAAC,EAAE,MAAO,QAAO,EAAE,SAAS,MAAM,SAAS,CAAC,KAAK,6EAA6E,GAAG,KAAK,CAAC,CAAC,EAAE;AAC9I,UAAI;AACF,cAAM,MAAM,MAAM,OAAO,GAAG,EAAE,QAAQ,WAAW,QAAQ,QAAQ,OAAO,OAAO,OAAO,OAAO,OAAO,CAAC;AACrG,cAAM,UAAkG,CAAC;AACzG,YAAI,WAAW,SAAS,IAAI,IAAK,SAAQ,KAAK,EAAE,MAAM,SAAS,MAAM,IAAI,IAAI,SAAS,QAAQ,GAAG,UAAU,YAAY,CAAC;AACxH,aAAK,WAAW,SAAS,WAAW,WAAW,IAAI,IAAK,SAAQ,KAAK,KAAK,IAAI,GAAG,CAAC;AAClF,gBAAQ,KAAK,KAAK,QAAQ,IAAI,KAAK,OAAI,IAAI,MAAM,KAAK,EAAE,SAAS,SAAS;AAAA,YAAe,EAAE,SAAS,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;AAC/I,gBAAQ,KAAK,KAAK;AAAA,EAA0B,aAAa,CAAC,CAAC;AAAA;AAAA,EAA4B,YAAY,CAAC,CAAC,EAAE,CAAC;AACxG,eAAO,EAAE,QAAQ;AAAA,MACnB,SAAS,GAAG;AACV,eAAO,EAAE,SAAS,MAAM,SAAS,CAAC,KAAK,kBAAmB,EAAY,OAAO,EAAE,CAAC,EAAE;AAAA,MACpF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa;AAAA,QACX,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,0BAA0B;AAAA,QAC9D,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,sBAAsB;AAAA,QAC3F,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,mCAAmC;AAAA,QACvE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,SAAS,WAAW;AAAA,QACtE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,mKAAmK;AAAA,MAC5M;AAAA,MACA,aAAa,EAAE,cAAc,MAAM,eAAe,KAAK;AAAA,IACzD;AAAA,IACA,OAAO,EAAE,KAAK,MAAM,KAAK,OAAO,OAAO,MAAM;AAC3C,UAAI;AACJ,UAAI;AACF,YAAI,KAAM,QAAO,cAAc,IAAI;AAAA,iBAC1B,IAAK,QAAO,SAAS,GAAG;AAAA,iBACxB,KAAK;AAEZ,cAAI,CAAC,UAAU,GAAG,EAAG,QAAO,EAAE,SAAS,MAAM,SAAS,CAAC,KAAK,sGAAsG,CAAC,EAAE;AACrK,iBAAO,MAAM,SAAS,GAAG;AAAA,QAC3B,MAAO,QAAO,EAAE,SAAS,MAAM,SAAS,CAAC,KAAK,+BAA+B,CAAC,EAAE;AAAA,MAClF,SAAS,GAAG;AACV,eAAO,EAAE,SAAS,MAAM,SAAS,CAAC,KAAK,2BAA4B,EAAY,OAAO,EAAE,CAAC,EAAE;AAAA,MAC7F;AACA,YAAM,EAAE,SAAS,YAAY,IAAI,QAAQ,MAAM,EAAE,OAAO,SAAS,GAAG,OAAO,CAAC;AAC5E,aAAO;AAAA,QACL,SAAS;AAAA,UACP,KAAK;AAAA,YACH;AAAA,YACA,aAAa,YAAY,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,OAAO,EAAE,OAAO,QAAQ,EAAE,QAAQ,MAAM,SAAS,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,QAAQ,MAAM,QAAQ,EAAE,KAAK,IAAI,KAAK,EAAE,KAAK,KAAK,SAAS,IAAI,kDAAkD,EAAE,KAAK,KAAK,MAAM,4BAA4B,OAAU,EAAE;AAAA,UAC1T,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,OAAO,+BAA+B,aAAa,mDAAmD,UAAU,mBAAmB;AAAA,IACrI,OAAO,SAAS,EAAE,UAAU,CAAC,EAAE,KAAK,IAAI,MAAM,UAAU,oBAAoB,MAAM,KAAK,UAAU,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC,EAAE;AAAA,EACjI;AAEA,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport { getChartInfo, getChartSchema, isHttpUrl, listCharts, loadRows, reactSnippet, render, specSnippet, suggest, validate, type ChartSpec } from \"ztd-charts-cli\";\nimport { GROUPS, LEVELS, parseCSV, normalizeData, specLocale, type ChartGroup, type ChartLevel } from \"ztd-charts/spec\";\n\nexport const SERVER_NAME = \"ztd-charts\";\nexport const SERVER_VERSION = \"0.2.1\";\n\nconst GROUP_IDS = GROUPS.map((g) => g.id) as [ChartGroup, ...ChartGroup[]];\nconst LEVEL_IDS = LEVELS.map((l) => l.id) as [ChartLevel, ...ChartLevel[]];\n\nconst text = (t: string) => ({ type: \"text\" as const, text: t });\nconst json = (v: unknown) => text(JSON.stringify(v, null, 2));\n\n/** 데이터가 큰 스펙은 앞부분만 보여줘요 (응답 크기 관리) */\nfunction trimSpec(spec: ChartSpec, rows = 20): ChartSpec {\n if (!Array.isArray(spec.data) || spec.data.length <= rows) return spec;\n return { ...spec, data: spec.data.slice(0, rows) };\n}\n\nexport function createServer(): McpServer {\n const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {}, resources: {} }, instructions: 'ztd-charts: 33 animated React chart types driven by a JSON spec { type, data, encoding, options, card }. Typical flow: suggest_chart (or list_charts) -> get_chart_schema -> write a spec -> validate_spec -> render_chart to preview -> use the returned React snippet. Built-in chart text (tooltips, screen reader text, badges) and number formats are English by default. For Korean, pass locale \"ko\" to render_chart or set options.locale in the spec, and write card text in Korean. validate_spec and suggest_chart take locale too.' });\n\n server.registerTool(\n \"list_charts\",\n {\n title: \"List chart types\",\n description: \"List the 33 chart types with name, level (basic, intermediate, advanced), group, one-line summary, when to use or avoid, and encoding channels. Filter by group or level.\",\n inputSchema: { group: z.enum(GROUP_IDS).optional().describe(\"Chart group\"), level: z.enum(LEVEL_IDS).optional().describe(\"Difficulty level\") },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n async ({ group, level }) => {\n const list = listCharts({ group, level });\n return { content: [json({ count: list.length, charts: list })] };\n },\n );\n\n server.registerTool(\n \"get_chart_schema\",\n {\n title: \"Get chart schema\",\n description: \"Get the JSON Schema for one chart type (encoding channels, options with types/defaults), plus a complete example spec and a React snippet. Omit type for the full schema of all 33 charts (about 200 KB, so prefer asking for one type).\",\n inputSchema: { type: z.string().optional().describe('Chart type, e.g. \"pie\", \"line\", \"sankey\". Aliases like \"scatter\", \"violin\" work.') },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n async ({ type }) => {\n if (!type) return { content: [json(getChartSchema())] };\n try {\n const info = getChartInfo(type);\n return { content: [json({ type: info.type, name: info.name, summary: info.summary, useWhen: info.useWhen, avoidWhen: info.avoidWhen, encoding: info.encoding, options: info.options, example: trimSpec(info.example), schema: info.schema }), text(info.react)] };\n } catch (e) {\n return { isError: true, content: [text((e as Error).message)] };\n }\n },\n );\n\n server.registerTool(\n \"validate_spec\",\n {\n title: \"Validate chart spec\",\n description: \"Validate a chart spec against the registry and its inline data: unknown type (with did-you-mean), missing/unknown encoding channels, fields missing from data, non-numeric value fields, option types and enums. Returns { valid, errors[], warnings[] } with JSON paths and messages. Set locale for the language of the messages.\",\n inputSchema: {\n spec: z.record(z.string(), z.unknown()).describe(\"{ type, data, encoding, options?, card?, theme? }\"),\n locale: z.string().optional().describe('Locale for the error and warning messages. Default \"en-US\", or the spec\\'s options.locale when set. \"ko\" or \"ko-KR\" gives Korean messages'),\n },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n async ({ spec, locale }) => {\n // render_chart와 같은 로캘(locale, 그다음 스펙 options.locale)로 검사해요. 둘 다 없으면 영어\n const r = validate(spec, specLocale(spec as unknown as ChartSpec, locale));\n return { content: [json(r)] };\n },\n );\n\n server.registerTool(\n \"render_chart\",\n {\n title: \"Render chart\",\n description: \"Server-render a chart spec to PNG (returned as an image) and/or SVG text, with the card frame (number, title, legend, takeaway, level badge) when spec.card is set. Also returns ready-to-paste React code. data may be inline rows, { csv }, or { url } (fetched). Set locale for the language of built-in chart text and number formats.\",\n inputSchema: {\n spec: z.record(z.string(), z.unknown()).describe(\"Chart spec\"),\n format: z.enum([\"png\", \"svg\", \"both\"]).optional().describe(\"Default png\"),\n width: z.number().int().min(200).max(2400).optional().describe(\"Pixel width, default 720\"),\n theme: z.enum([\"dark\", \"light\"]).optional().describe(\"Default dark\"),\n scale: z.number().min(1).max(4).optional().describe(\"PNG pixel ratio, default 2\"),\n locale: z.string().optional().describe('Locale for built-in chart text and number formats. Default \"en-US\" (1.2K). \"ko\" or \"ko-KR\" gives Korean text and 1.2만 style numbers'),\n },\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n async ({ spec, format = \"png\", width, theme, scale, locale }) => {\n const s = spec as unknown as ChartSpec;\n // 그릴 때와 같은 로캘(locale, 그다음 스펙 options.locale)로 검사해요\n const v = validate(s, specLocale(s, locale));\n if (!v.valid) return { isError: true, content: [text(\"Invalid spec. Fix the errors below (the same result validate_spec returns).\"), json(v)] };\n try {\n const res = await render(s, { format: format === \"svg\" ? \"svg\" : \"png\", width, theme, scale, locale });\n const content: ({ type: \"text\"; text: string } | { type: \"image\"; data: string; mimeType: string })[] = [];\n if (format !== \"svg\" && res.png) content.push({ type: \"image\", data: res.png.toString(\"base64\"), mimeType: \"image/png\" });\n if ((format === \"svg\" || format === \"both\") && res.svg) content.push(text(res.svg));\n content.push(text(`Size ${res.width}×${res.height}px${v.warnings.length ? `\\nWarnings: ${v.warnings.map((w) => w.message).join(\" / \")}` : \"\"}`));\n content.push(text(`// React (components)\\n${reactSnippet(s)}\\n// React (spec as is)\\n${specSnippet(s)}`));\n return { content };\n } catch (e) {\n return { isError: true, content: [text(`Render failed: ${(e as Error).message}`)] };\n }\n },\n );\n\n server.registerTool(\n \"suggest_chart\",\n {\n title: \"Suggest charts for data\",\n description: \"Profile tabular data (column kinds, cardinality, hierarchy, source/target pairs, dates) and recommend chart types ranked by fit, each with a reason and a ready spec. Pass csv text, rows (array of objects), or a url to CSV/JSON (Google Sheets CSV export, REST). Set locale for the language of the reasons and the suggested specs.\",\n inputSchema: {\n csv: z.string().optional().describe(\"CSV text with header row\"),\n rows: z.array(z.record(z.string(), z.unknown())).optional().describe(\"Array of row objects\"),\n url: z.string().optional().describe(\"http(s) URL returning CSV or JSON\"),\n limit: z.number().int().min(1).max(10).optional().describe(\"Default 5\"),\n locale: z.string().optional().describe('Locale for chart names, reasons and card text in the suggestions. Default \"en-US\". \"ko\" or \"ko-KR\" gives Korean text and sets options.locale \"ko-KR\" in each spec'),\n },\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n async ({ csv, rows, url, limit, locale }) => {\n let data: Record<string, unknown>[];\n try {\n if (rows) data = normalizeData(rows);\n else if (csv) data = parseCSV(csv);\n else if (url) {\n // http(s)만 받아요. loadRows는 파일 경로도 읽어서, 그대로 두면 서버가 도는 컴퓨터의 파일을 돌려줄 수 있어요\n if (!isHttpUrl(url)) return { isError: true, content: [text(\"url must start with http:// or https://. For a local file, pass its text as csv or its rows as rows.\")] };\n data = await loadRows(url, locale);\n } else return { isError: true, content: [text(\"Pass one of csv, rows or url.\")] };\n } catch (e) {\n return { isError: true, content: [text(`Couldn't read the data: ${(e as Error).message}`)] };\n }\n const { profile, suggestions } = suggest(data, { limit: limit ?? 5, locale });\n return {\n content: [\n json({\n profile,\n suggestions: suggestions.map((s) => ({ type: s.type, name: s.name, score: s.score, reason: s.reason, spec: trimSpec(s.spec, 8), note: s.spec.data && Array.isArray(s.spec.data) && s.spec.data.length > 8 ? `spec.data shows only the first 8 rows. Put all ${s.spec.data.length} rows in the real spec.` : undefined })),\n }),\n ],\n };\n },\n );\n\n server.registerResource(\n \"chart-spec-schema\",\n \"ztd-charts://schema/chart-spec.json\",\n { title: \"ztd charts spec JSON Schema\", description: \"JSON Schema (draft 2020-12) for all chart specs\", mimeType: \"application/json\" },\n async (uri) => ({ contents: [{ uri: uri.href, mimeType: \"application/json\", text: JSON.stringify(getChartSchema(), null, 2) }] }),\n );\n\n return server;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
|
|
3
3
|
declare const SERVER_NAME = "ztd-charts";
|
|
4
|
-
declare const SERVER_VERSION = "0.2.
|
|
4
|
+
declare const SERVER_VERSION = "0.2.1";
|
|
5
5
|
declare function createServer(): McpServer;
|
|
6
6
|
|
|
7
7
|
export { SERVER_NAME, SERVER_VERSION, createServer };
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ztd-charts-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "ztd-charts MCP server (stdio): list charts, get schemas, validate specs, render SVG/PNG, and get chart suggestions from data",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Junhan2",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
34
34
|
"zod": "^4.6.7",
|
|
35
|
-
"ztd-charts": "0.2.
|
|
36
|
-
"ztd-charts-cli": "0.2.
|
|
35
|
+
"ztd-charts": "0.2.1",
|
|
36
|
+
"ztd-charts-cli": "0.2.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"tsup": "^9.0.0",
|