@urbicon-ui/mcp-server 6.2.0 → 6.3.3
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/package.json +3 -3
- package/src/tools/find-components.ts +5 -14
- package/src/tools/get-checklist.ts +3 -1
- package/src/tools/get-css-reference.test.ts +77 -0
- package/src/tools/get-css-reference.ts +13 -8
- package/src/tools/get-recipe.ts +1 -1
- package/src/tools/suggest-implementation.ts +2 -2
- package/src/utils/format-catalog.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urbicon-ui/mcp-server",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.3",
|
|
4
4
|
"description": "Model Context Protocol server exposing the Urbicon UI component catalog, recipes and design intelligence to LLM agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
35
|
-
"@urbicon-ui/design-content": "6.
|
|
36
|
-
"@urbicon-ui/design-engine": "6.
|
|
35
|
+
"@urbicon-ui/design-content": "6.3.3",
|
|
36
|
+
"@urbicon-ui/design-engine": "6.3.3",
|
|
37
37
|
"zod": "^4.3.6"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
@@ -2,7 +2,7 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { matchComponents } from '@urbicon-ui/design-engine/search';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { loadCatalog } from '../data/catalog-loader.js';
|
|
5
|
-
import { formatCompactCatalog } from '../utils/format-catalog.js';
|
|
5
|
+
import { formatCompactCatalog, formatComponentLine } from '../utils/format-catalog.js';
|
|
6
6
|
|
|
7
7
|
export function registerFindComponentsTool(server: McpServer): void {
|
|
8
8
|
server.tool(
|
|
@@ -43,20 +43,11 @@ export function registerFindComponentsTool(server: McpServer): void {
|
|
|
43
43
|
let md = `# Search Results for "${query}"\n\n`;
|
|
44
44
|
md += `> ${results.length} matching components.\n\n`;
|
|
45
45
|
|
|
46
|
+
// Same line format as the browse view — including the origin-package tag for
|
|
47
|
+
// non-blocks components, so a match like `Table` (from @urbicon-ui/table) is
|
|
48
|
+
// never mistaken for a blocks export.
|
|
46
49
|
for (const comp of results) {
|
|
47
|
-
|
|
48
|
-
.filter(
|
|
49
|
-
(v) => !['true', 'false'].every((b) => v.values.includes(b) && v.values.length <= 2)
|
|
50
|
-
)
|
|
51
|
-
.map((v) => `${v.name}: ${v.values.join('/')}`)
|
|
52
|
-
.join(' · ');
|
|
53
|
-
|
|
54
|
-
md += `- **${comp.name}** — ${comp.description}`;
|
|
55
|
-
if (variants) md += ` | ${variants}`;
|
|
56
|
-
if (comp.relatedComponents.length > 0) {
|
|
57
|
-
md += ` | Related: ${comp.relatedComponents.join(', ')}`;
|
|
58
|
-
}
|
|
59
|
-
md += '\n';
|
|
50
|
+
md += formatComponentLine(comp);
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
md += '\n> Use `get_component` with the component slug for full API docs.\n';
|
|
@@ -34,7 +34,7 @@ export function registerGetChecklistTool(server: McpServer): void {
|
|
|
34
34
|
md +=
|
|
35
35
|
'- [ ] Use OKLCH interaction tokens for hover/active states (`bg-surface-hover`, `bg-surface-active`)\n';
|
|
36
36
|
md +=
|
|
37
|
-
'- [ ] Dark mode is automatic via `prefers-color-scheme` — semantic tokens switch automatically. For manual control, use `ThemeSwitcher` or
|
|
37
|
+
'- [ ] Dark mode is automatic via the CSS `light-dark()` function (`:root` sets `color-scheme: light dark`, following the OS `prefers-color-scheme`) — semantic tokens switch automatically. For manual control, use `ThemeSwitcher` or add a `.light`/`.dark` class to `<html>`. Do NOT add `dark:` overrides\n';
|
|
38
38
|
md +=
|
|
39
39
|
'- [ ] Use z-index tokens via CSS custom properties: `z-[var(--z-modal)]`, `z-[var(--z-dropdown)]`, etc.\n';
|
|
40
40
|
md += '\n';
|
|
@@ -44,6 +44,8 @@ export function registerGetChecklistTool(server: McpServer): void {
|
|
|
44
44
|
md += '- [ ] Use `slotClasses` for targeted style overrides on specific sub-elements\n';
|
|
45
45
|
md +=
|
|
46
46
|
'- [ ] For reusable variant sets, register named `preset`s via `BlocksProvider` (and select with the `preset` prop) — not external variant libraries; Urbicon UI ships its own zero-dependency variant engine\n';
|
|
47
|
+
md +=
|
|
48
|
+
'- [ ] To style only one variant/intent/state project-wide (e.g. just `variant="outlined"`), register prop-conditional `overrides` on `BlocksProvider` `defaults`/`presets` — what an unconditional `slotClasses` cannot express\n';
|
|
47
49
|
md +=
|
|
48
50
|
"- [ ] For conditional classes, pass an array to `class` (`class={['base', condition && 'extra']}`) — not concatenated conditional class strings\n";
|
|
49
51
|
md += '- [ ] Use `class` prop for simple additions — merges with defaults automatically\n';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { OVERVIEW, SECTIONS } from './get-css-reference.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Drift guard for the hand-maintained CSS token reference.
|
|
9
|
+
*
|
|
10
|
+
* `get_css_reference` inlines its token tables as TS strings — the MCP server
|
|
11
|
+
* ships standalone (no blocks CSS at runtime), the same constraint that keeps
|
|
12
|
+
* design-engine's `VALID_TOKEN_CORES` inline. The hazard of any hand-copied list
|
|
13
|
+
* is silent drift: `--color-border-hairline` was added to the CSS and went
|
|
14
|
+
* unmirrored here for a while. When the blocks CSS is present (i.e. running
|
|
15
|
+
* in-repo) we re-derive the semantic surface / text / border token cores and
|
|
16
|
+
* assert each is documented, so a newly added token can no longer disappear.
|
|
17
|
+
*
|
|
18
|
+
* Scope: the three families `get_css_reference` enumerates exhaustively (one row
|
|
19
|
+
* per token). It deliberately does NOT require every intent scale step
|
|
20
|
+
* (`primary-50 … primary-950`, documented via shorthand), feedback/interactive,
|
|
21
|
+
* chart, or internal-only token (e.g. `skeleton-shimmer`, used by the Skeleton
|
|
22
|
+
* wave, never a consumer utility) to be spelled out. Whole-set token validity is
|
|
23
|
+
* already guarded by design-engine's `tokens.test.ts`.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const semantic = resolve(
|
|
28
|
+
__dirname,
|
|
29
|
+
'..',
|
|
30
|
+
'..',
|
|
31
|
+
'..',
|
|
32
|
+
'blocks',
|
|
33
|
+
'src',
|
|
34
|
+
'lib',
|
|
35
|
+
'style',
|
|
36
|
+
'semantic.css'
|
|
37
|
+
);
|
|
38
|
+
const cssAvailable = existsSync(semantic);
|
|
39
|
+
|
|
40
|
+
const ALL_CONTENT = [OVERVIEW, ...Object.values(SECTIONS)].join('\n');
|
|
41
|
+
|
|
42
|
+
/** Families `get_css_reference` tables exhaustively, with the prose count to verify. */
|
|
43
|
+
const TABLED_FAMILIES = [
|
|
44
|
+
{ family: 'surface', section: SECTIONS.surfaces! },
|
|
45
|
+
{ family: 'text', section: SECTIONS.text! },
|
|
46
|
+
{ family: 'border', section: SECTIONS.borders! }
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
/** Unique semantic `--color-<family>-*` cores in the CSS (scoped re-declarations collapse). */
|
|
50
|
+
function deriveSemanticCores(family: string): string[] {
|
|
51
|
+
const css = readFileSync(semantic, 'utf-8');
|
|
52
|
+
const re = new RegExp(`--color-(${family}-[a-z-]+)\\s*:`, 'g');
|
|
53
|
+
const cores = new Set<string>();
|
|
54
|
+
for (const m of css.matchAll(re)) cores.add(m[1]!);
|
|
55
|
+
return [...cores].sort();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe.skipIf(!cssAvailable)('get_css_reference token drift guard', () => {
|
|
59
|
+
for (const { family } of TABLED_FAMILIES) {
|
|
60
|
+
it(`documents every semantic \`${family}-*\` token defined in the CSS`, () => {
|
|
61
|
+
const missing = deriveSemanticCores(family).filter((c) => !ALL_CONTENT.includes(c));
|
|
62
|
+
expect(
|
|
63
|
+
missing,
|
|
64
|
+
`Semantic ${family} tokens in the CSS but absent from get_css_reference: ${missing.join(', ')}`
|
|
65
|
+
).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const { family, section } of TABLED_FAMILIES) {
|
|
70
|
+
it(`states the correct ${family}-token count`, () => {
|
|
71
|
+
const stated = Number(section.match(/(\d+)\s+tokens for/)?.[1]);
|
|
72
|
+
expect(stated, 'prose count drifted from the real token count').toBe(
|
|
73
|
+
deriveSemanticCores(family).length
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
-
const OVERVIEW = `# Urbicon UI — CSS Design Tokens
|
|
4
|
+
export const OVERVIEW = `# Urbicon UI — CSS Design Tokens
|
|
5
5
|
|
|
6
6
|
## Architecture
|
|
7
7
|
Three CSS layers, imported in order:
|
|
@@ -32,8 +32,8 @@ Override tokens in your app CSS using Tailwind's \`@theme\` block:
|
|
|
32
32
|
\`\`\`
|
|
33
33
|
|
|
34
34
|
## Dark Mode
|
|
35
|
-
Mechanism:
|
|
36
|
-
Manual override: add \`.light\` or \`.dark\` class to \`<html>\` (via \`ThemeSwitcher\` component).
|
|
35
|
+
Mechanism: semantic tokens use the CSS \`light-dark()\` function; \`:root\` declares \`color-scheme: light dark\` so the browser resolves the matching branch automatically (following the OS \`prefers-color-scheme\`). No \`dark:\` overrides.
|
|
36
|
+
Manual override: add a \`.light\` or \`.dark\` class to \`<html>\` (via the \`ThemeSwitcher\` component) — the class only flips \`color-scheme\`, and \`light-dark()\` re-resolves on its own.
|
|
37
37
|
|
|
38
38
|
To override a token for ALL modes (light, dark, and manual overrides):
|
|
39
39
|
\`\`\`css
|
|
@@ -49,7 +49,7 @@ The \`@theme\` block sets the Tailwind utility value. The \`:root\` rule overrid
|
|
|
49
49
|
## Available Sections
|
|
50
50
|
→ \`get_css_reference(section="surfaces")\` — 11 surface background tokens
|
|
51
51
|
→ \`get_css_reference(section="text")\` — 9 text color tokens
|
|
52
|
-
→ \`get_css_reference(section="borders")\` —
|
|
52
|
+
→ \`get_css_reference(section="borders")\` — 5 border color tokens
|
|
53
53
|
→ \`get_css_reference(section="intents")\` — 6 intent palettes (primary, success, danger, etc.)
|
|
54
54
|
→ \`get_css_reference(section="shadows")\` — 5 shadow tokens + z-index scale
|
|
55
55
|
→ \`get_css_reference(section="theming")\` — How to create custom themes, available presets
|
|
@@ -114,16 +114,18 @@ Light → Dark mapping:
|
|
|
114
114
|
|
|
115
115
|
const BORDERS = `# Border Tokens
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
5 tokens for border colors. All auto-switch in dark mode.
|
|
118
118
|
|
|
119
119
|
| CSS Variable | Tailwind Utility | Purpose |
|
|
120
120
|
|---|---|---|
|
|
121
|
+
| \`--color-border-hairline\` | \`border-border-hairline\` | Faintest divider — translucent (alpha), not a neutral step |
|
|
121
122
|
| \`--color-border-subtle\` | \`border-border-subtle\` | Gentle grouping |
|
|
122
123
|
| \`--color-border-default\` | \`border-border-default\` | Standard borders |
|
|
123
124
|
| \`--color-border-emphasis\` | \`border-border-emphasis\` | Emphasized borders |
|
|
124
125
|
| \`--color-border-strong\` | \`border-border-strong\` | High-contrast borders |
|
|
125
126
|
|
|
126
127
|
Light → Dark mapping:
|
|
128
|
+
- \`border-hairline\`: black 8% → white 6% (translucent, blends onto any surface)
|
|
127
129
|
- \`border-subtle\`: neutral-200 → neutral-700
|
|
128
130
|
- \`border-default\`: neutral-300 → neutral-600
|
|
129
131
|
- \`border-emphasis\`: neutral-400 → neutral-500
|
|
@@ -385,13 +387,16 @@ A global \`@theme\` block (the built-in themes, the Theme Builder output) does N
|
|
|
385
387
|
|
|
386
388
|
## Component-Level Overrides
|
|
387
389
|
|
|
388
|
-
Use \`BlocksProvider\` to
|
|
390
|
+
Use \`BlocksProvider\` to style components project-wide — unconditional \`defaults\`, named \`presets\` (opt-in via the \`preset\` prop), and prop-conditional \`overrides\`:
|
|
389
391
|
\`\`\`svelte
|
|
390
392
|
<BlocksProvider defaults={{
|
|
391
393
|
Card: { slotClasses: { base: 'rounded-2xl shadow-lg' } },
|
|
392
|
-
Button: { slotClasses: { base: 'rounded-full font-bold uppercase' } }
|
|
394
|
+
Button: { slotClasses: { base: 'rounded-full font-bold uppercase' } },
|
|
395
|
+
// prop-conditional: style ONLY the outlined variant — what an unconditional slotClasses cannot express
|
|
396
|
+
Badge: { overrides: [{ variant: 'outlined', class: { base: 'border' } }] }
|
|
393
397
|
}}>
|
|
394
398
|
\`\`\`
|
|
399
|
+
Cascade (conflict-resolved per Tailwind bucket, later wins): \`defaults.slotClasses → defaults.overrides → preset.slotClasses → preset.overrides → instance slotClasses → instance class\`.
|
|
395
400
|
|
|
396
401
|
Or override per-instance:
|
|
397
402
|
\`\`\`svelte
|
|
@@ -413,7 +418,7 @@ So the only requirement is to import \`index.css\` (your app owns the Tailwind i
|
|
|
413
418
|
**Do NOT add manual \`@source\` directives, and do NOT import the \`foundation\`/\`semantic\`/\`interaction\` subfiles instead of \`index.css\`** — the subfiles omit the \`@source\` directives (and global classes), which is the usual cause of "responsive layouts break in production".
|
|
414
419
|
`;
|
|
415
420
|
|
|
416
|
-
const SECTIONS: Record<string, string> = {
|
|
421
|
+
export const SECTIONS: Record<string, string> = {
|
|
417
422
|
surfaces: SURFACES,
|
|
418
423
|
text: TEXT,
|
|
419
424
|
borders: BORDERS,
|
package/src/tools/get-recipe.ts
CHANGED
|
@@ -10,7 +10,7 @@ export function registerGetRecipeTool(server: McpServer): void {
|
|
|
10
10
|
scenario: z
|
|
11
11
|
.string()
|
|
12
12
|
.describe(
|
|
13
|
-
'Recipe id
|
|
13
|
+
'Recipe id — e.g. login, settings, dashboard, pricing, profile-card, onboarding-flow, wizard, notification-center, or an auth flow (auth-invitation-register, auth-passkey-login, auth-password-reset). Pass any unrecognised id to get the full, current list.'
|
|
14
14
|
)
|
|
15
15
|
},
|
|
16
16
|
{ readOnlyHint: true },
|
|
@@ -69,11 +69,11 @@ const IMPLEMENTATION_RULES = `## Implementation Rules
|
|
|
69
69
|
|
|
70
70
|
- **CSS imports** — Add to root layout, Tailwind first: \`@import 'tailwindcss';\` then \`@import '@urbicon-ui/blocks/style/index.css';\` (ships tokens + the \`@source\` directives). Import \`index.css\`, NOT the \`foundation\`/\`semantic\`/\`interaction\` subfiles, and add no manual \`@source\`
|
|
71
71
|
- **Semantic tokens only** — Use \`bg-surface-elevated\`, \`text-text-primary\`, \`border-border-default\` — never raw Tailwind colors
|
|
72
|
-
- **Dark mode** — Automatic via \`prefers-color-scheme\`. Do NOT add \`dark:\` overrides
|
|
72
|
+
- **Dark mode** — Automatic via the CSS \`light-dark()\` function (\`:root\` sets \`color-scheme: light dark\`, following the OS \`prefers-color-scheme\`). For manual control use \`ThemeSwitcher\` (toggles a \`.light\`/\`.dark\` class). Do NOT add \`dark:\` overrides
|
|
73
73
|
- **Focus** — Always \`focus-visible:\` (not \`focus:\`) for keyboard-only focus rings
|
|
74
74
|
- **State binding** — \`bind:value\`, \`bind:checked\`, \`bind:open\` for two-way state; callback props (\`onValueChange\`) for side effects
|
|
75
75
|
- **Custom content** — Use Svelte 5 snippets (\`{#snippet name()}...{/snippet}\`), not legacy slots
|
|
76
|
-
- **Styling overrides** — \`class\` for simple additions, \`slotClasses\` for per-slot targeting, \`unstyled\` to strip all defaults
|
|
76
|
+
- **Styling overrides** — per instance: \`class\` for simple additions, \`slotClasses\` for per-slot targeting, \`unstyled\` to strip all defaults. Project-wide via \`BlocksProvider\`: \`defaults\` (every instance), named \`presets\`, and prop-conditional \`overrides\` (style only one variant, e.g. \`variant="outlined"\`)
|
|
77
77
|
- **Mint** — Add \`mint="scale"\` or \`mint="ripple"\` sparingly on primary CTAs only
|
|
78
78
|
|
|
79
79
|
## Design Quality
|
|
@@ -69,7 +69,7 @@ export function formatCompactCatalog(
|
|
|
69
69
|
md += '```\n';
|
|
70
70
|
md += "Import: `import { Button, Card } from '@urbicon-ui/blocks';` (always from package root)\n";
|
|
71
71
|
md +=
|
|
72
|
-
'Dark mode: automatic via `prefers-color-scheme` — semantic tokens switch automatically. For manual control, use `ThemeSwitcher` or
|
|
72
|
+
'Dark mode: automatic via the CSS `light-dark()` function (`:root` sets `color-scheme: light dark`, following the OS `prefers-color-scheme`) — semantic tokens switch automatically. For manual control, use `ThemeSwitcher` or add a `.light`/`.dark` class to `<html>`.\n\n';
|
|
73
73
|
|
|
74
74
|
for (const tag of TAG_ORDER) {
|
|
75
75
|
const comps = grouped.get(tag);
|
|
@@ -110,7 +110,7 @@ function isBooleanVariant(values: string[]): boolean {
|
|
|
110
110
|
);
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
function formatComponentLine(comp: ComponentCatalogEntry): string {
|
|
113
|
+
export function formatComponentLine(comp: ComponentCatalogEntry): string {
|
|
114
114
|
const variants = comp.variants
|
|
115
115
|
.filter((v) => !isBooleanVariant(v.values))
|
|
116
116
|
.map((v) => `${v.name}: ${v.values.join('/')}`)
|