#!/usr/bin/env node /** b * Multi-Agent Orchestrator MCP Server v2.0 * * Cursor Agent CLI를 활용한 멀티에이전트 시스템 * - PM (Cursor IDE): 전체 조율 * - Sub-agents (agent CLI): 전문가별 작업 수행 * * 모든 AI 호출이 Cursor Team Plan으로 처리됨! * API 키 불필요! */ 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 { exec } from "child_process"; import { promisify } from "util"; import { platform } from "os"; import { AGENT_CONFIGS } from "./agents/prompts.js"; import { AgentType, ParallelResult } from "./agents/types.js"; import { logger } from "./utils/logger.js"; const execAsync = promisify(exec); // OS 감지 const isWindows = platform() === "win32"; logger.info(`Platform detected: ${platform()} (isWindows: ${isWindows})`); // MCP 서버 생성 const server = new Server( { name: "agent-orchestrator", version: "2.0.0", }, { capabilities: { tools: {}, }, } ); /** * Cursor Agent CLI를 통해 에이전트 호출 * Cursor Team Plan 사용 - API 키 불필요! * * 크로스 플랫폼 지원: * - Windows: cmd /c "echo. | agent ..." (stdin 닫기 위해) * - Mac/Linux: ~/.local/bin/agent 사용 */ async function callAgentCLI( agentType: AgentType, task: string, context?: string ): Promise { const config = AGENT_CONFIGS[agentType]; // 모델 선택: PM은 opus, 나머지는 sonnet const model = agentType === 'pm' ? 'opus-4.5' : 'sonnet-4.5'; logger.info(`Calling ${agentType} agent via CLI`, { model, task: task.substring(0, 100) }); try { const userMessage = context ? `${task}\n\n배경 정보:\n${context}` : task; // 프롬프트를 임시 파일에 저장하여 쉘 이스케이프 문제 회피 const fullPrompt = `${config.systemPrompt}\n\n---\n\n${userMessage}`; // Base64 인코딩으로 특수문자 문제 해결 const encodedPrompt = Buffer.from(fullPrompt).toString('base64'); let cmd: string; let shell: string; const agentPath = isWindows ? 'agent' : `${process.env.HOME}/.local/bin/agent`; if (isWindows) { // Windows: PowerShell을 통해 Base64 디코딩 후 실행 cmd = `powershell -Command "$prompt = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${encodedPrompt}')); echo $prompt | ${agentPath} --model ${model} --print"`; shell = 'powershell.exe'; } else { // Mac/Linux: echo로 base64 디코딩 후 파이프 cmd = `echo "${encodedPrompt}" | base64 -d | ${agentPath} --model ${model} --print`; shell = '/bin/bash'; } logger.debug(`Executing: ${agentPath} --model ${model} --print`); const { stdout, stderr } = await execAsync(cmd, { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024, // 10MB buffer timeout: 300000, // 5분 타임아웃 shell, env: { ...process.env, PATH: `${process.env.HOME}/.local/bin:${process.env.PATH}`, }, }); if (stderr && !stderr.includes('warning') && !stderr.includes('info')) { logger.warn(`${agentType} agent stderr`, { stderr: stderr.substring(0, 500) }); } logger.info(`${agentType} agent completed via CLI`); return stdout.trim(); } catch (error) { logger.error(`${agentType} agent CLI error`, error); throw error; } } /** * 도구 목록 핸들러 */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "ask_backend_agent", description: "백엔드 전문가에게 질문하거나 작업을 요청합니다. " + "API 설계, 서비스 로직, 라우팅, 미들웨어 관련 작업에 사용하세요. " + "담당 폴더: backend-node/src/ (Cursor Agent CLI, sonnet-4.5 모델)" + "주의: 단순 파일 읽기/수정은 PM이 직접 처리하세요. 깊은 분석이 필요할 때만 호출!", inputSchema: { type: "object" as const, properties: { task: { type: "string", description: "백엔드 에이전트에게 요청할 작업 내용", }, context: { type: "string", description: "작업에 필요한 배경 정보 (선택사항)", }, }, required: ["task"], }, }, { name: "ask_db_agent", description: "DB 전문가에게 질문하거나 작업을 요청합니다. " + "스키마 설계, SQL 쿼리, MyBatis 매퍼, 마이그레이션 관련 작업에 사용하세요. " + "담당 폴더: src/com/pms/mapper/, db/ (Cursor Agent CLI, sonnet-4.5 모델)" + "주의: 단순 스키마 확인은 PM이 직접 처리하세요. 복잡한 쿼리 설계/최적화 시에만 호출!", inputSchema: { type: "object" as const, properties: { task: { type: "string", description: "DB 에이전트에게 요청할 작업 내용", }, context: { type: "string", description: "작업에 필요한 배경 정보 (선택사항)", }, }, required: ["task"], }, }, { name: "ask_frontend_agent", description: "프론트엔드 전문가에게 질문하거나 작업을 요청합니다. " + "React 컴포넌트, 페이지, 스타일링, 상태관리 관련 작업에 사용하세요. " + "담당 폴더: frontend/ (Cursor Agent CLI, sonnet-4.5 모델)" + "주의: 단순 컴포넌트 읽기/수정은 PM이 직접 처리하세요. 구조 분석이 필요할 때만 호출!", inputSchema: { type: "object" as const, properties: { task: { type: "string", description: "프론트엔드 에이전트에게 요청할 작업 내용", }, context: { type: "string", description: "작업에 필요한 배경 정보 (선택사항)", }, }, required: ["task"], }, }, { name: "parallel_ask", description: "여러 전문가에게 동시에 질문합니다 (진짜 병렬 실행!). " + "3개 영역(FE+BE+DB) 크로스도메인 분석이 필요할 때만 사용하세요. " + "주의: 호출 시간이 오래 걸림! 단순 작업은 PM이 직접 처리하는 게 훨씬 빠릅니다. " + "적합한 경우: 전체 아키텍처 파악, 대규모 리팩토링 계획, 크로스도메인 영향 분석", inputSchema: { type: "object" as const, properties: { requests: { type: "array", description: "각 에이전트에게 보낼 요청 목록", items: { type: "object", properties: { agent: { type: "string", enum: ["backend", "db", "frontend"], description: "요청할 에이전트 타입", }, task: { type: "string", description: "해당 에이전트에게 요청할 작업", }, context: { type: "string", description: "배경 정보 (선택사항)", }, }, required: ["agent", "task"], }, }, }, required: ["requests"], }, }, { name: "get_agent_info", description: "에이전트 시스템의 현재 상태와 사용 가능한 에이전트 정보를 확인합니다.", inputSchema: { type: "object" as const, properties: {}, }, }, ], }; }); /** * 도구 호출 핸들러 */ server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; logger.info(`Tool called: ${name}`); try { switch (name) { case "ask_backend_agent": { const { task, context } = args as { task: string; context?: string }; const result = await callAgentCLI("backend", task, context); return { content: [{ type: "text" as const, text: result }], }; } case "ask_db_agent": { const { task, context } = args as { task: string; context?: string }; const result = await callAgentCLI("db", task, context); return { content: [{ type: "text" as const, text: result }], }; } case "ask_frontend_agent": { const { task, context } = args as { task: string; context?: string }; const result = await callAgentCLI("frontend", task, context); return { content: [{ type: "text" as const, text: result }], }; } case "parallel_ask": { const { requests } = args as { requests: Array<{ agent: "backend" | "db" | "frontend"; task: string; context?: string; }>; }; logger.info(`Parallel ask to ${requests.length} agents (TRUE PARALLEL!)`); // 진짜 병렬 실행! 모든 에이전트가 동시에 작업 const results: ParallelResult[] = await Promise.all( requests.map(async (req) => { try { const result = await callAgentCLI(req.agent, req.task, req.context); return { agent: req.agent, result }; } catch (error) { return { agent: req.agent, result: "", error: error instanceof Error ? error.message : "Unknown error", }; } }) ); // 결과를 보기 좋게 포맷팅 const formattedResults = results.map((r) => { const header = `\n${"=".repeat(60)}\n## ${r.agent.toUpperCase()} Agent 응답\n${"=".repeat(60)}\n`; if (r.error) { return `${header}❌ 에러: ${r.error}`; } return `${header}${r.result}`; }); return { content: [ { type: "text" as const, text: formattedResults.join("\n"), }, ], }; } case "get_agent_info": { const info = { system: "Multi-Agent Orchestrator v2.0", version: "2.0.0", backend: "Cursor Agent CLI (Team Plan)", cliPath: `${process.env.HOME}/.local/bin/agent`, apiKey: "NOT REQUIRED! Using Cursor Team Plan credits", agents: { pm: { role: "Project Manager", model: "opus-4.5 (Cursor IDE에서 직접)", description: "전체 조율, 사용자 의도 파악, 작업 분배", }, backend: { role: "Backend Specialist", model: "sonnet-4.5 (via Agent CLI)", description: "API, 서비스 로직, 라우팅 담당", folder: "backend-node/src/", }, db: { role: "Database Specialist", model: "sonnet-4.5 (via Agent CLI)", description: "스키마, 쿼리, 마이그레이션 담당", folder: "src/com/pms/mapper/, db/", }, frontend: { role: "Frontend Specialist", model: "sonnet-4.5 (via Agent CLI)", description: "컴포넌트, 페이지, 스타일링 담당", folder: "frontend/", }, }, features: { parallel_execution: true, cursor_team_plan: true, cursor_agent_cli: true, separate_api_key: false, cross_platform: true, }, usage: { single_agent: "ask_backend_agent, ask_db_agent, ask_frontend_agent", parallel: "parallel_ask로 여러 에이전트 동시 호출", workflow: "1. parallel_ask로 정보 수집 → 2. 개별 에이전트로 작업 분배", }, }; return { content: [ { type: "text" as const, text: JSON.stringify(info, null, 2), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { logger.error(`Tool error: ${name}`, error); return { content: [ { type: "text" as const, text: `❌ 에러 발생: ${error instanceof Error ? error.message : "Unknown error"}`, }, ], isError: true, }; } }); /** * 서버 시작 */ async function main() { logger.info("Starting Multi-Agent Orchestrator MCP Server v2.0..."); logger.info(`Backend: Cursor Agent CLI (${process.env.HOME}/.local/bin/agent)`); logger.info("Credits: Cursor Team Plan - No API Key Required!"); const transport = new StdioServerTransport(); await server.connect(transport); logger.info("MCP Server connected and ready!"); } main().catch((error) => { logger.error("Server failed to start", error); process.exit(1); });