@vizejs/musea-mcp-server 0.0.1-alpha.11
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 +50 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +12 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +3 -0
- package/dist/src-DMEJQckJ.js +313 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @vizejs/musea-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server for Musea design system integration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **AI Integration** - Connect Musea to AI assistants
|
|
8
|
+
- **Component Discovery** - Query available components
|
|
9
|
+
- **Design Token Access** - Retrieve design system tokens
|
|
10
|
+
- **Documentation** - Access component docs via MCP
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @vizejs/musea-mcp-server
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
### With Claude Desktop
|
|
21
|
+
|
|
22
|
+
Add to `claude_desktop_config.json`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"musea": {
|
|
28
|
+
"command": "musea-mcp-server",
|
|
29
|
+
"args": ["--project", "/path/to/project"]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Standalone
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
musea-mcp-server --project ./my-vue-app
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## MCP Tools
|
|
42
|
+
|
|
43
|
+
- `list_components` - List all components
|
|
44
|
+
- `get_component` - Get component details
|
|
45
|
+
- `get_variants` - Get component variants
|
|
46
|
+
- `get_tokens` - Get design tokens
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startServer } from "./src-DMEJQckJ.js";
|
|
3
|
+
|
|
4
|
+
//#region src/cli.ts
|
|
5
|
+
const projectRoot = process.argv[2] || process.env.MUSEA_PROJECT_ROOT || process.cwd();
|
|
6
|
+
console.error(`[musea-mcp] Starting server for project: ${projectRoot}`);
|
|
7
|
+
startServer(projectRoot).catch((error) => {
|
|
8
|
+
console.error("[musea-mcp] Failed to start:", error);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
//#endregion
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create and configure the MCP server.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create and configure the MCP server.
|
|
10
|
+
*/
|
|
11
|
+
declare function createMuseaServer(config: {
|
|
12
|
+
projectRoot: string;
|
|
13
|
+
include?: string[];
|
|
14
|
+
exclude?: string[];
|
|
15
|
+
}): Server;
|
|
16
|
+
/**
|
|
17
|
+
* Start the MCP server with stdio transport.
|
|
18
|
+
*/
|
|
19
|
+
declare function startServer(projectRoot: string): Promise<void>; //#endregion
|
|
20
|
+
export { createMuseaServer, createMuseaServer as default, startServer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ErrorCode, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
|
|
8
|
+
//#region src/index.ts
|
|
9
|
+
let native = null;
|
|
10
|
+
function loadNative() {
|
|
11
|
+
if (native) return native;
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
try {
|
|
14
|
+
native = require("@vizejs/native");
|
|
15
|
+
return native;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
throw new Error(`Failed to load @vizejs/native. Make sure it's installed: ${String(e)}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create and configure the MCP server.
|
|
22
|
+
*/
|
|
23
|
+
function createMuseaServer(config) {
|
|
24
|
+
const server = new Server({
|
|
25
|
+
name: "musea-mcp-server",
|
|
26
|
+
version: "0.0.1-alpha.11"
|
|
27
|
+
}, { capabilities: {
|
|
28
|
+
resources: {},
|
|
29
|
+
tools: {}
|
|
30
|
+
} });
|
|
31
|
+
const projectRoot = config.projectRoot;
|
|
32
|
+
const include = config.include ?? ["**/*.art.vue"];
|
|
33
|
+
const exclude = config.exclude ?? ["node_modules/**", "dist/**"];
|
|
34
|
+
let artCache = new Map();
|
|
35
|
+
let lastScanTime = 0;
|
|
36
|
+
async function scanArtFiles() {
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
if (now - lastScanTime < 5e3 && artCache.size > 0) return artCache;
|
|
39
|
+
const binding = loadNative();
|
|
40
|
+
const files = await findArtFiles(projectRoot, include, exclude);
|
|
41
|
+
artCache = new Map();
|
|
42
|
+
for (const file of files) try {
|
|
43
|
+
const source = await fs.promises.readFile(file, "utf-8");
|
|
44
|
+
const parsed = binding.parseArt(source, { filename: file });
|
|
45
|
+
artCache.set(file, {
|
|
46
|
+
path: file,
|
|
47
|
+
title: parsed.metadata.title,
|
|
48
|
+
description: parsed.metadata.description,
|
|
49
|
+
category: parsed.metadata.category,
|
|
50
|
+
tags: parsed.metadata.tags,
|
|
51
|
+
variantCount: parsed.variants.length
|
|
52
|
+
});
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(`Failed to parse ${file}:`, e);
|
|
55
|
+
}
|
|
56
|
+
lastScanTime = now;
|
|
57
|
+
return artCache;
|
|
58
|
+
}
|
|
59
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
60
|
+
const arts = await scanArtFiles();
|
|
61
|
+
const resources = [];
|
|
62
|
+
for (const [filePath, info] of arts) {
|
|
63
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
64
|
+
resources.push({
|
|
65
|
+
uri: `musea://art/${encodeURIComponent(relativePath)}`,
|
|
66
|
+
name: info.title,
|
|
67
|
+
description: info.description || `${info.category || "Component"} with ${info.variantCount} variants`,
|
|
68
|
+
mimeType: "application/json"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return { resources };
|
|
72
|
+
});
|
|
73
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
74
|
+
const { uri } = request.params;
|
|
75
|
+
if (!uri.startsWith("musea://art/")) throw new McpError(ErrorCode.InvalidRequest, `Unknown resource URI: ${uri}`);
|
|
76
|
+
const relativePath = decodeURIComponent(uri.slice(12));
|
|
77
|
+
const absolutePath = path.resolve(projectRoot, relativePath);
|
|
78
|
+
try {
|
|
79
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
80
|
+
const binding = loadNative();
|
|
81
|
+
const parsed = binding.parseArt(source, { filename: absolutePath });
|
|
82
|
+
return { contents: [{
|
|
83
|
+
uri,
|
|
84
|
+
mimeType: "application/json",
|
|
85
|
+
text: JSON.stringify({
|
|
86
|
+
path: relativePath,
|
|
87
|
+
metadata: parsed.metadata,
|
|
88
|
+
variants: parsed.variants.map((v) => ({
|
|
89
|
+
name: v.name,
|
|
90
|
+
template: v.template,
|
|
91
|
+
isDefault: v.is_default,
|
|
92
|
+
skipVrt: v.skip_vrt
|
|
93
|
+
})),
|
|
94
|
+
hasScriptSetup: parsed.has_script_setup,
|
|
95
|
+
hasScript: parsed.has_script,
|
|
96
|
+
styleCount: parsed.style_count
|
|
97
|
+
}, null, 2)
|
|
98
|
+
}] };
|
|
99
|
+
} catch (e) {
|
|
100
|
+
throw new McpError(ErrorCode.InternalError, `Failed to read art file: ${String(e)}`);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
104
|
+
return { tools: [
|
|
105
|
+
{
|
|
106
|
+
name: "list_components",
|
|
107
|
+
description: "List all components (Art files) in the project with their metadata",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
category: {
|
|
112
|
+
type: "string",
|
|
113
|
+
description: "Filter by category"
|
|
114
|
+
},
|
|
115
|
+
tag: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Filter by tag"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "get_component",
|
|
124
|
+
description: "Get detailed information about a specific component",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: { path: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "Path to the Art file (relative to project root)"
|
|
130
|
+
} },
|
|
131
|
+
required: ["path"]
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "get_variant",
|
|
136
|
+
description: "Get a specific variant from a component",
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: "object",
|
|
139
|
+
properties: {
|
|
140
|
+
path: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "Path to the Art file"
|
|
143
|
+
},
|
|
144
|
+
variant: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "Name of the variant"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
required: ["path", "variant"]
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "generate_csf",
|
|
154
|
+
description: "Generate Storybook CSF 3.0 code from an Art file",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: { path: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "Path to the Art file"
|
|
160
|
+
} },
|
|
161
|
+
required: ["path"]
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "search_components",
|
|
166
|
+
description: "Search components by title, description, or tags",
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: { query: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Search query"
|
|
172
|
+
} },
|
|
173
|
+
required: ["query"]
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
] };
|
|
177
|
+
});
|
|
178
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
179
|
+
const { name, arguments: args } = request.params;
|
|
180
|
+
const binding = loadNative();
|
|
181
|
+
switch (name) {
|
|
182
|
+
case "list_components": {
|
|
183
|
+
const arts = await scanArtFiles();
|
|
184
|
+
let results = Array.from(arts.values());
|
|
185
|
+
if (args?.category) results = results.filter((a) => a.category?.toLowerCase() === args.category.toLowerCase());
|
|
186
|
+
if (args?.tag) results = results.filter((a) => a.tags.some((t) => t.toLowerCase() === args.tag.toLowerCase()));
|
|
187
|
+
return { content: [{
|
|
188
|
+
type: "text",
|
|
189
|
+
text: JSON.stringify(results.map((r) => ({
|
|
190
|
+
path: path.relative(projectRoot, r.path),
|
|
191
|
+
title: r.title,
|
|
192
|
+
description: r.description,
|
|
193
|
+
category: r.category,
|
|
194
|
+
tags: r.tags,
|
|
195
|
+
variantCount: r.variantCount
|
|
196
|
+
})), null, 2)
|
|
197
|
+
}] };
|
|
198
|
+
}
|
|
199
|
+
case "get_component": {
|
|
200
|
+
const artPath = args?.path;
|
|
201
|
+
if (!artPath) throw new McpError(ErrorCode.InvalidParams, "path is required");
|
|
202
|
+
const absolutePath = path.resolve(projectRoot, artPath);
|
|
203
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
204
|
+
const parsed = binding.parseArt(source, { filename: absolutePath });
|
|
205
|
+
return { content: [{
|
|
206
|
+
type: "text",
|
|
207
|
+
text: JSON.stringify({
|
|
208
|
+
metadata: parsed.metadata,
|
|
209
|
+
variants: parsed.variants.map((v) => ({
|
|
210
|
+
name: v.name,
|
|
211
|
+
template: v.template,
|
|
212
|
+
isDefault: v.is_default,
|
|
213
|
+
skipVrt: v.skip_vrt
|
|
214
|
+
})),
|
|
215
|
+
hasScriptSetup: parsed.has_script_setup,
|
|
216
|
+
hasScript: parsed.has_script,
|
|
217
|
+
styleCount: parsed.style_count
|
|
218
|
+
}, null, 2)
|
|
219
|
+
}] };
|
|
220
|
+
}
|
|
221
|
+
case "get_variant": {
|
|
222
|
+
const artPath = args?.path;
|
|
223
|
+
const variantName = args?.variant;
|
|
224
|
+
if (!artPath || !variantName) throw new McpError(ErrorCode.InvalidParams, "path and variant are required");
|
|
225
|
+
const absolutePath = path.resolve(projectRoot, artPath);
|
|
226
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
227
|
+
const parsed = binding.parseArt(source, { filename: absolutePath });
|
|
228
|
+
const variant = parsed.variants.find((v) => v.name.toLowerCase() === variantName.toLowerCase());
|
|
229
|
+
if (!variant) throw new McpError(ErrorCode.InvalidParams, `Variant "${variantName}" not found`);
|
|
230
|
+
return { content: [{
|
|
231
|
+
type: "text",
|
|
232
|
+
text: JSON.stringify({
|
|
233
|
+
name: variant.name,
|
|
234
|
+
template: variant.template,
|
|
235
|
+
isDefault: variant.is_default,
|
|
236
|
+
skipVrt: variant.skip_vrt
|
|
237
|
+
}, null, 2)
|
|
238
|
+
}] };
|
|
239
|
+
}
|
|
240
|
+
case "generate_csf": {
|
|
241
|
+
const artPath = args?.path;
|
|
242
|
+
if (!artPath) throw new McpError(ErrorCode.InvalidParams, "path is required");
|
|
243
|
+
const absolutePath = path.resolve(projectRoot, artPath);
|
|
244
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
245
|
+
const csf = binding.artToCsf(source, { filename: absolutePath });
|
|
246
|
+
return { content: [{
|
|
247
|
+
type: "text",
|
|
248
|
+
text: csf.code
|
|
249
|
+
}] };
|
|
250
|
+
}
|
|
251
|
+
case "search_components": {
|
|
252
|
+
const query = (args?.query)?.toLowerCase();
|
|
253
|
+
if (!query) throw new McpError(ErrorCode.InvalidParams, "query is required");
|
|
254
|
+
const arts = await scanArtFiles();
|
|
255
|
+
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)));
|
|
256
|
+
return { content: [{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: JSON.stringify(results.map((r) => ({
|
|
259
|
+
path: path.relative(projectRoot, r.path),
|
|
260
|
+
title: r.title,
|
|
261
|
+
description: r.description,
|
|
262
|
+
category: r.category,
|
|
263
|
+
tags: r.tags
|
|
264
|
+
})), null, 2)
|
|
265
|
+
}] };
|
|
266
|
+
}
|
|
267
|
+
default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
return server;
|
|
271
|
+
}
|
|
272
|
+
async function findArtFiles(root, include, exclude) {
|
|
273
|
+
const files = [];
|
|
274
|
+
async function scan(dir) {
|
|
275
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
const fullPath = path.join(dir, entry.name);
|
|
278
|
+
const relative = path.relative(root, fullPath);
|
|
279
|
+
let excluded = false;
|
|
280
|
+
for (const pattern of exclude) if (matchGlob(relative, pattern) || matchGlob(entry.name, pattern)) {
|
|
281
|
+
excluded = true;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
if (excluded) continue;
|
|
285
|
+
if (entry.isDirectory()) await scan(fullPath);
|
|
286
|
+
else if (entry.isFile() && entry.name.endsWith(".art.vue")) {
|
|
287
|
+
for (const pattern of include) if (matchGlob(relative, pattern)) {
|
|
288
|
+
files.push(fullPath);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
await scan(root);
|
|
295
|
+
return files;
|
|
296
|
+
}
|
|
297
|
+
function matchGlob(filepath, pattern) {
|
|
298
|
+
const regex = pattern.replace(/\*\*/g, "{{DOUBLE_STAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLE_STAR}}/g, ".*").replace(/\./g, "\\.");
|
|
299
|
+
return new RegExp(`^${regex}$`).test(filepath);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Start the MCP server with stdio transport.
|
|
303
|
+
*/
|
|
304
|
+
async function startServer(projectRoot) {
|
|
305
|
+
const server = createMuseaServer({ projectRoot });
|
|
306
|
+
const transport = new StdioServerTransport();
|
|
307
|
+
await server.connect(transport);
|
|
308
|
+
console.error("[musea-mcp] Server started");
|
|
309
|
+
}
|
|
310
|
+
var src_default = createMuseaServer;
|
|
311
|
+
|
|
312
|
+
//#endregion
|
|
313
|
+
export { createMuseaServer, src_default, startServer };
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vizejs/musea-mcp-server",
|
|
3
|
+
"version": "0.0.1-alpha.11",
|
|
4
|
+
"description": "MCP server for Musea - AI-accessible component gallery",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"musea-mcp": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"vue",
|
|
24
|
+
"component-gallery",
|
|
25
|
+
"musea",
|
|
26
|
+
"ai"
|
|
27
|
+
],
|
|
28
|
+
"author": "ubugeeei",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/ubugeeei/vize.git",
|
|
33
|
+
"directory": "npm/musea-mcp-server"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
37
|
+
"@vizejs/native": "0.0.1-alpha.33"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.14.0",
|
|
41
|
+
"tsdown": "^0.9.0",
|
|
42
|
+
"typescript": "^5.7.0"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"provenance": true,
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsdown",
|
|
50
|
+
"dev": "tsdown --watch",
|
|
51
|
+
"start": "node dist/cli.js",
|
|
52
|
+
"lint": "oxlint --deny-warnings --type-aware --tsconfig tsconfig.json",
|
|
53
|
+
"lint:fix": "oxlint --type-aware --tsconfig tsconfig.json --fix",
|
|
54
|
+
"fmt": "oxfmt --write src",
|
|
55
|
+
"fmt:check": "oxfmt src"
|
|
56
|
+
}
|
|
57
|
+
}
|