@lumeo-ui/mcp-server 2.0.0-rc.29 → 2.0.0-rc.30
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 +286 -2
- package/dist/installInfo.js +101 -0
- package/package.json +1 -1
- package/src/components-api.json +2350 -2
- package/src/registry.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,8 +9,14 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Tools:
|
|
11
11
|
* - lumeo_list_components — list/filter all 131 components (name+category+description)
|
|
12
|
-
* - lumeo_get_component — full schema (params, enums, events, sub-components, files)
|
|
12
|
+
* - lumeo_get_component — full schema (params, enums, events, sub-components, files, examples)
|
|
13
13
|
* - lumeo_search — fuzzy search across name/category/description
|
|
14
|
+
* - lumeo_get_example — working Razor snippet(s) for a component
|
|
15
|
+
* - lumeo_get_install — NuGet + @using + DI + host includes + gotchas
|
|
16
|
+
* - lumeo_validate_markup — pre-flight check Razor for hallucinated APIs / bad enums / bad nesting
|
|
17
|
+
* - lumeo_get_theme_tokens — the colour/radius CSS-variable tokens (the only legal colours)
|
|
18
|
+
* - lumeo_list_patterns / lumeo_get_pattern — full-page composed examples (dashboard, auth, …)
|
|
19
|
+
* - lumeo_changelog — current version + schema generation timestamp + changelog link
|
|
14
20
|
*
|
|
15
21
|
* Resources (URI template):
|
|
16
22
|
* - lumeo://component/{name} — markdown reference per component
|
|
@@ -23,6 +29,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
23
29
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
24
30
|
import { loadComponentsApi, } from "./componentsApi.js";
|
|
25
31
|
import { components as curatedExamples } from "./components.js";
|
|
32
|
+
import { setupFor, PORTAL_COMPONENTS, NEEDS_OVERLAY_PROVIDER } from "./installInfo.js";
|
|
26
33
|
const DOCS_BASE = "https://lumeo.nativ.sh";
|
|
27
34
|
// ───────────────── Load source-of-truth schema ─────────────────
|
|
28
35
|
const api = loadComponentsApi() ?? {
|
|
@@ -34,6 +41,9 @@ const api = loadComponentsApi() ?? {
|
|
|
34
41
|
const components = Object.values(api.components).sort((a, b) => a.name.localeCompare(b.name));
|
|
35
42
|
const byName = new Map(components.map((c) => [c.name.toLowerCase(), c]));
|
|
36
43
|
const CATEGORIES = Array.from(new Set(components.map((c) => c.category))).sort();
|
|
44
|
+
const themeTokens = api.themeTokens ?? [];
|
|
45
|
+
const patterns = api.patterns ?? [];
|
|
46
|
+
const patternByKey = new Map(patterns.map((p) => [p.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""), p]));
|
|
37
47
|
// Hand-curated examples overlay (~30 components). Auto-gen schema wins for
|
|
38
48
|
// parameters/enums/events; the curated `example` Razor snippet is preserved
|
|
39
49
|
// as a documentation aid because LLMs benefit from seeing real usage.
|
|
@@ -187,10 +197,141 @@ function toGetPayload(c) {
|
|
|
187
197
|
cssVars: c.cssVars,
|
|
188
198
|
files: c.files,
|
|
189
199
|
subComponents,
|
|
190
|
-
|
|
200
|
+
examples: c.examples ?? [],
|
|
201
|
+
curatedExample: curatedExampleByName.get(c.name.toLowerCase()) ?? null,
|
|
191
202
|
docs: docsUrl(c),
|
|
192
203
|
};
|
|
193
204
|
}
|
|
205
|
+
// ───────────────── lumeo_get_install ─────────────────
|
|
206
|
+
function buildInstallInfo(c) {
|
|
207
|
+
const setup = setupFor(c.nugetPackage);
|
|
208
|
+
const subNames = Object.values(c.subComponents).map((s) => s.componentName);
|
|
209
|
+
const isPortal = PORTAL_COMPONENTS.has(c.name);
|
|
210
|
+
const needsOverlayProvider = NEEDS_OVERLAY_PROVIDER.has(c.name);
|
|
211
|
+
const requiredParams = c.parameters
|
|
212
|
+
.filter((p) => /\bEditorRequired\b/i.test(p.description ?? "") || /required/i.test(p.description ?? ""))
|
|
213
|
+
.map((p) => p.name);
|
|
214
|
+
return {
|
|
215
|
+
component: c.name,
|
|
216
|
+
package: setup.package,
|
|
217
|
+
install: {
|
|
218
|
+
dotnet: setup.dotnetAdd,
|
|
219
|
+
registryCli: setup.lumeoAddNote,
|
|
220
|
+
},
|
|
221
|
+
imports: setup.usings,
|
|
222
|
+
di: setup.di,
|
|
223
|
+
hostIncludes: setup.hostIncludes,
|
|
224
|
+
namespace: c.namespace ?? "Lumeo",
|
|
225
|
+
subComponents: subNames,
|
|
226
|
+
requirements: [
|
|
227
|
+
isPortal
|
|
228
|
+
? "Portal component: the page <body> (or an ancestor of the overlay root) needs the theme classes `bg-background text-foreground`, or this renders outside the theme cascade and looks unstyled."
|
|
229
|
+
: null,
|
|
230
|
+
needsOverlayProvider
|
|
231
|
+
? "For the service-driven API (e.g. ToastService / OverlayService), add an <OverlayProvider /> once in your layout."
|
|
232
|
+
: null,
|
|
233
|
+
requiredParams.length
|
|
234
|
+
? `Required parameters: ${requiredParams.map((p) => `\`${p}\``).join(", ")}.`
|
|
235
|
+
: null,
|
|
236
|
+
/Overlay/i.test(JSON.stringify(c.implements)) || isPortal
|
|
237
|
+
? "Overlay/dismiss patterns are handled internally (RegisterClickOutside / LockScroll / focus trap) — you don't wire those yourself."
|
|
238
|
+
: null,
|
|
239
|
+
].filter(Boolean),
|
|
240
|
+
notes: setup.notes,
|
|
241
|
+
docs: docsUrl(c),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// Build a lookup of every component + sub-component → its parameter set,
|
|
245
|
+
// plus the parent each sub-component must live inside.
|
|
246
|
+
const elementIndex = (() => {
|
|
247
|
+
const m = new Map();
|
|
248
|
+
const enumValueSet = (vals) => new Set(vals.map((v) => v.toLowerCase()));
|
|
249
|
+
for (const c of components) {
|
|
250
|
+
const enums = new Map();
|
|
251
|
+
for (const e of c.enums)
|
|
252
|
+
enums.set(e.name, enumValueSet(e.values));
|
|
253
|
+
m.set(c.name.toLowerCase(), { params: new Set(c.parameters.map((p) => p.name)), enums });
|
|
254
|
+
for (const s of Object.values(c.subComponents)) {
|
|
255
|
+
const subEnums = new Map();
|
|
256
|
+
for (const e of s.enums)
|
|
257
|
+
subEnums.set(e.name, enumValueSet(e.values));
|
|
258
|
+
m.set(s.componentName.toLowerCase(), { params: new Set(s.parameters.map((p) => p.name)), enums: subEnums, parent: c.name });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return m;
|
|
262
|
+
})();
|
|
263
|
+
function validateMarkup(markup) {
|
|
264
|
+
const issues = [];
|
|
265
|
+
// Find component tags: <Foo ...> or <Foo .../> or </Foo>. Lumeo components are PascalCase.
|
|
266
|
+
const tagRx = /<\/?([A-Z][A-Za-z0-9]*)((?:\s+[^<>]*?)?)\/?>/g;
|
|
267
|
+
// Track which known Lumeo components appear, to validate parent-child.
|
|
268
|
+
const present = new Set();
|
|
269
|
+
let m;
|
|
270
|
+
while ((m = tagRx.exec(markup)) !== null) {
|
|
271
|
+
const tag = m[1];
|
|
272
|
+
const isClose = m[0].startsWith("</");
|
|
273
|
+
const known = elementIndex.get(tag.toLowerCase());
|
|
274
|
+
if (!known)
|
|
275
|
+
continue; // not a Lumeo element (could be a user component / HTML — ignore)
|
|
276
|
+
present.add(tag);
|
|
277
|
+
if (isClose)
|
|
278
|
+
continue;
|
|
279
|
+
// Parse attributes (best-effort): Name="..." | Name='...' | Name="@expr" | Name=@expr | bare-name
|
|
280
|
+
const attrBlob = m[2] ?? "";
|
|
281
|
+
const attrRx = /([@A-Za-z_][\w-]*)\s*=\s*("(?:[^"]*)"|'(?:[^']*)'|@?[^\s"'<>]+)|([@A-Za-z_][\w-]*)(?=\s|$)/g;
|
|
282
|
+
let am;
|
|
283
|
+
while ((am = attrRx.exec(attrBlob)) !== null) {
|
|
284
|
+
let name = (am[1] ?? am[3] ?? "").trim();
|
|
285
|
+
if (!name)
|
|
286
|
+
continue;
|
|
287
|
+
// Strip Blazor directive prefixes/suffixes: @bind-Foo, @bind-Foo:event, Foo:stopPropagation, @onclick, @attributes, @key, @ref, @bind
|
|
288
|
+
if (name.startsWith("@bind-"))
|
|
289
|
+
name = name.slice("@bind-".length).split(":")[0];
|
|
290
|
+
else if (name.startsWith("@bind"))
|
|
291
|
+
continue; // @bind / @bind:event on inputs — skip
|
|
292
|
+
else if (name.startsWith("@on") || name === "@attributes" || name === "@key" || name === "@ref" || name === "@oninput" || name === "@onchange")
|
|
293
|
+
continue;
|
|
294
|
+
else if (name.startsWith("@"))
|
|
295
|
+
continue; // other directives
|
|
296
|
+
if (name.includes(":"))
|
|
297
|
+
name = name.split(":")[0]; // EventName:stopPropagation, :preventDefault
|
|
298
|
+
if (name === "class" || name === "style" || name === "id")
|
|
299
|
+
continue; // pass-through HTML attrs (Lumeo captures unmatched)
|
|
300
|
+
if (/^data-|^aria-/i.test(name))
|
|
301
|
+
continue; // captured unmatched
|
|
302
|
+
if (!known.params.has(name)) {
|
|
303
|
+
// Could be a captured-unmatched HTML attr — only flag if it looks like a typo'd Lumeo param (PascalCase).
|
|
304
|
+
if (/^[A-Z]/.test(name)) {
|
|
305
|
+
issues.push({ severity: "warning", component: tag, message: `Unknown parameter \`${name}\` on <${tag}>. Did you mean one of: ${[...known.params].slice(0, 8).join(", ")}…? (Or it's a pass-through HTML attribute, which is allowed.)` });
|
|
306
|
+
}
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
// Enum value check: Foo="Bar.Baz.Qux" or Foo="Qux"
|
|
310
|
+
const rawVal = (am[2] ?? "").replace(/^["']|["']$/g, "").trim();
|
|
311
|
+
if (!rawVal || rawVal.startsWith("@"))
|
|
312
|
+
continue; // dynamic expression — can't statically check
|
|
313
|
+
// Which enum does this param use? Match by param name heuristically against enum names.
|
|
314
|
+
for (const [enumName, vals] of known.enums) {
|
|
315
|
+
// crude: the param likely uses this enum if rawVal looks like EnumName.Value or matches a value
|
|
316
|
+
const lastSeg = rawVal.split(".").pop().toLowerCase();
|
|
317
|
+
const looksLikeThisEnum = rawVal.toLowerCase().includes(enumName.toLowerCase()) || vals.has(lastSeg);
|
|
318
|
+
if (looksLikeThisEnum && !vals.has(lastSeg)) {
|
|
319
|
+
issues.push({ severity: "error", component: tag, message: `\`${name}="${rawVal}"\` — \`${lastSeg}\` is not a valid ${enumName} value. Allowed: ${[...vals].join(", ")}.` });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Parent-child: every sub-component present should have its required parent present somewhere.
|
|
325
|
+
for (const tag of present) {
|
|
326
|
+
const known = elementIndex.get(tag.toLowerCase());
|
|
327
|
+
if (known.parent && !present.has(known.parent)) {
|
|
328
|
+
issues.push({ severity: "error", component: tag, message: `<${tag}> must be used inside <${known.parent}> (it reads a CascadingValue from it). No <${known.parent}> found in this markup.` });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Also flag obviously-unknown PascalCase tags that look like attempted Lumeo components.
|
|
332
|
+
// (Skip — too noisy; user components are also PascalCase. The known-element checks above are enough.)
|
|
333
|
+
return { ok: !issues.some((i) => i.severity === "error"), issues };
|
|
334
|
+
}
|
|
194
335
|
// ───────────────── Server setup ─────────────────
|
|
195
336
|
const server = new Server({
|
|
196
337
|
name: "lumeo-mcp",
|
|
@@ -247,6 +388,78 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
247
388
|
},
|
|
248
389
|
},
|
|
249
390
|
},
|
|
391
|
+
{
|
|
392
|
+
name: "lumeo_get_example",
|
|
393
|
+
description: "Get working Razor example snippet(s) for a component — the exact code behind the docs-site demos. " +
|
|
394
|
+
"Returns an array of { title, code }. Use this before writing markup for an unfamiliar component.",
|
|
395
|
+
inputSchema: {
|
|
396
|
+
type: "object",
|
|
397
|
+
required: ["name"],
|
|
398
|
+
properties: {
|
|
399
|
+
name: { type: "string", description: "Component name (e.g. \"Tabs\", \"DataGrid\"). Case-insensitive." },
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: "lumeo_get_install",
|
|
405
|
+
description: "Everything needed to actually USE a component: NuGet package + `dotnet add` line, `@using` imports, " +
|
|
406
|
+
"DI registration (builder.Services.AddLumeo…), host-page <script>/<link> includes, sub-components, and " +
|
|
407
|
+
"gotchas (portal components needing theme classes on <body>, OverlayProvider, required params).",
|
|
408
|
+
inputSchema: {
|
|
409
|
+
type: "object",
|
|
410
|
+
required: ["name"],
|
|
411
|
+
properties: {
|
|
412
|
+
name: { type: "string", description: "Component name. Case-insensitive." },
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: "lumeo_validate_markup",
|
|
418
|
+
description: "Validate a snippet of Razor that uses Lumeo components BEFORE compiling it. Checks: do the components exist? " +
|
|
419
|
+
"are parameter names valid (catches hallucinated APIs)? are enum values legal? are sub-components nested inside " +
|
|
420
|
+
"their required parent (e.g. <TabsContent> inside <Tabs>, <DialogContent> inside <Dialog>)? " +
|
|
421
|
+
"Returns { ok, issues: [{ severity, component, message }] }. Pass-through HTML/data-/aria- attributes are allowed.",
|
|
422
|
+
inputSchema: {
|
|
423
|
+
type: "object",
|
|
424
|
+
required: ["markup"],
|
|
425
|
+
properties: {
|
|
426
|
+
markup: { type: "string", description: "Razor markup to validate (the component/markup portion — @code blocks are ignored)." },
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: "lumeo_get_theme_tokens",
|
|
432
|
+
description: `List all ${themeTokens.length} Lumeo theme tokens (CSS custom properties driving colours + radii). ` +
|
|
433
|
+
"These are the ONLY colours you should use — as Tailwind-style utilities like `bg-primary`, `text-foreground`, " +
|
|
434
|
+
"`border-border`, `bg-card`, `text-muted-foreground`, `ring-ring`. Never raw hex/hsl, never `dark:` prefixes " +
|
|
435
|
+
"(dark mode is a CSS-variable swap). Returns { token, cssVar } pairs.",
|
|
436
|
+
inputSchema: { type: "object", properties: {} },
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "lumeo_list_patterns",
|
|
440
|
+
description: `List all ${patterns.length} Lumeo "patterns" / "blocks" — full-page composed examples (dashboard, auth, chat, ` +
|
|
441
|
+
"kanban, mail, settings, …) built entirely from Lumeo components. Returns { title, key, route, description }. " +
|
|
442
|
+
"Use lumeo_get_pattern for the full Razor source of one.",
|
|
443
|
+
inputSchema: { type: "object", properties: {} },
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: "lumeo_get_pattern",
|
|
447
|
+
description: "Get the complete Razor source of a Lumeo pattern/block (a full-page composed example). Returns title, " +
|
|
448
|
+
"description, route, and the demo code snippet(s). Great as a starting skeleton for a real page.",
|
|
449
|
+
inputSchema: {
|
|
450
|
+
type: "object",
|
|
451
|
+
required: ["key"],
|
|
452
|
+
properties: {
|
|
453
|
+
key: { type: "string", description: "Pattern key (kebab-case, from lumeo_list_patterns) — e.g. \"dashboard\", \"authentication\", \"chat\"." },
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: "lumeo_changelog",
|
|
459
|
+
description: "Current Lumeo package version, when the API schema was generated, and a link to the full changelog. " +
|
|
460
|
+
"Use to check which version's API surface this MCP reflects.",
|
|
461
|
+
inputSchema: { type: "object", properties: {} },
|
|
462
|
+
},
|
|
250
463
|
],
|
|
251
464
|
}));
|
|
252
465
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
@@ -278,6 +491,76 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
278
491
|
const results = searchCatalog(query).map(toListPayload);
|
|
279
492
|
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
280
493
|
}
|
|
494
|
+
case "lumeo_get_example": {
|
|
495
|
+
const wanted = typeof a.name === "string" ? a.name : "";
|
|
496
|
+
const c = findComponent(wanted);
|
|
497
|
+
if (!c) {
|
|
498
|
+
return { isError: true, content: [{ type: "text", text: `Component "${wanted}" not found. Use lumeo_search.` }] };
|
|
499
|
+
}
|
|
500
|
+
const examples = (c.examples ?? []).slice();
|
|
501
|
+
const curated = curatedExampleByName.get(c.name.toLowerCase());
|
|
502
|
+
if (curated && !examples.some((e) => e.code.trim() === curated.trim())) {
|
|
503
|
+
examples.push({ title: `${c.name} (curated)`, code: curated });
|
|
504
|
+
}
|
|
505
|
+
if (examples.length === 0) {
|
|
506
|
+
return { content: [{ type: "text", text: `No example on file for "${c.name}". See ${docsUrl(c)} or call lumeo_get_component for its full API.` }] };
|
|
507
|
+
}
|
|
508
|
+
return { content: [{ type: "text", text: JSON.stringify({ component: c.name, docs: docsUrl(c), examples }, null, 2) }] };
|
|
509
|
+
}
|
|
510
|
+
case "lumeo_get_install": {
|
|
511
|
+
const wanted = typeof a.name === "string" ? a.name : "";
|
|
512
|
+
const c = findComponent(wanted);
|
|
513
|
+
if (!c) {
|
|
514
|
+
return { isError: true, content: [{ type: "text", text: `Component "${wanted}" not found. Use lumeo_search.` }] };
|
|
515
|
+
}
|
|
516
|
+
return { content: [{ type: "text", text: JSON.stringify(buildInstallInfo(c), null, 2) }] };
|
|
517
|
+
}
|
|
518
|
+
case "lumeo_validate_markup": {
|
|
519
|
+
const markup = typeof a.markup === "string" ? a.markup : "";
|
|
520
|
+
if (!markup.trim()) {
|
|
521
|
+
return { isError: true, content: [{ type: "text", text: "No markup provided." }] };
|
|
522
|
+
}
|
|
523
|
+
const result = validateMarkup(markup);
|
|
524
|
+
const summary = result.ok
|
|
525
|
+
? (result.issues.length ? `OK with ${result.issues.length} warning(s).` : "OK — no issues found.")
|
|
526
|
+
: `${result.issues.filter((i) => i.severity === "error").length} error(s), ${result.issues.filter((i) => i.severity === "warning").length} warning(s).`;
|
|
527
|
+
return { content: [{ type: "text", text: JSON.stringify({ ...result, summary }, null, 2) }] };
|
|
528
|
+
}
|
|
529
|
+
case "lumeo_get_theme_tokens": {
|
|
530
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
531
|
+
count: themeTokens.length,
|
|
532
|
+
usage: "Use as Tailwind-style utilities: bg-{token}, text-{token}, border-{token}, ring-{token}, fill-{token}. e.g. `bg-primary text-primary-foreground`, `border-border/40`, `text-muted-foreground`. Radius tokens (radius, radius-sm, radius-lg, …) → `rounded-[var(--radius-lg)]`. Never raw hex; never `dark:` prefixes (dark mode swaps the variable values).",
|
|
533
|
+
tokens: themeTokens,
|
|
534
|
+
}, null, 2) }] };
|
|
535
|
+
}
|
|
536
|
+
case "lumeo_list_patterns": {
|
|
537
|
+
const list = patterns.map((p) => ({
|
|
538
|
+
title: p.title,
|
|
539
|
+
key: p.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""),
|
|
540
|
+
route: p.route,
|
|
541
|
+
description: p.description,
|
|
542
|
+
}));
|
|
543
|
+
return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
|
|
544
|
+
}
|
|
545
|
+
case "lumeo_get_pattern": {
|
|
546
|
+
const key = (typeof a.key === "string" ? a.key : "").toLowerCase();
|
|
547
|
+
const p = patternByKey.get(key) ?? patterns.find((x) => x.title.toLowerCase().includes(key) || x.route.includes(key));
|
|
548
|
+
if (!p) {
|
|
549
|
+
return { isError: true, content: [{ type: "text", text: `Pattern "${key}" not found. Use lumeo_list_patterns for valid keys.` }] };
|
|
550
|
+
}
|
|
551
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
552
|
+
title: p.title, route: `${DOCS_BASE}${p.route}`, description: p.description, examples: p.examples,
|
|
553
|
+
}, null, 2) }] };
|
|
554
|
+
}
|
|
555
|
+
case "lumeo_changelog": {
|
|
556
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
557
|
+
version: api.version,
|
|
558
|
+
apiSchemaGenerated: api.generated,
|
|
559
|
+
componentCount: api.stats.componentCount,
|
|
560
|
+
changelog: `${DOCS_BASE}/docs/changelog`,
|
|
561
|
+
note: "Detailed per-release notes live on the docs site. This MCP's API surface reflects the version above.",
|
|
562
|
+
}, null, 2) }] };
|
|
563
|
+
}
|
|
281
564
|
default:
|
|
282
565
|
return { isError: true, content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
283
566
|
}
|
|
@@ -338,6 +621,7 @@ async function main() {
|
|
|
338
621
|
await server.connect(transport);
|
|
339
622
|
process.stderr.write(`[lumeo-mcp] ready — ${components.length} components, ${CATEGORIES.length} categories, ` +
|
|
340
623
|
`${api.stats.totalParameters} params, ${api.stats.totalEnums} enums, ` +
|
|
624
|
+
`${themeTokens.length} theme tokens, ${patterns.length} patterns, ` +
|
|
341
625
|
`api v${api.version}, generated ${api.generated}\n`);
|
|
342
626
|
}
|
|
343
627
|
main().catch((err) => {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static install/setup metadata that isn't derivable from the Razor source.
|
|
3
|
+
* Keyed by NuGet package; plus a small list of component-specific gotchas
|
|
4
|
+
* (portal components needing theme classes on <body>, etc.). Hand-maintained —
|
|
5
|
+
* these change rarely. Surfaced via the `lumeo_get_install` MCP tool so an
|
|
6
|
+
* LLM gets everything it needs to actually run a component, not just its API.
|
|
7
|
+
*/
|
|
8
|
+
export const PACKAGE_SETUP = {
|
|
9
|
+
Lumeo: {
|
|
10
|
+
package: "Lumeo",
|
|
11
|
+
dotnetAdd: "dotnet add package Lumeo --prerelease",
|
|
12
|
+
lumeoAddNote: "Or copy a single component into your project: `lumeo add <component>` (after `lumeo init`).",
|
|
13
|
+
usings: ["@using Lumeo"],
|
|
14
|
+
di: ["builder.Services.AddLumeo(); // registers ComponentInteropService, OverlayService, ConsentService, etc."],
|
|
15
|
+
hostIncludes: [
|
|
16
|
+
`<link rel="stylesheet" href="_content/Lumeo/css/lumeo.css" />`,
|
|
17
|
+
`<script src="_content/Lumeo/js/components.js"></script>`,
|
|
18
|
+
],
|
|
19
|
+
notes: [
|
|
20
|
+
"Tailwind v4: Lumeo ships pre-compiled utilities in lumeo.css — no Tailwind build step required for the components themselves.",
|
|
21
|
+
"Dark mode is handled by CSS-variable swaps in lumeo.css. Do NOT use `dark:` Tailwind prefixes; toggle the `dark` class on <html>.",
|
|
22
|
+
"All colours come from theme tokens (bg-primary, text-foreground, border-border, …) — never raw hex/hsl. Use the `lumeo_get_theme_tokens` tool for the full list.",
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
"Lumeo.Charts": {
|
|
26
|
+
package: "Lumeo.Charts",
|
|
27
|
+
dotnetAdd: "dotnet add package Lumeo.Charts --prerelease",
|
|
28
|
+
lumeoAddNote: "`lumeo add chart` copies the Chart component + ECharts interop.",
|
|
29
|
+
usings: ["@using Lumeo"],
|
|
30
|
+
di: ["builder.Services.AddLumeo();", "builder.Services.AddLumeoCharts();"],
|
|
31
|
+
hostIncludes: [
|
|
32
|
+
`<script src="_content/Lumeo.Charts/js/echarts.min.js"></script>`,
|
|
33
|
+
`<script src="_content/Lumeo.Charts/js/chart-interop.js"></script>`,
|
|
34
|
+
],
|
|
35
|
+
notes: ["Wraps Apache ECharts — 30+ chart types via the declarative <Chart> wrapper or raw EChartOption / OptionJson."],
|
|
36
|
+
},
|
|
37
|
+
"Lumeo.DataGrid": {
|
|
38
|
+
package: "Lumeo.DataGrid",
|
|
39
|
+
dotnetAdd: "dotnet add package Lumeo.DataGrid --prerelease",
|
|
40
|
+
lumeoAddNote: "`lumeo add datagrid` copies the DataGrid + supporting types.",
|
|
41
|
+
usings: ["@using Lumeo"],
|
|
42
|
+
di: ["builder.Services.AddLumeo();", "builder.Services.AddLumeoDataGrid(); // registers IDataGridExportService"],
|
|
43
|
+
hostIncludes: [],
|
|
44
|
+
notes: [
|
|
45
|
+
"Excel/PDF export pulls in ClosedXML (MIT) and QuestPDF (dual-licensed — free under $1M revenue, otherwise paid). CSV/JSON export has no third-party dependency.",
|
|
46
|
+
"In Blazor WebAssembly, hide PDF export (`ExportFormats=\"DataGridExportFormat.Csv | DataGridExportFormat.Excel\"`) — QuestPDF throws PlatformNotSupportedException there.",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
"Lumeo.Editor": {
|
|
50
|
+
package: "Lumeo.Editor",
|
|
51
|
+
dotnetAdd: "dotnet add package Lumeo.Editor --prerelease",
|
|
52
|
+
lumeoAddNote: "`lumeo add rich-text-editor`.",
|
|
53
|
+
usings: ["@using Lumeo"],
|
|
54
|
+
di: ["builder.Services.AddLumeo();", "builder.Services.AddLumeoEditor();"],
|
|
55
|
+
hostIncludes: [`<script src="_content/Lumeo.Editor/js/editor-interop.js"></script>`],
|
|
56
|
+
notes: [],
|
|
57
|
+
},
|
|
58
|
+
"Lumeo.Scheduler": {
|
|
59
|
+
package: "Lumeo.Scheduler",
|
|
60
|
+
dotnetAdd: "dotnet add package Lumeo.Scheduler --prerelease",
|
|
61
|
+
lumeoAddNote: "`lumeo add scheduler`.",
|
|
62
|
+
usings: ["@using Lumeo"],
|
|
63
|
+
di: ["builder.Services.AddLumeo();", "builder.Services.AddLumeoScheduler();"],
|
|
64
|
+
hostIncludes: [],
|
|
65
|
+
notes: [],
|
|
66
|
+
},
|
|
67
|
+
"Lumeo.Gantt": {
|
|
68
|
+
package: "Lumeo.Gantt",
|
|
69
|
+
dotnetAdd: "dotnet add package Lumeo.Gantt --prerelease",
|
|
70
|
+
lumeoAddNote: "`lumeo add gantt`.",
|
|
71
|
+
usings: ["@using Lumeo"],
|
|
72
|
+
di: ["builder.Services.AddLumeo();", "builder.Services.AddLumeoGantt();"],
|
|
73
|
+
hostIncludes: [],
|
|
74
|
+
notes: [],
|
|
75
|
+
},
|
|
76
|
+
"Lumeo.Motion": {
|
|
77
|
+
package: "Lumeo.Motion",
|
|
78
|
+
dotnetAdd: "dotnet add package Lumeo.Motion --prerelease",
|
|
79
|
+
lumeoAddNote: "`lumeo add <motion-component>` (e.g. `lumeo add border-beam`).",
|
|
80
|
+
usings: ["@using Lumeo"],
|
|
81
|
+
di: ["builder.Services.AddLumeo();", "builder.Services.AddLumeoMotion();"],
|
|
82
|
+
hostIncludes: [`<script src="_content/Lumeo.Motion/js/motion-interop.js"></script>`],
|
|
83
|
+
notes: ["Animation primitives (BorderBeam, Marquee, NumberTicker, Confetti, …). Most are pure CSS; a few use a small JS interop module."],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Components whose content renders into a portal/overlay outside the normal
|
|
88
|
+
* component tree. They need `bg-background text-foreground` on <body> (or a
|
|
89
|
+
* wrapper that is an ancestor of the portal root) or they render outside the
|
|
90
|
+
* theme cascade and look unstyled.
|
|
91
|
+
*/
|
|
92
|
+
export const PORTAL_COMPONENTS = new Set([
|
|
93
|
+
"Dialog", "Sheet", "Drawer", "Toast", "Popover", "Tooltip", "AlertDialog",
|
|
94
|
+
"HoverCard", "ContextMenu", "DropdownMenu", "Command", "PopConfirm", "Tour",
|
|
95
|
+
"DatePicker", "DateTimePicker", "TimePicker", "Combobox", "Cascader", "Mention", "Select",
|
|
96
|
+
]);
|
|
97
|
+
/** Components that need an OverlayProvider in the layout for the service-driven API. */
|
|
98
|
+
export const NEEDS_OVERLAY_PROVIDER = new Set(["Toast", "Dialog", "Sheet", "Drawer"]);
|
|
99
|
+
export function setupFor(nugetPackage) {
|
|
100
|
+
return PACKAGE_SETUP[nugetPackage] ?? PACKAGE_SETUP.Lumeo;
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumeo-ui/mcp-server",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.30",
|
|
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",
|