gemini-design-mcp 3.3.1 → 3.5.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.
- package/build/index.js +41 -7
- package/build/lib/gemini.d.ts +2 -1
- package/build/lib/gemini.js +11 -5
- package/build/tools/create-frontend.d.ts +6 -0
- package/build/tools/create-frontend.js +7 -8
- package/build/tools/generate-vibes.d.ts +23 -0
- package/build/tools/generate-vibes.js +94 -0
- package/build/tools/modify-frontend.js +1 -1
- package/build/tools/snippet-frontend.js +1 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { createFrontendSchema, createFrontend } from "./tools/create-frontend.js";
|
|
5
5
|
import { modifyFrontendSchema, modifyFrontend } from "./tools/modify-frontend.js";
|
|
6
6
|
import { snippetFrontendSchema, snippetFrontend } from "./tools/snippet-frontend.js";
|
|
7
|
+
import { generateVibesSchema, generateVibes } from "./tools/generate-vibes.js";
|
|
7
8
|
// Create MCP server
|
|
8
9
|
const server = new McpServer({
|
|
9
10
|
name: "gemini-design-mcp",
|
|
@@ -50,13 +51,8 @@ C) PROJECT EXISTS with existing frontend code:
|
|
|
50
51
|
|
|
51
52
|
STEP 2: VIBE SELECTION (Required for new designs)
|
|
52
53
|
──────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
🏛️ "Pristine Museum" - Ultra-clean, white-cube aesthetic...
|
|
56
|
-
⚡ "Technical Precision" - Grid-focused, architectural...
|
|
57
|
-
🌊 "Fluid & Organic" - Soft curves, flowing gradients...
|
|
58
|
-
🔥 "Bold & Unapologetic" - High contrast, oversized typography...
|
|
59
|
-
🌙 "Dark Luxe" - Deep darks, metallic accents...
|
|
54
|
+
Call generate_vibes tool FIRST to get 5 creative vibes from Gemini.
|
|
55
|
+
Present options to user, they select one, then pass it here via designSystem.vibe.
|
|
60
56
|
|
|
61
57
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
62
58
|
📤 OUTPUT
|
|
@@ -216,6 +212,44 @@ You (Claude) are responsible for:
|
|
|
216
212
|
- Merging new imports
|
|
217
213
|
- Inserting the snippet at the correct location`, snippetFrontendSchema, snippetFrontend);
|
|
218
214
|
// =============================================================================
|
|
215
|
+
// TOOL 4: GENERATE_VIBES
|
|
216
|
+
// =============================================================================
|
|
217
|
+
server.tool("generate_vibes", `Generate 5 unique design vibes for a new project using Gemini AI.
|
|
218
|
+
|
|
219
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
220
|
+
🎯 WHEN TO USE THIS TOOL
|
|
221
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
222
|
+
|
|
223
|
+
Use this tool BEFORE calling create_frontend on a NEW project that has no existing design.
|
|
224
|
+
|
|
225
|
+
This tool generates creative, unique design vibes tailored to the project context.
|
|
226
|
+
The user then selects one, and you pass it to create_frontend.
|
|
227
|
+
|
|
228
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
229
|
+
📋 FLOW
|
|
230
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
231
|
+
|
|
232
|
+
1. User asks to create frontend for a NEW project
|
|
233
|
+
2. Call generate_vibes with project description
|
|
234
|
+
3. Present the 5 vibes to the user
|
|
235
|
+
4. User selects their preferred vibe
|
|
236
|
+
5. Call create_frontend with the selected vibe in designSystem.vibe
|
|
237
|
+
|
|
238
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
239
|
+
📤 OUTPUT
|
|
240
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
241
|
+
|
|
242
|
+
Returns 5 vibes, each with:
|
|
243
|
+
- emoji: Visual identifier
|
|
244
|
+
- name: Memorable vibe name
|
|
245
|
+
- description: 2-3 evocative sentences
|
|
246
|
+
- keywords: Style keywords for the designer
|
|
247
|
+
|
|
248
|
+
Example vibe:
|
|
249
|
+
🏛️ "Pristine Museum"
|
|
250
|
+
An ultra-clean, 'white-cube' aesthetic focused on vast negative space.
|
|
251
|
+
Keywords: minimal, whitespace, gallery, clean, sophisticated`, generateVibesSchema, generateVibes);
|
|
252
|
+
// =============================================================================
|
|
219
253
|
// START SERVER
|
|
220
254
|
// =============================================================================
|
|
221
255
|
async function main() {
|
package/build/lib/gemini.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GoogleGenAI } from "@google/genai";
|
|
2
|
+
export type ThinkingMode = "minimal" | "low" | "medium" | "high";
|
|
2
3
|
export declare const ai: GoogleGenAI | null;
|
|
3
4
|
export declare const DEFAULT_MODEL = "gemini-3-flash-preview";
|
|
4
|
-
export declare function generateWithGemini(systemPrompt: string, userPrompt: string, model?: string): Promise<string>;
|
|
5
|
+
export declare function generateWithGemini(systemPrompt: string, userPrompt: string, model?: string, thinkingMode?: ThinkingMode): Promise<string>;
|
package/build/lib/gemini.js
CHANGED
|
@@ -16,7 +16,7 @@ export const DEFAULT_MODEL = "gemini-3-flash-preview";
|
|
|
16
16
|
/**
|
|
17
17
|
* Generate content via the hosted proxy service
|
|
18
18
|
*/
|
|
19
|
-
async function generateViaProxy(systemPrompt, userPrompt, model) {
|
|
19
|
+
async function generateViaProxy(systemPrompt, userPrompt, model, thinkingMode = "minimal") {
|
|
20
20
|
const response = await fetch(PROXY_URL, {
|
|
21
21
|
method: "POST",
|
|
22
22
|
headers: {
|
|
@@ -36,6 +36,9 @@ async function generateViaProxy(systemPrompt, userPrompt, model) {
|
|
|
36
36
|
},
|
|
37
37
|
generationConfig: {
|
|
38
38
|
temperature: 1,
|
|
39
|
+
thinkingConfig: {
|
|
40
|
+
thinkingLevel: thinkingMode,
|
|
41
|
+
},
|
|
39
42
|
},
|
|
40
43
|
}),
|
|
41
44
|
});
|
|
@@ -54,7 +57,7 @@ async function generateViaProxy(systemPrompt, userPrompt, model) {
|
|
|
54
57
|
/**
|
|
55
58
|
* Generate content directly via Google SDK
|
|
56
59
|
*/
|
|
57
|
-
async function generateViaSdk(systemPrompt, userPrompt, model) {
|
|
60
|
+
async function generateViaSdk(systemPrompt, userPrompt, model, thinkingMode = "minimal") {
|
|
58
61
|
if (!ai) {
|
|
59
62
|
throw new Error("SDK not initialized");
|
|
60
63
|
}
|
|
@@ -64,17 +67,20 @@ async function generateViaSdk(systemPrompt, userPrompt, model) {
|
|
|
64
67
|
config: {
|
|
65
68
|
systemInstruction: systemPrompt,
|
|
66
69
|
temperature: 1,
|
|
70
|
+
thinkingConfig: {
|
|
71
|
+
thinkingLevel: thinkingMode,
|
|
72
|
+
},
|
|
67
73
|
},
|
|
68
74
|
});
|
|
69
75
|
return response.text ?? "";
|
|
70
76
|
}
|
|
71
|
-
export async function generateWithGemini(systemPrompt, userPrompt, model = DEFAULT_MODEL) {
|
|
77
|
+
export async function generateWithGemini(systemPrompt, userPrompt, model = DEFAULT_MODEL, thinkingMode = "minimal") {
|
|
72
78
|
try {
|
|
73
79
|
if (isHostedKey) {
|
|
74
|
-
return await generateViaProxy(systemPrompt, userPrompt, model);
|
|
80
|
+
return await generateViaProxy(systemPrompt, userPrompt, model, thinkingMode);
|
|
75
81
|
}
|
|
76
82
|
else {
|
|
77
|
-
return await generateViaSdk(systemPrompt, userPrompt, model);
|
|
83
|
+
return await generateViaSdk(systemPrompt, userPrompt, model, thinkingMode);
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
catch (error) {
|
|
@@ -8,26 +8,31 @@ export declare const createFrontendSchema: {
|
|
|
8
8
|
vibe: z.ZodObject<{
|
|
9
9
|
name: z.ZodString;
|
|
10
10
|
description: z.ZodString;
|
|
11
|
+
scale: z.ZodEnum<["refined", "balanced", "zoomed"]>;
|
|
11
12
|
keywords: z.ZodArray<z.ZodString, "many">;
|
|
12
13
|
}, "strip", z.ZodTypeAny, {
|
|
13
14
|
name: string;
|
|
14
15
|
description: string;
|
|
16
|
+
scale: "refined" | "balanced" | "zoomed";
|
|
15
17
|
keywords: string[];
|
|
16
18
|
}, {
|
|
17
19
|
name: string;
|
|
18
20
|
description: string;
|
|
21
|
+
scale: "refined" | "balanced" | "zoomed";
|
|
19
22
|
keywords: string[];
|
|
20
23
|
}>;
|
|
21
24
|
}, "strip", z.ZodTypeAny, {
|
|
22
25
|
vibe: {
|
|
23
26
|
name: string;
|
|
24
27
|
description: string;
|
|
28
|
+
scale: "refined" | "balanced" | "zoomed";
|
|
25
29
|
keywords: string[];
|
|
26
30
|
};
|
|
27
31
|
}, {
|
|
28
32
|
vibe: {
|
|
29
33
|
name: string;
|
|
30
34
|
description: string;
|
|
35
|
+
scale: "refined" | "balanced" | "zoomed";
|
|
31
36
|
keywords: string[];
|
|
32
37
|
};
|
|
33
38
|
}>>;
|
|
@@ -41,6 +46,7 @@ export declare function createFrontend(params: {
|
|
|
41
46
|
vibe: {
|
|
42
47
|
name: string;
|
|
43
48
|
description: string;
|
|
49
|
+
scale: "refined" | "balanced" | "zoomed";
|
|
44
50
|
keywords: string[];
|
|
45
51
|
};
|
|
46
52
|
};
|
|
@@ -16,10 +16,11 @@ export const createFrontendSchema = {
|
|
|
16
16
|
vibe: z.object({
|
|
17
17
|
name: z.string().describe("Vibe name (e.g., 'Pristine Museum', 'Dark Luxe')"),
|
|
18
18
|
description: z.string().describe("Rich, evocative description of the mood (2-3 sentences)"),
|
|
19
|
+
scale: z.enum(["refined", "balanced", "zoomed"]).describe("Scale: 'refined' (small, elegant), 'balanced' (standard), 'zoomed' (large elements)"),
|
|
19
20
|
keywords: z.array(z.string()).describe("Style keywords (e.g., ['minimal', 'whitespace', 'gallery'])"),
|
|
20
21
|
}).describe("The selected design vibe"),
|
|
21
22
|
}).optional().describe("Design system with selected vibe. REQUIRED for new projects without existing design. " +
|
|
22
|
-
"
|
|
23
|
+
"Call generate_vibes first, user selects, then pass the selection here."),
|
|
23
24
|
};
|
|
24
25
|
export async function createFrontend(params) {
|
|
25
26
|
const { request, filePath, techStack, context, designSystem } = params;
|
|
@@ -27,19 +28,17 @@ export async function createFrontend(params) {
|
|
|
27
28
|
let designSystemInstructions = '';
|
|
28
29
|
if (designSystem?.vibe) {
|
|
29
30
|
const { vibe } = designSystem;
|
|
31
|
+
const scale = vibe.scale || "balanced";
|
|
30
32
|
designSystemInstructions = `
|
|
31
33
|
MANDATORY DESIGN SYSTEM (User Selected Vibe):
|
|
32
34
|
|
|
33
35
|
**Design Vibe: "${vibe.name}"**
|
|
34
36
|
${vibe.description}
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
**Scale: ${scale}**
|
|
39
|
+
Keywords: ${vibe.keywords.join(', ')}
|
|
37
40
|
|
|
38
|
-
Interpret this vibe creatively
|
|
39
|
-
- Choose colors, gradients, and visual styling that embody this atmosphere
|
|
40
|
-
- Select a font from Google Fonts that perfectly matches this vibe
|
|
41
|
-
- Apply consistent visual language throughout the entire interface
|
|
42
|
-
- You have freedom in the specific implementation, but the overall feel MUST match this vibe
|
|
41
|
+
Interpret this vibe creatively — choose colors, typography, and styling that embody this atmosphere.
|
|
43
42
|
`;
|
|
44
43
|
}
|
|
45
44
|
// Build context instructions (ignore empty strings and "null" strings)
|
|
@@ -64,7 +63,7 @@ TECH STACK: ${techStack}
|
|
|
64
63
|
FILE PATH: ${filePath}
|
|
65
64
|
|
|
66
65
|
Remember: Return a COMPLETE file ready to save at ${filePath}`.trim();
|
|
67
|
-
const result = await generateWithGemini(systemPrompt, request);
|
|
66
|
+
const result = await generateWithGemini(systemPrompt, request, undefined, "high");
|
|
68
67
|
return {
|
|
69
68
|
content: [{ type: "text", text: result }],
|
|
70
69
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const generateVibesSchema: {
|
|
3
|
+
projectDescription: z.ZodString;
|
|
4
|
+
projectType: z.ZodOptional<z.ZodString>;
|
|
5
|
+
targetAudience: z.ZodOptional<z.ZodString>;
|
|
6
|
+
};
|
|
7
|
+
export declare function generateVibes(params: {
|
|
8
|
+
projectDescription: string;
|
|
9
|
+
projectType?: string;
|
|
10
|
+
targetAudience?: string;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
vibes: any;
|
|
17
|
+
} | {
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
vibes?: undefined;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { generateWithGemini } from "../lib/gemini.js";
|
|
3
|
+
export const generateVibesSchema = {
|
|
4
|
+
projectDescription: z.string().describe("Brief description of the project. " +
|
|
5
|
+
"Example: 'A SaaS dashboard for managing customer support tickets'"),
|
|
6
|
+
projectType: z.string().optional().describe("Type of project. " +
|
|
7
|
+
"Examples: 'landing page', 'dashboard', 'e-commerce', 'portfolio', 'blog', 'mobile app'"),
|
|
8
|
+
targetAudience: z.string().optional().describe("Who will use this app. " +
|
|
9
|
+
"Examples: 'developers', 'enterprise clients', 'young consumers', 'creative professionals'"),
|
|
10
|
+
};
|
|
11
|
+
const GENERATE_VIBES_PROMPT = `Generate 5 distinct mood/atmosphere options (VIBES) that match different interpretations of the project.
|
|
12
|
+
|
|
13
|
+
Each vibe should represent a unique visual direction and feeling.
|
|
14
|
+
Describe the mood, not specific colors - let the designer interpret creatively.
|
|
15
|
+
Think: "Corporate & Trustworthy" vs "Playful & Vibrant" vs "Dark & Techy"
|
|
16
|
+
|
|
17
|
+
IMPORTANT - Each vibe MUST include a SCALE recommendation:
|
|
18
|
+
- "zoomed" = Large elements, big buttons, big text, full-width cards (e.g., kids apps, accessibility-focused)
|
|
19
|
+
- "refined" = Subtle scale, smaller typography, contained elements, elegant proportions (e.g., luxury, Linear.app, Raycast)
|
|
20
|
+
- "balanced" = Standard scale, middle ground
|
|
21
|
+
|
|
22
|
+
The scale is NOT about density (dense vs sparse), it's about the SIZE of elements.
|
|
23
|
+
A luxury watch e-commerce site has refined/small scale. A kids learning platform has zoomed/large scale.
|
|
24
|
+
|
|
25
|
+
OUTPUT FORMAT (JSON):
|
|
26
|
+
Return ONLY a valid JSON array with exactly 5 vibes. No markdown, no explanation.
|
|
27
|
+
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
"emoji": "🏛️",
|
|
31
|
+
"name": "Vibe Name",
|
|
32
|
+
"description": "2-3 sentences describing the mood and atmosphere.",
|
|
33
|
+
"scale": "refined" | "balanced" | "zoomed",
|
|
34
|
+
"keywords": ["keyword1", "keyword2", "keyword3"]
|
|
35
|
+
}
|
|
36
|
+
]`;
|
|
37
|
+
export async function generateVibes(params) {
|
|
38
|
+
const { projectDescription, projectType, targetAudience } = params;
|
|
39
|
+
// Build context for Gemini
|
|
40
|
+
let contextParts = [`Project: ${projectDescription}`];
|
|
41
|
+
if (projectType) {
|
|
42
|
+
contextParts.push(`Type: ${projectType}`);
|
|
43
|
+
}
|
|
44
|
+
if (targetAudience) {
|
|
45
|
+
contextParts.push(`Target Audience: ${targetAudience}`);
|
|
46
|
+
}
|
|
47
|
+
const userPrompt = contextParts.join("\n");
|
|
48
|
+
const result = await generateWithGemini(GENERATE_VIBES_PROMPT, userPrompt, undefined, // default model
|
|
49
|
+
"high" // high thinking for creative vibes
|
|
50
|
+
);
|
|
51
|
+
// Parse the JSON response
|
|
52
|
+
try {
|
|
53
|
+
// Clean the response (remove potential markdown code blocks)
|
|
54
|
+
let cleanedResult = result.trim();
|
|
55
|
+
if (cleanedResult.startsWith("```json")) {
|
|
56
|
+
cleanedResult = cleanedResult.slice(7);
|
|
57
|
+
}
|
|
58
|
+
if (cleanedResult.startsWith("```")) {
|
|
59
|
+
cleanedResult = cleanedResult.slice(3);
|
|
60
|
+
}
|
|
61
|
+
if (cleanedResult.endsWith("```")) {
|
|
62
|
+
cleanedResult = cleanedResult.slice(0, -3);
|
|
63
|
+
}
|
|
64
|
+
cleanedResult = cleanedResult.trim();
|
|
65
|
+
const vibes = JSON.parse(cleanedResult);
|
|
66
|
+
// Format vibes for display
|
|
67
|
+
const formattedVibes = vibes.map((vibe, index) => {
|
|
68
|
+
return `${index + 1}. ${vibe.emoji} "${vibe.name}"
|
|
69
|
+
${vibe.description}
|
|
70
|
+
Scale: ${vibe.scale} | Keywords: ${vibe.keywords.join(", ")}`;
|
|
71
|
+
}).join("\n\n");
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: "text",
|
|
76
|
+
text: `Here are 5 design vibes generated for your project:\n\n${formattedVibes}\n\n---\n\nAsk the user to select one vibe. Once selected, pass it to create_frontend via the designSystem.vibe parameter with:\n- name: The vibe name (e.g., "Pristine Museum")\n- description: The full description\n- keywords: The array of keywords`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
// Also return structured data for programmatic use
|
|
80
|
+
vibes,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// If parsing fails, return raw result
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `Generated vibes:\n\n${result}\n\n---\n\nNote: Could not parse as JSON. Please extract the vibes manually.`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -37,7 +37,7 @@ ${targetCode}
|
|
|
37
37
|
MODIFICATION REQUESTED: ${modification}
|
|
38
38
|
|
|
39
39
|
Remember: Return ONLY the find/replace block. ONE modification, surgical precision.`.trim();
|
|
40
|
-
const result = await generateWithGemini(systemPrompt, modification);
|
|
40
|
+
const result = await generateWithGemini(systemPrompt, modification, undefined, "minimal");
|
|
41
41
|
return {
|
|
42
42
|
content: [{ type: "text", text: result }],
|
|
43
43
|
};
|
|
@@ -39,7 +39,7 @@ INSERTION CONTEXT:
|
|
|
39
39
|
${insertionContext}
|
|
40
40
|
|
|
41
41
|
Generate a snippet that will integrate smoothly at this location.`.trim();
|
|
42
|
-
const result = await generateWithGemini(systemPrompt, request);
|
|
42
|
+
const result = await generateWithGemini(systemPrompt, request, undefined, "minimal");
|
|
43
43
|
return {
|
|
44
44
|
content: [{ type: "text", text: result }],
|
|
45
45
|
};
|