@gemini-designer/mcp-server 0.1.0

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 (153) hide show
  1. package/.prettierrc +9 -0
  2. package/dist/components/catalog.d.ts +24 -0
  3. package/dist/components/catalog.d.ts.map +1 -0
  4. package/dist/components/catalog.js +186 -0
  5. package/dist/components/catalog.js.map +1 -0
  6. package/dist/config/index.d.ts +60 -0
  7. package/dist/config/index.d.ts.map +1 -0
  8. package/dist/config/index.js +199 -0
  9. package/dist/config/index.js.map +1 -0
  10. package/dist/context/builder.d.ts +32 -0
  11. package/dist/context/builder.d.ts.map +1 -0
  12. package/dist/context/builder.js +194 -0
  13. package/dist/context/builder.js.map +1 -0
  14. package/dist/context/filter.d.ts +28 -0
  15. package/dist/context/filter.d.ts.map +1 -0
  16. package/dist/context/filter.js +136 -0
  17. package/dist/context/filter.js.map +1 -0
  18. package/dist/context/grounding.d.ts +27 -0
  19. package/dist/context/grounding.d.ts.map +1 -0
  20. package/dist/context/grounding.js +162 -0
  21. package/dist/context/grounding.js.map +1 -0
  22. package/dist/context/guards.d.ts +31 -0
  23. package/dist/context/guards.d.ts.map +1 -0
  24. package/dist/context/guards.js +76 -0
  25. package/dist/context/guards.js.map +1 -0
  26. package/dist/context/repo-hints.d.ts +12 -0
  27. package/dist/context/repo-hints.d.ts.map +1 -0
  28. package/dist/context/repo-hints.js +40 -0
  29. package/dist/context/repo-hints.js.map +1 -0
  30. package/dist/generation/gemini-client.d.ts +27 -0
  31. package/dist/generation/gemini-client.d.ts.map +1 -0
  32. package/dist/generation/gemini-client.js +64 -0
  33. package/dist/generation/gemini-client.js.map +1 -0
  34. package/dist/generation/litellm-client.d.ts +16 -0
  35. package/dist/generation/litellm-client.d.ts.map +1 -0
  36. package/dist/generation/litellm-client.js +98 -0
  37. package/dist/generation/litellm-client.js.map +1 -0
  38. package/dist/generation/remote-client.d.ts +20 -0
  39. package/dist/generation/remote-client.d.ts.map +1 -0
  40. package/dist/generation/remote-client.js +69 -0
  41. package/dist/generation/remote-client.js.map +1 -0
  42. package/dist/index.d.ts +9 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +30 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/output/file-writer.d.ts +39 -0
  47. package/dist/output/file-writer.d.ts.map +1 -0
  48. package/dist/output/file-writer.js +153 -0
  49. package/dist/output/file-writer.js.map +1 -0
  50. package/dist/output/formatter.d.ts +26 -0
  51. package/dist/output/formatter.d.ts.map +1 -0
  52. package/dist/output/formatter.js +156 -0
  53. package/dist/output/formatter.js.map +1 -0
  54. package/dist/server.d.ts +9 -0
  55. package/dist/server.d.ts.map +1 -0
  56. package/dist/server.js +22 -0
  57. package/dist/server.js.map +1 -0
  58. package/dist/stack/detect.d.ts +49 -0
  59. package/dist/stack/detect.d.ts.map +1 -0
  60. package/dist/stack/detect.js +157 -0
  61. package/dist/stack/detect.js.map +1 -0
  62. package/dist/tokens/sync.d.ts +32 -0
  63. package/dist/tokens/sync.d.ts.map +1 -0
  64. package/dist/tokens/sync.js +188 -0
  65. package/dist/tokens/sync.js.map +1 -0
  66. package/dist/tools/analyze-screenshot-ui.d.ts +18 -0
  67. package/dist/tools/analyze-screenshot-ui.d.ts.map +1 -0
  68. package/dist/tools/analyze-screenshot-ui.js +133 -0
  69. package/dist/tools/analyze-screenshot-ui.js.map +1 -0
  70. package/dist/tools/analyze-tokens.d.ts +10 -0
  71. package/dist/tools/analyze-tokens.d.ts.map +1 -0
  72. package/dist/tools/analyze-tokens.js +107 -0
  73. package/dist/tools/analyze-tokens.js.map +1 -0
  74. package/dist/tools/catalog-components.d.ts +14 -0
  75. package/dist/tools/catalog-components.d.ts.map +1 -0
  76. package/dist/tools/catalog-components.js +85 -0
  77. package/dist/tools/catalog-components.js.map +1 -0
  78. package/dist/tools/create-ui.d.ts +10 -0
  79. package/dist/tools/create-ui.d.ts.map +1 -0
  80. package/dist/tools/create-ui.js +167 -0
  81. package/dist/tools/create-ui.js.map +1 -0
  82. package/dist/tools/detect-ui-stack.d.ts +15 -0
  83. package/dist/tools/detect-ui-stack.d.ts.map +1 -0
  84. package/dist/tools/detect-ui-stack.js +52 -0
  85. package/dist/tools/detect-ui-stack.js.map +1 -0
  86. package/dist/tools/generate-component-variants.d.ts +15 -0
  87. package/dist/tools/generate-component-variants.d.ts.map +1 -0
  88. package/dist/tools/generate-component-variants.js +199 -0
  89. package/dist/tools/generate-component-variants.js.map +1 -0
  90. package/dist/tools/generate-vibes.d.ts +10 -0
  91. package/dist/tools/generate-vibes.d.ts.map +1 -0
  92. package/dist/tools/generate-vibes.js +145 -0
  93. package/dist/tools/generate-vibes.js.map +1 -0
  94. package/dist/tools/index.d.ts +12 -0
  95. package/dist/tools/index.d.ts.map +1 -0
  96. package/dist/tools/index.js +36 -0
  97. package/dist/tools/index.js.map +1 -0
  98. package/dist/tools/modify-ui.d.ts +11 -0
  99. package/dist/tools/modify-ui.d.ts.map +1 -0
  100. package/dist/tools/modify-ui.js +207 -0
  101. package/dist/tools/modify-ui.js.map +1 -0
  102. package/dist/tools/scaffold-project.d.ts +10 -0
  103. package/dist/tools/scaffold-project.d.ts.map +1 -0
  104. package/dist/tools/scaffold-project.js +122 -0
  105. package/dist/tools/scaffold-project.js.map +1 -0
  106. package/dist/tools/snippet-ui.d.ts +11 -0
  107. package/dist/tools/snippet-ui.d.ts.map +1 -0
  108. package/dist/tools/snippet-ui.js +194 -0
  109. package/dist/tools/snippet-ui.js.map +1 -0
  110. package/dist/tools/sync-design-tokens.d.ts +14 -0
  111. package/dist/tools/sync-design-tokens.d.ts.map +1 -0
  112. package/dist/tools/sync-design-tokens.js +233 -0
  113. package/dist/tools/sync-design-tokens.js.map +1 -0
  114. package/dist/utils/walk.d.ts +15 -0
  115. package/dist/utils/walk.d.ts.map +1 -0
  116. package/dist/utils/walk.js +63 -0
  117. package/dist/utils/walk.js.map +1 -0
  118. package/eslint.config.js +37 -0
  119. package/package.json +56 -0
  120. package/src/__tests__/builder.test.ts +31 -0
  121. package/src/__tests__/config.test.ts +52 -0
  122. package/src/__tests__/filter.test.ts +109 -0
  123. package/src/components/catalog.ts +214 -0
  124. package/src/config/index.ts +237 -0
  125. package/src/context/builder.ts +233 -0
  126. package/src/context/filter.ts +164 -0
  127. package/src/context/grounding.ts +191 -0
  128. package/src/context/guards.ts +94 -0
  129. package/src/context/repo-hints.ts +43 -0
  130. package/src/generation/gemini-client.ts +94 -0
  131. package/src/generation/litellm-client.ts +121 -0
  132. package/src/generation/remote-client.ts +103 -0
  133. package/src/index.ts +36 -0
  134. package/src/output/file-writer.ts +181 -0
  135. package/src/output/formatter.ts +186 -0
  136. package/src/server.ts +28 -0
  137. package/src/stack/detect.ts +204 -0
  138. package/src/tokens/sync.ts +212 -0
  139. package/src/tools/analyze-screenshot-ui.ts +150 -0
  140. package/src/tools/analyze-tokens.ts +123 -0
  141. package/src/tools/catalog-components.ts +99 -0
  142. package/src/tools/create-ui.ts +194 -0
  143. package/src/tools/detect-ui-stack.ts +64 -0
  144. package/src/tools/generate-component-variants.ts +218 -0
  145. package/src/tools/generate-vibes.ts +177 -0
  146. package/src/tools/index.ts +42 -0
  147. package/src/tools/modify-ui.ts +230 -0
  148. package/src/tools/scaffold-project.ts +138 -0
  149. package/src/tools/snippet-ui.ts +222 -0
  150. package/src/tools/sync-design-tokens.ts +256 -0
  151. package/src/utils/walk.ts +75 -0
  152. package/tsconfig.json +34 -0
  153. package/vitest.config.ts +15 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * sync_design_tokens Tool
3
+ *
4
+ * Converts design tokens between formats and optionally writes them to files.
5
+ *
6
+ * Primary use cases:
7
+ * - Take existing CSS vars and generate Tailwind theme extension
8
+ * - Export Tokens Studio JSON for Figma/Tokens Studio workflows
9
+ * - Normalize token files for team consistency
10
+ */
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
12
+ import { Config } from '../config/index.js';
13
+ export declare function registerSyncDesignTokens(server: McpServer, config: Config): void;
14
+ //# sourceMappingURL=sync-design-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-design-tokens.d.ts","sourceRoot":"","sources":["../../src/tools/sync-design-tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AA6D5C,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAoLhF"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * sync_design_tokens Tool
3
+ *
4
+ * Converts design tokens between formats and optionally writes them to files.
5
+ *
6
+ * Primary use cases:
7
+ * - Take existing CSS vars and generate Tailwind theme extension
8
+ * - Export Tokens Studio JSON for Figma/Tokens Studio workflows
9
+ * - Normalize token files for team consistency
10
+ */
11
+ import { z } from 'zod';
12
+ import * as fs from 'node:fs';
13
+ import { assertReadablePath, assertWritablePath } from '../context/guards.js';
14
+ import { sanitizeContent } from '../context/filter.js';
15
+ import { writeFile } from '../output/file-writer.js';
16
+ import { parseCssVars, tokensToCssVars, tokensToTailwindTheme, tokensToTokensStudio, tokensToStyleDictionary, } from '../tokens/sync.js';
17
+ import { generateWithGemini } from '../generation/gemini-client.js';
18
+ const inputSchema = {
19
+ sourcePath: z.string().optional().describe('Path to a token source file (CSS vars or JSON).'),
20
+ sourceContent: z.string().optional().describe('Inline token content (if not using sourcePath).'),
21
+ sourceType: z
22
+ .enum(['css-vars', 'tokens-json', 'style-dictionary'])
23
+ .default('css-vars')
24
+ .describe('How to interpret the source.'),
25
+ targets: z
26
+ .array(z.enum(['css-vars', 'tailwind', 'tokens-studio', 'style-dictionary']))
27
+ .default(['css-vars', 'tailwind', 'tokens-studio'])
28
+ .describe('Target formats to generate.'),
29
+ aiNormalize: z
30
+ .boolean()
31
+ .default(false)
32
+ .describe('If true, use the model to improve token naming & grouping (uses tokens).'),
33
+ writeToFile: z.boolean().default(false).describe('If true, write generated outputs to files.'),
34
+ outputPaths: z
35
+ .object({
36
+ cssVars: z.string().optional(),
37
+ tailwind: z.string().optional(),
38
+ tokensStudio: z.string().optional(),
39
+ styleDictionary: z.string().optional(),
40
+ })
41
+ .optional()
42
+ .describe('Where to write outputs if writeToFile=true.'),
43
+ backup: z.boolean().default(true).describe('Create .bak backups for overwritten files.'),
44
+ };
45
+ const NORMALIZE_SYSTEM_PROMPT = `You are a design systems engineer. You will normalize and improve a design token set.
46
+
47
+ Goal:
48
+ - Improve token naming and grouping using semantic names (primary, surface, text, border, danger, success)
49
+ - Preserve ALL token values exactly (do not change hex codes, sizes, etc.)
50
+ - Remove duplicates when values are identical AND names are redundant
51
+
52
+ Output rules:
53
+ - Output ONE valid JSON array of tokens only. No markdown. No prose.
54
+ - Each token: {"name": string, "value": string, "type": string, "description"?: string}
55
+ `;
56
+ function cleanJsonArray(response) {
57
+ let out = response.trim();
58
+ const fenced = out.match(/```(?:json)?\n([\s\S]*?)```/);
59
+ if (fenced)
60
+ out = fenced[1].trim();
61
+ return out;
62
+ }
63
+ export function registerSyncDesignTokens(server, config) {
64
+ server.registerTool('sync_design_tokens', {
65
+ title: 'Sync Design Tokens',
66
+ description: 'Convert tokens between CSS vars, Tailwind theme, Tokens Studio JSON, and Style Dictionary. Optionally writes outputs to files.',
67
+ inputSchema,
68
+ }, async (args) => {
69
+ const sourcePath = args.sourcePath;
70
+ const sourceContent = args.sourceContent;
71
+ const sourceType = args.sourceType || 'css-vars';
72
+ const targets = args.targets || [
73
+ 'css-vars',
74
+ 'tailwind',
75
+ 'tokens-studio',
76
+ ];
77
+ const aiNormalize = args.aiNormalize === true;
78
+ const writeToFile = args.writeToFile === true;
79
+ const outputPaths = args.outputPaths;
80
+ const backup = args.backup !== false;
81
+ if (!sourcePath && !sourceContent) {
82
+ return {
83
+ content: [{ type: 'text', text: 'Error: Provide either sourcePath or sourceContent.' }],
84
+ isError: true,
85
+ };
86
+ }
87
+ let raw;
88
+ let safeSourcePath;
89
+ try {
90
+ if (sourcePath) {
91
+ safeSourcePath = assertReadablePath(sourcePath, config);
92
+ raw = fs.readFileSync(safeSourcePath, 'utf-8');
93
+ }
94
+ else {
95
+ raw = sourceContent;
96
+ }
97
+ }
98
+ catch (error) {
99
+ const message = error instanceof Error ? error.message : 'Could not read source';
100
+ return {
101
+ content: [{ type: 'text', text: `Error: ${message}` }],
102
+ isError: true,
103
+ };
104
+ }
105
+ raw = sanitizeContent(raw);
106
+ let tokens = [];
107
+ const warnings = [];
108
+ try {
109
+ if (sourceType === 'css-vars') {
110
+ const parsed = parseCssVars(raw, safeSourcePath);
111
+ tokens = parsed.tokens;
112
+ warnings.push(...parsed.warnings);
113
+ }
114
+ else {
115
+ // Very lightweight: accept either Tokens Studio or Style Dictionary style
116
+ const json = JSON.parse(raw);
117
+ const collected = [];
118
+ const walk = (obj, prefix) => {
119
+ if (!obj || typeof obj !== 'object')
120
+ return;
121
+ if (typeof obj.value === 'string' && typeof obj.type === 'string') {
122
+ collected.push({
123
+ name: [...prefix].join('.'),
124
+ value: obj.value,
125
+ type: obj.type,
126
+ description: typeof obj.description === 'string' ? obj.description : undefined,
127
+ source: { file: safeSourcePath ? safeSourcePath.split('/').pop() : undefined },
128
+ });
129
+ return;
130
+ }
131
+ for (const [k, v] of Object.entries(obj)) {
132
+ walk(v, [...prefix, k]);
133
+ }
134
+ };
135
+ walk(json, []);
136
+ tokens = collected;
137
+ if (tokens.length === 0)
138
+ warnings.push('No tokens found in JSON (expected nested {value,type} nodes).');
139
+ }
140
+ }
141
+ catch (error) {
142
+ const message = error instanceof Error ? error.message : 'Failed to parse tokens';
143
+ return {
144
+ content: [{ type: 'text', text: `Error: ${message}` }],
145
+ isError: true,
146
+ };
147
+ }
148
+ // Optional AI normalization
149
+ if (aiNormalize && tokens.length > 0) {
150
+ const userPrompt = `Normalize these tokens. Remember: never change values.
151
+
152
+ TOKENS (JSON array):
153
+ ${JSON.stringify(tokens, null, 2)}`;
154
+ try {
155
+ const resp = await generateWithGemini(config, NORMALIZE_SYSTEM_PROMPT, userPrompt, {
156
+ toolName: 'sync_design_tokens',
157
+ });
158
+ const cleaned = cleanJsonArray(resp);
159
+ const normalized = JSON.parse(cleaned);
160
+ if (Array.isArray(normalized)) {
161
+ tokens = normalized;
162
+ }
163
+ else {
164
+ warnings.push('AI normalization returned non-array output; keeping original tokens.');
165
+ }
166
+ }
167
+ catch (error) {
168
+ const message = error instanceof Error ? error.message : 'AI normalization failed';
169
+ warnings.push(`AI normalization failed: ${message}`);
170
+ }
171
+ }
172
+ const outputs = {};
173
+ for (const t of targets) {
174
+ if (t === 'css-vars')
175
+ outputs.cssVars = tokensToCssVars(tokens);
176
+ if (t === 'tailwind')
177
+ outputs.tailwind = tokensToTailwindTheme(tokens);
178
+ if (t === 'tokens-studio')
179
+ outputs.tokensStudio = tokensToTokensStudio(tokens);
180
+ if (t === 'style-dictionary')
181
+ outputs.styleDictionary = tokensToStyleDictionary(tokens);
182
+ }
183
+ // Optional writing
184
+ const written = [];
185
+ if (writeToFile) {
186
+ const mapping = [
187
+ { outKey: 'cssVars', pathKey: 'cssVars' },
188
+ { outKey: 'tailwind', pathKey: 'tailwind' },
189
+ { outKey: 'tokensStudio', pathKey: 'tokensStudio' },
190
+ { outKey: 'styleDictionary', pathKey: 'styleDictionary' },
191
+ ];
192
+ for (const m of mapping) {
193
+ const content = outputs[m.outKey];
194
+ const outPath = outputPaths?.[m.pathKey];
195
+ if (!content || !outPath)
196
+ continue;
197
+ try {
198
+ const safeOut = assertWritablePath(outPath, config);
199
+ const result = await writeFile(safeOut, content, { format: false, backup });
200
+ if (!result.success) {
201
+ return {
202
+ content: [
203
+ {
204
+ type: 'text',
205
+ text: `Error: Failed writing ${outPath}: ${result.error || 'Unknown error'}`,
206
+ },
207
+ ],
208
+ isError: true,
209
+ };
210
+ }
211
+ written.push({ path: result.filePath, backupPath: result.backupPath || undefined });
212
+ }
213
+ catch (error) {
214
+ const message = error instanceof Error ? error.message : 'Write failed';
215
+ return {
216
+ content: [{ type: 'text', text: `Error: ${message}` }],
217
+ isError: true,
218
+ };
219
+ }
220
+ }
221
+ }
222
+ const response = {
223
+ tokens,
224
+ outputs,
225
+ warnings,
226
+ written,
227
+ };
228
+ return {
229
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
230
+ };
231
+ });
232
+ }
233
+ //# sourceMappingURL=sync-design-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-design-tokens.js","sourceRoot":"","sources":["../../src/tools/sync-design-tokens.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * walk.ts
3
+ *
4
+ * Lightweight recursive file walker with sane defaults.
5
+ * - No extra deps (no glob)
6
+ * - Skips common large / sensitive directories
7
+ */
8
+ export interface WalkOptions {
9
+ includeExtensions?: string[];
10
+ excludeDirNames?: string[];
11
+ maxFiles?: number;
12
+ }
13
+ export declare function walkFiles(rootDir: string, options?: WalkOptions): string[];
14
+ export declare function toPosixPath(p: string): string;
15
+ //# sourceMappingURL=walk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../../src/utils/walk.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,MAAM,WAAW,WAAW;IACxB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAcD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,MAAM,EAAE,CAyC9E;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * walk.ts
3
+ *
4
+ * Lightweight recursive file walker with sane defaults.
5
+ * - No extra deps (no glob)
6
+ * - Skips common large / sensitive directories
7
+ */
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ const DEFAULT_EXCLUDE_DIRS = [
11
+ 'node_modules',
12
+ '.git',
13
+ '.next',
14
+ '.nuxt',
15
+ 'dist',
16
+ 'build',
17
+ '.turbo',
18
+ '.cache',
19
+ 'coverage',
20
+ ];
21
+ export function walkFiles(rootDir, options = {}) {
22
+ const includeExtensions = (options.includeExtensions || []).map((e) => e.toLowerCase());
23
+ const exclude = new Set((options.excludeDirNames || DEFAULT_EXCLUDE_DIRS).map((d) => d.toLowerCase()));
24
+ const maxFiles = typeof options.maxFiles === 'number' ? options.maxFiles : 10_000;
25
+ const results = [];
26
+ const queue = [rootDir];
27
+ while (queue.length > 0) {
28
+ const current = queue.pop();
29
+ let entries;
30
+ try {
31
+ entries = fs.readdirSync(current, { withFileTypes: true });
32
+ }
33
+ catch {
34
+ continue;
35
+ }
36
+ for (const ent of entries) {
37
+ if (results.length >= maxFiles)
38
+ return results;
39
+ const abs = path.join(current, ent.name);
40
+ if (ent.isDirectory()) {
41
+ if (exclude.has(ent.name.toLowerCase()))
42
+ continue;
43
+ queue.push(abs);
44
+ continue;
45
+ }
46
+ if (!ent.isFile())
47
+ continue;
48
+ if (includeExtensions.length === 0) {
49
+ results.push(abs);
50
+ continue;
51
+ }
52
+ const ext = path.extname(ent.name).toLowerCase();
53
+ if (includeExtensions.includes(ext)) {
54
+ results.push(abs);
55
+ }
56
+ }
57
+ }
58
+ return results;
59
+ }
60
+ export function toPosixPath(p) {
61
+ return p.replace(/\\/g, '/');
62
+ }
63
+ //# sourceMappingURL=walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk.js","sourceRoot":"","sources":["../../src/utils/walk.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAQlC,MAAM,oBAAoB,GAAG;IACzB,cAAc;IACd,MAAM;IACN,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,UAAU;CACb,CAAC;AAEF,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,UAAuB,EAAE;IAChE,MAAM,iBAAiB,GAAG,CAAC,OAAO,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IACxF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,eAAe,IAAI,oBAAoB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACvG,MAAM,QAAQ,GAAG,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;IAElF,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAa,CAAC,OAAO,CAAC,CAAC;IAElC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QAC7B,IAAI,OAAoB,CAAC;QACzB,IAAI,CAAC;YACD,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACL,SAAS;QACb,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,OAAO,CAAC,MAAM,IAAI,QAAQ;gBAAE,OAAO,OAAO,CAAC;YAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YAEzC,IAAI,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;gBACpB,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;oBAAE,SAAS;gBAClD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAChB,SAAS;YACb,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE;gBAAE,SAAS;YAC5B,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACjC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAClB,SAAS;YACb,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;YACjD,IAAI,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAClC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtB,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,37 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from '@typescript-eslint/eslint-plugin';
3
+ import tsparser from '@typescript-eslint/parser';
4
+
5
+ export default [
6
+ eslint.configs.recommended,
7
+ {
8
+ files: ['src/**/*.ts'],
9
+ languageOptions: {
10
+ parser: tsparser,
11
+ parserOptions: {
12
+ ecmaVersion: 2022,
13
+ sourceType: 'module',
14
+ },
15
+ globals: {
16
+ console: 'readonly',
17
+ process: 'readonly',
18
+ Buffer: 'readonly',
19
+ __dirname: 'readonly',
20
+ __filename: 'readonly',
21
+ },
22
+ },
23
+ plugins: {
24
+ '@typescript-eslint': tseslint,
25
+ },
26
+ rules: {
27
+ ...tseslint.configs.recommended.rules,
28
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
29
+ '@typescript-eslint/explicit-function-return-type': 'off',
30
+ '@typescript-eslint/no-explicit-any': 'warn',
31
+ 'no-console': 'off',
32
+ },
33
+ },
34
+ {
35
+ ignores: ['dist/', 'node_modules/', '*.config.js'],
36
+ },
37
+ ];
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@gemini-designer/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for AI-powered UI design and code generation with Gemini",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "gemini-designer-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "dev": "tsx watch src/index.ts",
12
+ "build": "tsc",
13
+ "start": "node dist/index.js",
14
+ "test": "vitest",
15
+ "test:coverage": "vitest --coverage",
16
+ "lint": "eslint src/",
17
+ "lint:fix": "eslint src/ --fix",
18
+ "format": "prettier --write src/",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "gemini",
25
+ "design-to-code",
26
+ "ai",
27
+ "frontend",
28
+ "ui-generation"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=20.0.0"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/yourusername/gemini-designer-mcp"
38
+ },
39
+ "homepage": "https://gemini-designer.dev",
40
+ "dependencies": {
41
+ "@google/generative-ai": "^0.21.0",
42
+ "@modelcontextprotocol/sdk": "^1.12.0",
43
+ "prettier": "^3.4.0",
44
+ "typescript": "^5.7.0",
45
+ "zod": "^3.24.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.10.0",
49
+ "@typescript-eslint/eslint-plugin": "^8.18.0",
50
+ "@typescript-eslint/parser": "^8.18.0",
51
+ "@vitest/coverage-v8": "^2.1.0",
52
+ "eslint": "^9.16.0",
53
+ "tsx": "^4.19.0",
54
+ "vitest": "^2.1.0"
55
+ }
56
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Tests for context/builder.ts
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { estimateTokens } from '../context/builder.js';
7
+
8
+ describe('estimateTokens', () => {
9
+ it('should estimate tokens based on character count', () => {
10
+ // ~4 chars per token
11
+ expect(estimateTokens('hello')).toBe(2); // 5 chars → ~2 tokens
12
+ expect(estimateTokens('')).toBe(0);
13
+ });
14
+
15
+ it('should handle longer text', () => {
16
+ const text = 'a'.repeat(100);
17
+ expect(estimateTokens(text)).toBe(25); // 100 chars → 25 tokens
18
+ });
19
+
20
+ it('should round up for partial tokens', () => {
21
+ const text = 'ab'; // 2 chars → 0.5 → rounds to 1
22
+ expect(estimateTokens(text)).toBe(1);
23
+ });
24
+
25
+ it('should handle code snippets', () => {
26
+ const code = 'const Button = () => <button>Click</button>;';
27
+ const tokens = estimateTokens(code);
28
+ expect(tokens).toBeGreaterThan(0);
29
+ expect(tokens).toBe(Math.ceil(code.length / 4));
30
+ });
31
+ });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Tests for config/index.ts
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
6
+
7
+ describe('Config', () => {
8
+ const originalEnv = process.env;
9
+
10
+ beforeEach(() => {
11
+ vi.resetModules();
12
+ process.env = { ...originalEnv };
13
+ });
14
+
15
+ afterEach(() => {
16
+ process.env = originalEnv;
17
+ });
18
+
19
+ describe('loadConfigFromEnv', () => {
20
+ it('should load API key from environment', async () => {
21
+ process.env.GEMINI_API_KEY = 'test-key';
22
+
23
+ // Dynamic import to get fresh module
24
+ const { loadConfig } = await import('../config/index.js');
25
+
26
+ // This would throw without the key, so we test it exists
27
+ expect(process.env.GEMINI_API_KEY).toBe('test-key');
28
+ });
29
+
30
+ it('should respect mode environment variable', () => {
31
+ process.env.GEMINI_DESIGNER_MODE = 'remote';
32
+ expect(process.env.GEMINI_DESIGNER_MODE).toBe('remote');
33
+ });
34
+
35
+ it('should respect debug environment variable', () => {
36
+ process.env.GEMINI_DESIGNER_DEBUG = 'true';
37
+ expect(process.env.GEMINI_DESIGNER_DEBUG).toBe('true');
38
+ });
39
+ });
40
+
41
+ describe('Config schema', () => {
42
+ it('should have correct default framework', () => {
43
+ // The default is 'react' in the schema
44
+ expect(true).toBe(true); // Placeholder for schema validation
45
+ });
46
+
47
+ it('should have correct default accessibility', () => {
48
+ // The default is 'wcag-aa' in the schema
49
+ expect(true).toBe(true); // Placeholder for schema validation
50
+ });
51
+ });
52
+ });
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Tests for context/filter.ts
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest';
6
+ import { isSensitiveFile, isPathAllowed, isUIRelevant, sanitizeContent } from '../context/filter.js';
7
+
8
+ describe('isSensitiveFile', () => {
9
+ it('should flag .env files as sensitive', () => {
10
+ expect(isSensitiveFile('.env')).toBe(true);
11
+ expect(isSensitiveFile('.env.local')).toBe(true);
12
+ expect(isSensitiveFile('/path/to/.env.production')).toBe(true);
13
+ });
14
+
15
+ it('should flag secret files as sensitive', () => {
16
+ expect(isSensitiveFile('secrets.json')).toBe(true);
17
+ expect(isSensitiveFile('/config/secrets.yaml')).toBe(true);
18
+ });
19
+
20
+ it('should flag private keys as sensitive', () => {
21
+ expect(isSensitiveFile('server.key')).toBe(true);
22
+ expect(isSensitiveFile('certificate.pem')).toBe(true);
23
+ expect(isSensitiveFile('id_rsa')).toBe(true);
24
+ });
25
+
26
+ it('should flag backend directories as sensitive', () => {
27
+ expect(isSensitiveFile('/src/api/routes.ts')).toBe(true);
28
+ expect(isSensitiveFile('/server/index.ts')).toBe(true);
29
+ expect(isSensitiveFile('/backend/handlers.ts')).toBe(true);
30
+ });
31
+
32
+ it('should not flag UI files as sensitive', () => {
33
+ expect(isSensitiveFile('Button.tsx')).toBe(false);
34
+ expect(isSensitiveFile('/components/Card.tsx')).toBe(false);
35
+ expect(isSensitiveFile('styles.css')).toBe(false);
36
+ });
37
+ });
38
+
39
+ describe('isPathAllowed', () => {
40
+ it('should allow paths within allowed directories', () => {
41
+ expect(isPathAllowed('/project/src/file.ts', ['/project'])).toBe(true);
42
+ expect(isPathAllowed('/project/components/Button.tsx', ['/project'])).toBe(true);
43
+ });
44
+
45
+ it('should reject paths outside allowed directories', () => {
46
+ expect(isPathAllowed('/etc/passwd', ['/project'])).toBe(false);
47
+ expect(isPathAllowed('/home/user/secrets', ['/project'])).toBe(false);
48
+ });
49
+
50
+ it('should support multiple allowed paths', () => {
51
+ const allowed = ['/project', '/shared'];
52
+ expect(isPathAllowed('/project/file.ts', allowed)).toBe(true);
53
+ expect(isPathAllowed('/shared/utils.ts', allowed)).toBe(true);
54
+ expect(isPathAllowed('/other/file.ts', allowed)).toBe(false);
55
+ });
56
+ });
57
+
58
+ describe('isUIRelevant', () => {
59
+ it('should identify CSS files as UI relevant', () => {
60
+ expect(isUIRelevant('styles.css')).toBe(true);
61
+ expect(isUIRelevant('theme.scss')).toBe(true);
62
+ expect(isUIRelevant('variables.less')).toBe(true);
63
+ });
64
+
65
+ it('should identify component files as UI relevant', () => {
66
+ expect(isUIRelevant('Button.tsx')).toBe(true);
67
+ expect(isUIRelevant('Card.jsx')).toBe(true);
68
+ expect(isUIRelevant('Header.vue')).toBe(true);
69
+ expect(isUIRelevant('Nav.svelte')).toBe(true);
70
+ });
71
+
72
+ it('should identify design token files as UI relevant', () => {
73
+ expect(isUIRelevant('tokens.json')).toBe(true);
74
+ expect(isUIRelevant('design-tokens.ts')).toBe(true);
75
+ expect(isUIRelevant('theme.ts')).toBe(true);
76
+ });
77
+
78
+ it('should not identify backend files as UI relevant', () => {
79
+ expect(isUIRelevant('database.ts')).toBe(false);
80
+ expect(isUIRelevant('server.ts')).toBe(false);
81
+ });
82
+ });
83
+
84
+ describe('sanitizeContent', () => {
85
+ it('should redact API key patterns', () => {
86
+ const content = 'const apiKey = "sk-abc123xyz";';
87
+ expect(sanitizeContent(content)).toContain('[REDACTED]');
88
+ });
89
+
90
+ it('should redact Bearer tokens', () => {
91
+ const content = 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ';
92
+ expect(sanitizeContent(content)).toContain('[REDACTED]');
93
+ });
94
+
95
+ it('should redact AWS keys', () => {
96
+ const content = 'aws_access_key = "AKIAIOSFODNN7EXAMPLE"';
97
+ expect(sanitizeContent(content)).toContain('[REDACTED]');
98
+ });
99
+
100
+ it('should redact database connection strings', () => {
101
+ const content = 'DATABASE_URL=postgresql://user:password@localhost:5432/db';
102
+ expect(sanitizeContent(content)).toContain('[REDACTED]');
103
+ });
104
+
105
+ it('should preserve non-sensitive content', () => {
106
+ const content = 'const Button = () => <button>Click me</button>;';
107
+ expect(sanitizeContent(content)).toBe(content);
108
+ });
109
+ });