@amcharts/amcharts5-mcp 1.1.9 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/content/SKILL.md +62 -1
- package/content/gantt.md +3 -3
- package/content/stock.md +4 -3
- package/content/timeline.md +29 -25
- package/content/ui-elements.md +5 -3
- package/content/wordcloud.md +4 -4
- package/content/xy.md +12 -1
- package/extended/examples/maps/map-sankey-series.md +129 -0
- package/extended/reference/hierarchy.md +1 -0
- package/extended/reference/ihierarchysettings.md +1 -0
- package/extended/reference/imovingaveragedeviationsettings.md +2 -1
- package/extended/reference/imovingaveragesettings.md +2 -1
- package/extended/reference/iserpentinechartsettings.md +1 -0
- package/extended/reference/ispiralchartsettings.md +1 -0
- package/extended/reference/itreesettings.md +3 -0
- package/extended/reference/ivalueaxissettings.md +1 -0
- package/extended/reference/ivoronoitreemapsettings.md +2 -1
- package/extended/reference/movingaverage.md +2 -1
- package/extended/reference/movingaveragedeviation.md +2 -1
- package/extended/reference/tree.md +3 -0
- package/extended/reference/valueaxis.md +1 -0
- package/extended/reference/voronoitreemap.md +2 -1
- package/package.json +9 -1
- package/src/content-fs.js +74 -0
- package/src/content.js +214 -0
- package/src/index.js +10 -516
- package/src/tools.js +391 -0
package/src/index.js
CHANGED
|
@@ -2,534 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
-
import {
|
|
6
|
-
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
5
|
+
import { readFileSync } from "fs";
|
|
7
6
|
import { join, dirname } from "path";
|
|
8
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
import { loadContentFromDisk } from "./content-fs.js";
|
|
9
|
+
import { registerTools } from "./tools.js";
|
|
9
10
|
|
|
10
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const ROOT = join(__dirname, "..");
|
|
12
13
|
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const SKILL_DIR = existsSync(SUBMODULE_DIR) ? SUBMODULE_DIR : CONTENT_DIR;
|
|
17
|
-
const REFERENCES_DIR = existsSync(join(SKILL_DIR, "references"))
|
|
18
|
-
? join(SKILL_DIR, "references")
|
|
19
|
-
: SKILL_DIR;
|
|
20
|
-
const EXTENDED_DIR = join(ROOT, "extended");
|
|
14
|
+
// Single source of truth for the server version: package.json
|
|
15
|
+
const PKG = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
|
|
16
|
+
const VERSION = PKG.version;
|
|
21
17
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
/** @type {Map<string, {title: string, content: string, sections: {heading: string, body: string}[]}>} */
|
|
27
|
-
const docs = new Map();
|
|
28
|
-
|
|
29
|
-
/** @type {Map<string, {title: string, content: string, sections: {heading: string, body: string}[], path: string}>} */
|
|
30
|
-
const extendedDocs = new Map();
|
|
31
|
-
|
|
32
|
-
/** @type {Map<string, {title: string, category: string, content: string, source: string}>} */
|
|
33
|
-
const examples = new Map();
|
|
34
|
-
|
|
35
|
-
/** Chart type aliases → reference file mapping */
|
|
36
|
-
const CHART_TYPE_MAP = {
|
|
37
|
-
// XY family
|
|
38
|
-
"xy": "xy", "line": "xy", "area": "xy", "bar": "xy", "column": "xy",
|
|
39
|
-
"candlestick": "xy", "ohlc": "xy", "scatter": "xy", "bubble": "xy",
|
|
40
|
-
"stacked": "xy", "stacked-bar": "xy", "stacked-column": "xy",
|
|
41
|
-
"stacked-area": "xy", "range": "xy", "waterfall": "xy",
|
|
42
|
-
// Pie family
|
|
43
|
-
"pie": "pie", "donut": "pie", "funnel": "pie", "pyramid": "pie",
|
|
44
|
-
"semi-circle": "pie", "pictorial": "pie", "sliced": "pie",
|
|
45
|
-
// Map
|
|
46
|
-
"map": "map", "choropleth": "map", "geo": "map", "geographic": "map",
|
|
47
|
-
"world": "map", "country": "map", "bubble-map": "map", "point-map": "map",
|
|
48
|
-
// Hierarchy
|
|
49
|
-
"hierarchy": "hierarchy", "treemap": "hierarchy", "sunburst": "hierarchy",
|
|
50
|
-
"force-directed": "hierarchy", "pack": "hierarchy", "partition": "hierarchy",
|
|
51
|
-
"tree": "hierarchy", "org-chart": "hierarchy",
|
|
52
|
-
// Flow
|
|
53
|
-
"flow": "flow", "sankey": "flow", "chord": "flow", "arc": "flow",
|
|
54
|
-
"alluvial": "flow",
|
|
55
|
-
// Radar
|
|
56
|
-
"radar": "radar", "spider": "radar", "polar": "radar", "gauge": "radar",
|
|
57
|
-
"speedometer": "radar", "meter": "radar",
|
|
58
|
-
// Stock
|
|
59
|
-
"stock": "stock", "financial": "stock", "trading": "stock",
|
|
60
|
-
// Timeline
|
|
61
|
-
"timeline": "timeline", "serpentine": "timeline", "spiral": "timeline",
|
|
62
|
-
"curve": "timeline",
|
|
63
|
-
// Gantt
|
|
64
|
-
"gantt": "gantt", "project-timeline": "gantt",
|
|
65
|
-
// Word cloud
|
|
66
|
-
"wordcloud": "wordcloud", "word-cloud": "wordcloud", "tag-cloud": "wordcloud",
|
|
67
|
-
// Venn
|
|
68
|
-
"venn": "venn",
|
|
69
|
-
// UI
|
|
70
|
-
"ui": "ui-elements", "ui-elements": "ui-elements", "button": "ui-elements",
|
|
71
|
-
"slider": "ui-elements", "controls": "ui-elements",
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function parseSections(content) {
|
|
75
|
-
const sections = [];
|
|
76
|
-
const lines = content.split("\n");
|
|
77
|
-
let currentHeading = "";
|
|
78
|
-
let currentLevel = 0;
|
|
79
|
-
let currentBody = [];
|
|
80
|
-
|
|
81
|
-
for (const line of lines) {
|
|
82
|
-
const headingMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
83
|
-
if (headingMatch) {
|
|
84
|
-
const level = headingMatch[1].length;
|
|
85
|
-
const title = headingMatch[2].trim();
|
|
86
|
-
if (level <= 2) {
|
|
87
|
-
// Major section boundary (# or ##)
|
|
88
|
-
if (currentHeading || currentBody.length > 0) {
|
|
89
|
-
sections.push({ heading: currentHeading, body: currentBody.join("\n") });
|
|
90
|
-
}
|
|
91
|
-
currentHeading = title;
|
|
92
|
-
currentLevel = level;
|
|
93
|
-
currentBody = [];
|
|
94
|
-
} else {
|
|
95
|
-
// Sub-heading (###) — include in current section body
|
|
96
|
-
currentBody.push(line);
|
|
97
|
-
}
|
|
98
|
-
} else {
|
|
99
|
-
currentBody.push(line);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (currentHeading || currentBody.length > 0) {
|
|
103
|
-
sections.push({ heading: currentHeading, body: currentBody.join("\n") });
|
|
104
|
-
}
|
|
105
|
-
return sections;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function loadFile(filePath, name) {
|
|
109
|
-
const content = readFileSync(filePath, "utf-8");
|
|
110
|
-
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
111
|
-
const title = titleMatch ? titleMatch[1] : name;
|
|
112
|
-
docs.set(name, { title, content, sections: parseSections(content) });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function loadContent() {
|
|
116
|
-
// Load core SKILL.md
|
|
117
|
-
const skillPath = join(SKILL_DIR, "SKILL.md");
|
|
118
|
-
if (existsSync(skillPath)) {
|
|
119
|
-
loadFile(skillPath, "SKILL");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Load all reference files
|
|
123
|
-
const files = readdirSync(REFERENCES_DIR).filter(f => f.endsWith(".md") && f !== "SKILL.md");
|
|
124
|
-
for (const file of files) {
|
|
125
|
-
loadFile(join(REFERENCES_DIR, file), file.replace(".md", ""));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
loadContent();
|
|
130
|
-
|
|
131
|
-
// ---------------------------------------------------------------------------
|
|
132
|
-
// Load extended docs and examples
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
|
|
135
|
-
function loadExtendedDir(dir, prefix = "") {
|
|
136
|
-
if (!existsSync(dir)) return;
|
|
137
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
138
|
-
for (const entry of entries) {
|
|
139
|
-
const fullPath = join(dir, entry.name);
|
|
140
|
-
if (entry.isDirectory()) {
|
|
141
|
-
loadExtendedDir(fullPath, prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
142
|
-
} else if (entry.name.endsWith(".md")) {
|
|
143
|
-
const key = prefix ? `${prefix}/${entry.name.replace(".md", "")}` : entry.name.replace(".md", "");
|
|
144
|
-
const content = readFileSync(fullPath, "utf-8");
|
|
145
|
-
|
|
146
|
-
// Parse frontmatter
|
|
147
|
-
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
148
|
-
const body = fmMatch ? fmMatch[2].trim() : content;
|
|
149
|
-
const fm = fmMatch ? fmMatch[1] : "";
|
|
150
|
-
const titleMatch = fm.match(/title:\s*"(.+?)"/);
|
|
151
|
-
const categoryMatch = fm.match(/category:\s*"(.+?)"/);
|
|
152
|
-
const sourceMatch = fm.match(/source:\s*"(.+?)"/);
|
|
153
|
-
const title = titleMatch ? titleMatch[1] : entry.name.replace(".md", "");
|
|
154
|
-
|
|
155
|
-
if (key.startsWith("examples/")) {
|
|
156
|
-
examples.set(key, {
|
|
157
|
-
title,
|
|
158
|
-
category: categoryMatch ? categoryMatch[1] : "",
|
|
159
|
-
content: body,
|
|
160
|
-
source: sourceMatch ? sourceMatch[1] : "",
|
|
161
|
-
});
|
|
162
|
-
} else {
|
|
163
|
-
extendedDocs.set(key, {
|
|
164
|
-
title,
|
|
165
|
-
content: body,
|
|
166
|
-
sections: parseSections(body),
|
|
167
|
-
path: key,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
loadExtendedDir(EXTENDED_DIR);
|
|
175
|
-
|
|
176
|
-
// ---------------------------------------------------------------------------
|
|
177
|
-
// Search helpers
|
|
178
|
-
// ---------------------------------------------------------------------------
|
|
179
|
-
|
|
180
|
-
function searchDocs(query, maxResults = 10) {
|
|
181
|
-
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
182
|
-
const results = [];
|
|
183
|
-
|
|
184
|
-
for (const [name, doc] of docs) {
|
|
185
|
-
for (const section of doc.sections) {
|
|
186
|
-
const text = (section.heading + " " + section.body).toLowerCase();
|
|
187
|
-
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
188
|
-
if (score > 0) {
|
|
189
|
-
results.push({
|
|
190
|
-
file: name,
|
|
191
|
-
docTitle: doc.title,
|
|
192
|
-
heading: section.heading,
|
|
193
|
-
body: section.body.trim(),
|
|
194
|
-
score,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
results.sort((a, b) => b.score - a.score);
|
|
201
|
-
return results.slice(0, maxResults);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
// MCP Server
|
|
206
|
-
// ---------------------------------------------------------------------------
|
|
18
|
+
// Load and index all content from disk (submodule + extended/).
|
|
19
|
+
const content = loadContentFromDisk();
|
|
207
20
|
|
|
208
21
|
const server = new McpServer({
|
|
209
22
|
name: "amcharts5",
|
|
210
|
-
version:
|
|
23
|
+
version: VERSION,
|
|
211
24
|
});
|
|
212
25
|
|
|
213
|
-
|
|
214
|
-
server.tool(
|
|
215
|
-
"list_chart_types",
|
|
216
|
-
"List all amCharts 5 chart types with their categories and reference file names",
|
|
217
|
-
{},
|
|
218
|
-
async () => {
|
|
219
|
-
const types = {};
|
|
220
|
-
for (const [alias, ref] of Object.entries(CHART_TYPE_MAP)) {
|
|
221
|
-
if (!types[ref]) types[ref] = [];
|
|
222
|
-
types[ref].push(alias);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
let text = "# amCharts 5 — Available Chart Types\n\n";
|
|
226
|
-
for (const [ref, aliases] of Object.entries(types)) {
|
|
227
|
-
const doc = docs.get(ref);
|
|
228
|
-
const title = doc ? doc.title : ref;
|
|
229
|
-
text += `## ${title}\n`;
|
|
230
|
-
text += `Reference: \`${ref}\`\n`;
|
|
231
|
-
text += `Keywords: ${aliases.join(", ")}\n\n`;
|
|
232
|
-
}
|
|
233
|
-
return { content: [{ type: "text", text }] };
|
|
234
|
-
}
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
// --- Tool: get_chart_reference ---
|
|
238
|
-
server.tool(
|
|
239
|
-
"get_chart_reference",
|
|
240
|
-
"Get the full reference documentation for a specific amCharts 5 chart type. Use list_chart_types first to see available types.",
|
|
241
|
-
{ chartType: z.string().describe("Chart type keyword, e.g. 'pie', 'sankey', 'xy', 'treemap', 'map', 'radar', 'stock', 'gantt', 'venn', 'wordcloud', 'timeline', 'flow', 'hierarchy', 'ui-elements'") },
|
|
242
|
-
async ({ chartType }) => {
|
|
243
|
-
const key = CHART_TYPE_MAP[chartType.toLowerCase()] || chartType.toLowerCase();
|
|
244
|
-
const doc = docs.get(key);
|
|
245
|
-
if (!doc) {
|
|
246
|
-
const available = [...new Set(Object.values(CHART_TYPE_MAP))].sort().join(", ");
|
|
247
|
-
return {
|
|
248
|
-
content: [{ type: "text", text: `No reference found for "${chartType}". Available: ${available}` }],
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
return { content: [{ type: "text", text: doc.content }] };
|
|
252
|
-
}
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
// --- Tool: get_core_reference ---
|
|
256
|
-
server.tool(
|
|
257
|
-
"get_core_reference",
|
|
258
|
-
"Get the core amCharts 5 reference (SKILL.md) — covers setup, colors, themes, legends, tooltips, events, data, adapters, disposal, and common pitfalls. Read this first before any chart-specific reference.",
|
|
259
|
-
{},
|
|
260
|
-
async () => {
|
|
261
|
-
const doc = docs.get("SKILL");
|
|
262
|
-
if (!doc) {
|
|
263
|
-
return { content: [{ type: "text", text: "Core SKILL.md not found." }] };
|
|
264
|
-
}
|
|
265
|
-
return { content: [{ type: "text", text: doc.content }] };
|
|
266
|
-
}
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
// --- Tool: search_docs ---
|
|
270
|
-
server.tool(
|
|
271
|
-
"search_docs",
|
|
272
|
-
"Search across all amCharts 5 documentation for a keyword or topic. Returns matching sections ranked by relevance.",
|
|
273
|
-
{
|
|
274
|
-
query: z.string().describe("Search query, e.g. 'legend', 'axis label rotation', 'tooltip format', 'data processor'"),
|
|
275
|
-
maxResults: z.number().optional().default(5).describe("Maximum number of results to return (default 5)"),
|
|
276
|
-
},
|
|
277
|
-
async ({ query, maxResults }) => {
|
|
278
|
-
const results = searchDocs(query, maxResults);
|
|
279
|
-
if (results.length === 0) {
|
|
280
|
-
return { content: [{ type: "text", text: `No results found for "${query}".` }] };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
let text = `# Search results for "${query}"\n\n`;
|
|
284
|
-
for (const r of results) {
|
|
285
|
-
text += `## ${r.docTitle} → ${r.heading}\n`;
|
|
286
|
-
text += `*(source: ${r.file}.md, relevance: ${r.score})*\n\n`;
|
|
287
|
-
// Truncate very long sections
|
|
288
|
-
const body = r.body.length > 2000 ? r.body.slice(0, 2000) + "\n\n...(truncated)" : r.body;
|
|
289
|
-
text += body + "\n\n---\n\n";
|
|
290
|
-
}
|
|
291
|
-
return { content: [{ type: "text", text }] };
|
|
292
|
-
}
|
|
293
|
-
);
|
|
294
|
-
|
|
295
|
-
// --- Tool: get_section ---
|
|
296
|
-
server.tool(
|
|
297
|
-
"get_section",
|
|
298
|
-
"Get a specific section from an amCharts 5 reference file by heading name. Use search_docs first to find the right section.",
|
|
299
|
-
{
|
|
300
|
-
file: z.string().describe("Reference file name without .md, e.g. 'xy', 'pie', 'SKILL', 'map'"),
|
|
301
|
-
heading: z.string().describe("Section heading to retrieve, e.g. 'Core setup pattern', 'Axis types', 'Common pitfalls'"),
|
|
302
|
-
},
|
|
303
|
-
async ({ file, heading }) => {
|
|
304
|
-
const doc = docs.get(file);
|
|
305
|
-
if (!doc) {
|
|
306
|
-
return { content: [{ type: "text", text: `File "${file}" not found.` }] };
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const section = doc.sections.find(
|
|
310
|
-
s => s.heading.toLowerCase().includes(heading.toLowerCase())
|
|
311
|
-
);
|
|
312
|
-
if (!section) {
|
|
313
|
-
const available = doc.sections.map(s => s.heading).filter(Boolean).join("\n- ");
|
|
314
|
-
return {
|
|
315
|
-
content: [{ type: "text", text: `Section "${heading}" not found in ${file}.md.\n\nAvailable sections:\n- ${available}` }],
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
return {
|
|
319
|
-
content: [{ type: "text", text: `# ${section.heading}\n\n${section.body}` }],
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
// --- Tool: get_quick_start ---
|
|
325
|
-
server.tool(
|
|
326
|
-
"get_quick_start",
|
|
327
|
-
"Get a quick-start template for a specific chart type — a minimal working example ready to customize.",
|
|
328
|
-
{
|
|
329
|
-
chartType: z.string().describe("Chart type, e.g. 'pie', 'line', 'bar', 'map', 'sankey', 'treemap'"),
|
|
330
|
-
format: z.enum(["html", "esm"]).optional().default("html").describe("Output format: 'html' for a full HTML page with CDN scripts, 'esm' for ES module imports"),
|
|
331
|
-
},
|
|
332
|
-
async ({ chartType, format }) => {
|
|
333
|
-
const key = CHART_TYPE_MAP[chartType.toLowerCase()] || chartType.toLowerCase();
|
|
334
|
-
const doc = docs.get(key);
|
|
335
|
-
if (!doc) {
|
|
336
|
-
return {
|
|
337
|
-
content: [{ type: "text", text: `No reference found for "${chartType}". Use list_chart_types to see available types.` }],
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Find the setup/core pattern section
|
|
342
|
-
const setupSection = doc.sections.find(s =>
|
|
343
|
-
/setup|core.*pattern|basic|quick.*start|required.*import/i.test(s.heading)
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
if (!setupSection) {
|
|
347
|
-
// Fallback: return first few sections
|
|
348
|
-
const intro = doc.sections.slice(0, 4).map(s => `## ${s.heading}\n${s.body}`).join("\n\n");
|
|
349
|
-
return { content: [{ type: "text", text: intro }] };
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Also grab the imports section
|
|
353
|
-
const importSection = doc.sections.find(s =>
|
|
354
|
-
/import/i.test(s.heading)
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
let text = `# Quick Start: ${doc.title}\n\n`;
|
|
358
|
-
if (importSection && importSection !== setupSection) {
|
|
359
|
-
if (format === "esm") {
|
|
360
|
-
text += `## Imports (ES modules)\n${importSection.body}\n\n`;
|
|
361
|
-
} else {
|
|
362
|
-
text += `## Imports\n${importSection.body}\n\n`;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
text += `## ${setupSection.heading}\n${setupSection.body}`;
|
|
366
|
-
|
|
367
|
-
return { content: [{ type: "text", text }] };
|
|
368
|
-
}
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
// --- Tool: search_all ---
|
|
372
|
-
server.tool(
|
|
373
|
-
"search_all",
|
|
374
|
-
"Search across ALL amCharts 5 content: skill references, full documentation, and code examples. Use this for broad searches across everything.",
|
|
375
|
-
{
|
|
376
|
-
query: z.string().describe("Search query, e.g. 'react integration', 'date axis formatting', 'stacked bar example'"),
|
|
377
|
-
maxResults: z.number().optional().default(10).describe("Maximum results (default 10)"),
|
|
378
|
-
},
|
|
379
|
-
async ({ query, maxResults }) => {
|
|
380
|
-
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
381
|
-
const results = [];
|
|
382
|
-
|
|
383
|
-
// Search skill docs
|
|
384
|
-
for (const [name, doc] of docs) {
|
|
385
|
-
for (const section of doc.sections) {
|
|
386
|
-
const text = (section.heading + " " + section.body).toLowerCase();
|
|
387
|
-
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
388
|
-
if (score > 0) {
|
|
389
|
-
results.push({ source: `skill/${name}`, title: doc.title, heading: section.heading, preview: section.body.slice(0, 300), score: score + 0.1, type: "skill" });
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Search extended docs
|
|
395
|
-
for (const [key, doc] of extendedDocs) {
|
|
396
|
-
for (const section of doc.sections) {
|
|
397
|
-
const text = (section.heading + " " + section.body).toLowerCase();
|
|
398
|
-
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
399
|
-
if (score > 0) {
|
|
400
|
-
results.push({ source: key, title: doc.title, heading: section.heading, preview: section.body.slice(0, 300), score, type: "docs" });
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Search examples
|
|
406
|
-
for (const [key, ex] of examples) {
|
|
407
|
-
const text = (ex.title + " " + ex.category + " " + ex.content).toLowerCase();
|
|
408
|
-
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
409
|
-
if (score > 0) {
|
|
410
|
-
results.push({ source: key, title: ex.title, heading: ex.category, preview: ex.content.slice(0, 300), score, type: "example" });
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
results.sort((a, b) => b.score - a.score);
|
|
415
|
-
const top = results.slice(0, maxResults);
|
|
416
|
-
|
|
417
|
-
if (top.length === 0) {
|
|
418
|
-
return { content: [{ type: "text", text: `No results found for "${query}".` }] };
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
let text = `# Search results for "${query}" (${results.length} total, showing ${top.length})\n\n`;
|
|
422
|
-
for (const r of top) {
|
|
423
|
-
text += `## [${r.type}] ${r.title}${r.heading ? ` → ${r.heading}` : ""}\n`;
|
|
424
|
-
text += `*Source: ${r.source} | Relevance: ${r.score}*\n\n`;
|
|
425
|
-
text += r.preview + "\n\n---\n\n";
|
|
426
|
-
}
|
|
427
|
-
return { content: [{ type: "text", text }] };
|
|
428
|
-
}
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
// --- Tool: get_doc ---
|
|
432
|
-
server.tool(
|
|
433
|
-
"get_doc",
|
|
434
|
-
"Get a full documentation page from the extended amCharts 5 docs. Use search_all first to find the right path.",
|
|
435
|
-
{
|
|
436
|
-
path: z.string().describe("Doc path, e.g. 'charts/xy-chart/cursor', 'concepts/events', 'getting-started/integrations/react'"),
|
|
437
|
-
},
|
|
438
|
-
async ({ path: docPath }) => {
|
|
439
|
-
const key = docPath.replace(/^\/|\/$/g, "").replace(/\.md$/, "");
|
|
440
|
-
const doc = extendedDocs.get(key);
|
|
441
|
-
if (!doc) {
|
|
442
|
-
// List available top-level paths
|
|
443
|
-
const paths = [...extendedDocs.keys()];
|
|
444
|
-
const topLevel = [...new Set(paths.map(p => p.split("/")[0]))].sort();
|
|
445
|
-
return {
|
|
446
|
-
content: [{ type: "text", text: `Doc "${key}" not found.\n\nAvailable top-level sections: ${topLevel.join(", ")}\n\nUse search_all to find the right path.` }],
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
return { content: [{ type: "text", text: `# ${doc.title}\n\n${doc.content}` }] };
|
|
450
|
-
}
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
// --- Tool: list_examples ---
|
|
454
|
-
server.tool(
|
|
455
|
-
"list_examples",
|
|
456
|
-
"List all available amCharts 5 code examples, optionally filtered by category.",
|
|
457
|
-
{
|
|
458
|
-
category: z.string().optional().describe("Filter by category, e.g. 'column-bar', 'line-area', 'pie-donut', 'maps', 'flow', 'hierarchy', 'stock', 'gantt', 'gauges', 'radar-polar', 'timeline', 'funnel-pyramid'. Omit to list all categories."),
|
|
459
|
-
},
|
|
460
|
-
async ({ category }) => {
|
|
461
|
-
if (!category) {
|
|
462
|
-
// List categories with counts
|
|
463
|
-
const cats = {};
|
|
464
|
-
for (const [, ex] of examples) {
|
|
465
|
-
cats[ex.category] = (cats[ex.category] || 0) + 1;
|
|
466
|
-
}
|
|
467
|
-
let text = "# amCharts 5 — Example Categories\n\n";
|
|
468
|
-
for (const [cat, count] of Object.entries(cats).sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
469
|
-
text += `- **${cat}** (${count} examples)\n`;
|
|
470
|
-
}
|
|
471
|
-
text += `\n**Total: ${examples.size} examples**\n`;
|
|
472
|
-
text += "\nUse list_examples with a category to see all examples in that category.";
|
|
473
|
-
return { content: [{ type: "text", text }] };
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const matching = [...examples.entries()]
|
|
477
|
-
.filter(([, ex]) => ex.category === category)
|
|
478
|
-
.sort((a, b) => a[1].title.localeCompare(b[1].title));
|
|
479
|
-
|
|
480
|
-
if (matching.length === 0) {
|
|
481
|
-
const cats = [...new Set([...examples.values()].map(e => e.category))].sort();
|
|
482
|
-
return { content: [{ type: "text", text: `No examples in category "${category}".\n\nAvailable: ${cats.join(", ")}` }] };
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
let text = `# ${category} examples (${matching.length})\n\n`;
|
|
486
|
-
for (const [key, ex] of matching) {
|
|
487
|
-
text += `- **${ex.title}** — \`${key}\`\n`;
|
|
488
|
-
}
|
|
489
|
-
text += "\nUse get_example with the path to get the full code.";
|
|
490
|
-
return { content: [{ type: "text", text }] };
|
|
491
|
-
}
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
// --- Tool: get_example ---
|
|
495
|
-
server.tool(
|
|
496
|
-
"get_example",
|
|
497
|
-
"Get the full code for a specific amCharts 5 example/demo. Use list_examples or search_all to find the path.",
|
|
498
|
-
{
|
|
499
|
-
path: z.string().describe("Example path, e.g. 'examples/flow/sankey-diagram', 'examples/pie-donut/donut-chart', 'examples/maps/drill-down-map'"),
|
|
500
|
-
},
|
|
501
|
-
async ({ path: exPath }) => {
|
|
502
|
-
const key = exPath.replace(/^\/|\/$/g, "").replace(/\.md$/, "");
|
|
503
|
-
const ex = examples.get(key);
|
|
504
|
-
if (!ex) {
|
|
505
|
-
return { content: [{ type: "text", text: `Example "${key}" not found. Use list_examples to browse available examples.` }] };
|
|
506
|
-
}
|
|
507
|
-
let text = `# ${ex.title}\n`;
|
|
508
|
-
text += `*Category: ${ex.category}*\n`;
|
|
509
|
-
if (ex.source) text += `*Source: ${ex.source}*\n`;
|
|
510
|
-
text += `\n${ex.content}`;
|
|
511
|
-
return { content: [{ type: "text", text }] };
|
|
512
|
-
}
|
|
513
|
-
);
|
|
514
|
-
|
|
515
|
-
// ---------------------------------------------------------------------------
|
|
516
|
-
// Resources: expose each doc as an MCP resource
|
|
517
|
-
// ---------------------------------------------------------------------------
|
|
518
|
-
|
|
519
|
-
for (const [name, doc] of docs) {
|
|
520
|
-
server.resource(
|
|
521
|
-
name,
|
|
522
|
-
`amcharts://docs/${name}`,
|
|
523
|
-
{ description: `amCharts 5 reference: ${doc.title}`, mimeType: "text/markdown" },
|
|
524
|
-
async () => ({
|
|
525
|
-
contents: [{ uri: `amcharts://docs/${name}`, text: doc.content, mimeType: "text/markdown" }],
|
|
526
|
-
})
|
|
527
|
-
);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// ---------------------------------------------------------------------------
|
|
531
|
-
// Start
|
|
532
|
-
// ---------------------------------------------------------------------------
|
|
26
|
+
registerTools(server, content);
|
|
533
27
|
|
|
534
28
|
const transport = new StdioServerTransport();
|
|
535
29
|
await server.connect(transport);
|