@vizejs/musea-mcp-server 0.58.0 → 0.60.0
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 +6 -4
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +26 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.mjs +2 -0
- package/dist/src-DqveCZc0.mjs +780 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,8 +11,10 @@ MCP (Model Context Protocol) server for Musea design system integration.
|
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
|
+
Install `vp` once from the [Vite+ install guide](https://viteplus.dev/guide/install), then add the server to your project:
|
|
15
|
+
|
|
14
16
|
```bash
|
|
15
|
-
|
|
17
|
+
vp install -D @vizejs/musea-mcp-server
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
## Usage
|
|
@@ -25,8 +27,8 @@ Add to `claude_desktop_config.json`:
|
|
|
25
27
|
{
|
|
26
28
|
"mcpServers": {
|
|
27
29
|
"musea": {
|
|
28
|
-
"command": "
|
|
29
|
-
"args": ["--project", "/path/to/project"]
|
|
30
|
+
"command": "vp",
|
|
31
|
+
"args": ["dlx", "@vizejs/musea-mcp-server", "--project", "/path/to/project"]
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -35,7 +37,7 @@ Add to `claude_desktop_config.json`:
|
|
|
35
37
|
### Standalone
|
|
36
38
|
|
|
37
39
|
```bash
|
|
38
|
-
musea-mcp-server --project ./my-vue-app
|
|
40
|
+
vp dlx @vizejs/musea-mcp-server --project ./my-vue-app
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
## MCP Tools
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as startServer } from "./src-DqveCZc0.mjs";
|
|
3
|
+
//#region src/cli.ts
|
|
4
|
+
/**
|
|
5
|
+
* Musea MCP Server CLI.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* musea-mcp [project-root] [--tokens-path <path>]
|
|
9
|
+
*
|
|
10
|
+
* Environment:
|
|
11
|
+
* MUSEA_PROJECT_ROOT - Project root directory (default: cwd)
|
|
12
|
+
* MUSEA_TOKENS_PATH - Path to design tokens file/directory
|
|
13
|
+
*/
|
|
14
|
+
let projectRoot = process.env.MUSEA_PROJECT_ROOT || process.cwd();
|
|
15
|
+
let tokensPath = process.env.MUSEA_TOKENS_PATH;
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
for (let i = 0; i < args.length; i++) if (args[i] === "--tokens-path" && i + 1 < args.length) tokensPath = args[++i];
|
|
18
|
+
else if (!args[i].startsWith("--")) projectRoot = args[i];
|
|
19
|
+
console.error(`[musea-mcp] Starting server for project: ${projectRoot}`);
|
|
20
|
+
if (tokensPath) console.error(`[musea-mcp] Tokens path: ${tokensPath}`);
|
|
21
|
+
startServer(projectRoot, { tokensPath }).catch((error) => {
|
|
22
|
+
console.error("[musea-mcp] Failed to start:", error);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
//#endregion
|
|
26
|
+
export {};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
declare function createMuseaServer(config: {
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
include?: string[];
|
|
7
|
+
exclude?: string[];
|
|
8
|
+
tokensPath?: string;
|
|
9
|
+
}): Server;
|
|
10
|
+
declare function startServer(projectRoot: string, options?: {
|
|
11
|
+
tokensPath?: string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { createMuseaServer, createMuseaServer as default, startServer };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
//#region src/native.ts
|
|
8
|
+
let native = null;
|
|
9
|
+
function loadNative() {
|
|
10
|
+
if (native) return native;
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
try {
|
|
13
|
+
native = require("@vizejs/native");
|
|
14
|
+
return native;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
throw new Error(`Failed to load @vizejs/native. Make sure it's installed: ${String(e)}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/scanner.ts
|
|
21
|
+
async function findArtFiles(root, include, exclude) {
|
|
22
|
+
const files = [];
|
|
23
|
+
async function scan(dir) {
|
|
24
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const fullPath = path.join(dir, entry.name);
|
|
27
|
+
const relative = path.relative(root, fullPath);
|
|
28
|
+
let excluded = false;
|
|
29
|
+
for (const pattern of exclude) if (matchGlob(relative, pattern) || matchGlob(entry.name, pattern)) {
|
|
30
|
+
excluded = true;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
if (excluded) continue;
|
|
34
|
+
if (entry.isDirectory()) await scan(fullPath);
|
|
35
|
+
else if (entry.isFile() && entry.name.endsWith(".art.vue")) {
|
|
36
|
+
for (const pattern of include) if (matchGlob(relative, pattern)) {
|
|
37
|
+
files.push(fullPath);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
await scan(root);
|
|
44
|
+
return files;
|
|
45
|
+
}
|
|
46
|
+
function matchGlob(filepath, pattern) {
|
|
47
|
+
const regex = pattern.replace(/\*\*/g, "{{DOUBLE_STAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLE_STAR}}/g, ".*").replace(/\./g, "\\.");
|
|
48
|
+
return new RegExp(`^${regex}$`).test(filepath);
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/tools/definitions.ts
|
|
52
|
+
/**
|
|
53
|
+
* MCP tool definitions for Musea.
|
|
54
|
+
*
|
|
55
|
+
* Declares the schema (name, description, input parameters) for each tool
|
|
56
|
+
* exposed by the MCP server: component analysis, registry, code generation,
|
|
57
|
+
* documentation, and design tokens.
|
|
58
|
+
*/
|
|
59
|
+
const toolDefinitions = [
|
|
60
|
+
{
|
|
61
|
+
name: "analyze_component",
|
|
62
|
+
description: "Statically analyze a Vue SFC to extract its props and emits. Useful for understanding a component's public API when building or reviewing a design system.",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: { path: {
|
|
66
|
+
type: "string",
|
|
67
|
+
description: "Path to the .vue component file (relative to project root)"
|
|
68
|
+
} },
|
|
69
|
+
required: ["path"]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "get_palette",
|
|
74
|
+
description: "Derive an interactive props palette (control types, defaults, ranges, options) for a component described by an Art file. Helps to understand how props can be tweaked in a design system playground.",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: { path: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Path to the .art.vue file (relative to project root)"
|
|
80
|
+
} },
|
|
81
|
+
required: ["path"]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "list_components",
|
|
86
|
+
description: "List components registered in the design system. Returns titles, categories, tags, and variant counts.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
category: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Filter by category"
|
|
93
|
+
},
|
|
94
|
+
tag: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description: "Filter by tag"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "get_component",
|
|
103
|
+
description: "Get full details of a design-system component: metadata, variant list, and script/style information.",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: { path: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "Path to the .art.vue file (relative to project root)"
|
|
109
|
+
} },
|
|
110
|
+
required: ["path"]
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "get_variant",
|
|
115
|
+
description: "Retrieve a single variant (template and metadata) from a component.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
path: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Path to the .art.vue file"
|
|
122
|
+
},
|
|
123
|
+
variant: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "Variant name"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
required: ["path", "variant"]
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "search_components",
|
|
133
|
+
description: "Full-text search over component titles, descriptions, and tags.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: { query: {
|
|
137
|
+
type: "string",
|
|
138
|
+
description: "Search query"
|
|
139
|
+
} },
|
|
140
|
+
required: ["query"]
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "generate_variants",
|
|
145
|
+
description: "Analyze a Vue component's props and auto-generate an .art.vue file containing appropriate variant combinations (default, boolean toggles, enum values, etc.).",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
componentPath: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "Path to the .vue component file (relative to project root)"
|
|
152
|
+
},
|
|
153
|
+
maxVariants: {
|
|
154
|
+
type: "number",
|
|
155
|
+
description: "Maximum number of variants to generate (default: 20)"
|
|
156
|
+
},
|
|
157
|
+
includeDefault: {
|
|
158
|
+
type: "boolean",
|
|
159
|
+
description: "Include a default variant (default: true)"
|
|
160
|
+
},
|
|
161
|
+
includeBooleanToggles: {
|
|
162
|
+
type: "boolean",
|
|
163
|
+
description: "Generate variants that toggle each boolean prop (default: true)"
|
|
164
|
+
},
|
|
165
|
+
includeEnumVariants: {
|
|
166
|
+
type: "boolean",
|
|
167
|
+
description: "Generate one variant per enum/union value (default: true)"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
required: ["componentPath"]
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "generate_csf",
|
|
175
|
+
description: "Convert an .art.vue file into Storybook CSF 3.0 code for integration with existing Storybook setups.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: { path: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "Path to the .art.vue file"
|
|
181
|
+
} },
|
|
182
|
+
required: ["path"]
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "generate_docs",
|
|
187
|
+
description: "Generate Markdown documentation for a design-system component from its .art.vue definition.",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
path: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "Path to the .art.vue file (relative to project root)"
|
|
194
|
+
},
|
|
195
|
+
includeSource: {
|
|
196
|
+
type: "boolean",
|
|
197
|
+
description: "Embed source code in the output (default: false)"
|
|
198
|
+
},
|
|
199
|
+
includeTemplates: {
|
|
200
|
+
type: "boolean",
|
|
201
|
+
description: "Embed variant templates in the output (default: false)"
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
required: ["path"]
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "generate_catalog",
|
|
209
|
+
description: "Produce a single Markdown catalog covering every component in the design system, grouped by category.",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
includeSource: {
|
|
214
|
+
type: "boolean",
|
|
215
|
+
description: "Embed source code in the catalog (default: false)"
|
|
216
|
+
},
|
|
217
|
+
includeTemplates: {
|
|
218
|
+
type: "boolean",
|
|
219
|
+
description: "Embed variant templates in the catalog (default: false)"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: "get_tokens",
|
|
226
|
+
description: "Read design tokens (colors, spacing, typography, etc.) from a Style Dictionary–compatible JSON file or directory. Auto-detects common paths if not specified.",
|
|
227
|
+
inputSchema: {
|
|
228
|
+
type: "object",
|
|
229
|
+
properties: {
|
|
230
|
+
tokensPath: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Path to tokens JSON file or directory (relative to project root). Auto-detects tokens/, design-tokens/, or style-dictionary/ if omitted."
|
|
233
|
+
},
|
|
234
|
+
format: {
|
|
235
|
+
type: "string",
|
|
236
|
+
enum: ["json", "markdown"],
|
|
237
|
+
description: "Output format (default: json)"
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
];
|
|
243
|
+
//#endregion
|
|
244
|
+
//#region src/tools/handler/analysis.ts
|
|
245
|
+
/**
|
|
246
|
+
* MCP tool handlers for component analysis.
|
|
247
|
+
*
|
|
248
|
+
* Handles `analyze_component` and `get_palette` tool calls.
|
|
249
|
+
*/
|
|
250
|
+
async function handleAnalyzeComponent(ctx, binding, args) {
|
|
251
|
+
const vuePath = args?.path;
|
|
252
|
+
if (!vuePath) throw new McpError(ErrorCode.InvalidParams, "path is required");
|
|
253
|
+
if (!binding.analyzeSfc) throw new McpError(ErrorCode.InternalError, "analyzeSfc not available in native binding");
|
|
254
|
+
const absolutePath = path.resolve(ctx.projectRoot, vuePath);
|
|
255
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
256
|
+
const analysis = binding.analyzeSfc(source, { filename: absolutePath });
|
|
257
|
+
return { content: [{
|
|
258
|
+
type: "text",
|
|
259
|
+
text: JSON.stringify({
|
|
260
|
+
props: analysis.props.map((p) => ({
|
|
261
|
+
name: p.name,
|
|
262
|
+
type: p.type,
|
|
263
|
+
required: p.required,
|
|
264
|
+
defaultValue: p.default_value
|
|
265
|
+
})),
|
|
266
|
+
emits: analysis.emits
|
|
267
|
+
}, null, 2)
|
|
268
|
+
}] };
|
|
269
|
+
}
|
|
270
|
+
async function handleGetPalette(ctx, binding, args) {
|
|
271
|
+
const artPath = args?.path;
|
|
272
|
+
if (!artPath) throw new McpError(ErrorCode.InvalidParams, "path is required");
|
|
273
|
+
if (!binding.generateArtPalette) throw new McpError(ErrorCode.InternalError, "generateArtPalette not available in native binding");
|
|
274
|
+
const absolutePath = path.resolve(ctx.projectRoot, artPath);
|
|
275
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
276
|
+
const palette = binding.generateArtPalette(source, { filename: absolutePath });
|
|
277
|
+
return { content: [{
|
|
278
|
+
type: "text",
|
|
279
|
+
text: JSON.stringify({
|
|
280
|
+
title: palette.title,
|
|
281
|
+
controls: palette.controls.map((c) => ({
|
|
282
|
+
name: c.name,
|
|
283
|
+
control: c.control,
|
|
284
|
+
defaultValue: c.default_value,
|
|
285
|
+
description: c.description,
|
|
286
|
+
required: c.required,
|
|
287
|
+
options: c.options,
|
|
288
|
+
range: c.range,
|
|
289
|
+
group: c.group
|
|
290
|
+
})),
|
|
291
|
+
groups: palette.groups,
|
|
292
|
+
json: palette.json,
|
|
293
|
+
typescript: palette.typescript
|
|
294
|
+
}, null, 2)
|
|
295
|
+
}] };
|
|
296
|
+
}
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/tools/handler/registry.ts
|
|
299
|
+
/**
|
|
300
|
+
* MCP tool handlers for the component registry.
|
|
301
|
+
*
|
|
302
|
+
* Handles `list_components`, `get_component`, `get_variant`, and
|
|
303
|
+
* `search_components` tool calls.
|
|
304
|
+
*/
|
|
305
|
+
async function handleListComponents(ctx, args) {
|
|
306
|
+
const arts = await ctx.scanArtFiles();
|
|
307
|
+
let results = Array.from(arts.values());
|
|
308
|
+
if (args?.category) results = results.filter((a) => a.category?.toLowerCase() === args.category.toLowerCase());
|
|
309
|
+
if (args?.tag) results = results.filter((a) => a.tags.some((t) => t.toLowerCase() === args.tag.toLowerCase()));
|
|
310
|
+
return { content: [{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: JSON.stringify(results.map((r) => ({
|
|
313
|
+
path: path.relative(ctx.projectRoot, r.path),
|
|
314
|
+
title: r.title,
|
|
315
|
+
description: r.description,
|
|
316
|
+
component: r.component,
|
|
317
|
+
category: r.category,
|
|
318
|
+
tags: r.tags,
|
|
319
|
+
variantCount: r.variantCount
|
|
320
|
+
})), null, 2)
|
|
321
|
+
}] };
|
|
322
|
+
}
|
|
323
|
+
async function handleGetComponent(ctx, binding, args) {
|
|
324
|
+
const artPath = args?.path;
|
|
325
|
+
if (!artPath) throw new McpError(ErrorCode.InvalidParams, "path is required");
|
|
326
|
+
const absolutePath = path.resolve(ctx.projectRoot, artPath);
|
|
327
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
328
|
+
const parsed = binding.parseArt(source, { filename: absolutePath });
|
|
329
|
+
return { content: [{
|
|
330
|
+
type: "text",
|
|
331
|
+
text: JSON.stringify({
|
|
332
|
+
metadata: parsed.metadata,
|
|
333
|
+
variants: parsed.variants.map((v) => ({
|
|
334
|
+
name: v.name,
|
|
335
|
+
template: v.template,
|
|
336
|
+
isDefault: v.is_default,
|
|
337
|
+
skipVrt: v.skip_vrt
|
|
338
|
+
})),
|
|
339
|
+
hasScriptSetup: parsed.has_script_setup,
|
|
340
|
+
hasScript: parsed.has_script,
|
|
341
|
+
styleCount: parsed.style_count
|
|
342
|
+
}, null, 2)
|
|
343
|
+
}] };
|
|
344
|
+
}
|
|
345
|
+
async function handleGetVariant(ctx, binding, args) {
|
|
346
|
+
const artPath = args?.path;
|
|
347
|
+
const variantName = args?.variant;
|
|
348
|
+
if (!artPath || !variantName) throw new McpError(ErrorCode.InvalidParams, "path and variant are required");
|
|
349
|
+
const absolutePath = path.resolve(ctx.projectRoot, artPath);
|
|
350
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
351
|
+
const variant = binding.parseArt(source, { filename: absolutePath }).variants.find((v) => v.name.toLowerCase() === variantName.toLowerCase());
|
|
352
|
+
if (!variant) throw new McpError(ErrorCode.InvalidParams, `Variant "${variantName}" not found`);
|
|
353
|
+
return { content: [{
|
|
354
|
+
type: "text",
|
|
355
|
+
text: JSON.stringify({
|
|
356
|
+
name: variant.name,
|
|
357
|
+
template: variant.template,
|
|
358
|
+
isDefault: variant.is_default,
|
|
359
|
+
skipVrt: variant.skip_vrt
|
|
360
|
+
}, null, 2)
|
|
361
|
+
}] };
|
|
362
|
+
}
|
|
363
|
+
async function handleSearchComponents(ctx, args) {
|
|
364
|
+
const query = (args?.query)?.toLowerCase();
|
|
365
|
+
if (!query) throw new McpError(ErrorCode.InvalidParams, "query is required");
|
|
366
|
+
const arts = await ctx.scanArtFiles();
|
|
367
|
+
const results = Array.from(arts.values()).filter((a) => a.title.toLowerCase().includes(query) || a.description?.toLowerCase().includes(query) || a.tags.some((t) => t.toLowerCase().includes(query)));
|
|
368
|
+
return { content: [{
|
|
369
|
+
type: "text",
|
|
370
|
+
text: JSON.stringify(results.map((r) => ({
|
|
371
|
+
path: path.relative(ctx.projectRoot, r.path),
|
|
372
|
+
title: r.title,
|
|
373
|
+
description: r.description,
|
|
374
|
+
component: r.component,
|
|
375
|
+
category: r.category,
|
|
376
|
+
tags: r.tags
|
|
377
|
+
})), null, 2)
|
|
378
|
+
}] };
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/tokens.ts
|
|
382
|
+
async function parseTokensFromPath(tokensPath) {
|
|
383
|
+
if ((await fs.promises.stat(tokensPath)).isDirectory()) {
|
|
384
|
+
const entries = await fs.promises.readdir(tokensPath, { withFileTypes: true });
|
|
385
|
+
const categories = [];
|
|
386
|
+
for (const entry of entries) if (entry.isFile() && (entry.name.endsWith(".json") || entry.name.endsWith(".tokens.json"))) {
|
|
387
|
+
const filePath = path.join(tokensPath, entry.name);
|
|
388
|
+
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
389
|
+
const tokens = JSON.parse(content);
|
|
390
|
+
const categoryName = path.basename(entry.name, path.extname(entry.name)).replace(".tokens", "");
|
|
391
|
+
categories.push({
|
|
392
|
+
name: formatCategoryName(categoryName),
|
|
393
|
+
tokens: extractTokenValues(tokens),
|
|
394
|
+
subcategories: extractSubcats(tokens)
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return categories;
|
|
398
|
+
}
|
|
399
|
+
const content = await fs.promises.readFile(tokensPath, "utf-8");
|
|
400
|
+
return flattenTokenStructure(JSON.parse(content));
|
|
401
|
+
}
|
|
402
|
+
function generateTokensMarkdown(categories) {
|
|
403
|
+
const renderCategory = (category, level = 2) => {
|
|
404
|
+
let md = `\n${"#".repeat(level)} ${category.name}\n\n`;
|
|
405
|
+
if (Object.keys(category.tokens).length > 0) {
|
|
406
|
+
md += "| Token | Value | Description |\n";
|
|
407
|
+
md += "|-------|-------|-------------|\n";
|
|
408
|
+
for (const [name, token] of Object.entries(category.tokens)) md += `| \`${name}\` | \`${token.value}\` | ${token.description || "-"} |\n`;
|
|
409
|
+
md += "\n";
|
|
410
|
+
}
|
|
411
|
+
if (category.subcategories) for (const sub of category.subcategories) md += renderCategory(sub, level + 1);
|
|
412
|
+
return md;
|
|
413
|
+
};
|
|
414
|
+
let markdown = "# Design Tokens\n";
|
|
415
|
+
for (const category of categories) markdown += renderCategory(category);
|
|
416
|
+
return markdown;
|
|
417
|
+
}
|
|
418
|
+
function isTokenLeaf(value) {
|
|
419
|
+
if (typeof value !== "object" || value === null) return false;
|
|
420
|
+
const obj = value;
|
|
421
|
+
return "value" in obj && (typeof obj.value === "string" || typeof obj.value === "number");
|
|
422
|
+
}
|
|
423
|
+
function extractTokenValues(obj) {
|
|
424
|
+
const tokens = {};
|
|
425
|
+
for (const [key, value] of Object.entries(obj)) if (isTokenLeaf(value)) {
|
|
426
|
+
const raw = value;
|
|
427
|
+
tokens[key] = {
|
|
428
|
+
value: raw.value,
|
|
429
|
+
type: raw.type,
|
|
430
|
+
description: raw.description
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return tokens;
|
|
434
|
+
}
|
|
435
|
+
function extractSubcats(obj) {
|
|
436
|
+
const subcategories = [];
|
|
437
|
+
for (const [key, value] of Object.entries(obj)) if (!isTokenLeaf(value) && typeof value === "object" && value !== null) {
|
|
438
|
+
const tokens = extractTokenValues(value);
|
|
439
|
+
const nested = extractSubcats(value);
|
|
440
|
+
if (Object.keys(tokens).length > 0 || nested && nested.length > 0) subcategories.push({
|
|
441
|
+
name: formatCategoryName(key),
|
|
442
|
+
tokens,
|
|
443
|
+
subcategories: nested
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return subcategories.length > 0 ? subcategories : void 0;
|
|
447
|
+
}
|
|
448
|
+
function flattenTokenStructure(tokens) {
|
|
449
|
+
const categories = [];
|
|
450
|
+
for (const [key, value] of Object.entries(tokens)) {
|
|
451
|
+
if (isTokenLeaf(value)) continue;
|
|
452
|
+
if (typeof value === "object" && value !== null) {
|
|
453
|
+
const categoryTokens = extractTokenValues(value);
|
|
454
|
+
const subcategories = flattenTokenStructure(value);
|
|
455
|
+
if (Object.keys(categoryTokens).length > 0 || subcategories.length > 0) categories.push({
|
|
456
|
+
name: formatCategoryName(key),
|
|
457
|
+
tokens: categoryTokens,
|
|
458
|
+
subcategories: subcategories.length > 0 ? subcategories : void 0
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return categories;
|
|
463
|
+
}
|
|
464
|
+
function formatCategoryName(name) {
|
|
465
|
+
return name.replace(/[-_]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/tools/handler/generation.ts
|
|
469
|
+
/**
|
|
470
|
+
* MCP tool handlers for code generation.
|
|
471
|
+
*
|
|
472
|
+
* Handles `generate_variants`, `generate_csf`, `generate_docs`,
|
|
473
|
+
* `generate_catalog`, and `get_tokens` tool calls.
|
|
474
|
+
*/
|
|
475
|
+
async function handleGenerateVariants(ctx, binding, args) {
|
|
476
|
+
const componentRelPath = args?.componentPath;
|
|
477
|
+
if (!componentRelPath) throw new McpError(ErrorCode.InvalidParams, "componentPath is required");
|
|
478
|
+
if (!binding.analyzeSfc) throw new McpError(ErrorCode.InternalError, "analyzeSfc not available in native binding");
|
|
479
|
+
if (!binding.generateVariants) throw new McpError(ErrorCode.InternalError, "generateVariants not available in native binding");
|
|
480
|
+
const absolutePath = path.resolve(ctx.projectRoot, componentRelPath);
|
|
481
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
482
|
+
const props = binding.analyzeSfc(source, { filename: absolutePath }).props.map((p) => ({
|
|
483
|
+
name: p.name,
|
|
484
|
+
prop_type: p.type,
|
|
485
|
+
required: p.required,
|
|
486
|
+
default_value: p.default_value
|
|
487
|
+
}));
|
|
488
|
+
const relPath = `./${path.basename(absolutePath)}`;
|
|
489
|
+
const result = binding.generateVariants(relPath, props, {
|
|
490
|
+
max_variants: args?.maxVariants,
|
|
491
|
+
include_default: args?.includeDefault,
|
|
492
|
+
include_boolean_toggles: args?.includeBooleanToggles,
|
|
493
|
+
include_enum_variants: args?.includeEnumVariants
|
|
494
|
+
});
|
|
495
|
+
return { content: [{
|
|
496
|
+
type: "text",
|
|
497
|
+
text: JSON.stringify({
|
|
498
|
+
componentName: result.component_name,
|
|
499
|
+
artFileContent: result.art_file_content,
|
|
500
|
+
variants: result.variants.map((v) => ({
|
|
501
|
+
name: v.name,
|
|
502
|
+
isDefault: v.is_default,
|
|
503
|
+
props: v.props,
|
|
504
|
+
description: v.description
|
|
505
|
+
}))
|
|
506
|
+
}, null, 2)
|
|
507
|
+
}] };
|
|
508
|
+
}
|
|
509
|
+
async function handleGenerateCsf(ctx, binding, args) {
|
|
510
|
+
const artPath = args?.path;
|
|
511
|
+
if (!artPath) throw new McpError(ErrorCode.InvalidParams, "path is required");
|
|
512
|
+
const absolutePath = path.resolve(ctx.projectRoot, artPath);
|
|
513
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
514
|
+
return { content: [{
|
|
515
|
+
type: "text",
|
|
516
|
+
text: binding.artToCsf(source, { filename: absolutePath }).code
|
|
517
|
+
}] };
|
|
518
|
+
}
|
|
519
|
+
async function handleGenerateDocs(ctx, binding, args) {
|
|
520
|
+
const artPath = args?.path;
|
|
521
|
+
if (!artPath) throw new McpError(ErrorCode.InvalidParams, "path is required");
|
|
522
|
+
if (!binding.generateArtDoc) throw new McpError(ErrorCode.InternalError, "generateArtDoc not available in native binding");
|
|
523
|
+
const absolutePath = path.resolve(ctx.projectRoot, artPath);
|
|
524
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
525
|
+
const doc = binding.generateArtDoc(source, { filename: absolutePath }, {
|
|
526
|
+
include_source: args?.includeSource,
|
|
527
|
+
include_templates: args?.includeTemplates,
|
|
528
|
+
include_metadata: true
|
|
529
|
+
});
|
|
530
|
+
return { content: [{
|
|
531
|
+
type: "text",
|
|
532
|
+
text: JSON.stringify({
|
|
533
|
+
markdown: doc.markdown,
|
|
534
|
+
title: doc.title,
|
|
535
|
+
category: doc.category,
|
|
536
|
+
variantCount: doc.variant_count
|
|
537
|
+
}, null, 2)
|
|
538
|
+
}] };
|
|
539
|
+
}
|
|
540
|
+
async function handleGenerateCatalog(ctx, binding, args) {
|
|
541
|
+
if (!binding.generateArtCatalog) throw new McpError(ErrorCode.InternalError, "generateArtCatalog not available in native binding");
|
|
542
|
+
const arts = await ctx.scanArtFiles();
|
|
543
|
+
const sources = [];
|
|
544
|
+
for (const [filePath] of arts) {
|
|
545
|
+
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
546
|
+
sources.push(source);
|
|
547
|
+
}
|
|
548
|
+
const catalog = binding.generateArtCatalog(sources, {
|
|
549
|
+
include_source: args?.includeSource,
|
|
550
|
+
include_templates: args?.includeTemplates,
|
|
551
|
+
include_metadata: true
|
|
552
|
+
});
|
|
553
|
+
return { content: [{
|
|
554
|
+
type: "text",
|
|
555
|
+
text: JSON.stringify({
|
|
556
|
+
markdown: catalog.markdown,
|
|
557
|
+
componentCount: catalog.component_count,
|
|
558
|
+
categories: catalog.categories,
|
|
559
|
+
tags: catalog.tags
|
|
560
|
+
}, null, 2)
|
|
561
|
+
}] };
|
|
562
|
+
}
|
|
563
|
+
async function handleGetTokens(ctx, args) {
|
|
564
|
+
const inputPath = args?.tokensPath;
|
|
565
|
+
const format = args?.format ?? "json";
|
|
566
|
+
let resolvedPath;
|
|
567
|
+
if (inputPath) resolvedPath = path.resolve(ctx.projectRoot, inputPath);
|
|
568
|
+
else resolvedPath = await ctx.resolveTokensPath();
|
|
569
|
+
if (!resolvedPath) throw new McpError(ErrorCode.InvalidParams, "No tokens path provided and none auto-detected. Looked for: tokens/, design-tokens/, style-dictionary/ directories.");
|
|
570
|
+
const categories = await parseTokensFromPath(resolvedPath);
|
|
571
|
+
if (format === "markdown") return { content: [{
|
|
572
|
+
type: "text",
|
|
573
|
+
text: generateTokensMarkdown(categories)
|
|
574
|
+
}] };
|
|
575
|
+
return { content: [{
|
|
576
|
+
type: "text",
|
|
577
|
+
text: JSON.stringify({ categories }, null, 2)
|
|
578
|
+
}] };
|
|
579
|
+
}
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/tools/handler/index.ts
|
|
582
|
+
/**
|
|
583
|
+
* MCP tool call handler for Musea.
|
|
584
|
+
*
|
|
585
|
+
* Routes incoming tool calls to the appropriate handler logic based on
|
|
586
|
+
* the tool name, using the native Rust binding and server context.
|
|
587
|
+
*/
|
|
588
|
+
async function handleToolCall(ctx, name, args) {
|
|
589
|
+
const binding = ctx.loadNative();
|
|
590
|
+
switch (name) {
|
|
591
|
+
case "analyze_component": return handleAnalyzeComponent(ctx, binding, args);
|
|
592
|
+
case "get_palette": return handleGetPalette(ctx, binding, args);
|
|
593
|
+
case "list_components": return handleListComponents(ctx, args);
|
|
594
|
+
case "get_component": return handleGetComponent(ctx, binding, args);
|
|
595
|
+
case "get_variant": return handleGetVariant(ctx, binding, args);
|
|
596
|
+
case "search_components": return handleSearchComponents(ctx, args);
|
|
597
|
+
case "generate_variants": return handleGenerateVariants(ctx, binding, args);
|
|
598
|
+
case "generate_csf": return handleGenerateCsf(ctx, binding, args);
|
|
599
|
+
case "generate_docs": return handleGenerateDocs(ctx, binding, args);
|
|
600
|
+
case "generate_catalog": return handleGenerateCatalog(ctx, binding, args);
|
|
601
|
+
case "get_tokens": return handleGetTokens(ctx, args);
|
|
602
|
+
default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region src/resources.ts
|
|
607
|
+
async function listResources(ctx) {
|
|
608
|
+
const arts = await ctx.scanArtFiles();
|
|
609
|
+
const resources = [];
|
|
610
|
+
for (const [filePath, info] of arts) {
|
|
611
|
+
const relativePath = path.relative(ctx.projectRoot, filePath);
|
|
612
|
+
resources.push({
|
|
613
|
+
uri: `musea://component/${encodeURIComponent(relativePath)}`,
|
|
614
|
+
name: info.title,
|
|
615
|
+
description: info.description || `${info.category || "Component"} — ${info.variantCount} variant(s)`,
|
|
616
|
+
mimeType: "application/json"
|
|
617
|
+
});
|
|
618
|
+
resources.push({
|
|
619
|
+
uri: `musea://docs/${encodeURIComponent(relativePath)}`,
|
|
620
|
+
name: `${info.title} — Documentation`,
|
|
621
|
+
description: `Markdown docs for ${info.title}`,
|
|
622
|
+
mimeType: "text/markdown"
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
if (await ctx.resolveTokensPath()) resources.push({
|
|
626
|
+
uri: "musea://tokens",
|
|
627
|
+
name: "Design Tokens",
|
|
628
|
+
description: "Project design tokens (colors, spacing, typography, …)",
|
|
629
|
+
mimeType: "application/json"
|
|
630
|
+
});
|
|
631
|
+
return { resources };
|
|
632
|
+
}
|
|
633
|
+
async function readResource(ctx, uri) {
|
|
634
|
+
if (uri.startsWith("musea://component/")) {
|
|
635
|
+
const relativePath = decodeURIComponent(uri.slice(18));
|
|
636
|
+
const absolutePath = path.resolve(ctx.projectRoot, relativePath);
|
|
637
|
+
try {
|
|
638
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
639
|
+
const parsed = ctx.loadNative().parseArt(source, { filename: absolutePath });
|
|
640
|
+
return { contents: [{
|
|
641
|
+
uri,
|
|
642
|
+
mimeType: "application/json",
|
|
643
|
+
text: JSON.stringify({
|
|
644
|
+
path: relativePath,
|
|
645
|
+
metadata: parsed.metadata,
|
|
646
|
+
variants: parsed.variants.map((v) => ({
|
|
647
|
+
name: v.name,
|
|
648
|
+
template: v.template,
|
|
649
|
+
isDefault: v.is_default,
|
|
650
|
+
skipVrt: v.skip_vrt
|
|
651
|
+
})),
|
|
652
|
+
hasScriptSetup: parsed.has_script_setup,
|
|
653
|
+
hasScript: parsed.has_script,
|
|
654
|
+
styleCount: parsed.style_count
|
|
655
|
+
}, null, 2)
|
|
656
|
+
}] };
|
|
657
|
+
} catch (e) {
|
|
658
|
+
throw new McpError(ErrorCode.InternalError, `Failed to read component: ${String(e)}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (uri.startsWith("musea://docs/")) {
|
|
662
|
+
const relativePath = decodeURIComponent(uri.slice(13));
|
|
663
|
+
const absolutePath = path.resolve(ctx.projectRoot, relativePath);
|
|
664
|
+
try {
|
|
665
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
666
|
+
const binding = ctx.loadNative();
|
|
667
|
+
if (!binding.generateArtDoc) throw new McpError(ErrorCode.InternalError, "generateArtDoc not available in native binding");
|
|
668
|
+
return { contents: [{
|
|
669
|
+
uri,
|
|
670
|
+
mimeType: "text/markdown",
|
|
671
|
+
text: binding.generateArtDoc(source, { filename: absolutePath }).markdown
|
|
672
|
+
}] };
|
|
673
|
+
} catch (e) {
|
|
674
|
+
if (e instanceof McpError) throw e;
|
|
675
|
+
throw new McpError(ErrorCode.InternalError, `Failed to generate docs: ${String(e)}`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (uri === "musea://tokens") {
|
|
679
|
+
const resolvedTokensPath = await ctx.resolveTokensPath();
|
|
680
|
+
if (!resolvedTokensPath) throw new McpError(ErrorCode.InternalError, "No tokens path configured or auto-detected");
|
|
681
|
+
try {
|
|
682
|
+
const categories = await parseTokensFromPath(resolvedTokensPath);
|
|
683
|
+
return { contents: [{
|
|
684
|
+
uri,
|
|
685
|
+
mimeType: "application/json",
|
|
686
|
+
text: JSON.stringify({ categories }, null, 2)
|
|
687
|
+
}] };
|
|
688
|
+
} catch (e) {
|
|
689
|
+
throw new McpError(ErrorCode.InternalError, `Failed to read tokens: ${String(e)}`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
throw new McpError(ErrorCode.InvalidRequest, `Unknown resource URI: ${uri}`);
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/index.ts
|
|
696
|
+
/**
|
|
697
|
+
* Musea MCP Server — Vue.js design system toolkit.
|
|
698
|
+
*
|
|
699
|
+
* Provides AI assistants with tools to:
|
|
700
|
+
* - Analyze Vue SFC components (props, emits)
|
|
701
|
+
* - Browse and search a component registry
|
|
702
|
+
* - Generate documentation, variants, and Storybook stories
|
|
703
|
+
* - Read and format design tokens
|
|
704
|
+
*/
|
|
705
|
+
function createMuseaServer(config) {
|
|
706
|
+
const server = new Server({
|
|
707
|
+
name: "musea-mcp-server",
|
|
708
|
+
version: "0.0.1-alpha.11"
|
|
709
|
+
}, { capabilities: {
|
|
710
|
+
resources: {},
|
|
711
|
+
tools: {}
|
|
712
|
+
} });
|
|
713
|
+
const projectRoot = config.projectRoot;
|
|
714
|
+
const include = config.include ?? ["**/*.art.vue"];
|
|
715
|
+
const exclude = config.exclude ?? ["node_modules/**", "dist/**"];
|
|
716
|
+
const tokensPath = config.tokensPath;
|
|
717
|
+
let artCache = /* @__PURE__ */ new Map();
|
|
718
|
+
let lastScanTime = 0;
|
|
719
|
+
async function scanArtFiles() {
|
|
720
|
+
const now = Date.now();
|
|
721
|
+
if (now - lastScanTime < 5e3 && artCache.size > 0) return artCache;
|
|
722
|
+
const binding = loadNative();
|
|
723
|
+
const files = await findArtFiles(projectRoot, include, exclude);
|
|
724
|
+
artCache = /* @__PURE__ */ new Map();
|
|
725
|
+
for (const file of files) try {
|
|
726
|
+
const source = await fs.promises.readFile(file, "utf-8");
|
|
727
|
+
const parsed = binding.parseArt(source, { filename: file });
|
|
728
|
+
artCache.set(file, {
|
|
729
|
+
path: file,
|
|
730
|
+
title: parsed.metadata.title,
|
|
731
|
+
description: parsed.metadata.description,
|
|
732
|
+
component: parsed.metadata.component,
|
|
733
|
+
category: parsed.metadata.category,
|
|
734
|
+
tags: parsed.metadata.tags,
|
|
735
|
+
variantCount: parsed.variants.length
|
|
736
|
+
});
|
|
737
|
+
} catch (e) {
|
|
738
|
+
console.error(`Failed to parse ${file}:`, e);
|
|
739
|
+
}
|
|
740
|
+
lastScanTime = now;
|
|
741
|
+
return artCache;
|
|
742
|
+
}
|
|
743
|
+
async function resolveTokensPath() {
|
|
744
|
+
if (tokensPath) return path.resolve(projectRoot, tokensPath);
|
|
745
|
+
for (const dir of [
|
|
746
|
+
"tokens",
|
|
747
|
+
"design-tokens",
|
|
748
|
+
"style-dictionary"
|
|
749
|
+
]) {
|
|
750
|
+
const candidate = path.join(projectRoot, dir);
|
|
751
|
+
try {
|
|
752
|
+
const stat = await fs.promises.stat(candidate);
|
|
753
|
+
if (stat.isDirectory() || stat.isFile()) return candidate;
|
|
754
|
+
} catch {}
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
const ctx = {
|
|
759
|
+
projectRoot,
|
|
760
|
+
loadNative,
|
|
761
|
+
scanArtFiles,
|
|
762
|
+
resolveTokensPath
|
|
763
|
+
};
|
|
764
|
+
server.setRequestHandler(ListResourcesRequestSchema, () => listResources(ctx));
|
|
765
|
+
server.setRequestHandler(ReadResourceRequestSchema, (req) => readResource(ctx, req.params.uri));
|
|
766
|
+
server.setRequestHandler(ListToolsRequestSchema, () => Promise.resolve({ tools: toolDefinitions }));
|
|
767
|
+
server.setRequestHandler(CallToolRequestSchema, (req) => handleToolCall(ctx, req.params.name, req.params.arguments));
|
|
768
|
+
return server;
|
|
769
|
+
}
|
|
770
|
+
async function startServer(projectRoot, options) {
|
|
771
|
+
const server = createMuseaServer({
|
|
772
|
+
projectRoot,
|
|
773
|
+
tokensPath: options?.tokensPath
|
|
774
|
+
});
|
|
775
|
+
const transport = new StdioServerTransport();
|
|
776
|
+
await server.connect(transport);
|
|
777
|
+
console.error("[musea-mcp] Server started");
|
|
778
|
+
}
|
|
779
|
+
//#endregion
|
|
780
|
+
export { startServer as n, createMuseaServer as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizejs/musea-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.0",
|
|
4
4
|
"description": "MCP server for building Vue.js design systems - component analysis, documentation, variant generation, and design tokens",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "0.5.0",
|
|
41
|
-
"@vizejs/native": "0.
|
|
41
|
+
"@vizejs/native": "0.60.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@tsdown/css": "0.21.9",
|