@gemini-designer/mcp-server 0.1.2 → 0.1.29
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/dist/components/catalog.d.ts.map +1 -1
- package/dist/components/catalog.js +10 -4
- package/dist/components/catalog.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +11 -6
- package/dist/config/index.js.map +1 -1
- package/dist/context/builder.d.ts.map +1 -1
- package/dist/context/builder.js.map +1 -1
- package/dist/context/filter.d.ts.map +1 -1
- package/dist/context/filter.js +5 -1
- package/dist/context/filter.js.map +1 -1
- package/dist/context/grounding.d.ts.map +1 -1
- package/dist/context/grounding.js +7 -3
- package/dist/context/grounding.js.map +1 -1
- package/dist/context/guards.d.ts.map +1 -1
- package/dist/context/guards.js +53 -0
- package/dist/context/guards.js.map +1 -1
- package/dist/context/repo-hints.js.map +1 -1
- package/dist/context/styling-detector.d.ts +24 -0
- package/dist/context/styling-detector.d.ts.map +1 -0
- package/dist/context/styling-detector.js +337 -0
- package/dist/context/styling-detector.js.map +1 -0
- package/dist/design/principles.js.map +1 -1
- package/dist/generation/gemini-client.d.ts.map +1 -1
- package/dist/generation/gemini-client.js.map +1 -1
- package/dist/generation/litellm-client.d.ts.map +1 -1
- package/dist/generation/litellm-client.js +14 -7
- package/dist/generation/litellm-client.js.map +1 -1
- package/dist/generation/remote-client.d.ts +10 -5
- package/dist/generation/remote-client.d.ts.map +1 -1
- package/dist/generation/remote-client.js +13 -2
- package/dist/generation/remote-client.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/output/file-writer.d.ts.map +1 -1
- package/dist/output/file-writer.js +4 -4
- package/dist/output/file-writer.js.map +1 -1
- package/dist/output/formatter.d.ts.map +1 -1
- package/dist/output/formatter.js +5 -2
- package/dist/output/formatter.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/stack/detect.d.ts.map +1 -1
- package/dist/stack/detect.js +42 -9
- package/dist/stack/detect.js.map +1 -1
- package/dist/tokens/sync.d.ts.map +1 -1
- package/dist/tokens/sync.js +22 -5
- package/dist/tokens/sync.js.map +1 -1
- package/dist/tools/analyze-screenshot-ui.d.ts.map +1 -1
- package/dist/tools/analyze-screenshot-ui.js +5 -5
- package/dist/tools/analyze-screenshot-ui.js.map +1 -1
- package/dist/tools/analyze-tokens.d.ts.map +1 -1
- package/dist/tools/analyze-tokens.js +3 -1
- package/dist/tools/analyze-tokens.js.map +1 -1
- package/dist/tools/catalog-components.d.ts.map +1 -1
- package/dist/tools/catalog-components.js +1 -4
- package/dist/tools/catalog-components.js.map +1 -1
- package/dist/tools/create-ui.d.ts +3 -0
- package/dist/tools/create-ui.d.ts.map +1 -1
- package/dist/tools/create-ui.js +203 -75
- package/dist/tools/create-ui.js.map +1 -1
- package/dist/tools/detect-ui-stack.js.map +1 -1
- package/dist/tools/generate-component-variants.d.ts.map +1 -1
- package/dist/tools/generate-component-variants.js +15 -4
- package/dist/tools/generate-component-variants.js.map +1 -1
- package/dist/tools/generate-vibes.d.ts.map +1 -1
- package/dist/tools/generate-vibes.js +7 -3
- package/dist/tools/generate-vibes.js.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/modify-ui.d.ts.map +1 -1
- package/dist/tools/modify-ui.js +7 -2
- package/dist/tools/modify-ui.js.map +1 -1
- package/dist/tools/scaffold-project.d.ts.map +1 -1
- package/dist/tools/scaffold-project.js +3 -1
- package/dist/tools/scaffold-project.js.map +1 -1
- package/dist/tools/snippet-ui.d.ts +3 -1
- package/dist/tools/snippet-ui.d.ts.map +1 -1
- package/dist/tools/snippet-ui.js +219 -88
- package/dist/tools/snippet-ui.js.map +1 -1
- package/dist/tools/sync-design-tokens.d.ts.map +1 -1
- package/dist/tools/sync-design-tokens.js +26 -11
- package/dist/tools/sync-design-tokens.js.map +1 -1
- package/dist/utils/walk.d.ts.map +1 -1
- package/dist/utils/walk.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +55 -55
- package/src/__tests__/builder.test.ts +19 -19
- package/src/__tests__/config.test.ts +63 -31
- package/src/__tests__/filter.test.ts +98 -92
- package/src/__tests__/remote-client.test.ts +179 -0
- package/src/components/catalog.ts +170 -166
- package/src/config/index.ts +185 -177
- package/src/context/builder.ts +157 -157
- package/src/context/filter.ts +110 -104
- package/src/context/grounding.ts +143 -129
- package/src/context/guards.ts +97 -38
- package/src/context/repo-hints.ts +24 -24
- package/src/context/styling-detector.ts +460 -0
- package/src/design/principles.ts +14 -14
- package/src/generation/gemini-client.ts +53 -56
- package/src/generation/litellm-client.ts +102 -86
- package/src/generation/remote-client.ts +100 -77
- package/src/index.ts +16 -16
- package/src/output/file-writer.ts +123 -123
- package/src/output/formatter.ts +139 -132
- package/src/server.ts +12 -11
- package/src/stack/detect.ts +226 -175
- package/src/tokens/sync.ts +189 -155
- package/src/tools/analyze-screenshot-ui.ts +89 -88
- package/src/tools/analyze-tokens.ts +80 -78
- package/src/tools/catalog-components.ts +68 -68
- package/src/tools/create-ui.ts +295 -142
- package/src/tools/detect-ui-stack.ts +36 -36
- package/src/tools/generate-component-variants.ts +155 -135
- package/src/tools/generate-vibes.ts +121 -117
- package/src/tools/index.ts +14 -14
- package/src/tools/modify-ui.ts +170 -165
- package/src/tools/scaffold-project.ts +68 -66
- package/src/tools/snippet-ui.ts +323 -172
- package/src/tools/sync-design-tokens.ts +217 -195
- package/src/utils/walk.ts +47 -45
- package/src/version.ts +6 -0
- package/tsconfig.json +23 -33
- package/vitest.config.ts +10 -10
- package/.prettierrc +0 -9
- package/eslint.config.js +0 -37
|
@@ -23,23 +23,31 @@ import { writeFile } from '../output/file-writer.js';
|
|
|
23
23
|
import { DESIGN_PRINCIPLES_COMPACT } from '../design/principles.js';
|
|
24
24
|
|
|
25
25
|
const inputSchema = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
componentFile: z
|
|
27
|
+
.string()
|
|
28
|
+
.describe('Path to an existing component file (e.g., src/components/Button.tsx).'),
|
|
29
|
+
variants: z
|
|
30
|
+
.object({
|
|
31
|
+
sizes: z.array(z.string()).optional(),
|
|
32
|
+
intents: z.array(z.string()).optional(),
|
|
33
|
+
states: z.array(z.string()).optional(),
|
|
34
|
+
themes: z.array(z.string()).optional(),
|
|
35
|
+
custom: z.array(z.string()).optional().describe('Any additional variant requirements.'),
|
|
36
|
+
})
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('Variant dimensions to generate.'),
|
|
39
|
+
instruction: z.string().optional().describe('Extra guidance about behavior, styling, or API.'),
|
|
40
|
+
storybook: z.boolean().default(true).describe('Generate a Storybook stories file if true.'),
|
|
41
|
+
storyFile: z
|
|
42
|
+
.string()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe('Optional path for the stories file (defaults next to component).'),
|
|
45
|
+
context: z.array(z.string()).optional().describe('Related files (tokens, theme, utilities).'),
|
|
46
|
+
applyChanges: z.boolean().default(false).describe('If true, write files to disk.'),
|
|
47
|
+
backup: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.default(true)
|
|
50
|
+
.describe('Create .bak backups before overwriting existing files.'),
|
|
43
51
|
};
|
|
44
52
|
|
|
45
53
|
const SYSTEM_PROMPT = `You are a senior frontend engineer creating DISTINCTIVE component variants.
|
|
@@ -68,67 +76,76 @@ API guidance:
|
|
|
68
76
|
- Apply distinctive typography and color for each variant.`;
|
|
69
77
|
|
|
70
78
|
function cleanJson(response: string): string {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
let out = response.trim();
|
|
80
|
+
const fenced = out.match(/```(?:json)?\n([\s\S]*?)```/);
|
|
81
|
+
if (fenced) out = fenced[1].trim();
|
|
82
|
+
return out;
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
function defaultStoryPath(componentFile: string): string {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
const dir = path.dirname(componentFile);
|
|
87
|
+
const base = path.basename(componentFile).replace(/\.(tsx|jsx|ts|js)$/, '');
|
|
88
|
+
return path.join(dir, `${base}.stories.tsx`);
|
|
81
89
|
}
|
|
82
90
|
|
|
83
91
|
export function registerGenerateComponentVariants(server: McpServer, config: Config): void {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
let safeComponent: string;
|
|
103
|
-
try {
|
|
104
|
-
safeComponent = assertReadablePath(componentFile, config);
|
|
105
|
-
} catch (error) {
|
|
106
|
-
const message = error instanceof Error ? error.message : 'Invalid componentFile';
|
|
107
|
-
return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true };
|
|
92
|
+
server.registerTool(
|
|
93
|
+
'generate_component_variants',
|
|
94
|
+
{
|
|
95
|
+
title: 'Generate Component Variants',
|
|
96
|
+
description:
|
|
97
|
+
'Enhance an existing component with variants (sizes/intents/states) and optionally generate Storybook stories.',
|
|
98
|
+
inputSchema,
|
|
99
|
+
},
|
|
100
|
+
async (args) => {
|
|
101
|
+
const componentFile = args.componentFile as string;
|
|
102
|
+
const variants =
|
|
103
|
+
(args.variants as
|
|
104
|
+
| {
|
|
105
|
+
sizes?: string[];
|
|
106
|
+
intents?: string[];
|
|
107
|
+
states?: string[];
|
|
108
|
+
themes?: string[];
|
|
109
|
+
custom?: string[];
|
|
108
110
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
111
|
+
| undefined) || {};
|
|
112
|
+
const instruction = (args.instruction as string | undefined) || '';
|
|
113
|
+
const storybook = args.storybook !== false;
|
|
114
|
+
const storyFile = (args.storyFile as string | undefined) || defaultStoryPath(componentFile);
|
|
115
|
+
const contextPaths = args.context as string[] | undefined;
|
|
116
|
+
const applyChanges = args.applyChanges === true;
|
|
117
|
+
const backup = args.backup !== false;
|
|
118
|
+
|
|
119
|
+
let safeComponent: string;
|
|
120
|
+
try {
|
|
121
|
+
safeComponent = assertReadablePath(componentFile, config);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const message = error instanceof Error ? error.message : 'Invalid componentFile';
|
|
124
|
+
return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let componentContent: string;
|
|
128
|
+
try {
|
|
129
|
+
componentContent = fs.readFileSync(safeComponent, 'utf-8');
|
|
130
|
+
} catch {
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: 'text' as const, text: `Error: Could not read ${componentFile}` }],
|
|
133
|
+
isError: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let contextContent = '';
|
|
138
|
+
if (contextPaths && contextPaths.length > 0) {
|
|
139
|
+
contextContent = await buildContext(contextPaths, config);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Deterministic repo grounding (stack + reusable components)
|
|
143
|
+
const grounding = await buildRepoGrounding(config, {
|
|
144
|
+
focusFileAbs: safeComponent,
|
|
145
|
+
instruction,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const userPrompt = `Enhance the component to support variants.
|
|
132
149
|
|
|
133
150
|
${grounding}
|
|
134
151
|
|
|
@@ -154,69 +171,72 @@ ${contextContent ? `RELATED FILES FOR CONTEXT:\n${contextContent}` : ''}
|
|
|
154
171
|
|
|
155
172
|
Remember: Output JSON only: {"files": [{"path": string, "content": string}]}`;
|
|
156
173
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const writes: Array<{ file: string; backup?: string }> = [];
|
|
181
|
-
for (const f of parsed.files) {
|
|
182
|
-
const target = f.path;
|
|
183
|
-
const content = f.content;
|
|
184
|
-
let safeOut: string;
|
|
185
|
-
try {
|
|
186
|
-
safeOut = assertWritablePath(target, config);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
const message = error instanceof Error ? error.message : 'Invalid output path';
|
|
189
|
-
return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const result = await writeFile(safeOut, content, { format: true, backup });
|
|
193
|
-
if (!result.success) {
|
|
194
|
-
return {
|
|
195
|
-
content: [
|
|
196
|
-
{
|
|
197
|
-
type: 'text' as const,
|
|
198
|
-
text: `Error: Failed writing ${target}: ${result.error || 'Unknown error'}`,
|
|
199
|
-
},
|
|
200
|
-
],
|
|
201
|
-
isError: true,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
writes.push({ file: result.filePath, backup: result.backupPath });
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const summary = [
|
|
208
|
-
'✅ Generated component variants and wrote files:',
|
|
209
|
-
...writes.map((w) => `- ${w.file}${w.backup ? ` (backup: ${w.backup})` : ''}`),
|
|
210
|
-
].join('\n');
|
|
211
|
-
|
|
212
|
-
return { content: [{ type: 'text' as const, text: summary }] };
|
|
213
|
-
} catch (error) {
|
|
214
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
215
|
-
return {
|
|
216
|
-
content: [{ type: 'text' as const, text: `Error generating variants: ${message}` }],
|
|
217
|
-
isError: true,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
174
|
+
try {
|
|
175
|
+
const resp = await generateWithGemini(config, SYSTEM_PROMPT, userPrompt, {
|
|
176
|
+
toolName: 'generate_component_variants',
|
|
177
|
+
});
|
|
178
|
+
const cleaned = cleanJson(resp);
|
|
179
|
+
const parsed = JSON.parse(cleaned) as { files?: Array<{ path: string; content: string }> };
|
|
180
|
+
|
|
181
|
+
if (!parsed || !Array.isArray(parsed.files) || parsed.files.length === 0) {
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: 'text' as const,
|
|
186
|
+
text: 'Error: Model output did not match expected JSON format: {"files": [...]}',
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
isError: true,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!applyChanges) {
|
|
194
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(parsed, null, 2) }] };
|
|
220
195
|
}
|
|
221
|
-
|
|
196
|
+
|
|
197
|
+
const writes: Array<{ file: string; backup?: string }> = [];
|
|
198
|
+
for (const f of parsed.files) {
|
|
199
|
+
const target = f.path;
|
|
200
|
+
const content = f.content;
|
|
201
|
+
let safeOut: string;
|
|
202
|
+
try {
|
|
203
|
+
safeOut = assertWritablePath(target, config);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const message = error instanceof Error ? error.message : 'Invalid output path';
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: 'text' as const, text: `Error: ${message}` }],
|
|
208
|
+
isError: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const result = await writeFile(safeOut, content, { format: true, backup });
|
|
213
|
+
if (!result.success) {
|
|
214
|
+
return {
|
|
215
|
+
content: [
|
|
216
|
+
{
|
|
217
|
+
type: 'text' as const,
|
|
218
|
+
text: `Error: Failed writing ${target}: ${result.error || 'Unknown error'}`,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
isError: true,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
writes.push({ file: result.filePath, backup: result.backupPath });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const summary = [
|
|
228
|
+
'✅ Generated component variants and wrote files:',
|
|
229
|
+
...writes.map((w) => `- ${w.file}${w.backup ? ` (backup: ${w.backup})` : ''}`),
|
|
230
|
+
].join('\n');
|
|
231
|
+
|
|
232
|
+
return { content: [{ type: 'text' as const, text: summary }] };
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: 'text' as const, text: `Error generating variants: ${message}` }],
|
|
237
|
+
isError: true,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
);
|
|
222
242
|
}
|
|
@@ -14,39 +14,41 @@ import { generateWithGemini } from '../generation/gemini-client.js';
|
|
|
14
14
|
import { DESIGN_PRINCIPLES_COMPACT, BANNED_FONTS, SUGGESTED_FONTS } from '../design/principles.js';
|
|
15
15
|
|
|
16
16
|
const inputSchema = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.optional()
|
|
49
|
-
|
|
17
|
+
projectContext: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Brief description of the project (e.g., "Modern SaaS dashboard for analytics")'),
|
|
21
|
+
existingTokens: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Path to existing design tokens or CSS file to analyze (optional)'),
|
|
25
|
+
vibeCount: z
|
|
26
|
+
.number()
|
|
27
|
+
.min(1)
|
|
28
|
+
.max(5)
|
|
29
|
+
.default(3)
|
|
30
|
+
.describe('Number of vibe options to generate (1-5)'),
|
|
31
|
+
preferences: z
|
|
32
|
+
.object({
|
|
33
|
+
colorTemp: z.enum(['warm', 'cool', 'neutral']).optional(),
|
|
34
|
+
density: z.enum(['compact', 'comfortable', 'spacious']).optional(),
|
|
35
|
+
style: z
|
|
36
|
+
.enum([
|
|
37
|
+
'minimal',
|
|
38
|
+
'playful',
|
|
39
|
+
'corporate',
|
|
40
|
+
'brutalist',
|
|
41
|
+
'glassmorphism',
|
|
42
|
+
'editorial',
|
|
43
|
+
'retro-futuristic',
|
|
44
|
+
'luxury',
|
|
45
|
+
'organic',
|
|
46
|
+
'art-deco',
|
|
47
|
+
])
|
|
48
|
+
.optional(),
|
|
49
|
+
})
|
|
50
|
+
.optional()
|
|
51
|
+
.describe('Optional style preferences to guide generation'),
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
const SYSTEM_PROMPT = `You are an expert UI/UX designer who creates DISTINCTIVE, memorable design systems.
|
|
@@ -106,98 +108,100 @@ SUGGESTED FONTS:
|
|
|
106
108
|
}`;
|
|
107
109
|
|
|
108
110
|
export function registerGenerateVibes(server: McpServer, config: Config): void {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
111
|
+
server.registerTool(
|
|
112
|
+
'generate_vibes',
|
|
113
|
+
{
|
|
114
|
+
title: 'Generate Design Vibes',
|
|
115
|
+
description:
|
|
116
|
+
'Generate design direction options (vibes) with color palettes, typography, and spacing. Call this before create_ui to establish design consistency.',
|
|
117
|
+
inputSchema,
|
|
118
|
+
},
|
|
119
|
+
async (args) => {
|
|
120
|
+
const projectContext = args.projectContext as string | undefined;
|
|
121
|
+
const vibeCount = args.vibeCount as number | undefined;
|
|
122
|
+
const preferences = args.preferences as
|
|
123
|
+
| {
|
|
124
|
+
colorTemp?: 'warm' | 'cool' | 'neutral';
|
|
125
|
+
density?: 'compact' | 'comfortable' | 'spacious';
|
|
126
|
+
style?: 'minimal' | 'playful' | 'corporate' | 'brutalist' | 'glassmorphism';
|
|
127
|
+
}
|
|
128
|
+
| undefined;
|
|
129
|
+
|
|
130
|
+
const existingTokensPath = args.existingTokens as string | undefined;
|
|
131
|
+
|
|
132
|
+
let existingTokensContext = '';
|
|
133
|
+
if (existingTokensPath) {
|
|
134
|
+
// Use the same context pipeline (path allowlist + sensitive file filtering + truncation)
|
|
135
|
+
existingTokensContext = await buildContext([existingTokensPath], config);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let userPrompt = buildPrompt({
|
|
139
|
+
projectContext,
|
|
140
|
+
vibeCount,
|
|
141
|
+
preferences,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (existingTokensContext) {
|
|
145
|
+
userPrompt += `\n\nExisting design tokens / styles to respect:\n${existingTokensContext}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const response = await generateWithGemini(config, SYSTEM_PROMPT, userPrompt, {
|
|
150
|
+
toolName: 'generate_vibes',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: 'text' as const,
|
|
157
|
+
text: response.trim(),
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: 'text' as const,
|
|
167
|
+
text: `Error generating vibes: ${message}`,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
isError: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
);
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
function buildPrompt(input: {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
projectContext?: string;
|
|
179
|
+
vibeCount?: number;
|
|
180
|
+
preferences?: {
|
|
181
|
+
colorTemp?: 'warm' | 'cool' | 'neutral';
|
|
182
|
+
density?: 'compact' | 'comfortable' | 'spacious';
|
|
183
|
+
style?: 'minimal' | 'playful' | 'corporate' | 'brutalist' | 'glassmorphism';
|
|
184
|
+
};
|
|
181
185
|
}): string {
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
const vibeCount = input.vibeCount ?? 3;
|
|
187
|
+
let prompt = `Generate ${vibeCount} distinct design vibes`;
|
|
184
188
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
if (input.projectContext) {
|
|
190
|
+
prompt += ` for this project: "${input.projectContext}"`;
|
|
191
|
+
}
|
|
188
192
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
if (input.preferences) {
|
|
194
|
+
const prefs = [];
|
|
195
|
+
if (input.preferences.colorTemp) prefs.push(`${input.preferences.colorTemp} color temperature`);
|
|
196
|
+
if (input.preferences.density) prefs.push(`${input.preferences.density} spacing density`);
|
|
197
|
+
if (input.preferences.style) prefs.push(`${input.preferences.style} style`);
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
199
|
+
if (prefs.length > 0) {
|
|
200
|
+
prompt += `. Preferences: ${prefs.join(', ')}`;
|
|
198
201
|
}
|
|
202
|
+
}
|
|
199
203
|
|
|
200
|
-
|
|
204
|
+
prompt += '. Return valid JSON only.';
|
|
201
205
|
|
|
202
|
-
|
|
206
|
+
return prompt;
|
|
203
207
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -22,21 +22,21 @@ import { registerAnalyzeScreenshotUI } from './analyze-screenshot-ui.js';
|
|
|
22
22
|
* Register all MCP tools
|
|
23
23
|
*/
|
|
24
24
|
export function registerTools(server: McpServer, config: Config): void {
|
|
25
|
-
|
|
25
|
+
console.error('[tools] Registering MCP tools...');
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
registerGenerateVibes(server, config);
|
|
28
|
+
registerCreateUI(server, config);
|
|
29
|
+
registerModifyUI(server, config);
|
|
30
|
+
registerSnippetUI(server, config);
|
|
31
|
+
registerScaffoldProject(server, config);
|
|
32
|
+
registerAnalyzeTokens(server, config);
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
// New grounding + power tools
|
|
35
|
+
registerDetectUIStack(server, config);
|
|
36
|
+
registerSyncDesignTokens(server, config);
|
|
37
|
+
registerCatalogComponents(server, config);
|
|
38
|
+
registerGenerateComponentVariants(server, config);
|
|
39
|
+
registerAnalyzeScreenshotUI(server, config);
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
console.error('[tools] All tools registered');
|
|
42
42
|
}
|