@lumeo-ui/mcp-server 3.4.0 → 3.5.1
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/dist/index.js +211 -5
- package/package.json +1 -1
- package/src/components-api.json +4013 -19
- package/src/registry.json +202 -16
package/dist/index.js
CHANGED
|
@@ -40,6 +40,26 @@ const api = loadComponentsApi() ?? {
|
|
|
40
40
|
};
|
|
41
41
|
const components = Object.values(api.components).sort((a, b) => a.name.localeCompare(b.name));
|
|
42
42
|
const byName = new Map(components.map((c) => [c.name.toLowerCase(), c]));
|
|
43
|
+
// Sub-component → parent map. Sub-components (SheetContent, SelectTrigger,
|
|
44
|
+
// CardHeader, DialogContent, TabsTrigger, AccordionItem, …) are real,
|
|
45
|
+
// usable elements but only exist nested under their parent in the schema.
|
|
46
|
+
// Without this, an agent looking one up by name got "not found" even
|
|
47
|
+
// though the element exists. Resolving to the parent surfaces the full
|
|
48
|
+
// composition (the parent payload lists all its sub-components).
|
|
49
|
+
const bySubName = new Map();
|
|
50
|
+
for (const c of components) {
|
|
51
|
+
for (const s of Object.values(c.subComponents)) {
|
|
52
|
+
const key = s.componentName.toLowerCase();
|
|
53
|
+
if (!byName.has(key) && !bySubName.has(key))
|
|
54
|
+
bySubName.set(key, c);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Service-layer public API (OverlayService, ThemeBuilder, IResponsiveService,
|
|
58
|
+
// global enums, …). These live in plain C#, not .razor, so they were
|
|
59
|
+
// previously invisible to agents. Indexed here so they're discoverable the
|
|
60
|
+
// same way components are.
|
|
61
|
+
const services = (api.services ?? []).slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
62
|
+
const serviceByName = new Map(services.map((s) => [s.name.toLowerCase(), s]));
|
|
43
63
|
const CATEGORIES = Array.from(new Set(components.map((c) => c.category))).sort();
|
|
44
64
|
const themeTokens = api.themeTokens ?? [];
|
|
45
65
|
const patterns = api.patterns ?? [];
|
|
@@ -50,7 +70,9 @@ const patternByKey = new Map(patterns.map((p) => [p.title.toLowerCase().replace(
|
|
|
50
70
|
const curatedExampleByName = new Map(curatedExamples.map((c) => [c.name.toLowerCase(), c.example]));
|
|
51
71
|
// ───────────────── Helpers ─────────────────
|
|
52
72
|
function findComponent(name) {
|
|
53
|
-
|
|
73
|
+
// Top-level match first; fall back to the parent of a matching sub-component
|
|
74
|
+
// so e.g. "SheetContent" / "CardHeader" / "SelectTrigger" resolve.
|
|
75
|
+
return byName.get(name.toLowerCase()) ?? bySubName.get(name.toLowerCase());
|
|
54
76
|
}
|
|
55
77
|
function score(c, q) {
|
|
56
78
|
const needle = q.toLowerCase();
|
|
@@ -67,6 +89,19 @@ function score(c, q) {
|
|
|
67
89
|
s += 10;
|
|
68
90
|
if (c.description.toLowerCase().includes(needle))
|
|
69
91
|
s += 5;
|
|
92
|
+
// Sub-component name matches surface the parent — so searching a nested
|
|
93
|
+
// element ("SheetContent", "TabsTrigger") finds the component that owns it.
|
|
94
|
+
for (const sub of Object.values(c.subComponents)) {
|
|
95
|
+
const sn = sub.componentName.toLowerCase();
|
|
96
|
+
if (sn === needle) {
|
|
97
|
+
s += 80;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (sn.includes(needle)) {
|
|
101
|
+
s += 20;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
70
105
|
return s;
|
|
71
106
|
}
|
|
72
107
|
function searchCatalog(query, category) {
|
|
@@ -132,6 +167,16 @@ function toComponentMarkdown(c) {
|
|
|
132
167
|
sections.push("## Enums", "", enumRows, "");
|
|
133
168
|
if (c.events.length)
|
|
134
169
|
sections.push("## Events", "", eventRows, "");
|
|
170
|
+
// Aggregate gotchas from the root component AND its sub-components — a
|
|
171
|
+
// gotcha is often declared on a sub-component (e.g. SheetContent carries
|
|
172
|
+
// the Sheet gotcha), so the root resource must surface those too.
|
|
173
|
+
const gotchaLines = [
|
|
174
|
+
...(c.gotchas ?? []).map((g) => `- ${g}`),
|
|
175
|
+
...Object.values(c.subComponents).flatMap((s) => (s.gotchas ?? []).map((g) => `- **${s.componentName}**: ${g}`)),
|
|
176
|
+
];
|
|
177
|
+
if (gotchaLines.length) {
|
|
178
|
+
sections.push("## Gotchas", "", gotchaLines.join("\n"), "");
|
|
179
|
+
}
|
|
135
180
|
if (Object.keys(c.subComponents).length)
|
|
136
181
|
sections.push("## Sub-components", "", subRows, "");
|
|
137
182
|
if (example)
|
|
@@ -159,6 +204,86 @@ function toCategoryMarkdown(category) {
|
|
|
159
204
|
"",
|
|
160
205
|
].join("\n");
|
|
161
206
|
}
|
|
207
|
+
// ───────────────── Services ─────────────────
|
|
208
|
+
function findService(name) {
|
|
209
|
+
return serviceByName.get(name.toLowerCase());
|
|
210
|
+
}
|
|
211
|
+
function toServiceMarkdown(s) {
|
|
212
|
+
const propRows = s.properties
|
|
213
|
+
.map((p) => `| \`${p.name}\` | \`${p.type}\` | \`${p.default ?? "—"}\` | ${p.summary ?? ""} |`)
|
|
214
|
+
.join("\n");
|
|
215
|
+
const methodRows = s.methods
|
|
216
|
+
.map((m) => `- \`${m.signature}\` → \`${m.returnType}\`${m.summary ? ` — ${m.summary}` : ""}`)
|
|
217
|
+
.join("\n");
|
|
218
|
+
const enumRows = s.enumValues
|
|
219
|
+
.map((e) => `- **${e.name}**${e.summary ? ` — ${e.summary}` : ""}`)
|
|
220
|
+
.join("\n");
|
|
221
|
+
const eventRows = s.events
|
|
222
|
+
.map((e) => `- **${e.name}** \`${e.type}\`${e.summary ? ` — ${e.summary}` : ""}`)
|
|
223
|
+
.join("\n");
|
|
224
|
+
const sections = [
|
|
225
|
+
`# ${s.name}`,
|
|
226
|
+
"",
|
|
227
|
+
`**Kind:** ${s.kind}`,
|
|
228
|
+
`**Namespace:** \`${s.namespace ?? "Lumeo"}\``,
|
|
229
|
+
"",
|
|
230
|
+
s.summary ?? "_(no summary)_",
|
|
231
|
+
"",
|
|
232
|
+
];
|
|
233
|
+
if (s.enumValues.length)
|
|
234
|
+
sections.push("## Values", "", enumRows, "");
|
|
235
|
+
if (s.properties.length) {
|
|
236
|
+
sections.push("## Properties", "", "| Name | Type | Default | Summary |", "|---|---|---|---|", propRows, "");
|
|
237
|
+
}
|
|
238
|
+
if (s.methods.length)
|
|
239
|
+
sections.push("## Methods", "", methodRows, "");
|
|
240
|
+
if (s.events.length)
|
|
241
|
+
sections.push("## Events", "", eventRows, "");
|
|
242
|
+
return sections.join("\n");
|
|
243
|
+
}
|
|
244
|
+
function toServiceListPayload(s) {
|
|
245
|
+
return { name: s.name, kind: s.kind, summary: s.summary };
|
|
246
|
+
}
|
|
247
|
+
function searchServices(query) {
|
|
248
|
+
const needle = query.toLowerCase();
|
|
249
|
+
if (!needle)
|
|
250
|
+
return services;
|
|
251
|
+
return services
|
|
252
|
+
.map((s) => {
|
|
253
|
+
let score = 0;
|
|
254
|
+
if (s.name.toLowerCase() === needle)
|
|
255
|
+
score += 100;
|
|
256
|
+
if (s.name.toLowerCase().includes(needle))
|
|
257
|
+
score += 30;
|
|
258
|
+
if ((s.summary ?? "").toLowerCase().includes(needle))
|
|
259
|
+
score += 5;
|
|
260
|
+
if (s.enumValues.some((e) => e.name.toLowerCase() === needle))
|
|
261
|
+
score += 20;
|
|
262
|
+
if (s.properties.some((p) => p.name.toLowerCase() === needle))
|
|
263
|
+
score += 10;
|
|
264
|
+
if (s.methods.some((m) => m.name.toLowerCase() === needle))
|
|
265
|
+
score += 10;
|
|
266
|
+
// Events too — so an event-only query (ViewportChanged, OnThemeChanged,
|
|
267
|
+
// OnShow, OnClose) surfaces the owning service/interface.
|
|
268
|
+
if (s.events.some((e) => e.name.toLowerCase() === needle))
|
|
269
|
+
score += 10;
|
|
270
|
+
// Substring (fuzzy) member matching so partial queries discover the
|
|
271
|
+
// service: "scrollable" → OverlayOptions.ScrollableBody, "showdialog" →
|
|
272
|
+
// OverlayService.ShowDialogAsync, "mobile" → MobileFullscreen, etc.
|
|
273
|
+
if (s.properties.some((p) => p.name.toLowerCase().includes(needle)))
|
|
274
|
+
score += 6;
|
|
275
|
+
if (s.methods.some((m) => m.name.toLowerCase().includes(needle)))
|
|
276
|
+
score += 6;
|
|
277
|
+
if (s.events.some((e) => e.name.toLowerCase().includes(needle)))
|
|
278
|
+
score += 6;
|
|
279
|
+
if (s.enumValues.some((e) => e.name.toLowerCase().includes(needle)))
|
|
280
|
+
score += 6;
|
|
281
|
+
return { s, score };
|
|
282
|
+
})
|
|
283
|
+
.filter((x) => x.score > 0)
|
|
284
|
+
.sort((a, b) => b.score - a.score)
|
|
285
|
+
.map((x) => x.s);
|
|
286
|
+
}
|
|
162
287
|
function toListPayload(c) {
|
|
163
288
|
return {
|
|
164
289
|
name: c.name,
|
|
@@ -180,6 +305,7 @@ function toGetPayload(c) {
|
|
|
180
305
|
events: s.events,
|
|
181
306
|
enums: s.enums,
|
|
182
307
|
records: s.records,
|
|
308
|
+
gotchas: s.gotchas ?? [],
|
|
183
309
|
}));
|
|
184
310
|
return {
|
|
185
311
|
name: c.name,
|
|
@@ -195,6 +321,7 @@ function toGetPayload(c) {
|
|
|
195
321
|
enums: c.enums,
|
|
196
322
|
records: c.records,
|
|
197
323
|
cssVars: c.cssVars,
|
|
324
|
+
gotchas: c.gotchas ?? [],
|
|
198
325
|
files: c.files,
|
|
199
326
|
subComponents,
|
|
200
327
|
examples: c.examples ?? [],
|
|
@@ -377,9 +504,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
377
504
|
},
|
|
378
505
|
},
|
|
379
506
|
},
|
|
507
|
+
{
|
|
508
|
+
name: "lumeo_list_services",
|
|
509
|
+
description: `List all ${services.length} Lumeo SERVICE-LAYER public API types — services (OverlayService, ` +
|
|
510
|
+
"ThemeService, ComponentInteropService), options records (OverlayOptions, AlertDialogOptions), " +
|
|
511
|
+
"interfaces (IResponsiveService, IThemeService), the fluent ThemeBuilder, and global enums " +
|
|
512
|
+
"(Size, Density, Side, Align, Orientation, Breakpoint, ThemeMode, …). These live in plain C#, " +
|
|
513
|
+
"not Razor. Returns { name, kind, summary } per type. Use lumeo_get_service for the full API.",
|
|
514
|
+
inputSchema: { type: "object", properties: {} },
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: "lumeo_get_service",
|
|
518
|
+
description: "Get the COMPLETE public API for a Lumeo service-layer type: its summary, public properties " +
|
|
519
|
+
"(name, type, default, XML doc), public methods (signature, return type, doc), and enum values. " +
|
|
520
|
+
"Covers OverlayService (ShowDialogAsync/ShowSheetAsync/ShowAlertDialogAsync), OverlayOptions " +
|
|
521
|
+
"(ScrollableBody, MobileFullscreen, …), ThemeBuilder, IResponsiveService, AlertDialogOptions, and " +
|
|
522
|
+
"the global enums. Sourced from the actual C# source via Roslyn.",
|
|
523
|
+
inputSchema: {
|
|
524
|
+
type: "object",
|
|
525
|
+
required: ["name"],
|
|
526
|
+
properties: {
|
|
527
|
+
name: {
|
|
528
|
+
type: "string",
|
|
529
|
+
description: "Service/type name (e.g. \"OverlayService\", \"OverlayOptions\", \"ThemeBuilder\", \"Size\"). Case-insensitive.",
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
},
|
|
380
534
|
{
|
|
381
535
|
name: "lumeo_search",
|
|
382
|
-
description: `Fuzzy search across all ${components.length} Lumeo components (name, category, description)
|
|
536
|
+
description: `Fuzzy search across all ${components.length} Lumeo components (name, category, description) ` +
|
|
537
|
+
`and ${services.length} service-layer types. Best matches first.`,
|
|
383
538
|
inputSchema: {
|
|
384
539
|
type: "object",
|
|
385
540
|
required: ["query"],
|
|
@@ -486,10 +641,41 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
486
641
|
}
|
|
487
642
|
return { content: [{ type: "text", text: JSON.stringify(toGetPayload(c), null, 2) }] };
|
|
488
643
|
}
|
|
644
|
+
case "lumeo_list_services": {
|
|
645
|
+
const results = services.map(toServiceListPayload);
|
|
646
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
647
|
+
}
|
|
648
|
+
case "lumeo_get_service": {
|
|
649
|
+
const wanted = typeof a.name === "string" ? a.name : "";
|
|
650
|
+
const s = findService(wanted);
|
|
651
|
+
if (!s) {
|
|
652
|
+
return {
|
|
653
|
+
isError: true,
|
|
654
|
+
content: [{
|
|
655
|
+
type: "text",
|
|
656
|
+
text: `Service "${wanted}" not found. Use lumeo_list_services to discover available service-layer types.`,
|
|
657
|
+
}],
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
return { content: [{ type: "text", text: JSON.stringify(s, null, 2) }] };
|
|
661
|
+
}
|
|
489
662
|
case "lumeo_search": {
|
|
490
663
|
const query = typeof a.query === "string" ? a.query : "";
|
|
491
|
-
|
|
492
|
-
|
|
664
|
+
// Flat array (the original lumeo_search shape) so existing clients that
|
|
665
|
+
// iterate the result list keep working. Component matches come first;
|
|
666
|
+
// service matches are appended. Each item carries a `resultType`
|
|
667
|
+
// discriminator ("component" | "service") for clients that want to
|
|
668
|
+
// distinguish — a harmless extra field for those that don't.
|
|
669
|
+
const results = [
|
|
670
|
+
...searchCatalog(query).map((c) => ({ resultType: "component", ...toListPayload(c) })),
|
|
671
|
+
...searchServices(query).map((s) => ({ resultType: "service", ...toServiceListPayload(s) })),
|
|
672
|
+
];
|
|
673
|
+
return {
|
|
674
|
+
content: [{
|
|
675
|
+
type: "text",
|
|
676
|
+
text: JSON.stringify(results, null, 2),
|
|
677
|
+
}],
|
|
678
|
+
};
|
|
493
679
|
}
|
|
494
680
|
case "lumeo_get_example": {
|
|
495
681
|
const wanted = typeof a.name === "string" ? a.name : "";
|
|
@@ -580,6 +766,12 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
|
580
766
|
description: `Overview of all Lumeo components in the ${cat} category.`,
|
|
581
767
|
mimeType: "text/markdown",
|
|
582
768
|
})),
|
|
769
|
+
...services.map((s) => ({
|
|
770
|
+
uri: `lumeo://service/${s.name}`,
|
|
771
|
+
name: `${s.name} (Lumeo ${s.kind})`,
|
|
772
|
+
description: s.summary ?? `Lumeo service-layer ${s.kind}.`,
|
|
773
|
+
mimeType: "text/markdown",
|
|
774
|
+
})),
|
|
583
775
|
],
|
|
584
776
|
}));
|
|
585
777
|
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
@@ -596,6 +788,12 @@ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
|
596
788
|
description: "Markdown overview of all components in a Lumeo category.",
|
|
597
789
|
mimeType: "text/markdown",
|
|
598
790
|
},
|
|
791
|
+
{
|
|
792
|
+
uriTemplate: "lumeo://service/{name}",
|
|
793
|
+
name: "Lumeo service-layer reference",
|
|
794
|
+
description: "Markdown reference for a single Lumeo service-layer type, generated from C# source.",
|
|
795
|
+
mimeType: "text/markdown",
|
|
796
|
+
},
|
|
599
797
|
],
|
|
600
798
|
}));
|
|
601
799
|
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
@@ -613,13 +811,21 @@ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
|
613
811
|
const cat = decodeURIComponent(categoryMatch[1]);
|
|
614
812
|
return { contents: [{ uri, mimeType: "text/markdown", text: toCategoryMarkdown(cat) }] };
|
|
615
813
|
}
|
|
814
|
+
const serviceMatch = /^lumeo:\/\/service\/(.+)$/i.exec(uri);
|
|
815
|
+
if (serviceMatch) {
|
|
816
|
+
const wanted = decodeURIComponent(serviceMatch[1]);
|
|
817
|
+
const s = findService(wanted);
|
|
818
|
+
if (!s)
|
|
819
|
+
throw new Error(`Unknown Lumeo service: ${wanted}`);
|
|
820
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: toServiceMarkdown(s) }] };
|
|
821
|
+
}
|
|
616
822
|
throw new Error(`Unsupported resource URI: ${uri}`);
|
|
617
823
|
});
|
|
618
824
|
// ───── Start ─────
|
|
619
825
|
async function main() {
|
|
620
826
|
const transport = new StdioServerTransport();
|
|
621
827
|
await server.connect(transport);
|
|
622
|
-
process.stderr.write(`[lumeo-mcp] ready — ${components.length} components, ${CATEGORIES.length} categories, ` +
|
|
828
|
+
process.stderr.write(`[lumeo-mcp] ready — ${components.length} components, ${services.length} services, ${CATEGORIES.length} categories, ` +
|
|
623
829
|
`${api.stats.totalParameters} params, ${api.stats.totalEnums} enums, ` +
|
|
624
830
|
`${themeTokens.length} theme tokens, ${patterns.length} patterns, ` +
|
|
625
831
|
`api v${api.version}, generated ${api.generated}\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumeo-ui/mcp-server",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"description": "Model Context Protocol server for the Lumeo Blazor component library. Lets LLMs (Claude, Copilot, Cursor) author correct Lumeo markup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|