@epilot/volt-ui-mcp 0.1.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 +24 -0
- package/index.js +316 -0
- package/package.json +19 -0
- package/registry.json +3819 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Volt UI MCP Server
|
|
2
|
+
|
|
3
|
+
Runs a local MCP server that exposes Volt UI components, props, and examples.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"mcpServers": {
|
|
10
|
+
"volt-ui-mcp": {
|
|
11
|
+
"command": "npx",
|
|
12
|
+
"args": ["-y", "volt-ui-mcp"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Build the registry
|
|
19
|
+
|
|
20
|
+
Run this from the repo root before publishing:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
bun run build:mcp
|
|
24
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
7
|
+
import {
|
|
8
|
+
CallToolRequestSchema,
|
|
9
|
+
ListResourcesRequestSchema,
|
|
10
|
+
ListToolsRequestSchema,
|
|
11
|
+
ReadResourceRequestSchema,
|
|
12
|
+
} from "@modelcontextprotocol/sdk/types.js"
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
15
|
+
const registryPath = path.join(__dirname, "registry.json")
|
|
16
|
+
|
|
17
|
+
const registry = loadRegistry(registryPath)
|
|
18
|
+
const server = new Server(
|
|
19
|
+
{
|
|
20
|
+
name: "volt-ui-mcp",
|
|
21
|
+
version: registry.schemaVersion ? `schema-${registry.schemaVersion}` : "0.1.0",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
capabilities: {
|
|
25
|
+
resources: {},
|
|
26
|
+
tools: {},
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
32
|
+
const baseResources = [
|
|
33
|
+
{
|
|
34
|
+
uri: "volt-ui://components",
|
|
35
|
+
name: "Volt UI Components",
|
|
36
|
+
description: "List of all available Volt UI components.",
|
|
37
|
+
mimeType: "application/json",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
uri: "volt-ui://blocks",
|
|
41
|
+
name: "Volt UI Blocks",
|
|
42
|
+
description: "List of all available Volt UI blocks/examples.",
|
|
43
|
+
mimeType: "application/json",
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
const componentResources = registry.components.map((component) => ({
|
|
48
|
+
uri: `volt-ui://components/${encodeURIComponent(component.name)}`,
|
|
49
|
+
name: component.name,
|
|
50
|
+
description: component.description ?? "",
|
|
51
|
+
mimeType: "application/json",
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
const blockResources = registry.blocks.map((block) => ({
|
|
55
|
+
uri: `volt-ui://blocks/${encodeURIComponent(block.name)}`,
|
|
56
|
+
name: block.title,
|
|
57
|
+
description: block.name,
|
|
58
|
+
mimeType: "application/json",
|
|
59
|
+
}))
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
resources: [...baseResources, ...componentResources, ...blockResources],
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
67
|
+
const uri = request.params.uri
|
|
68
|
+
const response = resolveResource(uri)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
contents: [
|
|
72
|
+
{
|
|
73
|
+
uri,
|
|
74
|
+
mimeType: "application/json",
|
|
75
|
+
text: JSON.stringify(response, null, 2),
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
82
|
+
return {
|
|
83
|
+
tools: [
|
|
84
|
+
{
|
|
85
|
+
name: "list_components",
|
|
86
|
+
description: "List all available Volt UI components.",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
query: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Optional text filter for component name or description.",
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
additionalProperties: false,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "get_component",
|
|
100
|
+
description: "Get detailed information about a Volt UI component.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
name: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Component name (e.g. Button, DialogContent).",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
required: ["name"],
|
|
110
|
+
additionalProperties: false,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "search_components",
|
|
115
|
+
description: "Search components by name, description, or prop names.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
query: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Search term.",
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
required: ["query"],
|
|
125
|
+
additionalProperties: false,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "list_blocks",
|
|
130
|
+
description: "List available Volt UI blocks/examples.",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {},
|
|
134
|
+
additionalProperties: false,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "get_block",
|
|
139
|
+
description: "Get a specific Volt UI block/example.",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
name: {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "Block name (e.g. card-example).",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
required: ["name"],
|
|
149
|
+
additionalProperties: false,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
157
|
+
const { name, arguments: args } = request.params
|
|
158
|
+
|
|
159
|
+
switch (name) {
|
|
160
|
+
case "list_components": {
|
|
161
|
+
const query = typeof args?.query === "string" ? args.query : ""
|
|
162
|
+
return toolResult(listComponents(query))
|
|
163
|
+
}
|
|
164
|
+
case "get_component": {
|
|
165
|
+
const componentName = typeof args?.name === "string" ? args.name : ""
|
|
166
|
+
return toolResult(getComponent(componentName))
|
|
167
|
+
}
|
|
168
|
+
case "search_components": {
|
|
169
|
+
const query = typeof args?.query === "string" ? args.query : ""
|
|
170
|
+
return toolResult(searchComponents(query))
|
|
171
|
+
}
|
|
172
|
+
case "list_blocks": {
|
|
173
|
+
return toolResult(listBlocks())
|
|
174
|
+
}
|
|
175
|
+
case "get_block": {
|
|
176
|
+
const blockName = typeof args?.name === "string" ? args.name : ""
|
|
177
|
+
return toolResult(getBlock(blockName))
|
|
178
|
+
}
|
|
179
|
+
default:
|
|
180
|
+
return toolResult({ error: `Unknown tool: ${name}` })
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
const transport = new StdioServerTransport()
|
|
185
|
+
await server.connect(transport)
|
|
186
|
+
|
|
187
|
+
function loadRegistry(filePath) {
|
|
188
|
+
try {
|
|
189
|
+
const raw = fs.readFileSync(filePath, "utf8")
|
|
190
|
+
return JSON.parse(raw)
|
|
191
|
+
} catch (error) {
|
|
192
|
+
return {
|
|
193
|
+
schemaVersion: 1,
|
|
194
|
+
components: [],
|
|
195
|
+
blocks: [],
|
|
196
|
+
error: String(error),
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function resolveResource(uri) {
|
|
202
|
+
if (uri === "volt-ui://components") {
|
|
203
|
+
return listComponents("")
|
|
204
|
+
}
|
|
205
|
+
if (uri === "volt-ui://blocks") {
|
|
206
|
+
return listBlocks()
|
|
207
|
+
}
|
|
208
|
+
if (uri.startsWith("volt-ui://components/")) {
|
|
209
|
+
const name = decodeURIComponent(uri.replace("volt-ui://components/", ""))
|
|
210
|
+
return getComponent(name)
|
|
211
|
+
}
|
|
212
|
+
if (uri.startsWith("volt-ui://blocks/")) {
|
|
213
|
+
const name = decodeURIComponent(uri.replace("volt-ui://blocks/", ""))
|
|
214
|
+
return getBlock(name)
|
|
215
|
+
}
|
|
216
|
+
return { error: `Unknown resource: ${uri}` }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function listComponents(query) {
|
|
220
|
+
const q = query.trim().toLowerCase()
|
|
221
|
+
const components = registry.components.filter((component) => {
|
|
222
|
+
if (!q) {
|
|
223
|
+
return true
|
|
224
|
+
}
|
|
225
|
+
return (
|
|
226
|
+
component.name.toLowerCase().includes(q) ||
|
|
227
|
+
(component.description ?? "").toLowerCase().includes(q)
|
|
228
|
+
)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
count: components.length,
|
|
233
|
+
components: components.map((component) => ({
|
|
234
|
+
name: component.name,
|
|
235
|
+
title: component.title,
|
|
236
|
+
description: component.description,
|
|
237
|
+
docsPath: component.docsPath,
|
|
238
|
+
docSlug: component.docSlug,
|
|
239
|
+
documentationUrl: component.documentationUrl,
|
|
240
|
+
apiReferenceUrl: component.apiReferenceUrl,
|
|
241
|
+
sourcePaths: component.sourcePaths,
|
|
242
|
+
})),
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getComponent(name) {
|
|
247
|
+
const component = findComponent(name)
|
|
248
|
+
if (!component) {
|
|
249
|
+
return { error: `Component not found: ${name}` }
|
|
250
|
+
}
|
|
251
|
+
return component
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function searchComponents(query) {
|
|
255
|
+
const q = query.trim().toLowerCase()
|
|
256
|
+
if (!q) {
|
|
257
|
+
return { count: 0, components: [] }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const components = registry.components.filter((component) => {
|
|
261
|
+
const haystack = [
|
|
262
|
+
component.name,
|
|
263
|
+
component.title,
|
|
264
|
+
component.description,
|
|
265
|
+
(component.props || []).map((prop) => prop.name).join(" "),
|
|
266
|
+
]
|
|
267
|
+
.filter(Boolean)
|
|
268
|
+
.join(" ")
|
|
269
|
+
.toLowerCase()
|
|
270
|
+
|
|
271
|
+
return haystack.includes(q)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
count: components.length,
|
|
276
|
+
components,
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function listBlocks() {
|
|
281
|
+
return {
|
|
282
|
+
count: registry.blocks.length,
|
|
283
|
+
blocks: registry.blocks.map((block) => ({
|
|
284
|
+
name: block.name,
|
|
285
|
+
title: block.title,
|
|
286
|
+
sourcePath: block.sourcePath,
|
|
287
|
+
})),
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function getBlock(name) {
|
|
292
|
+
const block = registry.blocks.find(
|
|
293
|
+
(entry) => entry.name.toLowerCase() === name.toLowerCase()
|
|
294
|
+
)
|
|
295
|
+
if (!block) {
|
|
296
|
+
return { error: `Block not found: ${name}` }
|
|
297
|
+
}
|
|
298
|
+
return block
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function findComponent(name) {
|
|
302
|
+
return registry.components.find(
|
|
303
|
+
(component) => component.name.toLowerCase() === name.toLowerCase()
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function toolResult(payload) {
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: JSON.stringify(payload, null, 2),
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
}
|
|
316
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@epilot/volt-ui-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"volt-ui-mcp": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.js",
|
|
11
|
+
"registry.json"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"prepack": "bun ../../scripts/build-mcp-registry.ts"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
18
|
+
}
|
|
19
|
+
}
|