@amcharts/amcharts5-mcp 1.2.0 → 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/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 +1 -1
- package/extended/reference/iserpentinechartsettings.md +1 -0
- package/extended/reference/ispiralchartsettings.md +1 -0
- package/package.json +9 -1
- package/src/content-fs.js +74 -0
- package/src/content.js +214 -0
- package/src/index.js +6 -629
- package/src/tools.js +391 -0
package/src/tools.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Tool + resource registration — shared by the stdio server and the Worker.
|
|
3
|
+
//
|
|
4
|
+
// registerTools(server, content) attaches every amCharts 5 tool and resource to
|
|
5
|
+
// an McpServer instance. `content` is the { docs, extendedDocs, examples } shape
|
|
6
|
+
// produced by buildContent(). No filesystem / transport assumptions live here.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { CHART_TYPE_MAP, extractImportBlock, searchDocs } from "./content.js";
|
|
11
|
+
|
|
12
|
+
export function registerTools(server, content) {
|
|
13
|
+
const { docs, extendedDocs, examples } = content;
|
|
14
|
+
|
|
15
|
+
// --- Tool: list_chart_types ---
|
|
16
|
+
server.tool(
|
|
17
|
+
"list_chart_types",
|
|
18
|
+
"List all amCharts 5 chart types with their categories and reference file names",
|
|
19
|
+
{},
|
|
20
|
+
async () => {
|
|
21
|
+
const types = {};
|
|
22
|
+
for (const [alias, ref] of Object.entries(CHART_TYPE_MAP)) {
|
|
23
|
+
if (!types[ref]) types[ref] = [];
|
|
24
|
+
types[ref].push(alias);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let text = "# amCharts 5 — Available Chart Types\n\n";
|
|
28
|
+
for (const [ref, aliases] of Object.entries(types)) {
|
|
29
|
+
const doc = docs.get(ref);
|
|
30
|
+
const title = doc ? doc.title : ref;
|
|
31
|
+
text += `## ${title}\n`;
|
|
32
|
+
text += `Reference: \`${ref}\`\n`;
|
|
33
|
+
text += `Keywords: ${aliases.join(", ")}\n\n`;
|
|
34
|
+
}
|
|
35
|
+
return { content: [{ type: "text", text }] };
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// --- Tool: get_chart_reference ---
|
|
40
|
+
server.tool(
|
|
41
|
+
"get_chart_reference",
|
|
42
|
+
"Get the full reference documentation for a specific amCharts 5 chart type. Use list_chart_types first to see available types.",
|
|
43
|
+
{ chartType: z.string().describe("Chart type keyword, e.g. 'pie', 'sankey', 'xy', 'treemap', 'map', 'radar', 'stock', 'gantt', 'venn', 'wordcloud', 'timeline', 'flow', 'hierarchy', 'ui-elements'") },
|
|
44
|
+
async ({ chartType }) => {
|
|
45
|
+
const key = CHART_TYPE_MAP[chartType.toLowerCase()] || chartType.toLowerCase();
|
|
46
|
+
const doc = docs.get(key);
|
|
47
|
+
if (!doc) {
|
|
48
|
+
const available = [...new Set(Object.values(CHART_TYPE_MAP))].sort().join(", ");
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: "text", text: `No reference found for "${chartType}". Available: ${available}` }],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { content: [{ type: "text", text: doc.content }] };
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// --- Tool: get_core_reference ---
|
|
58
|
+
server.tool(
|
|
59
|
+
"get_core_reference",
|
|
60
|
+
"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.",
|
|
61
|
+
{},
|
|
62
|
+
async () => {
|
|
63
|
+
const doc = docs.get("SKILL");
|
|
64
|
+
if (!doc) {
|
|
65
|
+
return { content: [{ type: "text", text: "Core SKILL.md not found." }] };
|
|
66
|
+
}
|
|
67
|
+
return { content: [{ type: "text", text: doc.content }] };
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// --- Tool: search_docs ---
|
|
72
|
+
server.tool(
|
|
73
|
+
"search_docs",
|
|
74
|
+
"Search the curated amCharts 5 skill docs (SKILL.md + chart references) for a keyword or topic, returning matching sections ranked by relevance. NOTE: by default this scans ONLY the skill layer. The per-class API reference and per-setting DEFAULTS (e.g. tooltip background cornerRadius, axis tick `visible` default, cursor line strokeDasharray) live in the extended docs — pass scope:'all' here, or use search_all / get_api_reference / get_doc, to reach them.",
|
|
75
|
+
{
|
|
76
|
+
query: z.string().describe("Search query, e.g. 'legend', 'axis label rotation', 'tooltip format', 'data processor'"),
|
|
77
|
+
maxResults: z.number().optional().default(5).describe("Maximum number of results to return (default 5)"),
|
|
78
|
+
scope: z.enum(["skill", "all"]).optional().default("skill").describe("'skill' (default) searches only the curated skill docs; 'all' also searches the extended docs / per-class API reference where settings and defaults live."),
|
|
79
|
+
},
|
|
80
|
+
async ({ query, maxResults, scope }) => {
|
|
81
|
+
const results = searchDocs(content, query, maxResults, scope);
|
|
82
|
+
if (results.length === 0) {
|
|
83
|
+
const hint = scope === "skill"
|
|
84
|
+
? ` Nothing in the skill docs — retry with scope:'all' to include the API reference, or use search_all.`
|
|
85
|
+
: "";
|
|
86
|
+
return { content: [{ type: "text", text: `No results found for "${query}".${hint}` }] };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let text = `# Search results for "${query}" (scope: ${scope})\n\n`;
|
|
90
|
+
for (const r of results) {
|
|
91
|
+
text += `## ${r.docTitle} → ${r.heading}\n`;
|
|
92
|
+
text += `*(source: ${r.source}/${r.file}.md, relevance: ${r.score})*\n\n`;
|
|
93
|
+
// Truncate very long sections
|
|
94
|
+
const body = r.body.length > 2000 ? r.body.slice(0, 2000) + "\n\n...(truncated)" : r.body;
|
|
95
|
+
text += body + "\n\n---\n\n";
|
|
96
|
+
}
|
|
97
|
+
return { content: [{ type: "text", text }] };
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// --- Tool: get_section ---
|
|
102
|
+
server.tool(
|
|
103
|
+
"get_section",
|
|
104
|
+
"Get a specific section from an amCharts 5 reference file by heading name. Use search_docs first to find the right section.",
|
|
105
|
+
{
|
|
106
|
+
file: z.string().describe("Reference file name without .md, e.g. 'xy', 'pie', 'SKILL', 'map'"),
|
|
107
|
+
heading: z.string().describe("Section heading to retrieve, e.g. 'Core setup pattern', 'Axis types', 'Common pitfalls'"),
|
|
108
|
+
},
|
|
109
|
+
async ({ file, heading }) => {
|
|
110
|
+
const doc = docs.get(file);
|
|
111
|
+
if (!doc) {
|
|
112
|
+
return { content: [{ type: "text", text: `File "${file}" not found.` }] };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const section = doc.sections.find(
|
|
116
|
+
s => s.heading.toLowerCase().includes(heading.toLowerCase())
|
|
117
|
+
);
|
|
118
|
+
if (!section) {
|
|
119
|
+
const available = doc.sections.map(s => s.heading).filter(Boolean).join("\n- ");
|
|
120
|
+
return {
|
|
121
|
+
content: [{ type: "text", text: `Section "${heading}" not found in ${file}.md.\n\nAvailable sections:\n- ${available}` }],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
content: [{ type: "text", text: `# ${section.heading}\n\n${section.body}` }],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// --- Tool: get_quick_start ---
|
|
131
|
+
server.tool(
|
|
132
|
+
"get_quick_start",
|
|
133
|
+
"Get a quick-start template for a specific chart type — a minimal working example ready to customize.",
|
|
134
|
+
{
|
|
135
|
+
chartType: z.string().describe("Chart type, e.g. 'pie', 'line', 'bar', 'map', 'sankey', 'treemap'"),
|
|
136
|
+
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"),
|
|
137
|
+
},
|
|
138
|
+
async ({ chartType, format }) => {
|
|
139
|
+
const key = CHART_TYPE_MAP[chartType.toLowerCase()] || chartType.toLowerCase();
|
|
140
|
+
const doc = docs.get(key);
|
|
141
|
+
if (!doc) {
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: "text", text: `No reference found for "${chartType}". Use list_chart_types to see available types.` }],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Grab the imports section first so we can exclude it from the setup match.
|
|
148
|
+
const importSection = doc.sections.find(s =>
|
|
149
|
+
/import/i.test(s.heading)
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// Find the setup/core pattern section — must NOT be the imports section
|
|
153
|
+
// (headings like "Required imports" otherwise win and the actual core
|
|
154
|
+
// setup pattern is never shown, making esm/html identical).
|
|
155
|
+
const setupSection = doc.sections.find(s =>
|
|
156
|
+
s !== importSection && /setup|core.*pattern|basic|quick.*start/i.test(s.heading)
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (!setupSection) {
|
|
160
|
+
// Fallback: return first few sections
|
|
161
|
+
const intro = doc.sections.slice(0, 4).map(s => `## ${s.heading}\n${s.body}`).join("\n\n");
|
|
162
|
+
return { content: [{ type: "text", text: intro }] };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let text = `# Quick Start: ${doc.title}\n\n`;
|
|
166
|
+
if (importSection && importSection !== setupSection) {
|
|
167
|
+
// Import sections contain both an "ES modules" and a "CDN / script tags"
|
|
168
|
+
// sub-block. Emit only the one matching the requested format so esm and
|
|
169
|
+
// html actually differ (instead of relabeling the same body).
|
|
170
|
+
const esm = extractImportBlock(importSection.body, "esm");
|
|
171
|
+
const cdn = extractImportBlock(importSection.body, "html");
|
|
172
|
+
if (format === "esm") {
|
|
173
|
+
text += `## Imports (ES modules)\n${esm || importSection.body}\n\n`;
|
|
174
|
+
} else {
|
|
175
|
+
text += `## Imports (CDN / script tags)\n${cdn || importSection.body}\n\n`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
text += `## ${setupSection.heading}\n${setupSection.body}`;
|
|
179
|
+
|
|
180
|
+
return { content: [{ type: "text", text }] };
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// --- Tool: search_all ---
|
|
185
|
+
server.tool(
|
|
186
|
+
"search_all",
|
|
187
|
+
"Search across ALL amCharts 5 content: skill references, full documentation, and code examples. Use this for broad searches across everything.",
|
|
188
|
+
{
|
|
189
|
+
query: z.string().describe("Search query, e.g. 'react integration', 'date axis formatting', 'stacked bar example'"),
|
|
190
|
+
maxResults: z.number().optional().default(10).describe("Maximum results (default 10)"),
|
|
191
|
+
},
|
|
192
|
+
async ({ query, maxResults }) => {
|
|
193
|
+
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
194
|
+
const results = [];
|
|
195
|
+
|
|
196
|
+
// Search skill docs
|
|
197
|
+
for (const [name, doc] of docs) {
|
|
198
|
+
for (const section of doc.sections) {
|
|
199
|
+
const text = (section.heading + " " + section.body).toLowerCase();
|
|
200
|
+
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
201
|
+
if (score > 0) {
|
|
202
|
+
results.push({ source: `skill/${name}`, title: doc.title, heading: section.heading, preview: section.body.slice(0, 300), score: score + 0.1, type: "skill" });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Search extended docs
|
|
208
|
+
for (const [key, doc] of extendedDocs) {
|
|
209
|
+
for (const section of doc.sections) {
|
|
210
|
+
const text = (section.heading + " " + section.body).toLowerCase();
|
|
211
|
+
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
212
|
+
if (score > 0) {
|
|
213
|
+
results.push({ source: key, title: doc.title, heading: section.heading, preview: section.body.slice(0, 300), score, type: "docs" });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Search examples
|
|
219
|
+
for (const [key, ex] of examples) {
|
|
220
|
+
const text = (ex.title + " " + ex.category + " " + ex.content).toLowerCase();
|
|
221
|
+
const score = terms.reduce((s, t) => s + (text.includes(t) ? 1 : 0), 0);
|
|
222
|
+
if (score > 0) {
|
|
223
|
+
results.push({ source: key, title: ex.title, heading: ex.category, preview: ex.content.slice(0, 300), score, type: "example" });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
results.sort((a, b) => b.score - a.score);
|
|
228
|
+
const top = results.slice(0, maxResults);
|
|
229
|
+
|
|
230
|
+
if (top.length === 0) {
|
|
231
|
+
return { content: [{ type: "text", text: `No results found for "${query}".` }] };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let text = `# Search results for "${query}" (${results.length} total, showing ${top.length})\n\n`;
|
|
235
|
+
for (const r of top) {
|
|
236
|
+
text += `## [${r.type}] ${r.title}${r.heading ? ` → ${r.heading}` : ""}\n`;
|
|
237
|
+
text += `*Source: ${r.source} | Relevance: ${r.score}*\n\n`;
|
|
238
|
+
text += r.preview + "\n\n---\n\n";
|
|
239
|
+
}
|
|
240
|
+
return { content: [{ type: "text", text }] };
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// --- Tool: get_doc ---
|
|
245
|
+
server.tool(
|
|
246
|
+
"get_doc",
|
|
247
|
+
"Get a full documentation page from the extended amCharts 5 docs. Use search_all first to find the right path.",
|
|
248
|
+
{
|
|
249
|
+
path: z.string().describe("Doc path, e.g. 'charts/xy-chart/cursor', 'concepts/events', 'getting-started/integrations/react', 'reference/xycursor' (per-class API). For a class's full settings + defaults prefer get_api_reference."),
|
|
250
|
+
},
|
|
251
|
+
async ({ path: docPath }) => {
|
|
252
|
+
const key = docPath.replace(/^\/|\/$/g, "").replace(/\.md$/, "");
|
|
253
|
+
const doc = extendedDocs.get(key);
|
|
254
|
+
if (!doc) {
|
|
255
|
+
// List available top-level paths
|
|
256
|
+
const paths = [...extendedDocs.keys()];
|
|
257
|
+
const topLevel = [...new Set(paths.map(p => p.split("/")[0]))].sort();
|
|
258
|
+
return {
|
|
259
|
+
content: [{ type: "text", text: `Doc "${key}" not found.\n\nAvailable top-level sections: ${topLevel.join(", ")}\n\nUse search_all to find the right path.` }],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return { content: [{ type: "text", text: `# ${doc.title}\n\n${doc.content}` }] };
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// --- Tool: get_api_reference ---
|
|
267
|
+
server.tool(
|
|
268
|
+
"get_api_reference",
|
|
269
|
+
"Get the per-class API reference for an amCharts 5 class — the class page PLUS its settings table (with DEFAULTS) and, optionally, its private/read-only settings. This is the authoritative source for exact setting names and default values (e.g. tooltip background cornerRadius, axis tick `visible` default). Pass a class name in any casing, e.g. 'XYCursor', 'Tooltip', 'PieSeries', 'AxisRendererX'.",
|
|
270
|
+
{
|
|
271
|
+
className: z.string().describe("amCharts 5 class name, e.g. 'XYCursor', 'Tooltip', 'PieSeries', 'ColumnSeries', 'AxisRendererX'. Casing/punctuation are ignored."),
|
|
272
|
+
includePrivate: z.boolean().optional().default(false).describe("Also include the private (read-only / internal) settings table. Default false."),
|
|
273
|
+
},
|
|
274
|
+
async ({ className, includePrivate }) => {
|
|
275
|
+
// Normalize: "XYCursor" / "xy-cursor" / "XY Cursor" → "xycursor"
|
|
276
|
+
const norm = className.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
277
|
+
const classDoc = extendedDocs.get(`reference/${norm}`);
|
|
278
|
+
const settingsDoc = extendedDocs.get(`reference/i${norm}settings`);
|
|
279
|
+
const privateDoc = extendedDocs.get(`reference/i${norm}private`);
|
|
280
|
+
|
|
281
|
+
if (!classDoc && !settingsDoc) {
|
|
282
|
+
// Friendly not-found: suggest close matches among the class pages
|
|
283
|
+
// (reference keys that are NOT i…settings / i…private interface pages).
|
|
284
|
+
const classKeys = [...extendedDocs.keys()]
|
|
285
|
+
.filter(k => k.startsWith("reference/"))
|
|
286
|
+
.map(k => k.slice("reference/".length))
|
|
287
|
+
.filter(k => !k.startsWith("i"));
|
|
288
|
+
const suggestions = classKeys
|
|
289
|
+
.filter(k => k.includes(norm) || norm.includes(k))
|
|
290
|
+
.slice(0, 10);
|
|
291
|
+
let msg = `No API reference found for "${className}" (normalized "${norm}").`;
|
|
292
|
+
if (suggestions.length) {
|
|
293
|
+
msg += `\n\nDid you mean: ${suggestions.join(", ")}?`;
|
|
294
|
+
}
|
|
295
|
+
msg += `\n\nTip: use search_all to find the right class, or list_chart_types for chart families.`;
|
|
296
|
+
return { content: [{ type: "text", text: msg }] };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let text = "";
|
|
300
|
+
if (classDoc) {
|
|
301
|
+
text += `# ${classDoc.title}\n\n${classDoc.content}\n\n`;
|
|
302
|
+
}
|
|
303
|
+
if (settingsDoc) {
|
|
304
|
+
text += `\n---\n\n# ${settingsDoc.title} (settings + defaults)\n\n${settingsDoc.content}\n\n`;
|
|
305
|
+
}
|
|
306
|
+
if (includePrivate && privateDoc) {
|
|
307
|
+
text += `\n---\n\n# ${privateDoc.title} (private / read-only)\n\n${privateDoc.content}\n\n`;
|
|
308
|
+
}
|
|
309
|
+
if (!includePrivate && privateDoc) {
|
|
310
|
+
text += `\n---\n\n*Private/read-only settings also exist — call get_api_reference("${className}", includePrivate: true) to include them.*\n`;
|
|
311
|
+
}
|
|
312
|
+
return { content: [{ type: "text", text: text.trim() }] };
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// --- Tool: list_examples ---
|
|
317
|
+
server.tool(
|
|
318
|
+
"list_examples",
|
|
319
|
+
"List all available amCharts 5 code examples, optionally filtered by category.",
|
|
320
|
+
{
|
|
321
|
+
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."),
|
|
322
|
+
},
|
|
323
|
+
async ({ category }) => {
|
|
324
|
+
if (!category) {
|
|
325
|
+
// List categories with counts
|
|
326
|
+
const cats = {};
|
|
327
|
+
for (const [, ex] of examples) {
|
|
328
|
+
cats[ex.category] = (cats[ex.category] || 0) + 1;
|
|
329
|
+
}
|
|
330
|
+
let text = "# amCharts 5 — Example Categories\n\n";
|
|
331
|
+
for (const [cat, count] of Object.entries(cats).sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
332
|
+
text += `- **${cat}** (${count} examples)\n`;
|
|
333
|
+
}
|
|
334
|
+
text += `\n**Total: ${examples.size} examples**\n`;
|
|
335
|
+
text += "\nUse list_examples with a category to see all examples in that category.";
|
|
336
|
+
return { content: [{ type: "text", text }] };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const matching = [...examples.entries()]
|
|
340
|
+
.filter(([, ex]) => ex.category === category)
|
|
341
|
+
.sort((a, b) => a[1].title.localeCompare(b[1].title));
|
|
342
|
+
|
|
343
|
+
if (matching.length === 0) {
|
|
344
|
+
const cats = [...new Set([...examples.values()].map(e => e.category))].sort();
|
|
345
|
+
return { content: [{ type: "text", text: `No examples in category "${category}".\n\nAvailable: ${cats.join(", ")}` }] };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
let text = `# ${category} examples (${matching.length})\n\n`;
|
|
349
|
+
for (const [key, ex] of matching) {
|
|
350
|
+
text += `- **${ex.title}** — \`${key}\`\n`;
|
|
351
|
+
}
|
|
352
|
+
text += "\nUse get_example with the path to get the full code.";
|
|
353
|
+
return { content: [{ type: "text", text }] };
|
|
354
|
+
}
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
// --- Tool: get_example ---
|
|
358
|
+
server.tool(
|
|
359
|
+
"get_example",
|
|
360
|
+
"Get the full code for a specific amCharts 5 example/demo. Use list_examples or search_all to find the path.",
|
|
361
|
+
{
|
|
362
|
+
path: z.string().describe("Example path, e.g. 'examples/flow/sankey-diagram', 'examples/pie-donut/donut-chart', 'examples/maps/drill-down-map'"),
|
|
363
|
+
},
|
|
364
|
+
async ({ path: exPath }) => {
|
|
365
|
+
const key = exPath.replace(/^\/|\/$/g, "").replace(/\.md$/, "");
|
|
366
|
+
const ex = examples.get(key);
|
|
367
|
+
if (!ex) {
|
|
368
|
+
return { content: [{ type: "text", text: `Example "${key}" not found. Use list_examples to browse available examples.` }] };
|
|
369
|
+
}
|
|
370
|
+
let text = `# ${ex.title}\n`;
|
|
371
|
+
text += `*Category: ${ex.category}*\n`;
|
|
372
|
+
if (ex.source) text += `*Source: ${ex.source}*\n`;
|
|
373
|
+
text += `\n${ex.content}`;
|
|
374
|
+
return { content: [{ type: "text", text }] };
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// -------------------------------------------------------------------------
|
|
379
|
+
// Resources: expose each skill doc as an MCP resource
|
|
380
|
+
// -------------------------------------------------------------------------
|
|
381
|
+
for (const [name, doc] of docs) {
|
|
382
|
+
server.resource(
|
|
383
|
+
name,
|
|
384
|
+
`amcharts://docs/${name}`,
|
|
385
|
+
{ description: `amCharts 5 reference: ${doc.title}`, mimeType: "text/markdown" },
|
|
386
|
+
async () => ({
|
|
387
|
+
contents: [{ uri: `amcharts://docs/${name}`, text: doc.content, mimeType: "text/markdown" }],
|
|
388
|
+
})
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|