@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/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
+ }