index.js•11.6 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { GoogleGenerativeAI } from "@google/generative-ai";
import { writeFile, mkdir } from "fs/promises";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Gemini API 초기화
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
console.error("Error: GEMINI_API_KEY environment variable is required");
process.exit(1);
}
const genAI = new GoogleGenerativeAI(apiKey);
// MCP 서버 생성
const server = new Server(
{
name: "gemini-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// 사용 가능한 도구 목록
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "ask_gemini",
description:
"Use Gemini for large context analysis (1M tokens), architecture design, or whole codebase review. Best for tasks requiring understanding of entire projects.",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
description: "The question or task for Gemini",
},
context: {
type: "string",
description:
"Optional: Large codebase, multiple files, or extensive context to analyze",
},
model: {
type: "string",
description:
"Model to use: 'flash' (default, free, fast) or 'pro' (3 Pro, latest model, better quality, paid)",
enum: ["flash", "pro"],
default: "flash",
},
},
required: ["prompt"],
},
},
{
name: "gemini_analyze_codebase",
description:
"Specialized tool for analyzing entire codebases. Gemini will find patterns, duplications, architectural issues, and suggest improvements.",
inputSchema: {
type: "object",
properties: {
codebase: {
type: "string",
description: "The entire codebase or multiple files concatenated",
},
focus: {
type: "string",
description:
"What to focus on: 'architecture', 'duplications', 'security', 'performance', or 'general'",
enum: [
"architecture",
"duplications",
"security",
"performance",
"general",
],
},
},
required: ["codebase"],
},
},
{
name: "generate_image_gemini",
description:
"Generate images using Gemini 2.5 Flash Image (Nano Banana). Best for contextual understanding, image editing, multi-image composition, and iterative refinement. Free tier available.",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
description:
"Description of the image to generate (in English, max 480 tokens)",
},
numberOfImages: {
type: "number",
description:
"Number of images to generate (1-4, default: 1)",
default: 1,
minimum: 1,
maximum: 4,
},
},
required: ["prompt"],
},
},
{
name: "generate_image_imagen",
description:
"Generate images using Imagen 4. Best for photorealistic quality, high-resolution outputs, and professional branding. Paid service.",
inputSchema: {
type: "object",
properties: {
prompt: {
type: "string",
description:
"Description of the image to generate (in English, max 480 tokens)",
},
numberOfImages: {
type: "number",
description:
"Number of images to generate (1-4, default: 1)",
default: 1,
minimum: 1,
maximum: 4,
},
},
required: ["prompt"],
},
},
],
};
});
// 도구 실행 핸들러
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "ask_gemini") {
const { prompt, context, model = "flash" } = args;
// 모델 선택
const modelName =
model === "pro"
? "gemini-3-pro-preview" // Gemini 3 Pro (최신 모델, 2025년 11월 출시)
: "gemini-2.5-flash"; // 2.5 Flash (무료)
const geminiModel = genAI.getGenerativeModel({ model: modelName });
// 프롬프트 구성
const fullPrompt = context
? `Context/Codebase:\n\`\`\`\n${context}\n\`\`\`\n\nTask: ${prompt}`
: prompt;
// Gemini 호출
const result = await geminiModel.generateContent(fullPrompt);
const response = await result.response;
const text = response.text();
return {
content: [
{
type: "text",
text: `[Gemini ${
model === "pro" ? "3.0 Pro" : "2.5 Flash"
}]\n\n${text}`,
},
],
};
}
if (name === "gemini_analyze_codebase") {
const { codebase, focus = "general" } = args;
const focusPrompts = {
architecture:
"Analyze the overall architecture, design patterns, and structural issues. Suggest improvements.",
duplications:
"Find duplicated code, similar patterns, and opportunities for refactoring. Be specific.",
security:
"Identify security vulnerabilities, unsafe practices, and potential exploits.",
performance:
"Find performance bottlenecks, inefficient algorithms, and optimization opportunities.",
general:
"Provide a comprehensive analysis covering architecture, code quality, potential issues, and improvements.",
};
const model = genAI.getGenerativeModel({ model: "gemini-2.5-flash" });
const prompt = `You are an expert code reviewer. Analyze this codebase with focus on: ${focus}
${focusPrompts[focus]}
Codebase:
\`\`\`
${codebase}
\`\`\`
Provide:
1. Key findings
2. Specific issues with file/line references
3. Actionable recommendations
4. Priority ranking (High/Medium/Low)`;
const result = await model.generateContent(prompt);
const response = await result.response;
return {
content: [
{
type: "text",
text: `[Gemini Codebase Analysis - ${focus}]\n\n${response.text()}`,
},
],
};
}
if (name === "generate_image_gemini") {
const { prompt, numberOfImages = 1 } = args;
const model = genAI.getGenerativeModel({
model: "gemini-2.5-flash-image",
});
const result = await model.generateContent({
contents: [{ role: "user", parts: [{ text: prompt }] }],
generationConfig: {
responseModalities: ["image"],
},
});
const response = await result.response;
const images = response.candidates?.[0]?.content?.parts?.filter(
(part) => part.inlineData
);
if (!images || images.length === 0) {
throw new Error("No images were generated");
}
// 이미지 저장
const outputDir = join(__dirname, "generated_images");
await mkdir(outputDir, { recursive: true });
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const savedPaths = [];
for (let i = 0; i < images.length; i++) {
const img = images[i];
const ext = img.inlineData.mimeType.split("/")[1] || "png";
const filename = `gemini_${timestamp}_${i + 1}.${ext}`;
const filepath = join(outputDir, filename);
const buffer = Buffer.from(img.inlineData.data, "base64");
await writeFile(filepath, buffer);
savedPaths.push(filepath);
}
return {
content: [
{
type: "text",
text: `[Gemini 2.5 Flash Image (Nano Banana)]\n\nGenerated ${images.length} image(s) for: "${prompt}"\n\nSaved to:\n${savedPaths.map(p => `- ${p}`).join("\n")}`,
},
...images.map((img) => ({
type: "image",
data: img.inlineData.data,
mimeType: img.inlineData.mimeType,
})),
],
};
}
if (name === "generate_image_imagen") {
const { prompt, numberOfImages = 1 } = args;
// REST API를 사용하여 Imagen 4 호출
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/imagen-4.0-generate-001:predict`,
{
method: "POST",
headers: {
"x-goog-api-key": apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
instances: [{ prompt: prompt }],
parameters: {
sampleCount: numberOfImages,
},
}),
}
);
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Imagen API error: ${response.status} - ${errorText}`);
}
const result = await response.json();
// predictions 배열 확인 (실제 API 응답 구조)
if (!result.predictions || result.predictions.length === 0) {
throw new Error(`No images were generated. Response: ${JSON.stringify(result)}`);
}
// 이미지 저장
const outputDir = join(__dirname, "generated_images");
await mkdir(outputDir, { recursive: true });
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const savedPaths = [];
for (let i = 0; i < result.predictions.length; i++) {
const img = result.predictions[i];
const filename = `imagen_${timestamp}_${i + 1}.png`;
const filepath = join(outputDir, filename);
// bytesBase64Encoded 필드 사용
const buffer = Buffer.from(img.bytesBase64Encoded, "base64");
await writeFile(filepath, buffer);
savedPaths.push(filepath);
}
return {
content: [
{
type: "text",
text: `[Imagen 4]\n\nGenerated ${result.predictions.length} image(s) for: "${prompt}"\n\nSaved to:\n${savedPaths.map(p => `- ${p}`).join("\n")}\n\nNote: All images include SynthID watermark for authenticity.`,
},
...result.predictions.map((img) => ({
type: "image",
data: img.bytesBase64Encoded,
mimeType: img.mimeType || "image/png",
})),
],
};
}
throw new Error(`Unknown tool: ${name}`);
} catch (error) {
return {
content: [
{
type: "text",
text: `Error calling Gemini: ${error.message}`,
},
],
isError: true,
};
}
});
// 서버 시작
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Gemini MCP server running");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});