gemini-design-mcp 3.6.14 → 3.7.1

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 CHANGED
@@ -59,7 +59,7 @@ Present options to user, they select one, then pass it here via designSystem.vib
59
59
  šŸ“¤ OUTPUT
60
60
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
61
61
 
62
- Returns a confirmation message after writing to disk (lightweight context).`, createFrontendSchema, createFrontend);
62
+ Returns the complete code. YOU (the agent) are responsible for writing it to disk.`, createFrontendSchema, createFrontend);
63
63
  // =============================================================================
64
64
  // TOOL 2: MODIFY_FRONTEND
65
65
  // =============================================================================
@@ -112,7 +112,7 @@ import { X } from "y";
112
112
  // REPLACE WITH:
113
113
  <new redesigned code>
114
114
 
115
- The modification is applied directly to disk by default.`, modifyFrontendSchema, modifyFrontend);
115
+ YOU (the agent) are responsible for applying this find/replace to the file.`, modifyFrontendSchema, modifyFrontend);
116
116
  // =============================================================================
117
117
  // TOOL 3: SNIPPET_FRONTEND
118
118
  // =============================================================================
@@ -234,11 +234,9 @@ import { Search } from "lucide-react";
234
234
  )}
235
235
  </div>
236
236
 
237
- You (Claude) are responsible for:
237
+ YOU (the agent) are responsible for:
238
238
  - Adding the logic (useState, handlers) BEFORE calling this tool
239
- - Providing insertAtLine or insertAfterPattern for direct insertion
240
-
241
- The snippet is inserted directly to disk by default (requires insertAtLine OR insertAfterPattern).`, snippetFrontendSchema, snippetFrontend);
239
+ - Inserting the returned snippet into the correct location in the file`, snippetFrontendSchema, snippetFrontend);
242
240
  // =============================================================================
243
241
  // TOOL 4: GENERATE_VIBES
244
242
  // =============================================================================
@@ -7,11 +7,11 @@ function getApiKey() {
7
7
  const apiKey = process.env.API_KEY;
8
8
  if (!apiKey) {
9
9
  throw new Error("Missing API_KEY environment variable. " +
10
- "Get your API key at https://gemini-design.com/dashboard/api-keys");
10
+ "Get your API key at https://gemini-design-mcp.com/dashboard/api-keys");
11
11
  }
12
12
  if (!apiKey.startsWith("gd_")) {
13
13
  throw new Error("Invalid API key format. API keys must start with 'gd_'. " +
14
- "Get your API key at https://gemini-design.com/dashboard/api-keys");
14
+ "Get your API key at https://gemini-design-mcp.com/dashboard/api-keys");
15
15
  }
16
16
  return apiKey;
17
17
  }
@@ -45,7 +45,17 @@ export async function generateWithGemini(systemPrompt, userPrompt, model = DEFAU
45
45
  });
46
46
  const result = await response.json();
47
47
  if (!response.ok) {
48
- throw new Error(result.error || `API error: ${response.status}`);
48
+ const errorMsg = result.error || `API error: ${response.status}`;
49
+ // Check for credit/quota related errors
50
+ if (errorMsg.toLowerCase().includes('credit') ||
51
+ errorMsg.toLowerCase().includes('quota') ||
52
+ errorMsg.toLowerCase().includes('insufficient') ||
53
+ errorMsg.toLowerCase().includes('limit') ||
54
+ response.status === 402 ||
55
+ response.status === 429) {
56
+ throw new Error(`${errorMsg}\n\nšŸ’³ Top up your credits here: https://gemini-design-mcp.com/settings/billing`);
57
+ }
58
+ throw new Error(errorMsg);
49
59
  }
50
60
  // Extract text from Gemini response format
51
61
  const text = result.candidates?.[0]?.content?.parts?.[0]?.text;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { generateWithGemini } from "../lib/gemini.js";
3
3
  import { CREATE_FRONTEND_PROMPT } from "../prompts/system.js";
4
- import { writeFileWithDirs, getFileSize, stripCodeFences } from "../lib/filesystem.js";
4
+ import { stripCodeFences } from "../lib/filesystem.js";
5
5
  import { scaleSchema, scaleDescriptions } from "../lib/scale.js";
6
6
  export const createFrontendSchema = {
7
7
  request: z.string().describe("What to create: describe the page, component, or section. " +
@@ -24,12 +24,9 @@ export const createFrontendSchema = {
24
24
  }).describe("The selected design vibe"),
25
25
  }).optional().describe("Design system with selected vibe. REQUIRED for new projects without existing design. " +
26
26
  "Call generate_vibes first, user selects, then pass the selection here."),
27
- writeFile: z.boolean().default(false).describe("Write the file directly to disk instead of returning the code. " +
28
- "Returns a confirmation message instead of the full code, keeping context lightweight. " +
29
- "Defaults to false."),
30
27
  };
31
28
  export async function createFrontend(params) {
32
- const { request, filePath, techStack, context, designSystem, writeFile } = params;
29
+ const { request, filePath, techStack, context, designSystem } = params;
33
30
  // Build design system instructions if provided
34
31
  let designSystemInstructions = '';
35
32
  if (designSystem?.vibe) {
@@ -74,18 +71,7 @@ Remember: Return a COMPLETE file ready to save at ${filePath}`.trim();
74
71
  const rawResult = await generateWithGemini(systemPrompt, request, undefined, "high", "create_frontend");
75
72
  // Strip markdown code fences from the result
76
73
  const result = stripCodeFences(rawResult);
77
- // Write file directly if requested
78
- if (writeFile) {
79
- writeFileWithDirs(filePath, result);
80
- const size = getFileSize(result);
81
- return {
82
- content: [{
83
- type: "text",
84
- text: `āœ… Created: ${filePath} (${size})`,
85
- }],
86
- };
87
- }
88
- // Default: return the code
74
+ // Return the code for the agent to write
89
75
  return {
90
76
  content: [{ type: "text", text: result }],
91
77
  };
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { generateWithGemini } from "../lib/gemini.js";
3
3
  import { MODIFY_FRONTEND_PROMPT } from "../prompts/system.js";
4
- import { readFileIfExists, writeFileWithDirs, parseFindReplace, applyFindReplace, mergeImports, } from "../lib/filesystem.js";
5
4
  import { scaleSchema, getScaleInstructions } from "../lib/scale.js";
6
5
  export const modifyFrontendSchema = {
7
6
  modification: z.string().describe("The SINGLE design modification to make. Be specific. " +
@@ -20,13 +19,9 @@ export const modifyFrontendSchema = {
20
19
  "Example: 'Project uses: var(--font-heading), var(--bg-primary), .section-padding class'"),
21
20
  scale: scaleSchema.optional().describe("Element sizing: 'refined' (small, elegant), 'balanced' (standard), 'zoomed' (large). " +
22
21
  "Controls button sizes, typography, spacing, icons."),
23
- writeFile: z.boolean().default(true).describe("Apply the modification directly to the file on disk. " +
24
- "Reads the file, applies find/replace, and writes back. " +
25
- "Returns a confirmation instead of the find/replace instructions. " +
26
- "Defaults to true."),
27
22
  };
28
23
  export async function modifyFrontend(params) {
29
- const { modification, targetCode, filePath, context, scale, writeFile } = params;
24
+ const { modification, targetCode, filePath, context, scale } = params;
30
25
  // Build context instructions
31
26
  let contextInstructions = '';
32
27
  if (context) {
@@ -51,52 +46,7 @@ MODIFICATION REQUESTED: ${modification}
51
46
 
52
47
  Remember: Return ONLY the find/replace block. ONE modification, surgical precision.`.trim();
53
48
  const result = await generateWithGemini(systemPrompt, modification, undefined, "minimal", "modify_frontend");
54
- // Apply modification directly if requested
55
- if (writeFile) {
56
- const fileContent = readFileIfExists(filePath);
57
- if (!fileContent) {
58
- return {
59
- content: [{
60
- type: "text",
61
- text: `āŒ Error: File not found: ${filePath}`,
62
- }],
63
- };
64
- }
65
- const parsed = parseFindReplace(result);
66
- if (!parsed) {
67
- // Fallback: return raw result if parsing fails
68
- return {
69
- content: [{
70
- type: "text",
71
- text: `āš ļø Could not parse Gemini output. Raw result:\n\n${result}`,
72
- }],
73
- };
74
- }
75
- // Apply the find/replace
76
- const { success, newContent, error } = applyFindReplace(fileContent, parsed.find, parsed.replace);
77
- if (!success) {
78
- return {
79
- content: [{
80
- type: "text",
81
- text: `āŒ Error: ${error}\n\nExpected to find:\n${parsed.find.slice(0, 200)}...`,
82
- }],
83
- };
84
- }
85
- // Merge imports if any
86
- let finalContent = newContent;
87
- if (parsed.imports) {
88
- finalContent = mergeImports(newContent, parsed.imports);
89
- }
90
- // Write the file
91
- writeFileWithDirs(filePath, finalContent);
92
- return {
93
- content: [{
94
- type: "text",
95
- text: `āœ… Modified: ${filePath}${parsed.imports ? `\n Added imports` : ""}`,
96
- }],
97
- };
98
- }
99
- // Default: return the find/replace instructions
49
+ // Return the find/replace instructions for the agent to apply
100
50
  return {
101
51
  content: [{ type: "text", text: result }],
102
52
  };
@@ -1,7 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { generateWithGemini } from "../lib/gemini.js";
3
3
  import { SNIPPET_FRONTEND_PROMPT } from "../prompts/system.js";
4
- import { readFileIfExists, writeFileWithDirs, parseSnippet, mergeImports, insertAfterLine, insertAfterPattern, } from "../lib/filesystem.js";
5
4
  import { scaleSchema, getScaleInstructions } from "../lib/scale.js";
6
5
  export const snippetFrontendSchema = {
7
6
  request: z.string().describe("What code snippet to generate. Be specific about what you need. " +
@@ -23,18 +22,9 @@ export const snippetFrontendSchema = {
23
22
  "Without this, Gemini will create standalone styles that won't match your design system."),
24
23
  scale: scaleSchema.optional().describe("Element sizing: 'refined' (small, elegant), 'balanced' (standard), 'zoomed' (large). " +
25
24
  "Controls button sizes, typography, spacing, icons."),
26
- writeFile: z.boolean().default(true).describe("Insert the snippet directly into the file on disk. " +
27
- "Requires insertAtLine OR insertAfterPattern to know where to insert. " +
28
- "Defaults to true."),
29
- insertAtLine: z.number().optional().describe("Line number (1-indexed) where to insert the snippet. " +
30
- "The snippet will be inserted AFTER this line. " +
31
- "Required if writeFile is true and insertAfterPattern is not provided."),
32
- insertAfterPattern: z.string().optional().describe("A string pattern to find in the file. The snippet will be inserted after this pattern. " +
33
- "Example: 'return (' or '<main>' or '</header>'. " +
34
- "Required if writeFile is true and insertAtLine is not provided."),
35
25
  };
36
26
  export async function snippetFrontend(params) {
37
- const { request, targetFile, techStack, insertionContext, context, scale, writeFile, insertAtLine: lineNumber, insertAfterPattern: pattern, } = params;
27
+ const { request, targetFile, techStack, insertionContext, context, scale } = params;
38
28
  // Build context instructions
39
29
  let contextInstructions = '';
40
30
  if (context) {
@@ -56,69 +46,7 @@ ${insertionContext}
56
46
 
57
47
  Generate a snippet that will integrate smoothly at this location.`.trim();
58
48
  const result = await generateWithGemini(systemPrompt, request, undefined, "minimal", "snippet_frontend");
59
- // Insert snippet directly if requested
60
- if (writeFile) {
61
- if (!lineNumber && !pattern) {
62
- return {
63
- content: [{
64
- type: "text",
65
- text: `āŒ Error: writeFile requires either insertAtLine or insertAfterPattern`,
66
- }],
67
- };
68
- }
69
- const fileContent = readFileIfExists(targetFile);
70
- if (!fileContent) {
71
- return {
72
- content: [{
73
- type: "text",
74
- text: `āŒ Error: File not found: ${targetFile}`,
75
- }],
76
- };
77
- }
78
- const parsed = parseSnippet(result);
79
- if (!parsed) {
80
- return {
81
- content: [{
82
- type: "text",
83
- text: `āš ļø Could not parse snippet. Raw result:\n\n${result}`,
84
- }],
85
- };
86
- }
87
- let newContent;
88
- // Insert using line number or pattern
89
- if (lineNumber) {
90
- newContent = insertAfterLine(fileContent, parsed.snippet, lineNumber);
91
- }
92
- else if (pattern) {
93
- const insertResult = insertAfterPattern(fileContent, parsed.snippet, pattern);
94
- if (!insertResult.success) {
95
- return {
96
- content: [{
97
- type: "text",
98
- text: `āŒ Error: ${insertResult.error}`,
99
- }],
100
- };
101
- }
102
- newContent = insertResult.newContent;
103
- }
104
- else {
105
- // Should never reach here due to earlier check
106
- newContent = fileContent;
107
- }
108
- // Merge imports if any
109
- if (parsed.imports) {
110
- newContent = mergeImports(newContent, parsed.imports);
111
- }
112
- // Write the file
113
- writeFileWithDirs(targetFile, newContent);
114
- return {
115
- content: [{
116
- type: "text",
117
- text: `āœ… Inserted snippet in: ${targetFile}${parsed.imports ? `\n Added imports` : ""}${lineNumber ? `\n After line ${lineNumber}` : ""}${pattern ? `\n After pattern: ${pattern}` : ""}`,
118
- }],
119
- };
120
- }
121
- // Default: return the snippet
49
+ // Return the snippet for the agent to insert
122
50
  return {
123
51
  content: [{ type: "text", text: result }],
124
52
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemini-design-mcp",
3
- "version": "3.6.14",
3
+ "version": "3.7.1",
4
4
  "description": "MCP server that uses Gemini 3 Pro for frontend/design code generation",
5
5
  "main": "build/index.js",
6
6
  "bin": {