# Gemini + MCP 데모 스크립트 v6: Gemini-MCP 연동 구현 ## 구현 개요 이제 Gemini SDK와 Vercel MCP를 실제로 연동하는 코드를 작성한다. ### 핵심 컴포넌트 1. **MCPAdapter**: MCP server와 통신하는 어댑터 2. **GeminiMCPClient**: Gemini 모델과 대화를 관리하는 클라이언트 3. **Schema Converter**: MCP ↔ Gemini 스키마 변환 ## 1. MCP Adapter 구현 ### types.ts 먼저 타입 정의: ```typescript // src/types.ts export interface MCPTool { name: string; description?: string; inputSchema: { type: string; properties?: Record; required?: string[]; }; } export interface MCPToolsListResponse { jsonrpc: string; id: number | string; result: { tools: MCPTool[]; }; } export interface MCPToolCallRequest { jsonrpc: string; method: string; params: { name: string; arguments: Record; }; id: number | string; } export interface MCPToolCallResponse { jsonrpc: string; id: number | string; result?: { content: Array<{ type: string; text: string; }>; }; error?: { code: number; message: string; }; } ``` ### mcp-adapter.ts MCP server와 통신하는 어댑터: ```typescript // src/mcp-adapter.ts import type { MCPTool, MCPToolsListResponse, MCPToolCallRequest, MCPToolCallResponse } from './types.js'; export class MCPAdapter { private endpoint: string; private token?: string; private requestId = 0; constructor(endpoint: string, token?: string) { this.endpoint = endpoint; this.token = token; } /** * MCP server에서 사용 가능한 tools 목록을 가져온다 */ async getTools(): Promise { const response = await fetch(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(this.token && { Authorization: `Bearer ${this.token}` }) }, body: JSON.stringify({ jsonrpc: '2.0', method: 'tools/list', id: ++this.requestId }) }); if (!response.ok) { throw new Error(`MCP tools/list failed: ${response.statusText}`); } const data: MCPToolsListResponse = await response.json(); return data.result.tools; } /** * MCP tool을 실행한다 */ async callTool( name: string, args: Record ): Promise { const request: MCPToolCallRequest = { jsonrpc: '2.0', method: 'tools/call', params: { name, arguments: args }, id: ++this.requestId }; const response = await fetch(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(this.token && { Authorization: `Bearer ${this.token}` }) }, body: JSON.stringify(request) }); if (!response.ok) { throw new Error(`MCP tools/call failed: ${response.statusText}`); } const data: MCPToolCallResponse = await response.json(); if (data.error) { throw new Error( `MCP tool error: ${data.error.message} (code: ${data.error.code})` ); } // MCP 응답에서 text content 추출 const textContent = data.result?.content ?.filter(c => c.type === 'text') .map(c => c.text) .join('\n') || ''; return textContent; } } ``` ## 2. Schema Converter 구현 MCP tool schema를 Gemini function declaration으로 변환: ```typescript // src/schema-converter.ts import type { FunctionDeclaration } from '@google/genai'; import type { MCPTool } from './types.js'; export class SchemaConverter { /** * MCP tool을 Gemini FunctionDeclaration으로 변환 */ static mcpToolToGeminiFunction(tool: MCPTool): FunctionDeclaration { return { name: tool.name, description: tool.description || `Execute ${tool.name}`, parametersJsonSchema: { type: tool.inputSchema.type || 'object', properties: tool.inputSchema.properties || {}, required: tool.inputSchema.required || [] } }; } /** * 여러 MCP tools를 Gemini function declarations로 변환 */ static convertTools(mcpTools: MCPTool[]): FunctionDeclaration[] { return mcpTools.map(tool => this.mcpToolToGeminiFunction(tool)); } } ``` ## 3. Gemini MCP Client 구현 모든 것을 통합하는 메인 클라이언트: ```typescript // src/gemini-mcp-client.ts import { GoogleGenAI } from '@google/genai'; import type { GenerativeModel, FunctionCall } from '@google/genai'; import { MCPAdapter } from './mcp-adapter.js'; import { SchemaConverter } from './schema-converter.js'; export class GeminiMCPClient { private genAI: GoogleGenAI; private model: GenerativeModel; private mcpAdapter: MCPAdapter; private chatHistory: any[] = []; constructor( geminiApiKey: string, mcpEndpoint: string, mcpToken?: string ) { this.genAI = new GoogleGenAI({ apiKey: geminiApiKey }); this.mcpAdapter = new MCPAdapter(mcpEndpoint, mcpToken); // 나중에 초기화할 예정 this.model = null as any; } /** * MCP tools를 로드하고 Gemini 모델을 초기화 */ async initialize(): Promise { console.log('Fetching MCP tools...'); const mcpTools = await this.mcpAdapter.getTools(); console.log(`Found ${mcpTools.length} MCP tools`); // MCP tools를 Gemini function declarations로 변환 const functionDeclarations = SchemaConverter.convertTools(mcpTools); // Gemini 모델 초기화 with tools this.model = this.genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp', tools: [{ functionDeclarations }] }); console.log('Gemini model initialized with MCP tools'); } /** * 사용자 메시지를 처리하고 응답 생성 */ async chat(userMessage: string): Promise { console.log(`\nUser: ${userMessage}`); // 메시지를 히스토리에 추가 this.chatHistory.push({ role: 'user', parts: [{ text: userMessage }] }); // Chat session 시작 const chat = this.model.startChat({ history: this.chatHistory.slice(0, -1) // 마지막 메시지 제외 }); // 메시지 전송 let result = await chat.sendMessage(userMessage); let response = result.response; // Function calls 처리 루프 while (response.functionCalls && response.functionCalls.length > 0) { console.log(`\nGemini wants to call ${response.functionCalls.length} function(s)`); const functionResults = await this.handleFunctionCalls( response.functionCalls ); // Function 결과를 Gemini에 전달 result = await chat.sendMessage(functionResults); response = result.response; } // 최종 텍스트 응답 추출 const finalText = response.text?.() || 'No response'; console.log(`\nGemini: ${finalText}`); return finalText; } /** * Function calls 처리 */ private async handleFunctionCalls( functionCalls: FunctionCall[] ): Promise { const results = []; for (const fc of functionCalls) { console.log(` Calling: ${fc.name}`); console.log(` Args: ${JSON.stringify(fc.args)}`); try { // MCP tool 호출 const result = await this.mcpAdapter.callTool( fc.name, fc.args as Record ); console.log(` Result: ${result.substring(0, 100)}...`); results.push({ functionResponse: { name: fc.name, response: { result } } }); } catch (error: any) { console.error(` Error: ${error.message}`); results.push({ functionResponse: { name: fc.name, response: { error: error.message } } }); } } return results; } } ``` ## 4. 사용 예제 실제로 사용하는 방법: ```typescript // src/index.ts import { GeminiMCPClient } from './gemini-mcp-client.js'; import * as dotenv from 'dotenv'; dotenv.config(); async function main() { const client = new GeminiMCPClient( process.env.GEMINI_API_KEY!, 'https://mcp.vercel.com', process.env.VERCEL_TOKEN ); // 초기화 (MCP tools 로드) await client.initialize(); // 대화 시작 const response1 = await client.chat( 'Vercel documentation에서 edge functions에 대해 검색해줘' ); const response2 = await client.chat( '내 Vercel 프로젝트 목록을 보여줘' ); const response3 = await client.chat( '가장 최근 배포의 상태를 알려줘' ); } main().catch(console.error); ``` ## 5. 환경 설정 ### package.json ```json { "name": "gemini-mcp-demo", "version": "1.0.0", "type": "module", "scripts": { "start": "tsx src/index.ts", "dev": "tsx watch src/index.ts" }, "dependencies": { "@google/genai": "^1.0.0", "dotenv": "^16.0.0" }, "devDependencies": { "@types/node": "^20.0.0", "tsx": "^4.0.0", "typescript": "^5.0.0" } } ``` ### .env ```bash GEMINI_API_KEY=your_gemini_api_key_here VERCEL_TOKEN=your_vercel_token_here ``` ### tsconfig.json ```json { "compilerOptions": { "target": "ES2022", "module": "ES2022", "moduleResolution": "node", "esModuleInterop": true, "strict": true, "skipLibCheck": true, "outDir": "./dist" }, "include": ["src/**/*"] } ``` ## 6. 실행 플로우 전체 플로우를 다시 정리하면: ``` 1. 초기화 ├─ MCPAdapter가 Vercel MCP에서 tools 조회 ├─ SchemaConverter가 MCP tools를 Gemini declarations로 변환 └─ Gemini model을 function declarations와 함께 초기화 2. 사용자 메시지 수신 └─ "Vercel 문서에서 edge functions 검색해줘" 3. Gemini 처리 ├─ 메시지 분석 ├─ search_docs function 호출 결정 └─ FunctionCall 반환 4. Function Call 실행 ├─ MCPAdapter.callTool('search_docs', {query: 'edge functions'}) ├─ Vercel MCP에 JSON-RPC 요청 └─ 결과 수신 5. 결과를 Gemini에 전달 ├─ Function response로 전달 └─ Gemini가 최종 답변 생성 6. 사용자에게 응답 └─ "Edge Functions는..." ``` ## 7. 에러 핸들링 실제 프로덕션 코드에서는 더 견고한 에러 핸들링이 필요: ```typescript // Enhanced error handling example try { const result = await this.mcpAdapter.callTool(fc.name, fc.args); // ... } catch (error: any) { if (error.message.includes('401')) { // 인증 오류 return { functionResponse: { name: fc.name, response: { error: 'Authentication failed. Please check your Vercel token.' } } }; } else if (error.message.includes('timeout')) { // 타임아웃 return { functionResponse: { name: fc.name, response: { error: 'Request timed out. Please try again.' } } }; } else { // 기타 오류 return { functionResponse: { name: fc.name, response: { error: error.message } } }; } } ``` ## 다음 단계 v7에서는 이 코드를 실제로 실행하고, 다양한 시나리오를 테스트하며, 동작하는 예제를 보여줄 것이다. --- **작성일**: 2025-11-26 **코드 저장소**: `src/` 디렉토리 참조 **주요 파일**: - `types.ts`: 타입 정의 - `mcp-adapter.ts`: MCP 통신 - `schema-converter.ts`: 스키마 변환 - `gemini-mcp-client.ts`: 메인 클라이언트 - `index.ts`: 실행 예제