@spaced-out/ui-design-system 0.5.18 → 0.5.19

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/mcp/index.js ADDED
@@ -0,0 +1,1239 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Genesis UI Design System MCP Server
5
+ *
6
+ * Provides AI assistants with access to the Genesis UI Design System through MCP.
7
+ * This version reads from pre-bundled JSON data.
8
+ *
9
+ * Usage with any MCP client:
10
+ * {
11
+ * "mcpServers": {
12
+ * "genesis-design-system": {
13
+ * "command": "npx",
14
+ * "args": ["-y", "@spaced-out/genesis-mcp-server@latest"]
15
+ * }
16
+ * }
17
+ * }
18
+ */
19
+
20
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
21
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
22
+ import {
23
+ CallToolRequestSchema,
24
+ ListResourcesRequestSchema,
25
+ ListToolsRequestSchema,
26
+ ReadResourceRequestSchema,
27
+ } from '@modelcontextprotocol/sdk/types.js';
28
+ import { readFileSync } from 'fs';
29
+ import { join, dirname } from 'path';
30
+ import { fileURLToPath } from 'url';
31
+
32
+ const __filename = fileURLToPath(import.meta.url);
33
+ const __dirname = dirname(__filename);
34
+
35
+ // Load pre-bundled design system data
36
+ let designSystemData;
37
+ try {
38
+ const dataPath = join(__dirname, 'data', 'design-system.json');
39
+ designSystemData = JSON.parse(readFileSync(dataPath, 'utf-8'));
40
+ } catch (error) {
41
+ console.error('❌ ERROR: Could not load design system data');
42
+ console.error(' Make sure to run "npm run build" before using this server');
43
+ console.error(` Error: ${error.message}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ // Log successful load
48
+ // note(vish): Using console.error() because stdout is reserved for MCP protocol messages.
49
+ // All logging/debugging output must go to stderr to avoid corrupting the protocol.
50
+ console.error('✅ Loaded Genesis design system data');
51
+ console.error(` Version: ${designSystemData.metadata.version}`);
52
+ console.error(` Components: ${Object.keys(designSystemData.components).length}`);
53
+ console.error(` Hooks: ${Object.keys(designSystemData.hooks).length}`);
54
+ console.error(` Built: ${designSystemData.metadata.buildDate}`);
55
+
56
+ /**
57
+ * Get all components
58
+ */
59
+ function getAllComponents() {
60
+ return Object.keys(designSystemData.components).sort();
61
+ }
62
+
63
+ /**
64
+ * Get all hooks
65
+ */
66
+ function getAllHooks() {
67
+ return Object.keys(designSystemData.hooks).sort();
68
+ }
69
+
70
+ /**
71
+ * Get component details
72
+ */
73
+ function getComponentDetails(componentName) {
74
+ return designSystemData.components[componentName] || null;
75
+ }
76
+
77
+ /**
78
+ * Get hook details
79
+ */
80
+ function getHookDetails(hookName) {
81
+ return designSystemData.hooks[hookName] || null;
82
+ }
83
+
84
+ /**
85
+ * Get all design tokens
86
+ */
87
+ function getAllDesignTokens() {
88
+ return designSystemData.tokens;
89
+ }
90
+
91
+ /**
92
+ * Search components by name or content
93
+ */
94
+ function searchComponents(query) {
95
+ const components = getAllComponents();
96
+ const results = [];
97
+ const lowerQuery = query.toLowerCase();
98
+
99
+ for (const componentName of components) {
100
+ if (componentName.toLowerCase().includes(lowerQuery)) {
101
+ const details = getComponentDetails(componentName);
102
+
103
+ let description = '';
104
+ if (details.files.story?.content) {
105
+ const storyContent = details.files.story.content;
106
+ const descMatch = storyContent.match(/description:\s*{[\s\S]*?component:\s*`([\s\S]*?)`/);
107
+ description = descMatch ? descMatch[1].trim().split('\n')[0] : '';
108
+ }
109
+
110
+ results.push({
111
+ name: componentName,
112
+ description,
113
+ hasStory: !!details.files.story,
114
+ hasCSS: !!details.files.css,
115
+ path: details.path,
116
+ });
117
+ }
118
+ }
119
+
120
+ return results;
121
+ }
122
+
123
+ /**
124
+ * Get CSS Module styling guidelines
125
+ */
126
+ function getCSSModuleGuidelines() {
127
+ return {
128
+ description: 'Comprehensive CSS Module guidelines and best practices for the Genesis Design System',
129
+ typescriptRequirement: '⚠️ CRITICAL: Use pure TypeScript only. NO Flow types (Flow.AbstractComponent). Use React.forwardRef<HTMLElement, Props> pattern.',
130
+ criticalRules: [
131
+ '⚠️ ALWAYS use design tokens instead of hardcoded values',
132
+ '⚠️ Use BEM modifier pattern: .baseClass.modifier NOT .baseClass--modifier',
133
+ '⚠️ Use descendant selectors for child element styling: .container .childClass NOT :global() selectors',
134
+ '⚠️ Prefer using size tokens (size58, size34) over hardcoded pixel values',
135
+ '⚠️ Import tokens from ../../styles/variables/_<category>.css',
136
+ ],
137
+ classNamingConventions: {
138
+ description: 'Follow BEM-inspired patterns with CSS Modules',
139
+ correct: [
140
+ '.wrapper { } // Base class',
141
+ '.header { } // Element',
142
+ '.header.isOpen { } // State modifier using additional class',
143
+ '.panel.active { } // State modifier',
144
+ '.container.warning { } // Theme/variant modifier',
145
+ '.container .warning { } // Descendant element styling',
146
+ ],
147
+ incorrect: [
148
+ '.header--isOpen { } // ❌ Don\'t use double dash for modifiers',
149
+ '.panel--active { } // ❌ Use single class modifier instead',
150
+ '.container--warning { } // ❌ Use .container.warning',
151
+ '.container--warning :global(.child) { } // ❌ Avoid :global, use .container .warning',
152
+ ],
153
+ reasoning: 'CSS Modules provide scoping, so we can use simpler class names. The double-dash BEM syntax is less necessary and adds verbosity. Use single dot for modifiers and descendant selectors for child elements.',
154
+ },
155
+ tokenUsage: {
156
+ description: 'Always prefer design tokens over hardcoded values',
157
+ sizeTokens: {
158
+ available: ['size12', 'size16', 'size18', 'size20', 'size24', 'size28', 'size32', 'size34', 'size40', 'size48', 'size58', 'size60', 'sizeFluid'],
159
+ usage: 'Use for widths, heights, min-widths, min-heights, icon sizes, etc.',
160
+ correct: 'width: size58; height: size34;',
161
+ incorrect: 'width: 58px; height: 34px;',
162
+ },
163
+ spaceTokens: {
164
+ available: ['spaceNone', 'spaceXSmall', 'spaceSmall', 'spaceMedium', 'spaceLarge', 'spaceXLarge'],
165
+ usage: 'Use for padding, margin, gaps',
166
+ correct: 'padding: spaceSmall spaceMedium;',
167
+ incorrect: 'padding: 12px 20px;',
168
+ },
169
+ borderTokens: {
170
+ available: ['borderRadiusNone', 'borderRadiusSmall', 'borderRadiusMedium', 'borderRadiusLarge', 'borderWidthNone', 'borderWidthPrimary', 'borderWidthSecondary', 'borderWidthTertiary'],
171
+ usage: 'Use for border-radius, border-width',
172
+ correct: 'border: borderWidthPrimary solid colorBorderPrimary;',
173
+ incorrect: 'border: 1px solid #E1E1E1; border-radius: 116px;',
174
+ },
175
+ colorTokens: {
176
+ description: 'Use semantic color tokens, never hex values',
177
+ correct: 'background-color: colorInformationLightest; color: colorInformation;',
178
+ incorrect: 'background: #e6f0fe; color: #0769f0;',
179
+ },
180
+ },
181
+ stateStylingPattern: {
182
+ description: 'How to apply state-based styling to containers and their children',
183
+ pattern: `// Base container
184
+ .container {
185
+ display: flex;
186
+ padding: spaceMedium;
187
+ border-radius: borderRadiusSmall;
188
+ align-items: center;
189
+ justify-content: center;
190
+ }
191
+
192
+ // Container background/border for each state
193
+ .container.information {
194
+ background-color: colorInformationLightest;
195
+ border-color: colorInformation;
196
+ }
197
+
198
+ .container.warning {
199
+ background-color: colorWarningLightest;
200
+ border-color: colorWarning;
201
+ }
202
+
203
+ // Child element styling using descendant selectors
204
+ .container .information {
205
+ color: colorInformation;
206
+ }
207
+
208
+ .container .warning {
209
+ color: colorWarning;
210
+ }`,
211
+ explanation: 'This pattern separates container state styling from child element styling. Apply state classes to both the container and child elements, then use descendant selectors to apply appropriate colors/styles.',
212
+ useCases: [
213
+ 'Icon colors within state containers (alerts, badges, cards)',
214
+ 'Text colors within status panels',
215
+ 'Button or link colors within themed sections',
216
+ 'Any child element that should inherit state-based styling',
217
+ ],
218
+ doNot: 'Avoid using :global() selector unless absolutely necessary for targeting third-party components you don\'t control',
219
+ },
220
+ compositionPattern: {
221
+ description: 'Leverage CSS Modules composition for reusability',
222
+ example: `.header {
223
+ composes: subTitleSmall from '../../styles/typography.module.css';
224
+ composes: borderPrimary from '../../styles/border.module.css';
225
+ // Additional styles...
226
+ }`,
227
+ availableCompositions: [
228
+ 'Typography: bodySmall, bodyMedium, bodyLarge, subTitleSmall, subTitleMedium, etc. from typography.module.css',
229
+ 'Borders: borderPrimary, borderSecondary, borderTopPrimary, borderBottomPrimary from border.module.css',
230
+ ],
231
+ },
232
+ realWorldExample: {
233
+ description: 'Complete example with state-based styling for multiple child elements',
234
+ code: `@value (
235
+ colorBorderPrimary,
236
+ colorBackgroundTertiary,
237
+ colorNeutralLightest,
238
+ colorNeutral,
239
+ colorInformationLightest,
240
+ colorInformation,
241
+ colorTextPrimary
242
+ ) from '../../styles/variables/_color.css';
243
+ @value (sizeFluid, size58, size34) from '../../styles/variables/_size.css';
244
+ @value (spaceSmall, spaceMedium, spaceXSmall) from '../../styles/variables/_space.css';
245
+ @value (borderRadiusSmall, borderWidthPrimary) from '../../styles/variables/_border.css';
246
+
247
+ .wrapper {
248
+ display: flex;
249
+ flex-flow: column;
250
+ width: sizeFluid;
251
+ }
252
+
253
+ .header {
254
+ composes: subTitleSmall from '../../styles/typography.module.css';
255
+ min-height: size58;
256
+ border-radius: borderRadiusSmall;
257
+ border: borderWidthPrimary solid colorBorderPrimary;
258
+ display: flex;
259
+ align-items: center;
260
+ padding: spaceSmall spaceMedium;
261
+ background-color: colorBackgroundTertiary;
262
+ }
263
+
264
+ /* State container - applies background */
265
+ .statusPanel {
266
+ display: flex;
267
+ gap: spaceSmall;
268
+ padding: spaceMedium;
269
+ border-radius: borderRadiusSmall;
270
+ border: borderWidthPrimary solid;
271
+ }
272
+
273
+ .statusPanel.neutral {
274
+ background-color: colorNeutralLightest;
275
+ border-color: colorNeutral;
276
+ }
277
+
278
+ .statusPanel.information {
279
+ background-color: colorInformationLightest;
280
+ border-color: colorInformation;
281
+ }
282
+
283
+ /* Child elements - use descendant selectors for colors */
284
+ .statusPanel .neutral {
285
+ color: colorNeutral;
286
+ }
287
+
288
+ .statusPanel .information {
289
+ color: colorInformation;
290
+ }
291
+
292
+ /* Works for text, icons, or any child element */
293
+ .statusText {
294
+ composes: bodySmall from '../../styles/typography.module.css';
295
+ }`,
296
+ },
297
+ commonMistakes: [
298
+ {
299
+ mistake: 'Using hardcoded pixel values',
300
+ wrong: 'width: 58px; height: 78px; padding: 12px 16px;',
301
+ right: 'width: size58; height: size58; padding: spaceSmall spaceMedium;',
302
+ },
303
+ {
304
+ mistake: 'Using hex colors directly',
305
+ wrong: 'background: #fcf8e7; color: #0769f0; border-color: #e1e1e1;',
306
+ right: 'background: colorWarningLightest; color: colorInformation; border-color: colorBorderPrimary;',
307
+ },
308
+ {
309
+ mistake: 'Using BEM double-dash with CSS Modules',
310
+ wrong: '.container--active { } .panel--warning { } .button--disabled { }',
311
+ right: '.container.active { } .panel.warning { } .button.disabled { }',
312
+ },
313
+ {
314
+ mistake: 'Using :global() unnecessarily for child elements',
315
+ wrong: '.container--warning :global(svg) { color: orange; } .alert--error :global(.text) { color: red; }',
316
+ right: '.container .warning { color: colorWarning; } .alert .error { color: colorDanger; }',
317
+ },
318
+ {
319
+ mistake: 'Not grouping token imports by category',
320
+ wrong: '@value (colorPrimary, spaceMedium, colorSecondary, borderRadius) from \'../../styles/variables/...\'',
321
+ right: 'Import each category from its own file: colors from _color.css, spacing from _space.css, etc.',
322
+ },
323
+ ],
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Get design token import guidelines
329
+ */
330
+ function getDesignTokenImportGuidelines() {
331
+ return {
332
+ description: 'Guidelines for importing design tokens in CSS Module files',
333
+ importPattern: 'All design tokens must be imported from their respective CSS variable files in src/styles/variables/',
334
+ availableTokenFiles: [
335
+ {
336
+ file: '../../styles/variables/_color.css',
337
+ description: 'Color tokens for text, backgrounds, borders, icons, etc.',
338
+ exampleTokens: ['colorTextPrimary', 'colorBackgroundPrimary', 'colorBorderPrimary', 'colorIconPrimary'],
339
+ },
340
+ {
341
+ file: '../../styles/variables/_space.css',
342
+ description: 'Spacing tokens for padding, margin, gaps, etc.',
343
+ exampleTokens: ['spaceXSmall', 'spaceSmall', 'spaceMedium', 'spaceLarge', 'spaceXLarge'],
344
+ },
345
+ {
346
+ file: '../../styles/variables/_border.css',
347
+ description: 'Border tokens for border-radius and border-width',
348
+ exampleTokens: ['borderRadiusSmall', 'borderRadiusMedium', 'borderRadiusLarge', 'borderWidthPrimary'],
349
+ },
350
+ {
351
+ file: '../../styles/variables/_size.css',
352
+ description: 'Size tokens for dimensions',
353
+ exampleTokens: ['sizeSmall', 'sizeMedium', 'sizeLarge'],
354
+ },
355
+ {
356
+ file: '../../styles/variables/_font.css',
357
+ description: 'Font tokens for typography',
358
+ exampleTokens: ['fontFamilyPrimary', 'fontSizeSmall', 'fontSizeMedium', 'fontWeightRegular'],
359
+ },
360
+ {
361
+ file: '../../styles/variables/_shadow.css',
362
+ description: 'Shadow tokens for box-shadow',
363
+ exampleTokens: ['shadowSmall', 'shadowMedium', 'shadowLarge'],
364
+ },
365
+ {
366
+ file: '../../styles/variables/_elevation.css',
367
+ description: 'Elevation tokens for layering',
368
+ exampleTokens: ['elevationLow', 'elevationMedium', 'elevationHigh'],
369
+ },
370
+ {
371
+ file: '../../styles/variables/_motion.css',
372
+ description: 'Motion tokens for animations and transitions',
373
+ exampleTokens: ['durationFast', 'durationNormal', 'durationSlow', 'easingStandard'],
374
+ },
375
+ {
376
+ file: '../../styles/variables/_opacity.css',
377
+ description: 'Opacity tokens',
378
+ exampleTokens: ['opacityDisabled', 'opacityMedium'],
379
+ },
380
+ ],
381
+ correctExample: `@value (
382
+ colorBackgroundTertiary,
383
+ colorBorderPrimary,
384
+ colorTextPrimary
385
+ ) from '../../styles/variables/_color.css';
386
+
387
+ @value (
388
+ borderRadiusLarge,
389
+ borderWidthPrimary
390
+ ) from '../../styles/variables/_border.css';
391
+
392
+ @value (
393
+ spaceSmall,
394
+ spaceMedium
395
+ ) from '../../styles/variables/_space.css';
396
+
397
+ .wrapper {
398
+ background: colorBackgroundTertiary;
399
+ border: borderWidthPrimary solid colorBorderPrimary;
400
+ border-radius: borderRadiusLarge;
401
+ padding: spaceMedium;
402
+ }`,
403
+ incorrectExample: `/* ❌ WRONG - Do not import from 'ui-design-system' */
404
+ @value (
405
+ colorBorderPrimary,
406
+ spaceSmall
407
+ ) from 'ui-design-system';`,
408
+ notes: [
409
+ 'Always use the relative path from the component directory: ../../styles/variables/_<category>.css',
410
+ 'Group imports by token category (color, space, border, etc.)',
411
+ 'Only import the tokens you actually use in your component',
412
+ 'Use CSS Modules @value syntax for importing tokens',
413
+ ],
414
+ };
415
+ }
416
+
417
+ /**
418
+ * Extract exported types from component code
419
+ */
420
+ function extractExportedTypes(componentCode) {
421
+ if (!componentCode) return [];
422
+
423
+ const exports = [];
424
+
425
+ // Match: export type TypeName = ...
426
+ const typeExportRegex = /export\s+type\s+(\w+)\s*=\s*([^;]+);/g;
427
+ let match;
428
+ while ((match = typeExportRegex.exec(componentCode)) !== null) {
429
+ exports.push({
430
+ kind: 'type',
431
+ name: match[1],
432
+ definition: match[2].trim(),
433
+ raw: match[0],
434
+ });
435
+ }
436
+
437
+ // Match: export interface InterfaceName { ... }
438
+ const interfaceExportRegex = /export\s+interface\s+(\w+)(?:<[^>]+>)?\s*{/g;
439
+ while ((match = interfaceExportRegex.exec(componentCode)) !== null) {
440
+ exports.push({
441
+ kind: 'interface',
442
+ name: match[1],
443
+ definition: 'interface',
444
+ raw: match[0],
445
+ });
446
+ }
447
+
448
+ // Match: export const CONSTANT_NAME = Object.freeze({ ... })
449
+ const constExportRegex = /export\s+const\s+([A-Z_]+)\s*=\s*Object\.freeze\s*\(\s*{([^}]+)}\s*\)/g;
450
+ while ((match = constExportRegex.exec(componentCode)) !== null) {
451
+ exports.push({
452
+ kind: 'const',
453
+ name: match[1],
454
+ definition: `Object.freeze({${match[2]}})`,
455
+ raw: match[0],
456
+ });
457
+ }
458
+
459
+ // Match: export enum EnumName { ... }
460
+ const enumExportRegex = /export\s+enum\s+(\w+)\s*{/g;
461
+ while ((match = enumExportRegex.exec(componentCode)) !== null) {
462
+ exports.push({
463
+ kind: 'enum',
464
+ name: match[1],
465
+ definition: 'enum',
466
+ raw: match[0],
467
+ });
468
+ }
469
+
470
+ return exports;
471
+ }
472
+
473
+ /**
474
+ * Get component template for onboarding
475
+ *
476
+ * IMPORTANT: This template uses pure TypeScript patterns.
477
+ * DO NOT use Flow types (Flow.AbstractComponent) in new components.
478
+ */
479
+ function getComponentTemplate() {
480
+ return {
481
+ 'ComponentName.tsx': `import * as React from 'react';
482
+
483
+ import classify from 'src/utils/classify';
484
+ import {generateTestId} from 'src/utils/qa';
485
+
486
+ import css from 'src/components/ComponentName/ComponentName.module.css';
487
+
488
+ // IMPORTANT: Use pure TypeScript - NO Flow types
489
+ // ✅ Correct: React.forwardRef<HTMLDivElement, Props>
490
+ // ❌ Wrong: Flow.AbstractComponent<Props, HTMLDivElement>
491
+
492
+ type ClassNames = Readonly<{
493
+ wrapper?: string;
494
+ }>;
495
+
496
+ export interface ComponentNameProps {
497
+ classNames?: ClassNames;
498
+ children?: React.ReactNode;
499
+ testId?: string;
500
+ }
501
+
502
+ export const ComponentName = React.forwardRef<HTMLDivElement, ComponentNameProps>(
503
+ (
504
+ {
505
+ classNames,
506
+ children,
507
+ testId,
508
+ ...props
509
+ },
510
+ ref,
511
+ ) => (
512
+ <div
513
+ {...props}
514
+ ref={ref}
515
+ className={classify(css.wrapper, classNames?.wrapper)}
516
+ data-testid={generateTestId({base: testId, slot: 'root'})}
517
+ >
518
+ {children}
519
+ </div>
520
+ ),
521
+ );
522
+
523
+ ComponentName.displayName = 'ComponentName';
524
+ `,
525
+
526
+ 'ComponentName.module.css': `@value (
527
+ colorBorderPrimary,
528
+ colorBackgroundPrimary
529
+ ) from '../../styles/variables/_color.css';
530
+
531
+ @value (
532
+ spaceSmall,
533
+ spaceMedium
534
+ ) from '../../styles/variables/_space.css';
535
+
536
+ @value (
537
+ borderRadiusSmall
538
+ ) from '../../styles/variables/_border.css';
539
+
540
+ .wrapper {
541
+ padding: spaceMedium;
542
+ background: colorBackgroundPrimary;
543
+ border: 1px solid colorBorderPrimary;
544
+ border-radius: borderRadiusSmall;
545
+ }
546
+ `,
547
+
548
+ 'ComponentName.stories.tsx': `import * as React from 'react';
549
+
550
+ import type {ComponentNameProps} from 'src/components/ComponentName/ComponentName';
551
+ import {ComponentName} from 'src/components/ComponentName/ComponentName';
552
+
553
+ export default {
554
+ tags: ['autodocs'],
555
+ title: 'Components/ComponentName',
556
+ component: ComponentName,
557
+ argTypes: {
558
+ children: {
559
+ description: 'The content of the component',
560
+ table: {
561
+ type: {summary: 'React.ReactNode'},
562
+ },
563
+ },
564
+ classNames: {
565
+ description: 'External classNames to be applied',
566
+ control: {
567
+ type: 'object',
568
+ },
569
+ table: {
570
+ type: {summary: '{wrapper?: string}'},
571
+ },
572
+ },
573
+ testId: {
574
+ description: 'Test ID for the component',
575
+ control: {
576
+ type: 'text',
577
+ },
578
+ table: {
579
+ type: {summary: 'string'},
580
+ },
581
+ },
582
+ },
583
+ parameters: {
584
+ docs: {
585
+ subtitle: 'Generates a ComponentName component',
586
+ description: {
587
+ component: \`
588
+ \\\`\\\`\\\`js
589
+ import { ComponentName } from "@spaced-out/ui-design-system/lib/components/ComponentName";
590
+ \\\`\\\`\\\`
591
+ Brief description of what this component does and when to use it.
592
+ \`,
593
+ },
594
+ },
595
+ storySource: {
596
+ componentPath: '/src/components/ComponentName/ComponentName',
597
+ },
598
+ },
599
+ };
600
+
601
+ export const DefaultStory = (args: ComponentNameProps) => (
602
+ <ComponentName {...args} />
603
+ );
604
+
605
+ DefaultStory.args = {
606
+ children: 'ComponentName content',
607
+ testId: 'component-name-default',
608
+ };
609
+
610
+ DefaultStory.storyName = 'Default';
611
+ `,
612
+
613
+ 'index.ts': `export {ComponentName} from 'src/components/ComponentName/ComponentName';
614
+ export type {ComponentNameProps} from 'src/components/ComponentName/ComponentName';
615
+ `,
616
+ };
617
+ }
618
+
619
+ /**
620
+ * Analyze component dependencies
621
+ */
622
+ function analyzeComponentDependencies(componentName) {
623
+ const details = getComponentDetails(componentName);
624
+ if (!details || !details.files.main) {
625
+ return null;
626
+ }
627
+
628
+ // Extract imports from the component
629
+ const content = details.files.main.content;
630
+ const importRegex = /import\s+.*?from\s+['"](.+?)['"]/g;
631
+ const imports = [];
632
+ let match;
633
+
634
+ while ((match = importRegex.exec(content)) !== null) {
635
+ imports.push(match[1]);
636
+ }
637
+
638
+ // Categorize imports
639
+ const dependencies = {
640
+ components: imports.filter(imp => imp.includes('src/components/')),
641
+ hooks: imports.filter(imp => imp.includes('src/hooks/')),
642
+ utils: imports.filter(imp => imp.includes('src/utils/')),
643
+ styles: imports.filter(imp => imp.includes('src/styles/')),
644
+ external: imports.filter(imp => !imp.startsWith('src/')),
645
+ };
646
+
647
+ return dependencies;
648
+ }
649
+
650
+ /**
651
+ * Initialize MCP Server
652
+ */
653
+ const server = new Server(
654
+ {
655
+ name: 'genesis-design-system',
656
+ version: designSystemData.metadata.version,
657
+ },
658
+ {
659
+ capabilities: {
660
+ resources: {},
661
+ tools: {},
662
+ },
663
+ }
664
+ );
665
+
666
+ /**
667
+ * List available resources
668
+ */
669
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
670
+ const components = getAllComponents();
671
+ const hooks = getAllHooks();
672
+
673
+ const resources = [
674
+ {
675
+ uri: 'genesis://overview',
676
+ name: 'Design System Overview',
677
+ mimeType: 'text/markdown',
678
+ description: 'Overview of the Genesis UI Design System',
679
+ },
680
+ {
681
+ uri: 'genesis://components',
682
+ name: 'All Components',
683
+ mimeType: 'application/json',
684
+ description: `List of all ${components.length} available components`,
685
+ },
686
+ {
687
+ uri: 'genesis://hooks',
688
+ name: 'All Hooks',
689
+ mimeType: 'application/json',
690
+ description: `List of all ${hooks.length} available hooks`,
691
+ },
692
+ {
693
+ uri: 'genesis://tokens',
694
+ name: 'Design Tokens',
695
+ mimeType: 'application/json',
696
+ description: 'All design tokens (colors, spacing, typography, etc.)',
697
+ },
698
+ ];
699
+
700
+ // Add individual component resources
701
+ components.forEach(component => {
702
+ resources.push({
703
+ uri: `genesis://component/${component}`,
704
+ name: `Component: ${component}`,
705
+ mimeType: 'text/markdown',
706
+ description: `Documentation and code for ${component} component`,
707
+ });
708
+ });
709
+
710
+ // Add individual hook resources
711
+ hooks.forEach(hook => {
712
+ resources.push({
713
+ uri: `genesis://hook/${hook}`,
714
+ name: `Hook: ${hook}`,
715
+ mimeType: 'text/markdown',
716
+ description: `Documentation and code for ${hook} hook`,
717
+ });
718
+ });
719
+
720
+ return { resources };
721
+ });
722
+
723
+ /**
724
+ * Read resource content
725
+ */
726
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
727
+ const uri = request.params.uri;
728
+
729
+ if (uri === 'genesis://overview') {
730
+ const overview = `# Genesis UI Design System
731
+
732
+ **Version:** ${designSystemData.metadata.version}
733
+ **Last Built:** ${new Date(designSystemData.metadata.buildDate).toLocaleString()}
734
+
735
+ ## Statistics
736
+
737
+ - **Components:** ${Object.keys(designSystemData.components).length}
738
+ - **Hooks:** ${Object.keys(designSystemData.hooks).length}
739
+ - **Design Token Categories:** ${Object.keys(designSystemData.tokens).length}
740
+
741
+ ## Available Components
742
+
743
+ ${getAllComponents().map(c => `- ${c}`).join('\n')}
744
+
745
+ ## Available Hooks
746
+
747
+ ${getAllHooks().map(h => `- ${h}`).join('\n')}
748
+
749
+ ## Design Token Categories
750
+
751
+ ${Object.keys(designSystemData.tokens).map(cat => `- **${cat}**: ${Object.keys(designSystemData.tokens[cat]).length} file(s)`).join('\n')}
752
+ `;
753
+
754
+ return {
755
+ contents: [
756
+ {
757
+ uri,
758
+ mimeType: 'text/markdown',
759
+ text: overview,
760
+ },
761
+ ],
762
+ };
763
+ }
764
+
765
+ if (uri === 'genesis://components') {
766
+ const components = getAllComponents();
767
+ return {
768
+ contents: [
769
+ {
770
+ uri,
771
+ mimeType: 'application/json',
772
+ text: JSON.stringify(components, null, 2),
773
+ },
774
+ ],
775
+ };
776
+ }
777
+
778
+ if (uri === 'genesis://hooks') {
779
+ const hooks = getAllHooks();
780
+ return {
781
+ contents: [
782
+ {
783
+ uri,
784
+ mimeType: 'application/json',
785
+ text: JSON.stringify(hooks, null, 2),
786
+ },
787
+ ],
788
+ };
789
+ }
790
+
791
+ if (uri === 'genesis://tokens') {
792
+ const tokens = getAllDesignTokens();
793
+ return {
794
+ contents: [
795
+ {
796
+ uri,
797
+ mimeType: 'application/json',
798
+ text: JSON.stringify(tokens, null, 2),
799
+ },
800
+ ],
801
+ };
802
+ }
803
+
804
+ if (uri.startsWith('genesis://component/')) {
805
+ const componentName = uri.replace('genesis://component/', '');
806
+ const details = getComponentDetails(componentName);
807
+
808
+ if (!details) {
809
+ throw new Error(`Component ${componentName} not found`);
810
+ }
811
+
812
+ let markdown = `# ${componentName}\n\n`;
813
+ markdown += `**Path:** \`${details.path}\`\n\n`;
814
+ markdown += `**Files:** ${details.allFiles.join(', ')}\n\n`;
815
+
816
+ // Extract and display exported types prominently
817
+ const exportedTypes = extractExportedTypes(details.files.main?.content);
818
+ if (exportedTypes.length > 0) {
819
+ markdown += `## 🎯 Exported Types (REUSE THESE!)\n\n`;
820
+ markdown += `This component exports ${exportedTypes.length} reusable type(s)/constant(s):\n\n`;
821
+
822
+ for (const type of exportedTypes) {
823
+ markdown += `### ${type.name}\n`;
824
+ markdown += `- **Kind:** ${type.kind}\n`;
825
+ markdown += `- **Definition:** \`${type.definition}\`\n`;
826
+ markdown += `- **Import:** \`import type { ${type.name} } from 'src/components/${componentName}';\`\n\n`;
827
+ }
828
+
829
+ markdown += `⚠️ **IMPORTANT:** Always import and reuse these types instead of redefining them!\n\n`;
830
+ }
831
+
832
+ if (details.files.story) {
833
+ markdown += `## Story Documentation\n\n`;
834
+ markdown += '```typescript\n';
835
+ markdown += details.files.story.content;
836
+ markdown += '\n```\n\n';
837
+ }
838
+
839
+ if (details.files.main) {
840
+ markdown += `## Component Implementation\n\n`;
841
+ markdown += '```typescript\n';
842
+ markdown += details.files.main.content;
843
+ markdown += '\n```\n\n';
844
+ }
845
+
846
+ if (details.files.css) {
847
+ markdown += `## Styles\n\n`;
848
+ markdown += '```css\n';
849
+ markdown += details.files.css.content;
850
+ markdown += '\n```\n\n';
851
+ }
852
+
853
+ return {
854
+ contents: [
855
+ {
856
+ uri,
857
+ mimeType: 'text/markdown',
858
+ text: markdown,
859
+ },
860
+ ],
861
+ };
862
+ }
863
+
864
+ if (uri.startsWith('genesis://hook/')) {
865
+ const hookName = uri.replace('genesis://hook/', '');
866
+ const details = getHookDetails(hookName);
867
+
868
+ if (!details) {
869
+ throw new Error(`Hook ${hookName} not found`);
870
+ }
871
+
872
+ let markdown = `# ${hookName}\n\n`;
873
+ markdown += `**Path:** \`${details.path}\`\n\n`;
874
+ markdown += `**Files:** ${details.allFiles.join(', ')}\n\n`;
875
+
876
+ if (details.files.story) {
877
+ markdown += `## Story Documentation\n\n`;
878
+ markdown += '```typescript\n';
879
+ markdown += details.files.story.content;
880
+ markdown += '\n```\n\n';
881
+ }
882
+
883
+ if (details.files.main) {
884
+ markdown += `## Hook Implementation\n\n`;
885
+ markdown += '```typescript\n';
886
+ markdown += details.files.main.content;
887
+ markdown += '\n```\n\n';
888
+ }
889
+
890
+ return {
891
+ contents: [
892
+ {
893
+ uri,
894
+ mimeType: 'text/markdown',
895
+ text: markdown,
896
+ },
897
+ ],
898
+ };
899
+ }
900
+
901
+ throw new Error(`Unknown resource: ${uri}`);
902
+ });
903
+
904
+ /**
905
+ * List available tools
906
+ */
907
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
908
+ return {
909
+ tools: [
910
+ {
911
+ name: 'list_components',
912
+ description: 'List all available components in the design system',
913
+ inputSchema: {
914
+ type: 'object',
915
+ properties: {},
916
+ },
917
+ },
918
+ {
919
+ name: 'get_component',
920
+ description: 'Get detailed information about a specific component including its TypeScript definition, story, styles, and EXPORTED TYPES. The exported types section shows reusable types/interfaces/constants that you should import and use instead of redefining them (e.g., IconType, IconSize from Icon component).',
921
+ inputSchema: {
922
+ type: 'object',
923
+ properties: {
924
+ name: {
925
+ type: 'string',
926
+ description: 'Name of the component (e.g., "Button", "Dropdown", "Icon")',
927
+ },
928
+ },
929
+ required: ['name'],
930
+ },
931
+ },
932
+ {
933
+ name: 'search_components',
934
+ description: 'Search for components by name',
935
+ inputSchema: {
936
+ type: 'object',
937
+ properties: {
938
+ query: {
939
+ type: 'string',
940
+ description: 'Search query',
941
+ },
942
+ },
943
+ required: ['query'],
944
+ },
945
+ },
946
+ {
947
+ name: 'list_hooks',
948
+ description: 'List all available hooks in the design system',
949
+ inputSchema: {
950
+ type: 'object',
951
+ properties: {},
952
+ },
953
+ },
954
+ {
955
+ name: 'get_hook',
956
+ description: 'Get detailed information about a specific hook',
957
+ inputSchema: {
958
+ type: 'object',
959
+ properties: {
960
+ name: {
961
+ type: 'string',
962
+ description: 'Name of the hook (e.g., "useDebounce", "useModal")',
963
+ },
964
+ },
965
+ required: ['name'],
966
+ },
967
+ },
968
+ {
969
+ name: 'get_design_tokens',
970
+ description: 'Get all design tokens including colors, spacing, typography, shadows, etc.',
971
+ inputSchema: {
972
+ type: 'object',
973
+ properties: {
974
+ category: {
975
+ type: 'string',
976
+ description: 'Optional: Filter by token category (e.g., "color", "space", "size")',
977
+ },
978
+ },
979
+ },
980
+ },
981
+ {
982
+ name: 'get_component_template',
983
+ description: 'Get template files for creating a new component (useful for onboarding new components)',
984
+ inputSchema: {
985
+ type: 'object',
986
+ properties: {},
987
+ },
988
+ },
989
+ {
990
+ name: 'get_design_token_import_guidelines',
991
+ description: 'Get guidelines and best practices for importing design tokens in CSS Module files. This includes the correct import paths, available token files, and examples of correct vs incorrect imports. ALWAYS call this function before creating CSS Module files to ensure correct token imports.',
992
+ inputSchema: {
993
+ type: 'object',
994
+ properties: {},
995
+ },
996
+ },
997
+ {
998
+ name: 'get_css_module_guidelines',
999
+ description: 'Get comprehensive CSS Module styling guidelines including class naming conventions, token usage, icon color patterns, and common mistakes to avoid. CRITICAL: Call this before creating or modifying any CSS Module files to ensure consistency with the design system patterns.',
1000
+ inputSchema: {
1001
+ type: 'object',
1002
+ properties: {},
1003
+ },
1004
+ },
1005
+ {
1006
+ name: 'analyze_component_dependencies',
1007
+ description: 'Analyze dependencies of a component to understand what other components and utilities it uses',
1008
+ inputSchema: {
1009
+ type: 'object',
1010
+ properties: {
1011
+ name: {
1012
+ type: 'string',
1013
+ description: 'Name of the component',
1014
+ },
1015
+ },
1016
+ required: ['name'],
1017
+ },
1018
+ },
1019
+ ],
1020
+ };
1021
+ });
1022
+
1023
+ /**
1024
+ * Handle tool calls
1025
+ */
1026
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1027
+ const { name, arguments: args } = request.params;
1028
+
1029
+ try {
1030
+ switch (name) {
1031
+ case 'list_components': {
1032
+ const components = getAllComponents();
1033
+ return {
1034
+ content: [
1035
+ {
1036
+ type: 'text',
1037
+ text: JSON.stringify(components, null, 2),
1038
+ },
1039
+ ],
1040
+ };
1041
+ }
1042
+
1043
+ case 'get_component': {
1044
+ const details = getComponentDetails(args.name);
1045
+ if (!details) {
1046
+ return {
1047
+ content: [
1048
+ {
1049
+ type: 'text',
1050
+ text: `Component "${args.name}" not found`,
1051
+ },
1052
+ ],
1053
+ isError: true,
1054
+ };
1055
+ }
1056
+
1057
+ // Extract exported types from the component code
1058
+ const exportedTypes = extractExportedTypes(details.files.main?.content);
1059
+
1060
+ // Enhance details with exported types information
1061
+ const enhancedDetails = {
1062
+ ...details,
1063
+ exportedTypes: exportedTypes.length > 0 ? {
1064
+ summary: `This component exports ${exportedTypes.length} type(s)/constant(s) that you should reuse:`,
1065
+ types: exportedTypes,
1066
+ usage: exportedTypes.length > 0
1067
+ ? `Import these types: import type { ${exportedTypes.map(t => t.name).join(', ')} } from 'src/components/${args.name}';`
1068
+ : '',
1069
+ } : null,
1070
+ };
1071
+
1072
+ return {
1073
+ content: [
1074
+ {
1075
+ type: 'text',
1076
+ text: JSON.stringify(enhancedDetails, null, 2),
1077
+ },
1078
+ ],
1079
+ };
1080
+ }
1081
+
1082
+ case 'search_components': {
1083
+ const results = searchComponents(args.query);
1084
+ return {
1085
+ content: [
1086
+ {
1087
+ type: 'text',
1088
+ text: JSON.stringify(results, null, 2),
1089
+ },
1090
+ ],
1091
+ };
1092
+ }
1093
+
1094
+ case 'list_hooks': {
1095
+ const hooks = getAllHooks();
1096
+ return {
1097
+ content: [
1098
+ {
1099
+ type: 'text',
1100
+ text: JSON.stringify(hooks, null, 2),
1101
+ },
1102
+ ],
1103
+ };
1104
+ }
1105
+
1106
+ case 'get_hook': {
1107
+ const details = getHookDetails(args.name);
1108
+ if (!details) {
1109
+ return {
1110
+ content: [
1111
+ {
1112
+ type: 'text',
1113
+ text: `Hook "${args.name}" not found`,
1114
+ },
1115
+ ],
1116
+ isError: true,
1117
+ };
1118
+ }
1119
+ return {
1120
+ content: [
1121
+ {
1122
+ type: 'text',
1123
+ text: JSON.stringify(details, null, 2),
1124
+ },
1125
+ ],
1126
+ };
1127
+ }
1128
+
1129
+ case 'get_design_tokens': {
1130
+ const allTokens = getAllDesignTokens();
1131
+ const tokens = args.category ? { [args.category]: allTokens[args.category] } : allTokens;
1132
+ return {
1133
+ content: [
1134
+ {
1135
+ type: 'text',
1136
+ text: JSON.stringify(tokens, null, 2),
1137
+ },
1138
+ ],
1139
+ };
1140
+ }
1141
+
1142
+ case 'get_component_template': {
1143
+ const template = getComponentTemplate();
1144
+ return {
1145
+ content: [
1146
+ {
1147
+ type: 'text',
1148
+ text: JSON.stringify(template, null, 2),
1149
+ },
1150
+ ],
1151
+ };
1152
+ }
1153
+
1154
+ case 'get_design_token_import_guidelines': {
1155
+ const guidelines = getDesignTokenImportGuidelines();
1156
+ return {
1157
+ content: [
1158
+ {
1159
+ type: 'text',
1160
+ text: JSON.stringify(guidelines, null, 2),
1161
+ },
1162
+ ],
1163
+ };
1164
+ }
1165
+
1166
+ case 'get_css_module_guidelines': {
1167
+ const guidelines = getCSSModuleGuidelines();
1168
+ return {
1169
+ content: [
1170
+ {
1171
+ type: 'text',
1172
+ text: JSON.stringify(guidelines, null, 2),
1173
+ },
1174
+ ],
1175
+ };
1176
+ }
1177
+
1178
+ case 'analyze_component_dependencies': {
1179
+ const dependencies = analyzeComponentDependencies(args.name);
1180
+ if (!dependencies) {
1181
+ return {
1182
+ content: [
1183
+ {
1184
+ type: 'text',
1185
+ text: `Component "${args.name}" not found or has no main file`,
1186
+ },
1187
+ ],
1188
+ isError: true,
1189
+ };
1190
+ }
1191
+ return {
1192
+ content: [
1193
+ {
1194
+ type: 'text',
1195
+ text: JSON.stringify(dependencies, null, 2),
1196
+ },
1197
+ ],
1198
+ };
1199
+ }
1200
+
1201
+ default:
1202
+ return {
1203
+ content: [
1204
+ {
1205
+ type: 'text',
1206
+ text: `Unknown tool: ${name}`,
1207
+ },
1208
+ ],
1209
+ isError: true,
1210
+ };
1211
+ }
1212
+ } catch (error) {
1213
+ return {
1214
+ content: [
1215
+ {
1216
+ type: 'text',
1217
+ text: `Error executing tool ${name}: ${error.message}`,
1218
+ },
1219
+ ],
1220
+ isError: true,
1221
+ };
1222
+ }
1223
+ });
1224
+
1225
+ /**
1226
+ * Start the server
1227
+ */
1228
+ async function main() {
1229
+ const transport = new StdioServerTransport();
1230
+ await server.connect(transport);
1231
+ console.error('Genesis Design System MCP Server running on stdio');
1232
+ console.error(`Version: ${designSystemData.metadata.version}`);
1233
+ }
1234
+
1235
+ main().catch((error) => {
1236
+ console.error('Fatal error:', error);
1237
+ process.exit(1);
1238
+ });
1239
+