gemini-design-mcp 3.7.2 → 3.9.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 CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
5
+ import express from "express";
6
+ import cors from "cors";
4
7
  import { createFrontendSchema, createFrontend } from "./tools/create-frontend.js";
5
8
  import { modifyFrontendSchema, modifyFrontend } from "./tools/modify-frontend.js";
6
9
  import { snippetFrontendSchema, snippetFrontend } from "./tools/snippet-frontend.js";
@@ -8,7 +11,7 @@ import { generateVibesSchema, generateVibes } from "./tools/generate-vibes.js";
8
11
  // Create MCP server
9
12
  const server = new McpServer({
10
13
  name: "gemini-design-mcp",
11
- version: "3.2.0",
14
+ version: "3.9.0",
12
15
  });
13
16
  // =============================================================================
14
17
  // TOOL 1: CREATE_FRONTEND
@@ -279,9 +282,92 @@ Keywords: minimal, whitespace, gallery, clean, sophisticated`, generateVibesSche
279
282
  // START SERVER
280
283
  // =============================================================================
281
284
  async function main() {
282
- const transport = new StdioServerTransport();
283
- await server.connect(transport);
284
- console.error("gemini-design-mcp v3.2.0 running on stdio");
285
+ const transportType = process.env.TRANSPORT || "stdio";
286
+
287
+ if (transportType === "sse") {
288
+ // SSE/HTTP mode for remote connections
289
+ const app = express();
290
+
291
+ app.use(cors({
292
+ origin: "*",
293
+ methods: ["GET", "POST", "OPTIONS"],
294
+ allowedHeaders: ["Content-Type", "Authorization", "mcp-session-id"],
295
+ credentials: true,
296
+ }));
297
+ app.use(express.json());
298
+
299
+ // Store active transports by session
300
+ const transports = new Map();
301
+
302
+ // Health check endpoint
303
+ app.get("/health", (req, res) => {
304
+ res.json({
305
+ status: "ok",
306
+ service: "gemini-design-mcp",
307
+ version: "3.9.0",
308
+ transport: "sse",
309
+ timestamp: Date.now(),
310
+ });
311
+ });
312
+
313
+ // SSE endpoint - establishes connection
314
+ app.get("/sse", async (req, res) => {
315
+ const authHeader = req.headers.authorization;
316
+ const apiKey = authHeader?.replace("Bearer ", "");
317
+
318
+ // Accept both platform keys (gd_) and Google API keys (AIza)
319
+ const isValidKey = apiKey && (apiKey.startsWith("gd_") || apiKey.startsWith("AIza"));
320
+ if (!isValidKey) {
321
+ return res.status(401).json({
322
+ error: "Missing or invalid API key. Use Authorization: Bearer <key>. " +
323
+ "Accepted formats: gd_xxx (platform) or AIza... (Google API key)",
324
+ });
325
+ }
326
+
327
+ // Set API_KEY env var for this request (tools read from process.env)
328
+ process.env.API_KEY = apiKey;
329
+
330
+ // Create SSE transport
331
+ const transport = new SSEServerTransport("/messages", res);
332
+ const sessionId = `sess_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
333
+ transports.set(sessionId, transport);
334
+
335
+ // Send session ID to client
336
+ res.setHeader("mcp-session-id", sessionId);
337
+
338
+ // Clean up on disconnect
339
+ res.on("close", () => {
340
+ transports.delete(sessionId);
341
+ });
342
+
343
+ await server.connect(transport);
344
+ });
345
+
346
+ // Messages endpoint - receives JSON-RPC from client
347
+ app.post("/messages", async (req, res) => {
348
+ const sessionId = req.query.sessionId || req.headers["mcp-session-id"];
349
+ const transport = transports.get(sessionId);
350
+
351
+ if (!transport) {
352
+ return res.status(400).json({ error: "Invalid or expired session" });
353
+ }
354
+
355
+ await transport.handlePostMessage(req, res);
356
+ });
357
+
358
+ const port = parseInt(process.env.PORT || "3000", 10);
359
+ app.listen(port, "0.0.0.0", () => {
360
+ console.log(`gemini-design-mcp v3.9.0 running on SSE transport`);
361
+ console.log(` SSE endpoint: http://0.0.0.0:${port}/sse`);
362
+ console.log(` Messages endpoint: http://0.0.0.0:${port}/messages`);
363
+ console.log(` Health check: http://0.0.0.0:${port}/health`);
364
+ });
365
+ } else {
366
+ // Stdio mode (default, for local IDE usage)
367
+ const transport = new StdioServerTransport();
368
+ await server.connect(transport);
369
+ console.error("gemini-design-mcp v3.9.0 running on stdio");
370
+ }
285
371
  }
286
372
  main().catch((error) => {
287
373
  console.error("Fatal error:", error);
@@ -2,71 +2,105 @@
2
2
  const PROXY_URL = "https://dashing-alpaca-785.convex.site/v1/generate";
3
3
  // Default model - Gemini 3 Flash Preview
4
4
  export const DEFAULT_MODEL = "gemini-3-flash-preview";
5
- // Get and validate API key
5
+ // Import Google Generative AI SDK for BYOK (Bring Your Own Key)
6
+ import { GoogleGenAI } from "@google/genai";
7
+ // Get and validate API key - supports both platform keys (gd_) and Google API keys (AIza)
6
8
  function getApiKey() {
7
9
  const apiKey = process.env.API_KEY;
8
10
  if (!apiKey) {
9
11
  throw new Error("Missing API_KEY environment variable. " +
10
- "Get your API key at https://gemini-design-mcp.com/dashboard/api-keys");
12
+ "Use either a platform key (gd_xxx) from https://gemini-design-mcp.com/dashboard/api-keys " +
13
+ "or a Google API key (AIza...) from https://aistudio.google.com/apikey");
11
14
  }
12
- if (!apiKey.startsWith("gd_")) {
13
- throw new Error("Invalid API key format. API keys must start with 'gd_'. " +
14
- "Get your API key at https://gemini-design-mcp.com/dashboard/api-keys");
15
+ // Accept both platform keys (gd_) and Google API keys (AIza)
16
+ if (!apiKey.startsWith("gd_") && !apiKey.startsWith("AIza")) {
17
+ throw new Error("Invalid API key format. Use either:\n" +
18
+ "- Platform key starting with 'gd_' from https://gemini-design-mcp.com/dashboard/api-keys\n" +
19
+ "- Google API key starting with 'AIza' from https://aistudio.google.com/apikey");
15
20
  }
16
21
  return apiKey;
17
22
  }
23
+ // Generate via platform proxy (for gd_ keys)
24
+ async function generateViaProxy(apiKey, systemPrompt, userPrompt, model, thinkingMode, endpoint) {
25
+ const response = await fetch(PROXY_URL, {
26
+ method: "POST",
27
+ headers: {
28
+ "Content-Type": "application/json",
29
+ "Authorization": `Bearer ${apiKey}`,
30
+ },
31
+ body: JSON.stringify({
32
+ model,
33
+ endpoint,
34
+ contents: [
35
+ {
36
+ role: "user",
37
+ parts: [{ text: userPrompt }],
38
+ },
39
+ ],
40
+ systemInstruction: {
41
+ parts: [{ text: systemPrompt }],
42
+ },
43
+ generationConfig: {
44
+ temperature: 1,
45
+ },
46
+ // Pass thinking mode for billing/logging purposes
47
+ thinkingMode,
48
+ }),
49
+ });
50
+ const result = await response.json();
51
+ if (!response.ok) {
52
+ const errorMsg = result.error || `API error: ${response.status}`;
53
+ // Check for credit/quota related errors
54
+ if (errorMsg.toLowerCase().includes('credit') ||
55
+ errorMsg.toLowerCase().includes('quota') ||
56
+ errorMsg.toLowerCase().includes('insufficient') ||
57
+ errorMsg.toLowerCase().includes('limit') ||
58
+ response.status === 402 ||
59
+ response.status === 429) {
60
+ throw new Error(`${errorMsg}\n\n Top up your credits here: https://gemini-design-mcp.com/settings/billing`);
61
+ }
62
+ throw new Error(errorMsg);
63
+ }
64
+ // Extract text from Gemini response format
65
+ const text = result.candidates?.[0]?.content?.parts?.[0]?.text;
66
+ if (!text) {
67
+ throw new Error("No content in response");
68
+ }
69
+ return text;
70
+ }
71
+ // Generate via direct Google API (for AIza keys - BYOK)
72
+ async function generateViaDirect(apiKey, systemPrompt, userPrompt, model) {
73
+ const ai = new GoogleGenAI({ apiKey });
74
+ const response = await ai.models.generateContent({
75
+ model,
76
+ contents: userPrompt,
77
+ config: {
78
+ systemInstruction: systemPrompt,
79
+ temperature: 1,
80
+ },
81
+ });
82
+ const text = response.text;
83
+ if (!text) {
84
+ throw new Error("No content in response from Google API");
85
+ }
86
+ return text;
87
+ }
18
88
  export async function generateWithGemini(systemPrompt, userPrompt, model = DEFAULT_MODEL, thinkingMode = "minimal", endpoint = "generate") {
19
89
  const apiKey = getApiKey();
90
+ const isGoogleKey = apiKey.startsWith("AIza");
20
91
  try {
21
- const response = await fetch(PROXY_URL, {
22
- method: "POST",
23
- headers: {
24
- "Content-Type": "application/json",
25
- "Authorization": `Bearer ${apiKey}`,
26
- },
27
- body: JSON.stringify({
28
- model,
29
- endpoint,
30
- contents: [
31
- {
32
- role: "user",
33
- parts: [{ text: userPrompt }],
34
- },
35
- ],
36
- systemInstruction: {
37
- parts: [{ text: systemPrompt }],
38
- },
39
- generationConfig: {
40
- temperature: 1,
41
- },
42
- // Pass thinking mode for billing/logging purposes
43
- thinkingMode,
44
- }),
45
- });
46
- const result = await response.json();
47
- if (!response.ok) {
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);
59
- }
60
- // Extract text from Gemini response format
61
- const text = result.candidates?.[0]?.content?.parts?.[0]?.text;
62
- if (!text) {
63
- throw new Error("No content in response");
92
+ if (isGoogleKey) {
93
+ // BYOK: Direct call to Google API
94
+ return await generateViaDirect(apiKey, systemPrompt, userPrompt, model);
95
+ } else {
96
+ // Platform key: Use proxy
97
+ return await generateViaProxy(apiKey, systemPrompt, userPrompt, model, thinkingMode, endpoint);
64
98
  }
65
- return text;
66
99
  }
67
100
  catch (error) {
68
101
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
69
- console.error("Gemini Design API error:", errorMessage);
70
- throw new Error(`Gemini Design API error: ${errorMessage}`);
102
+ const source = isGoogleKey ? "Google Gemini API" : "Gemini Design API";
103
+ console.error(`${source} error:`, errorMessage);
104
+ throw new Error(`${source} error: ${errorMessage}`);
71
105
  }
72
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gemini-design-mcp",
3
- "version": "3.7.2",
3
+ "version": "3.9.0",
4
4
  "description": "MCP server that uses Gemini 3 Pro for frontend/design code generation",
5
5
  "main": "build/index.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "build": "tsc",
12
12
  "dev": "tsc --watch",
13
13
  "start": "node build/index.js",
14
+ "start:sse": "TRANSPORT=sse node build/index.js",
14
15
  "prepublishOnly": "echo 'Publishing...'"
15
16
  },
16
17
  "keywords": [
@@ -28,10 +29,14 @@
28
29
  "dependencies": {
29
30
  "@modelcontextprotocol/sdk": "^1.0.0",
30
31
  "@google/genai": "^1.33.0",
31
- "zod": "^3.23.0"
32
+ "zod": "^3.23.0",
33
+ "express": "^4.21.0",
34
+ "cors": "^2.8.5"
32
35
  },
33
36
  "devDependencies": {
34
37
  "@types/node": "^20.0.0",
38
+ "@types/express": "^4.17.21",
39
+ "@types/cors": "^2.8.17",
35
40
  "typescript": "^5.0.0"
36
41
  },
37
42
  "engines": {