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 +90 -4
- package/build/lib/gemini.js +85 -51
- package/package.json +7 -2
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.
|
|
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
|
|
283
|
-
|
|
284
|
-
|
|
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);
|
package/build/lib/gemini.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
"
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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.
|
|
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": {
|