@urbicon-ui/mcp-server 6.1.4

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 (130) hide show
  1. package/README.md +161 -0
  2. package/dist/data/catalog-loader.d.ts +37 -0
  3. package/dist/data/catalog-loader.d.ts.map +1 -0
  4. package/dist/data/catalog-loader.js +15 -0
  5. package/dist/data/catalog-loader.js.map +1 -0
  6. package/dist/data/component-loader.d.ts +2 -0
  7. package/dist/data/component-loader.d.ts.map +1 -0
  8. package/dist/data/component-loader.js +17 -0
  9. package/dist/data/component-loader.js.map +1 -0
  10. package/dist/data/recipe-loader.d.ts +4 -0
  11. package/dist/data/recipe-loader.d.ts.map +1 -0
  12. package/dist/data/recipe-loader.js +102 -0
  13. package/dist/data/recipe-loader.js.map +1 -0
  14. package/dist/data/template-loader.d.ts +8 -0
  15. package/dist/data/template-loader.d.ts.map +1 -0
  16. package/dist/data/template-loader.js +33 -0
  17. package/dist/data/template-loader.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +57 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/resources/catalog.d.ts +3 -0
  23. package/dist/resources/catalog.d.ts.map +1 -0
  24. package/dist/resources/catalog.js +20 -0
  25. package/dist/resources/catalog.js.map +1 -0
  26. package/dist/resources/component.d.ts +3 -0
  27. package/dist/resources/component.d.ts.map +1 -0
  28. package/dist/resources/component.js +29 -0
  29. package/dist/resources/component.js.map +1 -0
  30. package/dist/resources/guides.d.ts +3 -0
  31. package/dist/resources/guides.d.ts.map +1 -0
  32. package/dist/resources/guides.js +36 -0
  33. package/dist/resources/guides.js.map +1 -0
  34. package/dist/server.d.ts +3 -0
  35. package/dist/server.d.ts.map +1 -0
  36. package/dist/server.js.map +1 -0
  37. package/dist/tools/find-components.d.ts +3 -0
  38. package/dist/tools/find-components.d.ts.map +1 -0
  39. package/dist/tools/find-components.js +21 -0
  40. package/dist/tools/find-components.js.map +1 -0
  41. package/dist/tools/get-recipe.d.ts +3 -0
  42. package/dist/tools/get-recipe.d.ts.map +1 -0
  43. package/dist/tools/get-recipe.js +48 -0
  44. package/dist/tools/get-recipe.js.map +1 -0
  45. package/dist/tools/suggest-implementation.d.ts +3 -0
  46. package/dist/tools/suggest-implementation.d.ts.map +1 -0
  47. package/dist/tools/suggest-implementation.js +178 -0
  48. package/dist/tools/suggest-implementation.js.map +1 -0
  49. package/dist/transports/http.d.ts +2 -0
  50. package/dist/transports/http.d.ts.map +1 -0
  51. package/dist/transports/http.js +77 -0
  52. package/dist/transports/http.js.map +1 -0
  53. package/dist/transports/stdio.d.ts +3 -0
  54. package/dist/transports/stdio.d.ts.map +1 -0
  55. package/dist/transports/stdio.js +6 -0
  56. package/dist/transports/stdio.js.map +1 -0
  57. package/dist/tsconfig.tsbuildinfo +1 -0
  58. package/dist/utils/format-catalog.d.ts +7 -0
  59. package/dist/utils/format-catalog.d.ts.map +1 -0
  60. package/dist/utils/format-catalog.js +93 -0
  61. package/dist/utils/format-catalog.js.map +1 -0
  62. package/dist/utils/paths.d.ts +7 -0
  63. package/dist/utils/paths.d.ts.map +1 -0
  64. package/dist/utils/paths.js +23 -0
  65. package/dist/utils/paths.js.map +1 -0
  66. package/dist/utils/search.d.ts +3 -0
  67. package/dist/utils/search.d.ts.map +1 -0
  68. package/dist/utils/search.js +44 -0
  69. package/dist/utils/search.js.map +1 -0
  70. package/package.json +42 -0
  71. package/src/data/catalog-loader.test.ts +42 -0
  72. package/src/data/catalog-loader.ts +78 -0
  73. package/src/data/component-loader.ts +68 -0
  74. package/src/data/design-system-loader.test.ts +82 -0
  75. package/src/data/design-system-loader.ts +125 -0
  76. package/src/data/icon-loader.test.ts +85 -0
  77. package/src/data/icon-loader.ts +90 -0
  78. package/src/data/recipe-loader.test.ts +49 -0
  79. package/src/data/recipe-loader.ts +131 -0
  80. package/src/data/template-loader.ts +55 -0
  81. package/src/design-linter/heuristics.ts +162 -0
  82. package/src/design-linter/index.ts +14 -0
  83. package/src/design-linter/linter.test.ts +257 -0
  84. package/src/design-linter/linter.ts +62 -0
  85. package/src/design-linter/rules.ts +348 -0
  86. package/src/design-linter/tokens.test.ts +80 -0
  87. package/src/design-linter/tokens.ts +203 -0
  88. package/src/design-linter/types.ts +66 -0
  89. package/src/design-manifest/index.ts +20 -0
  90. package/src/design-manifest/manifest.test.ts +175 -0
  91. package/src/design-manifest/manifest.ts +250 -0
  92. package/src/design-manifest/scan.test.ts +51 -0
  93. package/src/design-manifest/scan.ts +74 -0
  94. package/src/design-manifest/types.ts +40 -0
  95. package/src/design-rubric/rubric.test.ts +43 -0
  96. package/src/design-rubric/rubric.ts +140 -0
  97. package/src/eval/briefs.ts +104 -0
  98. package/src/eval/eval.test.ts +99 -0
  99. package/src/eval/index.ts +11 -0
  100. package/src/eval/score.ts +112 -0
  101. package/src/index.ts +75 -0
  102. package/src/prompts/design-prompts.test.ts +51 -0
  103. package/src/prompts/design-prompts.ts +127 -0
  104. package/src/resources/catalog.ts +23 -0
  105. package/src/resources/guides.ts +60 -0
  106. package/src/server.test.ts +69 -0
  107. package/src/server.ts +48 -0
  108. package/src/tools/find-components.ts +83 -0
  109. package/src/tools/find-icons.ts +77 -0
  110. package/src/tools/get-checklist.ts +139 -0
  111. package/src/tools/get-component.ts +204 -0
  112. package/src/tools/get-css-reference.ts +446 -0
  113. package/src/tools/get-design-context.ts +43 -0
  114. package/src/tools/get-design-principles.ts +72 -0
  115. package/src/tools/get-pattern.ts +69 -0
  116. package/src/tools/get-recipe.ts +80 -0
  117. package/src/tools/record-design-decision.ts +99 -0
  118. package/src/tools/suggest-implementation.ts +251 -0
  119. package/src/tools/sync-design-manifest.ts +92 -0
  120. package/src/tools/validate-design.ts +84 -0
  121. package/src/transports/http.ts +79 -0
  122. package/src/transports/stdio.ts +7 -0
  123. package/src/utils/format-catalog.test.ts +144 -0
  124. package/src/utils/format-catalog.ts +130 -0
  125. package/src/utils/paths.test.ts +101 -0
  126. package/src/utils/paths.ts +78 -0
  127. package/src/utils/search.test.ts +141 -0
  128. package/src/utils/search.ts +106 -0
  129. package/tsconfig.json +27 -0
  130. package/vitest.config.ts +15 -0
@@ -0,0 +1,204 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import type { ComponentCatalogEntry } from '../data/catalog-loader.js';
4
+ import { loadCatalog } from '../data/catalog-loader.js';
5
+ import type { LlmTxtSection } from '../data/component-loader.js';
6
+ import { extractSection, loadComponentLlmTxt } from '../data/component-loader.js';
7
+
8
+ const VALID_SECTIONS: LlmTxtSection[] = ['overview', 'examples', 'variants', 'api', 'slots'];
9
+
10
+ /** Count prop rows in the llm.txt API section markdown table */
11
+ function countPropsInApiSection(content: string): number {
12
+ const apiSection = extractSection(content, 'api');
13
+ if (!apiSection) return 0;
14
+
15
+ const lines = apiSection.split('\n');
16
+ // Count table rows (start with `|` but skip header and separator rows)
17
+ let count = 0;
18
+ let pastHeader = false;
19
+ for (const line of lines) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed.startsWith('|')) continue;
22
+ if (trimmed.startsWith('| ---') || trimmed.startsWith('|---')) {
23
+ pastHeader = true;
24
+ continue;
25
+ }
26
+ if (trimmed.startsWith('| Prop')) continue; // header row
27
+ if (pastHeader) count++;
28
+ }
29
+ return count;
30
+ }
31
+
32
+ /** Extract the first ```svelte code block from the examples section */
33
+ function extractFirstExample(content: string): string | null {
34
+ const examples = extractSection(content, 'examples');
35
+ if (!examples) return null;
36
+
37
+ const match = examples.match(/```svelte\n([\s\S]*?)```/);
38
+ return match?.[1]?.trim() ?? null;
39
+ }
40
+
41
+ /** PascalCase component name to kebab-case slug */
42
+ function toSlug(name: string): string {
43
+ return name.replace(/([A-Z])/g, (_, c, i) => (i > 0 ? '-' : '') + c.toLowerCase());
44
+ }
45
+
46
+ /** Generate compact summary from catalog data + llm.txt */
47
+ function generateCompactView(entry: ComponentCatalogEntry, llmContent: string): string {
48
+ let md = `# ${entry.name}\n\n`;
49
+ md += `${entry.description}\n\n`;
50
+ md += `**Import:** \`${entry.import}\`\n\n`;
51
+
52
+ // Key props table from keyPropTypes
53
+ const propEntries = Object.entries(entry.keyPropTypes);
54
+ const totalProps = countPropsInApiSection(llmContent);
55
+ const shownCount = propEntries.length;
56
+
57
+ if (shownCount > 0) {
58
+ md += `## Key Props`;
59
+ if (totalProps > 0) md += ` (${shownCount} of ${totalProps})`;
60
+ md += '\n\n';
61
+ md += '| Prop | Type | Default |\n';
62
+ md += '|---|---|---|\n';
63
+
64
+ // Build a default lookup from variants
65
+ const defaults = new Map(
66
+ entry.variants.filter((v) => v.default).map((v) => [v.name, v.default])
67
+ );
68
+
69
+ for (const [prop, type] of propEntries) {
70
+ const def = defaults.get(prop) ?? '—';
71
+ md += `| ${prop} | \`${type}\` | ${def} |\n`;
72
+ }
73
+
74
+ if (totalProps > shownCount) {
75
+ md += `\n> ${totalProps - shownCount} more props available via \`get_component("${entry.slug}", section="api")\`\n`;
76
+ }
77
+ md += '\n';
78
+ }
79
+
80
+ // Variants (if any meaningful ones exist beyond what's in keyPropTypes)
81
+ const meaningfulVariants = entry.variants.filter((v) => {
82
+ const sorted = [...v.values].sort();
83
+ return !(
84
+ (sorted.length === 1 && (sorted[0] === 'true' || sorted[0] === 'false')) ||
85
+ (sorted.length === 2 && sorted[0] === 'false' && sorted[1] === 'true')
86
+ );
87
+ });
88
+
89
+ if (meaningfulVariants.length > 0) {
90
+ md += '## Variants\n\n';
91
+ for (const v of meaningfulVariants) {
92
+ const def = v.default ? ` (default: ${v.default})` : '';
93
+ md += `- **${v.name}**: ${v.values.join(' / ')}${def}\n`;
94
+ }
95
+ md += '\n';
96
+ }
97
+
98
+ // First example
99
+ const example = extractFirstExample(llmContent);
100
+ if (example) {
101
+ md += '## Example\n\n';
102
+ md += `\`\`\`svelte\n${example}\n\`\`\`\n\n`;
103
+ }
104
+
105
+ // Slots
106
+ if (entry.slots.length > 0) {
107
+ md += `## Slots\n\n\`${entry.slots.join('`, `')}\`\n\n`;
108
+ }
109
+
110
+ // Related components
111
+ if (entry.relatedComponents.length > 0) {
112
+ md += `## See Also\n\n`;
113
+ for (const rel of entry.relatedComponents) {
114
+ md += `- \`get_component("${toSlug(rel)}")\` — ${rel}\n`;
115
+ }
116
+ md += '\n';
117
+ }
118
+
119
+ // Drill-down hints
120
+ md += '---\n\n';
121
+ md += '**More details:**\n';
122
+ md += `- \`get_component("${entry.slug}", section="api")\` — full props table\n`;
123
+ md += `- \`get_component("${entry.slug}", section="examples")\` — all code examples\n`;
124
+ md += `- \`get_component("${entry.slug}", section="full")\` — complete documentation\n`;
125
+ md += '- `get_css_reference()` — CSS token names and override patterns\n';
126
+
127
+ return md;
128
+ }
129
+
130
+ export function registerGetComponentTool(server: McpServer): void {
131
+ server.tool(
132
+ 'get_component',
133
+ 'Get API documentation for a specific Urbicon UI component. Default: compact summary with key props, one example, and slots. Use section="full" for complete docs, or section="api"/"examples"/"variants"/"slots" for specific parts.',
134
+ {
135
+ name: z
136
+ .string()
137
+ .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Slug must be lowercase alphanumeric with hyphens')
138
+ .describe('Component slug, e.g. "button", "input", "date-picker", "command-palette"'),
139
+ section: z
140
+ .enum(['overview', 'examples', 'variants', 'api', 'slots', 'full'])
141
+ .optional()
142
+ .describe(
143
+ 'Section to return. overview, examples, variants, api (full props table), slots, or full (complete docs). Omit for compact summary.'
144
+ )
145
+ },
146
+ { readOnlyHint: true },
147
+ async ({ name, section }) => {
148
+ const content = await loadComponentLlmTxt(name);
149
+
150
+ if (!content) {
151
+ return {
152
+ content: [
153
+ {
154
+ type: 'text' as const,
155
+ text: `Component "${name}" not found. Use \`find_components\` to browse available components.`
156
+ }
157
+ ]
158
+ };
159
+ }
160
+
161
+ // section="full" → return complete llm.txt (old default behavior)
162
+ if (section === 'full') {
163
+ return {
164
+ content: [{ type: 'text' as const, text: content }]
165
+ };
166
+ }
167
+
168
+ // Specific section → extract and return
169
+ if (section) {
170
+ const extracted = extractSection(content, section);
171
+
172
+ if (!extracted) {
173
+ return {
174
+ content: [
175
+ {
176
+ type: 'text' as const,
177
+ text: `Section "${section}" not found for component "${name}". Available sections: ${VALID_SECTIONS.join(', ')}, full.`
178
+ }
179
+ ]
180
+ };
181
+ }
182
+
183
+ return {
184
+ content: [{ type: 'text' as const, text: extracted }]
185
+ };
186
+ }
187
+
188
+ // No section → compact summary from catalog + llm.txt
189
+ const catalog = await loadCatalog();
190
+ const entry = catalog.components.find((c) => c.slug === name);
191
+
192
+ if (entry) {
193
+ return {
194
+ content: [{ type: 'text' as const, text: generateCompactView(entry, content) }]
195
+ };
196
+ }
197
+
198
+ // Fallback: if not in catalog, return full content
199
+ return {
200
+ content: [{ type: 'text' as const, text: content }]
201
+ };
202
+ }
203
+ );
204
+ }
@@ -0,0 +1,446 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+
4
+ const OVERVIEW = `# Urbicon UI — CSS Design Tokens
5
+
6
+ ## Architecture
7
+ Three CSS layers, imported in order:
8
+ 1. \`foundation.css\` — Raw OKLCH color scales (neutral, primary, secondary, success, warning, danger)
9
+ 2. \`semantic.css\` — Purpose-based tokens that reference foundation (\`--color-surface-base\`, \`--color-text-primary\`, etc.)
10
+ 3. \`interaction.css\` — Animation timing, easing, shadows, focus rings
11
+
12
+ ## Naming Convention
13
+ ALL semantic tokens use the \`--color-*\` CSS variable prefix.
14
+ Tailwind utilities map directly:
15
+
16
+ | Tailwind Utility | CSS Variable |
17
+ |---|---|
18
+ | \`bg-surface-base\` | \`var(--color-surface-base)\` |
19
+ | \`text-text-primary\` | \`var(--color-text-primary)\` |
20
+ | \`border-border-default\` | \`var(--color-border-default)\` |
21
+ | \`bg-primary\` | \`var(--color-primary)\` |
22
+ | \`text-success\` | \`var(--color-success)\` |
23
+
24
+ ## Theme Override
25
+ Override tokens in your app CSS using Tailwind's \`@theme\` block:
26
+ \`\`\`css
27
+ @theme {
28
+ --color-surface-base: #080818;
29
+ --color-surface-elevated: #0e0e2a;
30
+ --color-text-primary: #e0e0ff;
31
+ }
32
+ \`\`\`
33
+
34
+ ## Dark Mode
35
+ Mechanism: \`@media (prefers-color-scheme: dark)\` on \`:root\` in \`semantic.css\`.
36
+ Manual override: add \`.light\` or \`.dark\` class to \`<html>\` (via \`ThemeSwitcher\` component).
37
+
38
+ To override a token for ALL modes (light, dark, and manual overrides):
39
+ \`\`\`css
40
+ @theme {
41
+ --color-surface-base: #080818;
42
+ }
43
+ :root, :root.light, :root.dark {
44
+ --color-surface-base: #080818;
45
+ }
46
+ \`\`\`
47
+ The \`@theme\` block sets the Tailwind utility value. The \`:root\` rule overrides the runtime value for all theme modes.
48
+
49
+ ## Available Sections
50
+ → \`get_css_reference(section="surfaces")\` — 11 surface background tokens
51
+ → \`get_css_reference(section="text")\` — 9 text color tokens
52
+ → \`get_css_reference(section="borders")\` — 4 border color tokens
53
+ → \`get_css_reference(section="intents")\` — 6 intent palettes (primary, success, danger, etc.)
54
+ → \`get_css_reference(section="shadows")\` — 5 shadow tokens + z-index scale
55
+ → \`get_css_reference(section="theming")\` — How to create custom themes, available presets
56
+ `;
57
+
58
+ const SURFACES = `# Surface Tokens
59
+
60
+ 11 tokens for background colors. All auto-switch in dark mode.
61
+
62
+ | CSS Variable | Tailwind Utility | Purpose |
63
+ |---|---|---|
64
+ | \`--color-surface-base\` | \`bg-surface-base\` | Page background |
65
+ | \`--color-surface-quiet\` | \`bg-surface-quiet\` | Softly tinted in-page zone (Lighter default) |
66
+ | \`--color-surface-subtle\` | \`bg-surface-subtle\` | Subtle differentiation |
67
+ | \`--color-surface-elevated\` | \`bg-surface-elevated\` | Cards, panels (floating with shadow) |
68
+ | \`--color-surface-overlay\` | \`bg-surface-overlay\` | Modals, popovers |
69
+ | \`--color-surface-interactive\` | \`bg-surface-interactive\` | Interactive element backgrounds |
70
+ | \`--color-surface-hover\` | \`bg-surface-hover\` | Hover state |
71
+ | \`--color-surface-active\` | \`bg-surface-active\` | Active/pressed state |
72
+ | \`--color-surface-disabled\` | \`bg-surface-disabled\` | Disabled elements |
73
+ | \`--color-surface-selected\` | \`bg-surface-selected\` | Selected items (uses primary-50) |
74
+ | \`--color-surface-inverted\` | \`bg-surface-inverted\` | Inverted surfaces (tooltips) |
75
+
76
+ Light → Dark mapping examples:
77
+ - \`surface-base\`: neutral-0 (white) → neutral-900 (near-black)
78
+ - \`surface-quiet\`: neutral-25 → neutral-850
79
+ - \`surface-elevated\`: neutral-50 → neutral-800
80
+ - \`surface-hover\`: neutral-100 → neutral-750
81
+
82
+ Override example (dark neon theme):
83
+ \`\`\`css
84
+ @theme {
85
+ --color-surface-base: #080818;
86
+ --color-surface-quiet: #0a0a20;
87
+ --color-surface-elevated: #0e0e2a;
88
+ --color-surface-overlay: #12123a;
89
+ }
90
+ \`\`\`
91
+ `;
92
+
93
+ const TEXT = `# Text Tokens
94
+
95
+ 9 tokens for text colors. All auto-switch in dark mode.
96
+
97
+ | CSS Variable | Tailwind Utility | Purpose |
98
+ |---|---|---|
99
+ | \`--color-text-primary\` | \`text-text-primary\` | Main text |
100
+ | \`--color-text-secondary\` | \`text-text-secondary\` | Supporting text |
101
+ | \`--color-text-tertiary\` | \`text-text-tertiary\` | Muted text, metadata |
102
+ | \`--color-text-quaternary\` | \`text-text-quaternary\` | Most subtle text |
103
+ | \`--color-text-disabled\` | \`text-text-disabled\` | Disabled text |
104
+ | \`--color-text-inverted\` | \`text-text-inverted\` | Text on inverted surfaces |
105
+ | \`--color-text-on-primary\` | \`text-text-on-primary\` | Text on intent-colored backgrounds |
106
+ | \`--color-text-on-dark\` | \`text-text-on-dark\` | Text on dark surfaces |
107
+ | \`--color-text-on-surface\` | \`text-text-on-surface\` | Text on any surface (auto-contrast) |
108
+
109
+ Light → Dark mapping:
110
+ - \`text-primary\`: neutral-900 (dark) → neutral-100 (light)
111
+ - \`text-secondary\`: neutral-700 → neutral-300
112
+ - \`text-tertiary\`: neutral-500 → neutral-400
113
+ `;
114
+
115
+ const BORDERS = `# Border Tokens
116
+
117
+ 4 tokens for border colors. All auto-switch in dark mode.
118
+
119
+ | CSS Variable | Tailwind Utility | Purpose |
120
+ |---|---|---|
121
+ | \`--color-border-subtle\` | \`border-border-subtle\` | Gentle grouping |
122
+ | \`--color-border-default\` | \`border-border-default\` | Standard borders |
123
+ | \`--color-border-emphasis\` | \`border-border-emphasis\` | Emphasized borders |
124
+ | \`--color-border-strong\` | \`border-border-strong\` | High-contrast borders |
125
+
126
+ Light → Dark mapping:
127
+ - \`border-subtle\`: neutral-200 → neutral-700
128
+ - \`border-default\`: neutral-300 → neutral-600
129
+ - \`border-emphasis\`: neutral-400 → neutral-500
130
+ - \`border-strong\`: neutral-500 → neutral-400
131
+
132
+ Also available for intent-colored borders:
133
+ \`border-primary\`, \`border-success\`, \`border-warning\`, \`border-danger\`, \`border-secondary\`, \`border-neutral\`
134
+ `;
135
+
136
+ const INTENTS = `# Intent Color System
137
+
138
+ 6 intent palettes, each with 5 semantic variants + 11 foundation steps.
139
+
140
+ ## Semantic Intent Tokens (auto dark mode)
141
+
142
+ Each intent has these variants (example: \`primary\`):
143
+
144
+ | CSS Variable | Tailwind Utility | Purpose | Light | Dark |
145
+ |---|---|---|---|---|
146
+ | \`--color-primary\` | \`bg-primary\` / \`text-primary\` | Base intent color | primary-600 | primary-500 |
147
+ | \`--color-primary-hover\` | \`bg-primary-hover\` | Hover state | primary-700 | primary-400 |
148
+ | \`--color-primary-active\` | \`bg-primary-active\` | Pressed state | primary-800 | — |
149
+ | \`--color-primary-subtle\` | \`bg-primary-subtle\` | Soft background | primary-50 | primary-900 |
150
+ | \`--color-primary-emphasis\` | \`bg-primary-emphasis\` | Strong/dark variant | primary-900 | — |
151
+
152
+ Same pattern applies to: \`success-*\`, \`warning-*\`, \`danger-*\`, \`secondary-*\`, \`neutral-*\`
153
+
154
+ ## Foundation Intent Scales
155
+
156
+ Each intent has 11 numbered steps (50–950) for granular control:
157
+ \`\`\`
158
+ --color-primary-50 through --color-primary-950
159
+ --color-success-50 through --color-success-950
160
+ --color-warning-50 through --color-warning-950
161
+ --color-danger-50 through --color-danger-950
162
+ --color-secondary-50 through --color-secondary-950
163
+ --color-neutral-50 through --color-neutral-950
164
+ \`\`\`
165
+
166
+ Tailwind usage: \`bg-primary-500\`, \`text-danger-700\`, \`border-success-300\`, etc.
167
+
168
+ ## Feedback Tokens (for status messages)
169
+
170
+ | CSS Variable | Tailwind Utility | Maps to |
171
+ |---|---|---|
172
+ | \`--color-feedback-info\` | \`bg-feedback-info\` / \`text-feedback-info\` | primary-500 |
173
+ | \`--color-feedback-info-subtle\` | \`bg-feedback-info-subtle\` | primary-50 |
174
+ | \`--color-feedback-success\` | \`bg-feedback-success\` / \`text-feedback-success\` | success-500 |
175
+ | \`--color-feedback-success-subtle\` | \`bg-feedback-success-subtle\` | success-50 |
176
+ | \`--color-feedback-warning\` | \`bg-feedback-warning\` / \`text-feedback-warning\` | warning-500 |
177
+ | \`--color-feedback-warning-subtle\` | \`bg-feedback-warning-subtle\` | warning-50 |
178
+ | \`--color-feedback-error\` | \`bg-feedback-error\` / \`text-feedback-error\` | danger-500 |
179
+ | \`--color-feedback-error-subtle\` | \`bg-feedback-error-subtle\` | danger-50 |
180
+
181
+ ## Interactive Tokens
182
+
183
+ | CSS Variable | Tailwind Utility | Purpose |
184
+ |---|---|---|
185
+ | \`--color-interactive-hover\` | \`bg-interactive-hover\` | 10% primary overlay |
186
+ | \`--color-interactive-active\` | \`bg-interactive-active\` | 20% primary overlay |
187
+ | \`--color-interactive-focus\` | \`ring-interactive-focus\` | Focus ring color (primary-500) |
188
+ | \`--color-interactive-disabled\` | \`bg-interactive-disabled\` | Disabled state (neutral-200) |
189
+
190
+ ## Override Example: Custom Intent
191
+ \`\`\`css
192
+ @theme {
193
+ --color-primary-50: oklch(0.95 0.03 280);
194
+ --color-primary-100: oklch(0.9 0.06 280);
195
+ --color-primary-200: oklch(0.82 0.09 280);
196
+ --color-primary-300: oklch(0.74 0.12 280);
197
+ --color-primary-400: oklch(0.66 0.14 280);
198
+ --color-primary-500: oklch(0.58 0.14 280);
199
+ --color-primary-600: oklch(0.52 0.14 280);
200
+ --color-primary-700: oklch(0.44 0.12 280);
201
+ --color-primary-800: oklch(0.36 0.1 280);
202
+ --color-primary-900: oklch(0.28 0.07 280);
203
+ --color-primary-950: oklch(0.18 0.04 280);
204
+ }
205
+ \`\`\`
206
+ Semantic tokens (\`--color-primary\`, \`--color-primary-hover\`, etc.) automatically reference the new scale.
207
+ `;
208
+
209
+ const SHADOWS = `# Shadow & Z-Index Tokens
210
+
211
+ ## Shadow Tokens
212
+
213
+ | CSS Variable | Tailwind Utility | Purpose |
214
+ |---|---|---|
215
+ | \`--color-shadow-xs\` | \`shadow-[var(--blocks-shadow-xs)]\` | Minimal shadow |
216
+ | \`--color-shadow-sm\` | \`shadow-[var(--blocks-shadow-sm)]\` | Buttons, small elements |
217
+ | \`--color-shadow-base\` | \`shadow-[var(--blocks-shadow-base)]\` | Default elevation |
218
+ | \`--color-shadow-md\` | \`shadow-[var(--blocks-shadow-md)]\` | Hover states, menus |
219
+ | \`--color-shadow-lg\` | \`shadow-[var(--blocks-shadow-lg)]\` | Modals, popovers |
220
+
221
+ Shadows automatically increase opacity in dark mode (0.05 → 0.2 for xs, etc.).
222
+
223
+ ## Z-Index Scale
224
+
225
+ | CSS Variable | Value | Purpose |
226
+ |---|---|---|
227
+ | \`--z-hide\` | -1 | Hidden |
228
+ | \`--z-base\` | 0 | Default |
229
+ | \`--z-docked\` | 10 | Docked elements |
230
+ | \`--z-dropdown\` | 9999 | Menus, listboxes, floating panels |
231
+ | \`--z-sticky\` | 1100 | Sticky headers |
232
+ | \`--z-banner\` | 1200 | Banners |
233
+ | \`--z-overlay\` | 1300 | Overlays |
234
+ | \`--z-sidebar\` | 1350 | Sidebars |
235
+ | \`--z-modal\` | 1400 | Modals |
236
+ | \`--z-popover\` | 1500 | Popovers |
237
+ | \`--z-toast\` | 1700 | Toasts |
238
+ | \`--z-tooltip\` | 1800 | Tooltips |
239
+
240
+ Usage: \`z-[var(--z-modal)]\`
241
+
242
+ ## Border Radius Scale
243
+
244
+ | CSS Variable | Tailwind | Value |
245
+ |---|---|---|
246
+ | \`--radius-xs\` | \`rounded-xs\` | 0.125rem |
247
+ | \`--radius-sm\` | \`rounded-sm\` | 0.25rem |
248
+ | \`--radius-md\` | \`rounded-md\` | 0.375rem |
249
+ | \`--radius-lg\` | \`rounded-lg\` | 0.5rem |
250
+ | \`--radius-xl\` | \`rounded-xl\` | 0.75rem |
251
+ | \`--radius-2xl\` | \`rounded-2xl\` | 1rem |
252
+ | \`--radius-3xl\` | \`rounded-3xl\` | 1.5rem |
253
+ | \`--radius-4xl\` | \`rounded-4xl\` | 2rem |
254
+
255
+ ## Duration & Easing Tokens
256
+
257
+ | CSS Variable | Value |
258
+ |---|---|
259
+ | \`--blocks-duration-instant\` | 75ms |
260
+ | \`--blocks-duration-fast\` | 150ms |
261
+ | \`--blocks-duration-normal\` | 250ms |
262
+ | \`--blocks-duration-slow\` | 350ms |
263
+ | \`--blocks-duration-slower\` | 500ms |
264
+ | \`--blocks-ease-confident\` | Standard transitions |
265
+ | \`--blocks-ease-springy\` | Bouncy animations |
266
+ | \`--blocks-ease-smooth\` | Gentle animations |
267
+ | \`--blocks-ease-snappy\` | Quick, decisive |
268
+ `;
269
+
270
+ const THEMING = `# Theming Guide
271
+
272
+ ## Built-in Themes
273
+
274
+ Import AFTER base styles:
275
+ \`\`\`css
276
+ @import '@urbicon-ui/blocks/style/index.css';
277
+ @import '@urbicon-ui/blocks/style/themes/ocean.css';
278
+ \`\`\`
279
+
280
+ Available: \`ocean.css\` (blue-teal), \`forest.css\` (green), \`sunset.css\` (orange-amber), \`rose.css\` (pink), \`neutral.css\` (grayscale)
281
+
282
+ Each theme overrides three foundation ramps: \`--color-primary-*\`, \`--color-secondary-*\`, and \`--color-neutral-*\`. The neutral ramp is the chassis — surfaces, text and borders all derive from it (see below), so each theme re-tints it to match the accent's temperature (Sunset shifts it warm, Ocean cool). Each colored theme additionally re-tunes only the intent ramps that would collide with its accent (e.g. Forest pushes success/warning off its green/lime, Ocean moves info off the brand blue), sets a matching \`--blocks-shadow-tint\`, and re-tints the NEUTRAL intent (\`bg-neutral\` / \`text-neutral\` / neutral borders) to its temperature via \`--neutral-chrome-hue\`. Other non-colliding intents (success/warning/danger/info) stay at the library defaults. The Neutral theme inherits the cool default chrome and leaves those intents + shadow tint untouched.
283
+
284
+ The neutral intent is special: it keeps the \`--color-warm-neutral-*\` ramp's lightness profile (tuned so white-on-\`bg-neutral\` AND \`text-neutral\`-on-surface both stay legible across light/dark) and only swaps the hue via \`--neutral-chrome-hue\`. Set that token (default 240) in a \`:root\` rule to re-tint neutral controls without touching contrast — e.g. \`:root { --neutral-chrome-hue: 50; }\` for a warm theme. Do NOT repoint the neutral intent onto the chassis \`--color-neutral-*\` ramp: its surface-tuned lightness breaks the white-text / neutral-text contrast balance.
285
+
286
+ ## The neutral ramp IS the chassis — match its temperature to your accent
287
+
288
+ This is the single most common theming mistake: recolor \`--color-primary-*\` only, ship it, and the warm brand button ends up sitting on cool blue-grey cards. Reason: \`surface-*\`, \`text-*\` and \`border-*\` do NOT derive from primary — they derive from \`--color-neutral-*\`, which defaults to a cool Hue 240. A warm accent on a Hue-240 chassis reads broken.
289
+
290
+ Fix: when you change the accent hue, re-tint the neutral ramp to the same temperature family. Keep each stop's lightness and chroma identical to the foundation ramp (chroma stays tiny, ≤0.017) and shift only the hue — this keeps the chassis near-grey, preserves WCAG contrast, and the warmth/coolness flows automatically to every surface, text and border token via \`semantic.css\`. No per-token surface overrides needed.
291
+
292
+ ## Creating a Custom Theme
293
+
294
+ Pick an accent hue (0–360) and a chassis hue in the same temperature family (often the accent hue itself, or pulled slightly toward grey). Generate matched OKLCH ramps — the Theme Builder at \`/customization/theme-builder\` does this for you:
295
+ \`\`\`css
296
+ /* my-theme.css — warm brand on a warm chassis */
297
+ @theme {
298
+ /* Primary: hue 280 (purple) */
299
+ --color-primary-50: oklch(0.95 0.03 280);
300
+ --color-primary-100: oklch(0.9 0.06 280);
301
+ --color-primary-200: oklch(0.82 0.09 280);
302
+ --color-primary-300: oklch(0.74 0.12 280);
303
+ --color-primary-400: oklch(0.66 0.14 280);
304
+ --color-primary-500: oklch(0.58 0.14 280);
305
+ --color-primary-600: oklch(0.52 0.14 280);
306
+ --color-primary-700: oklch(0.44 0.12 280);
307
+ --color-primary-800: oklch(0.36 0.1 280);
308
+ --color-primary-900: oklch(0.28 0.07 280);
309
+ --color-primary-950: oklch(0.18 0.04 280);
310
+
311
+ /* Secondary: hue 320 (magenta) */
312
+ --color-secondary-50: oklch(0.95 0.03 320);
313
+ /* ... same pattern with hue 320 ... */
314
+ --color-secondary-950: oklch(0.18 0.04 320);
315
+
316
+ /* Chassis: neutral ramp re-tinted to hue 290 (matches the purple accent).
317
+ Same L + C as the foundation neutral ramp — only the hue moves. */
318
+ --color-neutral-25: oklch(0.99 0.002 290);
319
+ --color-neutral-50: oklch(0.98 0.005 290);
320
+ --color-neutral-100: oklch(0.95 0.008 290);
321
+ --color-neutral-200: oklch(0.89 0.012 290);
322
+ --color-neutral-300: oklch(0.83 0.014 290);
323
+ --color-neutral-400: oklch(0.7 0.015 290);
324
+ --color-neutral-500: oklch(0.55 0.016 290);
325
+ --color-neutral-600: oklch(0.42 0.017 290);
326
+ --color-neutral-650: oklch(0.38 0.016 290);
327
+ --color-neutral-700: oklch(0.32 0.016 290);
328
+ --color-neutral-750: oklch(0.28 0.014 290);
329
+ --color-neutral-800: oklch(0.23 0.015 290);
330
+ --color-neutral-850: oklch(0.18 0.014 290);
331
+ --color-neutral-900: oklch(0.15 0.012 290);
332
+ --color-neutral-950: oklch(0.08 0.008 290);
333
+ }
334
+ \`\`\`
335
+ For a temperature-free, true grayscale chassis (content-focused UIs), set chroma to 0 on every neutral stop instead of shifting the hue.
336
+
337
+ ## Intent-hue collisions
338
+
339
+ If your accent hue lands near an intent hue, the two become hard to tell apart. The library intents sit at: success 140, warning 80, danger 25, info 220, secondary 280. A green brand (~140) collides with success; an amber brand (~80) collides with warning. When that happens, re-tune the colliding intent ramp away from the accent (push the hue ±15–25° and/or drop its lightness so it reads as "status", not "brand"). \`apps/docs/src/lib/style/editorial.css\` is a worked example (green brand → success pushed to 150 + darkened, warning pulled to amber 55).
340
+
341
+ ## Overriding Semantic Tokens
342
+
343
+ To override semantic tokens directly (e.g. a fully art-directed dark theme, or custom surface stops beyond what re-tinting the neutral ramp gives you):
344
+ \`\`\`css
345
+ @theme {
346
+ --color-surface-base: #080818;
347
+ --color-surface-elevated: #0e0e2a;
348
+ --color-text-primary: #e0e0ff;
349
+ }
350
+ \`\`\`
351
+
352
+ If your override should apply regardless of light/dark mode, also set:
353
+ \`\`\`css
354
+ :root, :root.light, :root.dark {
355
+ --color-surface-base: #080818;
356
+ --color-surface-elevated: #0e0e2a;
357
+ --color-text-primary: #e0e0ff;
358
+ }
359
+ \`\`\`
360
+
361
+ ## Gotcha: SCOPED themes must re-declare derived tokens
362
+
363
+ This trap only applies when you scope a theme to a CLASS (e.g. \`.theme-sunset { ... }\` toggled at runtime) instead of a global \`@theme\`/\`:root\` block.
364
+
365
+ Custom-property substitution happens per element at computed-value time, and inheritance passes the ALREADY-substituted value. \`semantic.css\` defines derived tokens at \`:root\`, e.g.
366
+ \`\`\`css
367
+ :root { --color-primary: light-dark(var(--color-primary-600), var(--color-primary-500)); }
368
+ \`\`\`
369
+ That \`var(--color-primary-600)\` resolves against the \`:root\` ramp. If you override \`--color-primary-600\` only inside \`.theme-sunset\`, the element inherits the already-resolved (default) \`--color-primary\` — your new ramp is ignored for every derived token.
370
+
371
+ Fix: inside the scoped block, re-declare the derived tokens too, so substitution re-runs at that level:
372
+ \`\`\`css
373
+ .theme-sunset {
374
+ --color-primary-600: oklch(0.55 0.15 55);
375
+ /* ...rest of the ramp... */
376
+
377
+ /* re-declare so the derived tokens re-substitute against the new ramp */
378
+ --color-primary: light-dark(var(--color-primary-600), var(--color-primary-500));
379
+ --color-primary-hover: light-dark(var(--color-primary-700), var(--color-primary-400));
380
+ --color-primary-subtle: light-dark(var(--color-primary-50), var(--color-primary-900));
381
+ /* ...and the same for any neutral-derived surface/text/border tokens you rely on */
382
+ }
383
+ \`\`\`
384
+ A global \`@theme\` block (the built-in themes, the Theme Builder output) does NOT hit this — everything lands on \`:root\`, the same element where the derived tokens compute, so re-declaration is unnecessary. Prefer global themes unless you genuinely need multiple themes live on one page. \`apps/docs/src/lib/style/editorial.css\` is the canonical scoped example.
385
+
386
+ ## Component-Level Overrides
387
+
388
+ Use \`BlocksProvider\` to change component defaults globally:
389
+ \`\`\`svelte
390
+ <BlocksProvider defaults={{
391
+ Card: { slotClasses: { base: 'rounded-2xl shadow-lg' } },
392
+ Button: { slotClasses: { base: 'rounded-full font-bold uppercase' } }
393
+ }}>
394
+ \`\`\`
395
+
396
+ Or override per-instance:
397
+ \`\`\`svelte
398
+ <Card class="rounded-2xl" padding="lg">...</Card>
399
+ <Button unstyled slotClasses={{ base: 'custom-button-class' }}>...</Button>
400
+ \`\`\`
401
+
402
+ ## Tailwind 4 — Scanning Component Classes
403
+
404
+ Tailwind 4 does not scan \`node_modules\` by default, so the responsive utilities (\`lg:hidden\`, \`md:grid-cols-2\`, etc.) used inside Urbicon UI components must be registered as content sources. **The library does this for you:** \`@urbicon-ui/blocks/style/index.css\` ships the \`@source\` directives that point Tailwind at the component classes, and \`@urbicon-ui/table/style/index.css\` does the same for the Table.
405
+
406
+ So the only requirement is to import \`index.css\` (your app owns the Tailwind import; it comes first):
407
+ \`\`\`css
408
+ @import 'tailwindcss';
409
+ @import '@urbicon-ui/blocks/style/index.css'; /* tokens + @source directives */
410
+ @import '@urbicon-ui/table/style/index.css'; /* if using Table */
411
+ \`\`\`
412
+
413
+ **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
+ `;
415
+
416
+ const SECTIONS: Record<string, string> = {
417
+ surfaces: SURFACES,
418
+ text: TEXT,
419
+ borders: BORDERS,
420
+ intents: INTENTS,
421
+ shadows: SHADOWS,
422
+ theming: THEMING
423
+ };
424
+
425
+ export function registerGetCssReferenceTool(server: McpServer): void {
426
+ server.tool(
427
+ 'get_css_reference',
428
+ 'Get CSS variable names, Tailwind utility mappings, and override patterns for the Urbicon UI design token system. Essential for theming and custom styling.',
429
+ {
430
+ section: z
431
+ .enum(['surfaces', 'text', 'borders', 'intents', 'shadows', 'theming'])
432
+ .optional()
433
+ .describe(
434
+ 'Token category. Omit for overview with naming conventions and dark mode mechanism.'
435
+ )
436
+ },
437
+ { readOnlyHint: true },
438
+ async ({ section }) => {
439
+ const text = section ? SECTIONS[section] : OVERVIEW;
440
+
441
+ return {
442
+ content: [{ type: 'text' as const, text: text ?? OVERVIEW }]
443
+ };
444
+ }
445
+ );
446
+ }