#
tokens: 2624/50000 10/10 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .env.example
├── .gitignore
├── ecosystem.config.js
├── llms-install.md
├── mcp-gemini-prd.md
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── api
│   │   ├── gemini.ts
│   │   ├── google.ts
│   │   ├── search.ts
│   │   └── youtube.ts
│   ├── config.ts
│   ├── index.ts
│   ├── mcp.ts
│   ├── server.ts
│   ├── types
│   │   └── gemini.ts
│   └── utils
│       └── logger.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
# Server Configuration
PORT=8000
HOST=0.0.0.0
NODE_ENV=development

# API Keys
GOOGLE_API_KEY=your-google-api-key-here
YOUTUBE_API_KEY=your-youtube-api-key-here

# Logging
LOG_LEVEL=debug 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment
.env
.env.local
.env.*.local

# Build
dist/
build/

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db 
```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
import pino from 'pino';

// stderr로 로그 출력
export const logger = pino({
  level: process.env.DEBUG === 'true' ? 'debug' : 'info',
  formatters: {
    level: (label) => {
      return { level: label };
    },
  },
  timestamp: () => `,"time":"${new Date().toISOString()}"`,
}, process.stderr); 
```

--------------------------------------------------------------------------------
/ecosystem.config.js:
--------------------------------------------------------------------------------

```javascript
module.exports = {
  apps: [{
    name: 'mcp-gemini',
    script: 'dist/index.js',
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 3000
    }
  }]
}; 
```

--------------------------------------------------------------------------------
/src/types/gemini.ts:
--------------------------------------------------------------------------------

```typescript
export interface ChatMessage {
  role: string;
  content: string;
}

export interface GenerateRequest {
  prompt: string;
}

export interface VideoAnalysisRequest {
  videoUrl: string;
  query: string;
}

export interface SearchRequest {
  query: string;
}

export interface ChatStartRequest {
  history?: ChatMessage[];
}

export interface ChatStartResponse {
  sessionId: string;
}

export interface ApiResponse<T> {
  result: T;
  error?: string;
} 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "tests"]
} 
```

--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------

```typescript
import dotenv from 'dotenv';
import { GenerationConfig } from '@google/generative-ai';

// 환경 변수 로드
dotenv.config();

export interface ServerConfig {
  port: number;
  host: string;
  nodeEnv: string;
  googleApiKey: string;
  logLevel: string;
  defaultConfig: GenerationConfig;
}

// 환경 변수 유효성 검사
const validateEnv = (): void => {
  if (!process.env.GOOGLE_API_KEY) {
    throw new Error('GOOGLE_API_KEY is required');
  }
};

// 설정 객체 생성
export const config: ServerConfig = {
  port: parseInt(process.env.PORT || '8000', 10),
  host: process.env.HOST || '0.0.0.0',
  nodeEnv: process.env.NODE_ENV || 'development',
  googleApiKey: process.env.GOOGLE_API_KEY || '',
  logLevel: process.env.LOG_LEVEL || 'info',
  defaultConfig: {
    temperature: 1,
    topP: 0.95,
    topK: 40,
    maxOutputTokens: 8192,
  },
};

validateEnv();

export default config; 
```

--------------------------------------------------------------------------------
/src/api/google.ts:
--------------------------------------------------------------------------------

```typescript
import axios from 'axios';
import { logger } from '../utils/logger';

const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY;
const SEARCH_ENGINE_ID = process.env.SEARCH_ENGINE_ID || '017576662512468239146:omuauf_lfve';

interface SearchResult {
  title: string;
  link: string;
  snippet: string;
}

export async function searchWeb(query: string): Promise<string> {
  try {
    const response = await axios.get('https://www.googleapis.com/customsearch/v1', {
      params: {
        key: GOOGLE_API_KEY,
        cx: SEARCH_ENGINE_ID,
        q: query
      }
    });

    const items = response.data.items as SearchResult[];
    if (!items || items.length === 0) {
      return '검색 결과가 없습니다.';
    }

    const results = items.slice(0, 5).map(item => {
      return `제목: ${item.title}\n링크: ${item.link}\n내용: ${item.snippet}\n`;
    }).join('\n');

    return results;
  } catch (error) {
    logger.error('웹 검색 실패', error);
    throw new Error('웹 검색 중 오류가 발생했습니다.');
  }
} 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "mcp-gemini",
  "version": "1.0.0",
  "description": "MCP server for Google Gemini API integration",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "pm2 start ecosystem.config.js --env production",
    "dev": "ts-node src/index.ts",
    "test": "jest",
    "test:watch": "jest --watch",
    "lint": "eslint src/**/*.ts",
    "format": "prettier --write \"src/**/*.ts\"",
    "stop": "pm2 stop mcp-gemini",
    "restart": "pm2 restart mcp-gemini",
    "logs": "pm2 logs mcp-gemini",
    "status": "pm2 status"
  },
  "keywords": [
    "mcp",
    "gemini",
    "ai",
    "claude"
  ],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@fastify/cors": "^8.5.0",
    "@google/generative-ai": "^0.1.3",
    "@modelcontextprotocol/sdk": "^1.8.0",
    "axios": "^1.8.4",
    "dotenv": "^16.4.7",
    "fastify": "^4.29.0",
    "googleapis": "^148.0.0",
    "json-rpc-2.0": "^1.7.0",
    "pino": "^8.21.0",
    "pino-pretty": "^10.3.1",
    "typescript": "^5.0.0",
    "zod": "^3.24.2",
    "pm2": "^5.3.1"
  },
  "devDependencies": {
    "@types/jest": "^29.0.0",
    "@types/node": "^20.0.0",
    "jest": "^29.0.0",
    "ts-jest": "^29.0.0",
    "ts-node": "^10.0.0"
  }
}

```

--------------------------------------------------------------------------------
/src/mcp.ts:
--------------------------------------------------------------------------------

```typescript
import { generateContent } from './api/gemini';
import { analyzeVideo, getVideoInfo } from './api/youtube';
import { searchWeb } from './api/google';

interface JsonRpcRequest {
  jsonrpc: string;
  id: number;
  method: string;
  params: any;
}

interface JsonRpcResponse {
  jsonrpc: string;
  id: number;
  result?: any;
  error?: {
    code: number;
    message: string;
    data?: any;
  };
}

type ToolFunction = (input: string) => Promise<string>;

interface Tools {
  [key: string]: ToolFunction;
}

const SUPPORTED_TOOLS: Tools = {
  'generate': generateContent,
  'analyze-video': async (input: string) => {
    try {
      const [videoUrl, query] = input.split('|');
      console.error(`비디오 분석 중: URL=${videoUrl}, 질문=${query}`);
      const videoInfo = await getVideoInfo(videoUrl);
      return await analyzeVideo(videoInfo, query);
    } catch (error) {
      console.error('비디오 분석 오류:', error);
      throw error;
    }
  },
  'search': searchWeb
};

export async function handleJsonRpcMessage(request: JsonRpcRequest): Promise<JsonRpcResponse | null> {
  console.error(`메시지 수신: ${JSON.stringify(request)}`);

  if (request.method === 'initialize') {
    console.error('초기화 요청 처리 중');
    const response = {
      jsonrpc: '2.0',
      id: request.id,
      result: {
        capabilities: {
          tools: Object.keys(SUPPORTED_TOOLS).map(name => ({
            name,
            description: `${name} 기능`,
            parameters: {
              type: 'object',
              properties: {
                input: { type: 'string' }
              },
              required: ['input']
            }
          }))
        }
      }
    };
    console.error(`초기화 응답: ${JSON.stringify(response)}`);
    return response;
  }

  if (request.method.startsWith('tools/')) {
    const toolName = request.params.name;
    console.error(`도구 호출: ${toolName}`);
    
    const tool = SUPPORTED_TOOLS[toolName];
    
    if (!tool) {
      console.error(`지원하지 않는 도구: ${toolName}`);
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32601,
          message: `Tool '${toolName}' not found`
        }
      };
    }

    try {
      console.error(`도구 실행 중: ${toolName}, 파라미터: ${JSON.stringify(request.params.parameters)}`);
      const result = await tool(request.params.parameters.input);
      console.error(`도구 실행 완료: ${toolName}`);
      
      const response = {
        jsonrpc: '2.0',
        id: request.id,
        result: { output: result }
      };
      
      console.error(`도구 응답: ${JSON.stringify(response).substring(0, 100)}...`);
      return response;
    } catch (err) {
      const error = err as Error;
      console.error(`도구 실행 오류: ${error.message}`);
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: -32000,
          message: 'Internal error',
          data: error.message
        }
      };
    }
  }

  console.error(`지원하지 않는 메서드: ${request.method}`);
  return null;
} 
```