@epilot/volt-ui-mcp 0.1.4 → 0.3.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/index.js +13 -335
- package/lib.d.ts +40 -0
- package/lib.js +499 -0
- package/package.json +12 -2
- package/registry.json +10503 -9197
- package/tools.js +226 -0
package/index.js
CHANGED
|
@@ -11,16 +11,20 @@ import {
|
|
|
11
11
|
ReadResourceRequestSchema,
|
|
12
12
|
} from "@modelcontextprotocol/sdk/types.js"
|
|
13
13
|
|
|
14
|
+
import { createQueries, toolResult } from "./lib.js"
|
|
15
|
+
import { TOOL_SPECS, toJsonSchema, callTool, SERVER_VERSION } from "./tools.js"
|
|
16
|
+
|
|
14
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
15
18
|
const registryPath = path.join(__dirname, "registry.json")
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
// Fail loud on a missing/empty registry — never serve "0 components" silently.
|
|
21
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"))
|
|
22
|
+
const queries = createQueries(registry)
|
|
23
|
+
|
|
18
24
|
const server = new Server(
|
|
19
25
|
{
|
|
20
26
|
name: "volt-ui-mcp",
|
|
21
|
-
version:
|
|
22
|
-
? `schema-${registry.schemaVersion}`
|
|
23
|
-
: "0.1.0",
|
|
27
|
+
version: SERVER_VERSION,
|
|
24
28
|
},
|
|
25
29
|
{
|
|
26
30
|
capabilities: {
|
|
@@ -31,358 +35,32 @@ const server = new Server(
|
|
|
31
35
|
)
|
|
32
36
|
|
|
33
37
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
uri: "volt-ui://components",
|
|
37
|
-
name: "Volt UI Components",
|
|
38
|
-
description: "List of all available Volt UI components.",
|
|
39
|
-
mimeType: "application/json",
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
uri: "volt-ui://tokens",
|
|
43
|
-
name: "Volt UI Tokens",
|
|
44
|
-
description: "List of Volt UI design tokens.",
|
|
45
|
-
mimeType: "application/json",
|
|
46
|
-
},
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
const componentResources = registry.components.map((component) => ({
|
|
50
|
-
uri: `volt-ui://components/${encodeURIComponent(component.name)}`,
|
|
51
|
-
name: component.name,
|
|
52
|
-
description: component.description ?? "",
|
|
53
|
-
mimeType: "application/json",
|
|
54
|
-
}))
|
|
55
|
-
|
|
56
|
-
const tokenNames = Array.from(
|
|
57
|
-
new Set((registry.tokens || []).map((token) => token.name))
|
|
58
|
-
).sort((a, b) => a.localeCompare(b))
|
|
59
|
-
const tokenResources = tokenNames.map((name) => ({
|
|
60
|
-
uri: `volt-ui://tokens/${encodeURIComponent(name)}`,
|
|
61
|
-
name,
|
|
62
|
-
description: "Design token",
|
|
63
|
-
mimeType: "application/json",
|
|
64
|
-
}))
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
resources: [...baseResources, ...componentResources, ...tokenResources],
|
|
68
|
-
}
|
|
38
|
+
return { resources: queries.listResources() }
|
|
69
39
|
})
|
|
70
40
|
|
|
71
41
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
72
42
|
const uri = request.params.uri
|
|
73
|
-
const response = resolveResource(uri)
|
|
43
|
+
const response = queries.resolveResource(uri)
|
|
74
44
|
|
|
75
45
|
return {
|
|
76
46
|
contents: [
|
|
77
47
|
{
|
|
78
48
|
uri,
|
|
79
49
|
mimeType: "application/json",
|
|
80
|
-
text: JSON.stringify(response
|
|
50
|
+
text: JSON.stringify(response),
|
|
81
51
|
},
|
|
82
52
|
],
|
|
83
53
|
}
|
|
84
54
|
})
|
|
85
55
|
|
|
86
56
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
87
|
-
return {
|
|
88
|
-
tools: [
|
|
89
|
-
{
|
|
90
|
-
name: "list_components",
|
|
91
|
-
description: "List all available Volt UI components.",
|
|
92
|
-
inputSchema: {
|
|
93
|
-
type: "object",
|
|
94
|
-
properties: {
|
|
95
|
-
query: {
|
|
96
|
-
type: "string",
|
|
97
|
-
description:
|
|
98
|
-
"Optional text filter for component name or description.",
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
additionalProperties: false,
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: "get_component",
|
|
106
|
-
description: "Get detailed information about a Volt UI component.",
|
|
107
|
-
inputSchema: {
|
|
108
|
-
type: "object",
|
|
109
|
-
properties: {
|
|
110
|
-
name: {
|
|
111
|
-
type: "string",
|
|
112
|
-
description: "Component name (e.g. Button, DialogContent).",
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
required: ["name"],
|
|
116
|
-
additionalProperties: false,
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
name: "search_components",
|
|
121
|
-
description: "Search components by name, description, or prop names.",
|
|
122
|
-
inputSchema: {
|
|
123
|
-
type: "object",
|
|
124
|
-
properties: {
|
|
125
|
-
query: {
|
|
126
|
-
type: "string",
|
|
127
|
-
description: "Search term.",
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
required: ["query"],
|
|
131
|
-
additionalProperties: false,
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
name: "list_tokens",
|
|
136
|
-
description: "List Volt UI design tokens.",
|
|
137
|
-
inputSchema: {
|
|
138
|
-
type: "object",
|
|
139
|
-
properties: {
|
|
140
|
-
query: {
|
|
141
|
-
type: "string",
|
|
142
|
-
description: "Optional text filter for token name or value.",
|
|
143
|
-
},
|
|
144
|
-
theme: {
|
|
145
|
-
type: "string",
|
|
146
|
-
description: "Optional theme filter (light, dark, global).",
|
|
147
|
-
},
|
|
148
|
-
group: {
|
|
149
|
-
type: "string",
|
|
150
|
-
description:
|
|
151
|
-
"Optional group filter (palette, semantic, utility).",
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
additionalProperties: false,
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: "get_token",
|
|
159
|
-
description: "Get details for a specific Volt UI token.",
|
|
160
|
-
inputSchema: {
|
|
161
|
-
type: "object",
|
|
162
|
-
properties: {
|
|
163
|
-
name: {
|
|
164
|
-
type: "string",
|
|
165
|
-
description: "Token name (e.g. --volt-blue-9).",
|
|
166
|
-
},
|
|
167
|
-
},
|
|
168
|
-
required: ["name"],
|
|
169
|
-
additionalProperties: false,
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: "search_tokens",
|
|
174
|
-
description: "Search tokens by name or value.",
|
|
175
|
-
inputSchema: {
|
|
176
|
-
type: "object",
|
|
177
|
-
properties: {
|
|
178
|
-
query: {
|
|
179
|
-
type: "string",
|
|
180
|
-
description: "Search term.",
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
required: ["query"],
|
|
184
|
-
additionalProperties: false,
|
|
185
|
-
},
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
}
|
|
57
|
+
return { tools: TOOL_SPECS.map(toJsonSchema) }
|
|
189
58
|
})
|
|
190
59
|
|
|
191
60
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
192
61
|
const { name, arguments: args } = request.params
|
|
193
|
-
|
|
194
|
-
switch (name) {
|
|
195
|
-
case "list_components": {
|
|
196
|
-
const query = typeof args?.query === "string" ? args.query : ""
|
|
197
|
-
return toolResult(listComponents(query))
|
|
198
|
-
}
|
|
199
|
-
case "get_component": {
|
|
200
|
-
const componentName = typeof args?.name === "string" ? args.name : ""
|
|
201
|
-
return toolResult(getComponent(componentName))
|
|
202
|
-
}
|
|
203
|
-
case "search_components": {
|
|
204
|
-
const query = typeof args?.query === "string" ? args.query : ""
|
|
205
|
-
return toolResult(searchComponents(query))
|
|
206
|
-
}
|
|
207
|
-
case "list_tokens": {
|
|
208
|
-
const query = typeof args?.query === "string" ? args.query : ""
|
|
209
|
-
const theme = typeof args?.theme === "string" ? args.theme : ""
|
|
210
|
-
const group = typeof args?.group === "string" ? args.group : ""
|
|
211
|
-
return toolResult(listTokens({ query, theme, group }))
|
|
212
|
-
}
|
|
213
|
-
case "get_token": {
|
|
214
|
-
const tokenName = typeof args?.name === "string" ? args.name : ""
|
|
215
|
-
return toolResult(getToken(tokenName))
|
|
216
|
-
}
|
|
217
|
-
case "search_tokens": {
|
|
218
|
-
const query = typeof args?.query === "string" ? args.query : ""
|
|
219
|
-
return toolResult(searchTokens(query))
|
|
220
|
-
}
|
|
221
|
-
default:
|
|
222
|
-
return toolResult({ error: `Unknown tool: ${name}` })
|
|
223
|
-
}
|
|
62
|
+
return toolResult(callTool(queries, name, args))
|
|
224
63
|
})
|
|
225
64
|
|
|
226
65
|
const transport = new StdioServerTransport()
|
|
227
66
|
await server.connect(transport)
|
|
228
|
-
|
|
229
|
-
function loadRegistry(filePath) {
|
|
230
|
-
try {
|
|
231
|
-
const raw = fs.readFileSync(filePath, "utf8")
|
|
232
|
-
return JSON.parse(raw)
|
|
233
|
-
} catch (error) {
|
|
234
|
-
return {
|
|
235
|
-
schemaVersion: 1,
|
|
236
|
-
components: [],
|
|
237
|
-
tokens: [],
|
|
238
|
-
error: String(error),
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function resolveResource(uri) {
|
|
244
|
-
if (uri === "volt-ui://components") {
|
|
245
|
-
return listComponents("")
|
|
246
|
-
}
|
|
247
|
-
if (uri === "volt-ui://tokens") {
|
|
248
|
-
return listTokens({})
|
|
249
|
-
}
|
|
250
|
-
if (uri.startsWith("volt-ui://components/")) {
|
|
251
|
-
const name = decodeURIComponent(uri.replace("volt-ui://components/", ""))
|
|
252
|
-
return getComponent(name)
|
|
253
|
-
}
|
|
254
|
-
if (uri.startsWith("volt-ui://tokens/")) {
|
|
255
|
-
const name = decodeURIComponent(uri.replace("volt-ui://tokens/", ""))
|
|
256
|
-
return getToken(name)
|
|
257
|
-
}
|
|
258
|
-
return { error: `Unknown resource: ${uri}` }
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function listComponents(query) {
|
|
262
|
-
const q = query.trim().toLowerCase()
|
|
263
|
-
const components = registry.components.filter((component) => {
|
|
264
|
-
if (!q) {
|
|
265
|
-
return true
|
|
266
|
-
}
|
|
267
|
-
return (
|
|
268
|
-
component.name.toLowerCase().includes(q) ||
|
|
269
|
-
(component.description ?? "").toLowerCase().includes(q)
|
|
270
|
-
)
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
count: components.length,
|
|
275
|
-
components: components.map((component) => ({
|
|
276
|
-
name: component.name,
|
|
277
|
-
title: component.title,
|
|
278
|
-
description: component.description,
|
|
279
|
-
docsPath: component.docsPath,
|
|
280
|
-
docSlug: component.docSlug,
|
|
281
|
-
documentationUrl: component.documentationUrl,
|
|
282
|
-
apiReferenceUrl: component.apiReferenceUrl,
|
|
283
|
-
sourcePaths: component.sourcePaths,
|
|
284
|
-
})),
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function getComponent(name) {
|
|
289
|
-
const component = findComponent(name)
|
|
290
|
-
if (!component) {
|
|
291
|
-
return { error: `Component not found: ${name}` }
|
|
292
|
-
}
|
|
293
|
-
return component
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function searchComponents(query) {
|
|
297
|
-
const q = query.trim().toLowerCase()
|
|
298
|
-
if (!q) {
|
|
299
|
-
return { count: 0, components: [] }
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const components = registry.components.filter((component) => {
|
|
303
|
-
const haystack = [
|
|
304
|
-
component.name,
|
|
305
|
-
component.title,
|
|
306
|
-
component.description,
|
|
307
|
-
(component.props || []).map((prop) => prop.name).join(" "),
|
|
308
|
-
]
|
|
309
|
-
.filter(Boolean)
|
|
310
|
-
.join(" ")
|
|
311
|
-
.toLowerCase()
|
|
312
|
-
|
|
313
|
-
return haystack.includes(q)
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
-
count: components.length,
|
|
318
|
-
components,
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function listTokens({ query = "", theme = "", group = "" }) {
|
|
323
|
-
const q = query.trim().toLowerCase()
|
|
324
|
-
const t = theme.trim().toLowerCase()
|
|
325
|
-
const g = group.trim().toLowerCase()
|
|
326
|
-
const tokens = (registry.tokens || []).filter((token) => {
|
|
327
|
-
if (t && token.theme.toLowerCase() !== t) {
|
|
328
|
-
return false
|
|
329
|
-
}
|
|
330
|
-
if (g && token.group.toLowerCase() !== g) {
|
|
331
|
-
return false
|
|
332
|
-
}
|
|
333
|
-
if (!q) {
|
|
334
|
-
return true
|
|
335
|
-
}
|
|
336
|
-
return (
|
|
337
|
-
token.name.toLowerCase().includes(q) ||
|
|
338
|
-
token.value.toLowerCase().includes(q)
|
|
339
|
-
)
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
return {
|
|
343
|
-
count: tokens.length,
|
|
344
|
-
tokens,
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function getToken(name) {
|
|
349
|
-
const target = name.trim().toLowerCase()
|
|
350
|
-
if (!target) {
|
|
351
|
-
return { error: "Token name is required." }
|
|
352
|
-
}
|
|
353
|
-
const tokens = (registry.tokens || []).filter(
|
|
354
|
-
(token) => token.name.toLowerCase() === target
|
|
355
|
-
)
|
|
356
|
-
if (tokens.length === 0) {
|
|
357
|
-
return { error: `Token not found: ${name}` }
|
|
358
|
-
}
|
|
359
|
-
return {
|
|
360
|
-
name,
|
|
361
|
-
tokens,
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function searchTokens(query) {
|
|
366
|
-
const q = query.trim().toLowerCase()
|
|
367
|
-
if (!q) {
|
|
368
|
-
return { count: 0, tokens: [] }
|
|
369
|
-
}
|
|
370
|
-
return listTokens({ query: q })
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function findComponent(name) {
|
|
374
|
-
return registry.components.find(
|
|
375
|
-
(component) => component.name.toLowerCase() === name.toLowerCase()
|
|
376
|
-
)
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function toolResult(payload) {
|
|
380
|
-
return {
|
|
381
|
-
content: [
|
|
382
|
-
{
|
|
383
|
-
type: "text",
|
|
384
|
-
text: JSON.stringify(payload, null, 2),
|
|
385
|
-
},
|
|
386
|
-
],
|
|
387
|
-
}
|
|
388
|
-
}
|
package/lib.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const DEFAULT_TOKEN_LIMIT: number
|
|
2
|
+
export const MAX_TOKEN_LIMIT: number
|
|
3
|
+
|
|
4
|
+
export type ToolResult = {
|
|
5
|
+
content: Array<{ type: "text"; text: string }>
|
|
6
|
+
isError?: true
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function toolResult(payload: unknown): ToolResult
|
|
10
|
+
|
|
11
|
+
export type Queries = {
|
|
12
|
+
listComponents(query?: string): unknown
|
|
13
|
+
/** include: CSV of extra detail to add to the lean default — "examples", "tokens", or "all". */
|
|
14
|
+
getComponent(name: string, include?: string): unknown
|
|
15
|
+
searchComponents(query: string): unknown
|
|
16
|
+
listTokens(opts?: {
|
|
17
|
+
query?: string
|
|
18
|
+
theme?: string
|
|
19
|
+
group?: string
|
|
20
|
+
limit?: number
|
|
21
|
+
offset?: number
|
|
22
|
+
}): unknown
|
|
23
|
+
getToken(name: string): unknown
|
|
24
|
+
searchTokens(query: string): unknown
|
|
25
|
+
listGuidelines(query?: string): unknown
|
|
26
|
+
getGuideline(slug: string): unknown
|
|
27
|
+
resolveResource(uri: string): unknown
|
|
28
|
+
listResources(): Array<{
|
|
29
|
+
uri: string
|
|
30
|
+
name: string
|
|
31
|
+
description: string
|
|
32
|
+
mimeType: string
|
|
33
|
+
}>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Throws if the registry is missing or has no components (fail loud — an empty
|
|
38
|
+
* registry must never masquerade as a working server).
|
|
39
|
+
*/
|
|
40
|
+
export function createQueries(registry: unknown): Queries
|