@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.
Files changed (4) hide show
  1. package/README.md +24 -0
  2. package/index.js +316 -0
  3. package/package.json +19 -0
  4. 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
+ }