@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,127 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+
4
+ /**
5
+ * MCP prompts that ship the *process* — the generate → validate → judge →
6
+ * synthesise loop (docs/DESIGN-MCP.md, Option E). MCP prompts are the
7
+ * client-agnostic way to deliver a workflow: any MCP client (Claude Code,
8
+ * Cursor, …) can invoke them, and they orchestrate the server's own tools
9
+ * (get_design_context, get_pattern, validate_design, get_design_principles).
10
+ *
11
+ * The creative loop itself runs in the consumer's harness (it needs file access
12
+ * and iteration); these prompts encode the steps so a single-shot generation
13
+ * doesn't regress to the mean.
14
+ */
15
+
16
+ /** Clamp the requested variant count to a sane range. Prompt args arrive as strings. */
17
+ export function variantCount(raw: string | undefined): number {
18
+ const n = Number.parseInt(raw ?? '', 10);
19
+ if (!Number.isFinite(n)) return 3;
20
+ return Math.min(5, Math.max(2, n));
21
+ }
22
+
23
+ function patternStep(pattern: string | undefined): string {
24
+ return pattern
25
+ ? `call \`get_pattern("${pattern}")\` and follow its layout, component-selection, and behavioural rules`
26
+ : 'if a composition pattern fits the brief (settings-page, dashboard, form-page, tab-navigation, onboarding-guide), call `get_pattern("<name>")` to load it';
27
+ }
28
+
29
+ const FOOTER =
30
+ 'Output the final code, then a one-line rationale for each major design choice. Keep the rationale honest — name the trade-offs.';
31
+
32
+ export function designPagePrompt(
33
+ brief: string,
34
+ pattern: string | undefined,
35
+ variants: string | undefined
36
+ ): string {
37
+ const n = variantCount(variants);
38
+ return `You are designing a new page for a project built on Urbicon UI:
39
+
40
+ > **${brief}**
41
+
42
+ Run this loop. Do not skip steps — a single-shot answer regresses to a generic template.
43
+
44
+ 1. **Context.** Call \`get_design_context\` and honour the project's paradigm, theme, density, and recorded decisions (ADRs). Then ${patternStep(pattern)}.
45
+ 2. **Ground rules.** Call \`get_design_principles\` for the heuristics and \`get_css_reference\` for the exact token names. Note the paradigm's token profile via \`get_design_principles(topic="theming")\`.
46
+ 3. **Generate ${n} variants.** Produce ${n} genuinely different implementations, each taking a distinct compositional approach *within* the paradigm — vary density, hierarchy emphasis, and the one signature moment. Do not let them converge. Use only real semantic tokens (no \`bg-status-*\`, no invented names).
47
+ 4. **Validate.** Run \`validate_design\` on every variant. Fix each error and warning. A variant that cannot pass is disqualified.
48
+ 5. **Judge.** Call \`get_design_principles(as="rubric")\` and score each surviving variant /40. Prefer a panel: judge correctness, hierarchy, paradigm-fidelity, and distinctiveness as separate lenses rather than one overall gut number.
49
+ 6. **Synthesise.** Pick the winner, then graft the best ideas from the runners-up. Run \`validate_design\` once more on the merged result — it must come back clean.
50
+ 7. **Record.** If the page follows a pattern, add \`data-design-pattern="<name>"\` to its root element and call \`sync_design_manifest\`. If you deviated from a pattern or principle on purpose, call \`record_design_decision\`.
51
+
52
+ ${FOOTER}`;
53
+ }
54
+
55
+ export function redesignPrompt(
56
+ brief: string,
57
+ code: string | undefined,
58
+ variants: string | undefined
59
+ ): string {
60
+ const n = variantCount(variants);
61
+ const current = code
62
+ ? `\n\nCurrent implementation:\n\n\`\`\`svelte\n${code}\n\`\`\``
63
+ : '\n\nFirst read the current implementation of the page in question.';
64
+ return `You are redesigning an existing page in a project built on Urbicon UI:
65
+
66
+ > **${brief}**${current}
67
+
68
+ Run a diagnosis-first loop:
69
+
70
+ 1. **Context.** Call \`get_design_context\` to recover the project's paradigm, theme, and prior decisions.
71
+ 2. **Diagnose.** Run \`validate_design\` on the current code, then call \`get_design_principles(as="rubric")\` and score the current page /40. Your revision targets are **every linter finding** plus the **two lowest-scoring criteria** — nothing else.
72
+ 3. **Generate ${n} variants** that fix exactly those weaknesses. Preserve the page's behaviour, data flow, and overall structure; change only what the diagnosis flagged. Use only real tokens.
73
+ 4. **Validate.** Run \`validate_design\` on each; fix every error and warning.
74
+ 5. **Judge.** Re-score each variant with the rubric. A redesign that does not beat the original on its target criteria is not shippable.
75
+ 6. **Synthesise.** Merge the best result, then run \`validate_design\` once more.
76
+ 7. **Record.** Call \`record_design_decision\` for any deliberate deviation; \`sync_design_manifest\` if pattern usage changed.
77
+
78
+ End with a before/after table of the targeted criteria (old score → new score) and ${FOOTER.toLowerCase()}`;
79
+ }
80
+
81
+ export function registerDesignPrompts(server: McpServer): void {
82
+ server.prompt(
83
+ 'design-page',
84
+ 'Design a new page with the full generate → validate → judge → synthesise loop (variant exploration + rubric selection + linter gate). Keeps generation from regressing to a generic template.',
85
+ {
86
+ brief: z.string().describe('What to build, e.g. "a billing settings page for a SaaS admin".'),
87
+ pattern: z
88
+ .string()
89
+ .optional()
90
+ .describe(
91
+ 'Composition pattern to follow (settings-page, dashboard, form-page, …). Optional.'
92
+ ),
93
+ variants: z.string().optional().describe('How many variants to explore (2–5, default 3).')
94
+ },
95
+ ({ brief, pattern, variants }) => ({
96
+ messages: [
97
+ {
98
+ role: 'user' as const,
99
+ content: { type: 'text' as const, text: designPagePrompt(brief, pattern, variants) }
100
+ }
101
+ ]
102
+ })
103
+ );
104
+
105
+ server.prompt(
106
+ 'redesign',
107
+ 'Redesign an existing page: diagnose with validate_design + the rubric, then fix exactly the flagged weaknesses through variant exploration. Preserves behaviour and structure.',
108
+ {
109
+ brief: z
110
+ .string()
111
+ .describe('What to redesign and why, e.g. "the dashboard feels flat and generic".'),
112
+ code: z
113
+ .string()
114
+ .optional()
115
+ .describe('The current page source. Optional — omit to have the model read it first.'),
116
+ variants: z.string().optional().describe('How many variants to explore (2–5, default 3).')
117
+ },
118
+ ({ brief, code, variants }) => ({
119
+ messages: [
120
+ {
121
+ role: 'user' as const,
122
+ content: { type: 'text' as const, text: redesignPrompt(brief, code, variants) }
123
+ }
124
+ ]
125
+ })
126
+ );
127
+ }
@@ -0,0 +1,23 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { loadCatalog } from '../data/catalog-loader.js';
3
+ import { formatCompactCatalog } from '../utils/format-catalog.js';
4
+
5
+ export function registerCatalogResource(server: McpServer): void {
6
+ server.resource('catalog', 'urbicon://catalog', async (uri) => {
7
+ const catalog = await loadCatalog();
8
+
9
+ const md = formatCompactCatalog(catalog.components, {
10
+ recipes: catalog.recipes
11
+ });
12
+
13
+ return {
14
+ contents: [
15
+ {
16
+ uri: uri.href,
17
+ mimeType: 'text/markdown',
18
+ text: md
19
+ }
20
+ ]
21
+ };
22
+ });
23
+ }
@@ -0,0 +1,60 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { TemplateSections } from '../data/template-loader.js';
3
+ import { loadTemplateSections } from '../data/template-loader.js';
4
+
5
+ const GUIDE_RESOURCES: { id: string; name: string; key: keyof TemplateSections }[] = [
6
+ {
7
+ id: 'api-grammar',
8
+ name: 'API Grammar Guide',
9
+ key: 'api-grammar'
10
+ },
11
+ {
12
+ id: 'component-families',
13
+ name: 'Component Families Guide',
14
+ key: 'component-families'
15
+ },
16
+ {
17
+ id: 'tokens',
18
+ name: 'Design Tokens Guide',
19
+ key: 'tokens'
20
+ },
21
+ {
22
+ id: 'design-quality',
23
+ name: 'Design Quality Guide',
24
+ key: 'design-quality'
25
+ },
26
+ {
27
+ id: 'customization',
28
+ name: 'Customization Guide',
29
+ key: 'customization'
30
+ },
31
+ {
32
+ id: 'style-patterns',
33
+ name: 'Style Patterns Guide',
34
+ key: 'style-patterns'
35
+ },
36
+ {
37
+ id: 'auth-setup',
38
+ name: 'Auth Setup Guide',
39
+ key: 'auth-setup'
40
+ }
41
+ ];
42
+
43
+ export function registerGuideResources(server: McpServer): void {
44
+ for (const guide of GUIDE_RESOURCES) {
45
+ server.resource(`guide-${guide.id}`, `urbicon://guide/${guide.id}`, async (uri) => {
46
+ const sections = await loadTemplateSections();
47
+ const content = sections[guide.key];
48
+
49
+ return {
50
+ contents: [
51
+ {
52
+ uri: uri.href,
53
+ mimeType: 'text/markdown',
54
+ text: content || `Guide section "${guide.id}" not found.`
55
+ }
56
+ ]
57
+ };
58
+ });
59
+ }
60
+ }
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createServer } from './server.js';
3
+
4
+ /**
5
+ * Smoke tests for the MCP server assembly. The SDK's `McpServer` keeps its
6
+ * tool/resource maps on private fields (`_registeredTools`, `_registeredResources`,
7
+ * `_registeredResourceTemplates`). We deliberately reach into those here because
8
+ * the point of this test is to guard the wiring in `server.ts` — if a tool
9
+ * registration goes missing or is renamed, this file should tell us about it.
10
+ */
11
+ interface McpServerInternals {
12
+ _registeredTools: Record<string, unknown>;
13
+ _registeredResources: Record<string, unknown>;
14
+ _registeredResourceTemplates: Record<string, unknown>;
15
+ _registeredPrompts: Record<string, unknown>;
16
+ }
17
+
18
+ const EXPECTED_TOOLS = [
19
+ 'find_components',
20
+ 'get_component',
21
+ 'get_recipe',
22
+ 'suggest_implementation',
23
+ 'get_implementation_checklist',
24
+ 'get_css_reference',
25
+ 'find_icons',
26
+ 'get_design_principles',
27
+ 'get_pattern',
28
+ 'validate_design',
29
+ 'get_design_context',
30
+ 'record_design_decision',
31
+ 'sync_design_manifest'
32
+ ] as const;
33
+
34
+ describe('createServer', () => {
35
+ it('boots without throwing', () => {
36
+ expect(() => createServer()).not.toThrow();
37
+ });
38
+
39
+ it('returns an object', () => {
40
+ const server = createServer();
41
+ expect(server).toBeTypeOf('object');
42
+ expect(server).not.toBeNull();
43
+ });
44
+
45
+ it('registers exactly the expected set of tools', () => {
46
+ const server = createServer() as unknown as McpServerInternals;
47
+ const toolNames = Object.keys(server._registeredTools);
48
+
49
+ for (const expected of EXPECTED_TOOLS) {
50
+ expect(toolNames, `missing tool: ${expected}`).toContain(expected);
51
+ }
52
+ expect(toolNames).toHaveLength(EXPECTED_TOOLS.length);
53
+ });
54
+
55
+ it('registers at least one catalog resource', () => {
56
+ const server = createServer() as unknown as McpServerInternals;
57
+ const resourceCount =
58
+ Object.keys(server._registeredResources).length +
59
+ Object.keys(server._registeredResourceTemplates).length;
60
+ expect(resourceCount).toBeGreaterThan(0);
61
+ });
62
+
63
+ it('registers the design-process prompts', () => {
64
+ const server = createServer() as unknown as McpServerInternals;
65
+ const promptNames = Object.keys(server._registeredPrompts);
66
+ expect(promptNames).toContain('design-page');
67
+ expect(promptNames).toContain('redesign');
68
+ });
69
+ });
package/src/server.ts ADDED
@@ -0,0 +1,48 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { registerDesignPrompts } from './prompts/design-prompts.js';
3
+ import { registerCatalogResource } from './resources/catalog.js';
4
+ import { registerGuideResources } from './resources/guides.js';
5
+ import { registerFindComponentsTool } from './tools/find-components.js';
6
+ import { registerFindIconsTool } from './tools/find-icons.js';
7
+ import { registerGetChecklistTool } from './tools/get-checklist.js';
8
+ import { registerGetComponentTool } from './tools/get-component.js';
9
+ import { registerGetCssReferenceTool } from './tools/get-css-reference.js';
10
+ import { registerGetDesignContextTool } from './tools/get-design-context.js';
11
+ import { registerGetDesignPrinciplesTool } from './tools/get-design-principles.js';
12
+ import { registerGetPatternTool } from './tools/get-pattern.js';
13
+ import { registerGetRecipeTool } from './tools/get-recipe.js';
14
+ import { registerRecordDesignDecisionTool } from './tools/record-design-decision.js';
15
+ import { registerSuggestImplementationTool } from './tools/suggest-implementation.js';
16
+ import { registerSyncDesignManifestTool } from './tools/sync-design-manifest.js';
17
+ import { registerValidateDesignTool } from './tools/validate-design.js';
18
+
19
+ export function createServer(): McpServer {
20
+ const server = new McpServer({
21
+ name: 'urbicon-ui',
22
+ version: '0.5.0'
23
+ });
24
+
25
+ // Resources
26
+ registerCatalogResource(server);
27
+ registerGuideResources(server);
28
+
29
+ // Tools
30
+ registerFindComponentsTool(server);
31
+ registerGetComponentTool(server);
32
+ registerGetRecipeTool(server);
33
+ registerSuggestImplementationTool(server);
34
+ registerGetChecklistTool(server);
35
+ registerGetCssReferenceTool(server);
36
+ registerFindIconsTool(server);
37
+ registerGetDesignPrinciplesTool(server);
38
+ registerGetPatternTool(server);
39
+ registerValidateDesignTool(server);
40
+ registerGetDesignContextTool(server);
41
+ registerRecordDesignDecisionTool(server);
42
+ registerSyncDesignManifestTool(server);
43
+
44
+ // Prompts — the deliverable design process (Option E)
45
+ registerDesignPrompts(server);
46
+
47
+ return server;
48
+ }
@@ -0,0 +1,83 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { loadCatalog } from '../data/catalog-loader.js';
4
+ import { formatCompactCatalog } from '../utils/format-catalog.js';
5
+ import { matchComponents } from '../utils/search.js';
6
+
7
+ export function registerFindComponentsTool(server: McpServer): void {
8
+ server.tool(
9
+ 'find_components',
10
+ 'Browse and search the Urbicon UI component catalog. Without a query, lists all components grouped by category. With a query, performs fuzzy search across names, descriptions, and tags.',
11
+ {
12
+ query: z
13
+ .string()
14
+ .optional()
15
+ .describe(
16
+ 'Search query to find components by name, description, or functionality (e.g. "date input", "modal", "accordeon")'
17
+ ),
18
+ tags: z
19
+ .array(z.string())
20
+ .optional()
21
+ .describe(
22
+ 'Filter by category (form, layout, feedback, navigation, action, display, data) or by origin (auth = all components from the @urbicon-ui/auth package)'
23
+ )
24
+ },
25
+ { readOnlyHint: true, openWorldHint: true },
26
+ async ({ query, tags }) => {
27
+ const catalog = await loadCatalog();
28
+
29
+ if (query) {
30
+ const results = matchComponents(catalog.components, query, tags, 10);
31
+
32
+ if (results.length === 0) {
33
+ return {
34
+ content: [
35
+ {
36
+ type: 'text' as const,
37
+ text: `No components found for "${query}". Try broader terms or browse all with \`find_components\` (no query).`
38
+ }
39
+ ]
40
+ };
41
+ }
42
+
43
+ let md = `# Search Results for "${query}"\n\n`;
44
+ md += `> ${results.length} matching components.\n\n`;
45
+
46
+ for (const comp of results) {
47
+ const variants = comp.variants
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';
60
+ }
61
+
62
+ md += '\n> Use `get_component` with the component slug for full API docs.\n';
63
+ md += '> Use `get_css_reference` for CSS token documentation.\n';
64
+ md += '> Use `find_icons()` to browse all available icons.\n';
65
+
66
+ return {
67
+ content: [{ type: 'text' as const, text: md }]
68
+ };
69
+ }
70
+
71
+ let md = formatCompactCatalog(catalog.components, {
72
+ recipes: catalog.recipes,
73
+ tags
74
+ });
75
+ md += '\n> Use `get_css_reference` for CSS token documentation.\n';
76
+ md += '> Use `find_icons()` to browse all available icons.\n';
77
+
78
+ return {
79
+ content: [{ type: 'text' as const, text: md }]
80
+ };
81
+ }
82
+ );
83
+ }
@@ -0,0 +1,77 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { loadIcons } from '../data/icon-loader.js';
3
+
4
+ const CATEGORY_ORDER = [
5
+ 'navigation',
6
+ 'action',
7
+ 'status',
8
+ 'media',
9
+ 'communication',
10
+ 'data',
11
+ 'layout',
12
+ 'toggle'
13
+ ] as const;
14
+
15
+ export function registerFindIconsTool(server: McpServer): void {
16
+ server.tool(
17
+ 'find_icons',
18
+ 'Get the complete Urbicon UI icon reference — all icons grouped by category with usage instructions.',
19
+ {},
20
+ { readOnlyHint: true, openWorldHint: false },
21
+ async () => {
22
+ const icons = await loadIcons();
23
+
24
+ if (icons.length === 0) {
25
+ return {
26
+ content: [
27
+ {
28
+ type: 'text' as const,
29
+ text: 'Icon metadata not available. Ensure the blocks package is accessible.'
30
+ }
31
+ ]
32
+ };
33
+ }
34
+
35
+ // Group by category
36
+ const byCategory = new Map<string, typeof icons>();
37
+ for (const icon of icons) {
38
+ for (const cat of icon.categories) {
39
+ if (!byCategory.has(cat)) byCategory.set(cat, []);
40
+ byCategory.get(cat)!.push(icon);
41
+ }
42
+ }
43
+
44
+ let md = `# Urbicon UI Icon Reference (${icons.length} icons)\n\n`;
45
+ md += '## Usage\n\n';
46
+ md += '**Direct import:**\n';
47
+ md += '```svelte\n';
48
+ md += '<script>\n';
49
+ md += " import { SearchIcon } from '@urbicon-ui/blocks';\n";
50
+ md += '</script>\n\n';
51
+ md += '<SearchIcon size={24} />\n';
52
+ md += '```\n\n';
53
+ md += '**Dynamic (via IconProvider):**\n';
54
+ md += '```svelte\n';
55
+ md += '<Icon name="search" />\n';
56
+ md += '```\n\n';
57
+ md +=
58
+ '**Props:** `size` (default 24), `strokeWidth` (default 2), `class`, `rotate`, `flip`, `animation`\n\n';
59
+ md += '---\n\n';
60
+
61
+ for (const cat of CATEGORY_ORDER) {
62
+ const catIcons = byCategory.get(cat);
63
+ if (!catIcons || catIcons.length === 0) continue;
64
+
65
+ const sorted = [...catIcons].sort((a, b) => a.componentName.localeCompare(b.componentName));
66
+
67
+ md += `### ${cat} (${sorted.length})\n\n`;
68
+ for (const icon of sorted) {
69
+ md += `- ${icon.componentName} · \`${icon.name}\`\n`;
70
+ }
71
+ md += '\n';
72
+ }
73
+
74
+ return { content: [{ type: 'text' as const, text: md }] };
75
+ }
76
+ );
77
+ }
@@ -0,0 +1,139 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { loadCatalog } from '../data/catalog-loader.js';
4
+
5
+ export function registerGetChecklistTool(server: McpServer): void {
6
+ server.tool(
7
+ 'get_implementation_checklist',
8
+ 'Get a best-practice checklist for implementing UI with Urbicon UI components. Optionally provide component names for context-specific advice.',
9
+ {
10
+ components: z
11
+ .array(z.string())
12
+ .optional()
13
+ .describe('Component names being used, e.g. ["Button", "Input", "Card"]')
14
+ },
15
+ { readOnlyHint: true },
16
+ async ({ components }) => {
17
+ let md = '# Urbicon UI Implementation Checklist\n\n';
18
+
19
+ // Project setup
20
+ md += '## Project Setup\n\n';
21
+ md += '- [ ] Install: `bun add @urbicon-ui/blocks @urbicon-ui/i18n`\n';
22
+ md +=
23
+ "- [ ] Add CSS imports to your root layout or entry CSS — your app owns the Tailwind import and it MUST come first:\n ```css\n @import 'tailwindcss';\n @import '@urbicon-ui/blocks/style/index.css'; /* tokens + @source directives */\n @import '@urbicon-ui/table/style/index.css'; /* only if using Table */\n ```\n";
24
+ md +=
25
+ "- [ ] Import components from package root: `import { Button } from '@urbicon-ui/blocks'` (never from internal paths)\n";
26
+ md +=
27
+ '- [ ] **Import `style/index.css`, not the subfiles, and add NO manual `@source`** — `index.css` ships the Tailwind `@source` directives that make Tailwind scan component classes from `node_modules`, so responsive utilities (`lg:hidden`, `md:grid-cols-2`, etc.) inside library components compile correctly. The `foundation`/`semantic`/`interaction` subfiles omit those directives (and global classes like `.sr-only`); importing them instead of `index.css` is the usual cause of broken responsive layouts in production.\n';
28
+ md += '\n';
29
+
30
+ // Core checklist
31
+ md += '## Design Tokens\n\n';
32
+ md +=
33
+ '- [ ] Use semantic color tokens (`bg-surface-elevated`, `text-text-primary`, `border-border-default`) — not raw Tailwind colors (`bg-gray-100`)\n';
34
+ md +=
35
+ '- [ ] Use OKLCH interaction tokens for hover/active states (`bg-surface-hover`, `bg-surface-active`)\n';
36
+ md +=
37
+ '- [ ] Dark mode is automatic via `prefers-color-scheme` — semantic tokens switch automatically. For manual control, use `ThemeSwitcher` or set `data-theme="dark"` on `<html>`. Do NOT add `dark:` overrides\n';
38
+ md +=
39
+ '- [ ] Use z-index tokens via CSS custom properties: `z-[var(--z-modal)]`, `z-[var(--z-dropdown)]`, etc.\n';
40
+ md += '\n';
41
+
42
+ md += '## Styling\n\n';
43
+ md += '- [ ] Use `unstyled` prop to strip default styles when building custom designs\n';
44
+ md += '- [ ] Use `slotClasses` for targeted style overrides on specific sub-elements\n';
45
+ md +=
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
+ "- [ ] For conditional classes, pass an array to `class` (`class={['base', condition && 'extra']}`) — not concatenated conditional class strings\n";
49
+ md += '- [ ] Use `class` prop for simple additions — merges with defaults automatically\n';
50
+ md += '\n';
51
+
52
+ md += '## Accessibility\n\n';
53
+ md +=
54
+ '- [ ] Use `focus-visible:` for focus rings (not `focus:`) — keyboard-only visibility\n';
55
+ md +=
56
+ '- [ ] Provide `aria-label` on icon-only buttons and interactive elements without visible text\n';
57
+ md += '- [ ] Use semantic HTML elements — components render correct elements by default\n';
58
+ md += '- [ ] Test keyboard navigation — Tab, Enter, Space, Escape, Arrow keys\n';
59
+ md += '\n';
60
+
61
+ md += '## Micro-Interactions (Mint)\n\n';
62
+ md +=
63
+ '- [ ] Add `mint` prop for subtle feedback: `"scale"`, `"ripple"`, `"shake"`, or combine with arrays\n';
64
+ md += '- [ ] Use `mint` sparingly — primary CTAs and key interactive elements only\n';
65
+ md += '\n';
66
+
67
+ md += '## Component Integration\n\n';
68
+ md += '- [ ] Use `bind:value` / `bind:checked` / `bind:open` for two-way state binding\n';
69
+ md += '- [ ] Use callback props (`onValueChange`, `onOpenChange`) for side effects\n';
70
+ md += '- [ ] Use Svelte 5 snippets (`{#snippet ...}`) for custom content slots\n';
71
+ md += '\n';
72
+
73
+ md += '## i18n\n\n';
74
+ md += "- [ ] Wrap user-facing text in `$t('key')` from `@urbicon-ui/i18n`\n";
75
+ md +=
76
+ '- [ ] Components handle internal translations automatically — no manual i18n for built-in labels\n';
77
+ md += '\n';
78
+
79
+ md += '## Design Quality\n\n';
80
+ md +=
81
+ '- [ ] Visual weight varies across the page — not all Cards use the same `variant` and `padding`. Prominent content is `elevated`/`lg`, secondary is `outlined`/`md`\n';
82
+ md +=
83
+ '- [ ] Intent colors appear ONLY for semantic meaning (status, severity, actions) — never as decoration. Neutral surfaces dominate (80–90%)\n';
84
+ md +=
85
+ '- [ ] Spacing varies: tight (`gap-2`/`gap-3`) within related items, generous (`gap-8`/`gap-10`) between sections — not uniform everywhere\n';
86
+ md +=
87
+ "- [ ] Border-radius follows a deliberate strategy — overridden via `class` or `slotClasses` where component defaults don't match the design identity\n";
88
+ md +=
89
+ '- [ ] Data-driven styling: different states/severities are visually distinct through padding, font-weight, Badge `variant`, and text color — not just labels\n';
90
+ md +=
91
+ '- [ ] Each major section has one clearly dominant element. If everything is emphasized, nothing is\n';
92
+ md +=
93
+ '- [ ] Implementation has its own visual identity — not a copy of recipe or example styling\n';
94
+ md += '\n';
95
+
96
+ // Context-specific advice
97
+ if (components && components.length > 0) {
98
+ const catalog = await loadCatalog();
99
+ const catalogMap = new Map(catalog.components.map((c) => [c.name.toLowerCase(), c]));
100
+
101
+ const found = components
102
+ .map((name) => catalogMap.get(name.toLowerCase()))
103
+ .filter((c) => c !== undefined);
104
+
105
+ if (found.length > 0) {
106
+ md += '## Component-Specific Notes\n\n';
107
+
108
+ for (const comp of found) {
109
+ md += `### ${comp.name}\n`;
110
+ if (comp.variants.length > 0) {
111
+ const key = comp.variants
112
+ .filter((v) => !['true', 'false'].every((b) => v.values.includes(b)))
113
+ .map((v) => `\`${v.name}\`: ${v.values.join(' / ')}`)
114
+ .join(', ');
115
+ if (key) md += `- Variants: ${key}\n`;
116
+ }
117
+ if (comp.slots.length > 0) {
118
+ md += `- Customizable slots: \`${comp.slots.join('`, `')}\`\n`;
119
+ }
120
+ if (comp.relatedComponents.length > 0) {
121
+ md += `- Consider also: ${comp.relatedComponents.join(', ')}\n`;
122
+ }
123
+ md += '\n';
124
+ }
125
+ }
126
+ }
127
+
128
+ // Cross-references
129
+ md += '---\n\n**Related tools:**\n';
130
+ md += '- `get_css_reference()` — CSS variable names and override patterns\n';
131
+ md += '- `get_component("<slug>")` — specific component API details\n';
132
+ md += '- `find_icons()` — browse all available icons\n';
133
+
134
+ return {
135
+ content: [{ type: 'text' as const, text: md }]
136
+ };
137
+ }
138
+ );
139
+ }