@baseline-ui/mcp 0.46.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.
- package/CHANGELOG.md +23 -0
- package/README.md +74 -0
- package/dist/data/component-docs.json +69 -0
- package/dist/data/component-source.json +75 -0
- package/dist/data/component-types.json +75 -0
- package/dist/data/components.json +586 -0
- package/dist/data/docs.json +1 -0
- package/dist/data/getting-started.json +3 -0
- package/dist/data/icons.json +638 -0
- package/dist/data/react-aria-components.json +44 -0
- package/dist/data/tokens-metadata.json +54 -0
- package/dist/data/usage-examples.json +42 -0
- package/dist/index.js +395 -0
- package/package.json +45 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
10
|
+
import { mdxFromMarkdown } from "mdast-util-mdx";
|
|
11
|
+
import { mdx } from "micromark-extension-mdx";
|
|
12
|
+
import { toString } from "mdast-util-to-string";
|
|
13
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
14
|
+
function errorToString(err) {
|
|
15
|
+
if (err &&
|
|
16
|
+
typeof err === "object" &&
|
|
17
|
+
"stack" in err &&
|
|
18
|
+
typeof err.stack === "string") {
|
|
19
|
+
return err.stack;
|
|
20
|
+
}
|
|
21
|
+
if (err &&
|
|
22
|
+
typeof err === "object" &&
|
|
23
|
+
"message" in err &&
|
|
24
|
+
typeof err.message === "string") {
|
|
25
|
+
return err.message;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.stringify(err);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return String(err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
35
|
+
const __dirname = path.dirname(__filename);
|
|
36
|
+
function readBundledJson(filename) {
|
|
37
|
+
try {
|
|
38
|
+
const p = path.resolve(__dirname, "data", filename);
|
|
39
|
+
if (!fs.existsSync(p)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const txt = fs.readFileSync(p, "utf8");
|
|
43
|
+
return JSON.parse(txt);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function listComponents() {
|
|
50
|
+
const bundled = readBundledJson("components.json");
|
|
51
|
+
return Array.isArray(bundled) ? bundled : [];
|
|
52
|
+
}
|
|
53
|
+
function listIcons() {
|
|
54
|
+
const bundled = readBundledJson("icons.json");
|
|
55
|
+
return Array.isArray(bundled)
|
|
56
|
+
? [...bundled].toSorted((a, b) => a.localeCompare(b))
|
|
57
|
+
: [];
|
|
58
|
+
}
|
|
59
|
+
function parseSectionsFromMarkdown(content) {
|
|
60
|
+
const lines = content.split(/\r?\n/);
|
|
61
|
+
const ast = fromMarkdown(content, {
|
|
62
|
+
extensions: [mdx()],
|
|
63
|
+
mdastExtensions: [mdxFromMarkdown()],
|
|
64
|
+
});
|
|
65
|
+
const sections = [];
|
|
66
|
+
function findHeadings(node, startLine = 0) {
|
|
67
|
+
if (node &&
|
|
68
|
+
typeof node === "object" &&
|
|
69
|
+
"type" in node &&
|
|
70
|
+
node.type === "heading" &&
|
|
71
|
+
"depth" in node &&
|
|
72
|
+
node.depth === 2) {
|
|
73
|
+
const name = toString(node);
|
|
74
|
+
const position = "position" in node && typeof node.position === "object"
|
|
75
|
+
? node.position
|
|
76
|
+
: null;
|
|
77
|
+
const start = position &&
|
|
78
|
+
"start" in position &&
|
|
79
|
+
position.start &&
|
|
80
|
+
typeof position.start === "object"
|
|
81
|
+
? position.start
|
|
82
|
+
: null;
|
|
83
|
+
if (start && "line" in start && typeof start.line === "number") {
|
|
84
|
+
sections.push({
|
|
85
|
+
name,
|
|
86
|
+
startLine: start.line - 1,
|
|
87
|
+
endLine: lines.length,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (node &&
|
|
92
|
+
typeof node === "object" &&
|
|
93
|
+
"children" in node &&
|
|
94
|
+
Array.isArray(node.children)) {
|
|
95
|
+
for (const child of node.children) {
|
|
96
|
+
findHeadings(child, startLine);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
findHeadings(ast);
|
|
101
|
+
for (let s = 0; s < sections.length - 1; s++) {
|
|
102
|
+
sections[s].endLine = sections[s + 1].startLine;
|
|
103
|
+
}
|
|
104
|
+
return sections;
|
|
105
|
+
}
|
|
106
|
+
function extractNameAndDescription(content) {
|
|
107
|
+
const ast = fromMarkdown(content, {
|
|
108
|
+
extensions: [mdx()],
|
|
109
|
+
mdastExtensions: [mdxFromMarkdown()],
|
|
110
|
+
});
|
|
111
|
+
let name = "";
|
|
112
|
+
let description = undefined;
|
|
113
|
+
if ("children" in ast && Array.isArray(ast.children)) {
|
|
114
|
+
for (let i = 0; i < ast.children.length; i++) {
|
|
115
|
+
const node = ast.children[i];
|
|
116
|
+
if (node.type === "heading" && node.depth === 1 && !name) {
|
|
117
|
+
name = toString(node);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (name &&
|
|
121
|
+
!description &&
|
|
122
|
+
(node.type === "paragraph" || node.type === "text")) {
|
|
123
|
+
description = toString(node).trim();
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { name, description };
|
|
129
|
+
}
|
|
130
|
+
let reactAriaComponentsCache = null;
|
|
131
|
+
function getReactAriaComponentInfo(componentName) {
|
|
132
|
+
if (reactAriaComponentsCache === null) {
|
|
133
|
+
const racData = readBundledJson("react-aria-components.json");
|
|
134
|
+
reactAriaComponentsCache =
|
|
135
|
+
racData && typeof racData === "object"
|
|
136
|
+
? racData
|
|
137
|
+
: {};
|
|
138
|
+
}
|
|
139
|
+
return reactAriaComponentsCache?.[componentName] ?? null;
|
|
140
|
+
}
|
|
141
|
+
function getComponentPage(componentName) {
|
|
142
|
+
const bundled = readBundledJson("component-docs.json");
|
|
143
|
+
if (!bundled || typeof bundled !== "object" || !(componentName in bundled)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const content = String(bundled[componentName]);
|
|
148
|
+
const { name, description } = extractNameAndDescription(content);
|
|
149
|
+
const sections = parseSectionsFromMarkdown(content);
|
|
150
|
+
return {
|
|
151
|
+
name: name || componentName,
|
|
152
|
+
description,
|
|
153
|
+
sections,
|
|
154
|
+
content,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.error(`Error reading component page for ${componentName}:`, error);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function startServer() {
|
|
163
|
+
const server = new McpServer({
|
|
164
|
+
name: "baseline-ui-docs-server",
|
|
165
|
+
version: packageJson.version,
|
|
166
|
+
});
|
|
167
|
+
// List components tool
|
|
168
|
+
server.registerTool("list_baseline_components", {
|
|
169
|
+
title: "List Baseline UI components",
|
|
170
|
+
description: "Returns a list of available components in Baseline UI design system. When presenting results to users, format them as a readable list or table, not as raw JSON.",
|
|
171
|
+
inputSchema: { includeDescription: z.boolean().optional() },
|
|
172
|
+
}, ({ includeDescription }) => {
|
|
173
|
+
const components = listComponents();
|
|
174
|
+
const items = components.map((c) => includeDescription
|
|
175
|
+
? { name: c.name, description: c.description || "", path: c.path }
|
|
176
|
+
: { name: c.name });
|
|
177
|
+
return {
|
|
178
|
+
content: [{ type: "text", text: JSON.stringify(items, null, 2) }],
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
// Get component info tool
|
|
182
|
+
server.registerTool("get_baseline_component_info", {
|
|
183
|
+
title: "Get component info",
|
|
184
|
+
description: "Returns component description and list of documentation sections. Present the description naturally to users and mention available sections they can explore.",
|
|
185
|
+
inputSchema: { componentName: z.string() },
|
|
186
|
+
}, ({ componentName }) => {
|
|
187
|
+
const page = getComponentPage(componentName);
|
|
188
|
+
if (!page) {
|
|
189
|
+
// Check for React Aria Components documentation
|
|
190
|
+
const racInfo = getReactAriaComponentInfo(componentName);
|
|
191
|
+
if (racInfo) {
|
|
192
|
+
const out = {
|
|
193
|
+
name: racInfo.name,
|
|
194
|
+
description: racInfo.description,
|
|
195
|
+
sections: ["Documentation", "Usage"],
|
|
196
|
+
note: "This component is from React Aria Components. Full documentation is available at the provided URL.",
|
|
197
|
+
};
|
|
198
|
+
return {
|
|
199
|
+
content: [{ type: "text", text: JSON.stringify(out, null, 2) }],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
throw new Error(`Component '${componentName}' not found. Use list_baseline_components to see available components.`);
|
|
203
|
+
}
|
|
204
|
+
const out = {
|
|
205
|
+
name: page.name,
|
|
206
|
+
description: page.description ?? "",
|
|
207
|
+
sections: page.sections.map((s) => s.name),
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: "text", text: JSON.stringify(out, null, 2) }],
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
// Get component documentation tool
|
|
214
|
+
server.registerTool("get_baseline_component_docs", {
|
|
215
|
+
title: "Get component documentation",
|
|
216
|
+
description: "Returns the full markdown documentation for a component, or a specific section if provided. Render markdown content appropriately when presenting to users, including code examples with proper syntax highlighting.",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
componentName: z.string(),
|
|
219
|
+
sectionName: z.string().optional(),
|
|
220
|
+
},
|
|
221
|
+
}, ({ componentName, sectionName }) => {
|
|
222
|
+
const page = getComponentPage(componentName);
|
|
223
|
+
if (!page) {
|
|
224
|
+
// Check for React Aria Components documentation
|
|
225
|
+
const racInfo = getReactAriaComponentInfo(componentName);
|
|
226
|
+
if (racInfo) {
|
|
227
|
+
const docs = `# ${racInfo.name}
|
|
228
|
+
|
|
229
|
+
${racInfo.description}
|
|
230
|
+
|
|
231
|
+
## Documentation
|
|
232
|
+
|
|
233
|
+
For detailed documentation, props, examples, and API reference, visit:
|
|
234
|
+
${racInfo.docsUrl}
|
|
235
|
+
|
|
236
|
+
## Source Code
|
|
237
|
+
|
|
238
|
+
View the source code on GitHub:
|
|
239
|
+
${racInfo.sourceUrl}
|
|
240
|
+
|
|
241
|
+
## Usage
|
|
242
|
+
|
|
243
|
+
Import from @baseline-ui/core:
|
|
244
|
+
|
|
245
|
+
\`\`\`tsx
|
|
246
|
+
import { ${racInfo.name} } from "@baseline-ui/core";
|
|
247
|
+
\`\`\`
|
|
248
|
+
|
|
249
|
+
For detailed usage examples and props, please refer to the React Aria Components documentation linked above.`;
|
|
250
|
+
return { content: [{ type: "text", text: docs }] };
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`Component '${componentName}' not found. Use list_baseline_components to see available components.`);
|
|
253
|
+
}
|
|
254
|
+
if (!sectionName) {
|
|
255
|
+
return { content: [{ type: "text", text: page.content }] };
|
|
256
|
+
}
|
|
257
|
+
const lines = page.content.split(/\r?\n/);
|
|
258
|
+
let section = page.sections.find((s) => s.name === sectionName);
|
|
259
|
+
if (!section) {
|
|
260
|
+
section = page.sections.find((s) => s.name.toLowerCase() === sectionName.toLowerCase());
|
|
261
|
+
}
|
|
262
|
+
if (!section) {
|
|
263
|
+
const available = page.sections.map((s) => s.name).join(", ");
|
|
264
|
+
throw new Error(`Section '${sectionName}' not found in ${componentName}. Available: ${available}`);
|
|
265
|
+
}
|
|
266
|
+
const snippet = lines
|
|
267
|
+
.slice(section.startLine, section.endLine)
|
|
268
|
+
.join("\n");
|
|
269
|
+
return { content: [{ type: "text", text: snippet }] };
|
|
270
|
+
});
|
|
271
|
+
// Search icons tool
|
|
272
|
+
server.registerTool("search_baseline_icons", {
|
|
273
|
+
title: "Search Baseline UI icons",
|
|
274
|
+
description: "Searches the Baseline UI icon set by one or more terms; returns matching icon names. When presenting results, show icon names in a clear, organized format. Include import syntax example: import { IconName } from '@baseline-ui/icons';",
|
|
275
|
+
inputSchema: { terms: z.union([z.string(), z.array(z.string())]) },
|
|
276
|
+
}, ({ terms }) => {
|
|
277
|
+
const allNames = listIcons();
|
|
278
|
+
const rawTerms = Array.isArray(terms) ? terms : [terms];
|
|
279
|
+
const normalized = [
|
|
280
|
+
...new Set(rawTerms
|
|
281
|
+
.map((t) => String(t ?? "")
|
|
282
|
+
.trim()
|
|
283
|
+
.toLowerCase())
|
|
284
|
+
.filter(Boolean)),
|
|
285
|
+
];
|
|
286
|
+
if (normalized.length === 0) {
|
|
287
|
+
throw new Error("Provide at least one non-empty search term.");
|
|
288
|
+
}
|
|
289
|
+
const results = allNames.filter((name) => {
|
|
290
|
+
const nameLower = name.toLowerCase();
|
|
291
|
+
return normalized.some((term) => nameLower.includes(term) || term.includes(nameLower));
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
295
|
+
};
|
|
296
|
+
});
|
|
297
|
+
// Get component types/props tool
|
|
298
|
+
server.registerTool("get_baseline_component_types", {
|
|
299
|
+
title: "Get component types",
|
|
300
|
+
description: "Returns the TypeScript type definitions for a component. When presenting prop types to users, explain the types clearly and highlight required vs optional props, default values, and any important type constraints.",
|
|
301
|
+
inputSchema: { componentName: z.string() },
|
|
302
|
+
}, ({ componentName }) => {
|
|
303
|
+
const bundled = readBundledJson("component-types.json");
|
|
304
|
+
if (bundled && typeof bundled === "object" && componentName in bundled) {
|
|
305
|
+
const content = String(bundled[componentName]);
|
|
306
|
+
return { content: [{ type: "text", text: content }] };
|
|
307
|
+
}
|
|
308
|
+
// Check for React Aria Components
|
|
309
|
+
const racInfo = getReactAriaComponentInfo(componentName);
|
|
310
|
+
if (racInfo) {
|
|
311
|
+
const typeInfo = `// ${racInfo.name} type definitions
|
|
312
|
+
|
|
313
|
+
${racInfo.description}
|
|
314
|
+
|
|
315
|
+
For complete type definitions, visit:
|
|
316
|
+
${racInfo.docsUrl}
|
|
317
|
+
|
|
318
|
+
Or view the source code:
|
|
319
|
+
${racInfo.sourceUrl}`;
|
|
320
|
+
return { content: [{ type: "text", text: typeInfo }] };
|
|
321
|
+
}
|
|
322
|
+
throw new Error(`Types file not found for '${componentName}'.`);
|
|
323
|
+
});
|
|
324
|
+
// Get component source code tool
|
|
325
|
+
server.registerTool("get_baseline_component_source", {
|
|
326
|
+
title: "Get component source code",
|
|
327
|
+
description: "Returns the source code for a component implementation. When showing source code to users, present it with proper syntax highlighting and explain key implementation details or patterns if relevant to the user's question.",
|
|
328
|
+
inputSchema: { componentName: z.string() },
|
|
329
|
+
}, ({ componentName }) => {
|
|
330
|
+
const bundled = readBundledJson("component-source.json");
|
|
331
|
+
if (bundled && typeof bundled === "object" && componentName in bundled) {
|
|
332
|
+
const content = String(bundled[componentName]);
|
|
333
|
+
return { content: [{ type: "text", text: content }] };
|
|
334
|
+
}
|
|
335
|
+
// Check for React Aria Components
|
|
336
|
+
const racInfo = getReactAriaComponentInfo(componentName);
|
|
337
|
+
if (racInfo) {
|
|
338
|
+
const sourceInfo = `// ${racInfo.name} source code
|
|
339
|
+
|
|
340
|
+
${racInfo.description}
|
|
341
|
+
|
|
342
|
+
Source code is available on GitHub:
|
|
343
|
+
${racInfo.sourceUrl}
|
|
344
|
+
|
|
345
|
+
For usage examples and documentation:
|
|
346
|
+
${racInfo.docsUrl}`;
|
|
347
|
+
return { content: [{ type: "text", text: sourceInfo }] };
|
|
348
|
+
}
|
|
349
|
+
throw new Error(`Source file not found for '${componentName}'.`);
|
|
350
|
+
});
|
|
351
|
+
// Get getting started guide tool
|
|
352
|
+
server.registerTool("get_baseline_getting_started", {
|
|
353
|
+
title: "Get Baseline UI getting started guide",
|
|
354
|
+
description: "Returns the complete getting started guide for setting up Baseline UI in a new project. This includes installation instructions, required CSS imports, and provider setup. ALWAYS use this tool when helping users set up Baseline UI for the first time or when they encounter setup-related issues.",
|
|
355
|
+
inputSchema: {},
|
|
356
|
+
}, () => {
|
|
357
|
+
const bundled = readBundledJson("getting-started.json");
|
|
358
|
+
if (bundled && typeof bundled === "object" && "content" in bundled) {
|
|
359
|
+
return {
|
|
360
|
+
content: [{ type: "text", text: String(bundled.content) }],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
throw new Error("Getting started guide not found.");
|
|
364
|
+
});
|
|
365
|
+
const transport = new StdioServerTransport();
|
|
366
|
+
await server.connect(transport);
|
|
367
|
+
}
|
|
368
|
+
function printUsage() {
|
|
369
|
+
const usage = `Usage: @baseline-ui/mcp
|
|
370
|
+
|
|
371
|
+
Starts the MCP server for Baseline UI design system documentation.
|
|
372
|
+
|
|
373
|
+
Environment Variables:
|
|
374
|
+
BASELINE_UI_ROOT Path to the Baseline UI repository root (default: auto-detected)
|
|
375
|
+
|
|
376
|
+
Examples:
|
|
377
|
+
npx @baseline-ui/mcp
|
|
378
|
+
BASELINE_UI_ROOT=/path/to/baseline-ui npx @baseline-ui/mcp`;
|
|
379
|
+
console.log(usage);
|
|
380
|
+
}
|
|
381
|
+
// CLI entry
|
|
382
|
+
void (async () => {
|
|
383
|
+
try {
|
|
384
|
+
const arg = (process.argv[2] || "").trim();
|
|
385
|
+
if (arg === "--help" || arg === "-h" || arg === "help") {
|
|
386
|
+
printUsage();
|
|
387
|
+
process.exit(0);
|
|
388
|
+
}
|
|
389
|
+
await startServer();
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
console.error(errorToString(error));
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@baseline-ui/mcp",
|
|
3
|
+
"version": "0.46.1",
|
|
4
|
+
"description": "MCP server for Baseline UI design system documentation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": "dist/index.js",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@babel/parser": "^7.28.5",
|
|
9
|
+
"@babel/traverse": "^7.28.5",
|
|
10
|
+
"@modelcontextprotocol/sdk": "1.21.1",
|
|
11
|
+
"fast-glob": "^3.3.3",
|
|
12
|
+
"mdast-util-from-markdown": "^2.0.2",
|
|
13
|
+
"mdast-util-mdx": "^3.0.0",
|
|
14
|
+
"mdast-util-to-string": "^4.0.0",
|
|
15
|
+
"micromark-extension-mdx": "^2.1.0",
|
|
16
|
+
"zod": "^3.23.8"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/mdast": "^4.0.4",
|
|
20
|
+
"@types/node": "^24.7.0",
|
|
21
|
+
"typescript": "5.9.3"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=22.20.0"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/ritz078/baseline-ui"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"CHANGELOG.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "node ./scripts/build-data.mjs && tsc -p tsconfig.json",
|
|
41
|
+
"start": "node dist/index.js",
|
|
42
|
+
"dev": "node --enable-source-maps dist/index.js",
|
|
43
|
+
"test:static": "tsc --noEmit"
|
|
44
|
+
}
|
|
45
|
+
}
|