Skip to main content
Glama
sqlite-server.js45 kB
/** * WorkflowMCP SQLite Server - Phase 2.5 * JSON 파일 저장소를 SQLite로 전환한 완전한 MCP 서버 * 26개 MCP 도구 모두 SQLite 기반으로 작동 */ 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 { SimpleSQLiteStorage } from './database/SimpleSQLiteStorage.js'; // SQLite 저장소 인스턴스 let sqliteStorage; /** * 서버 초기화 */ async function initializeServer() { console.log('🗄️ Initializing WorkflowMCP SQLite Server...'); try { // SQLite 저장소 초기화 sqliteStorage = new SimpleSQLiteStorage(); await sqliteStorage.initialize(); console.log('✅ SQLite storage initialized successfully'); return true; } catch (error) { console.error('❌ Server initialization failed:', error.message); throw error; } } /** * MCP 서버 생성 및 도구 등록 */ const server = new Server({ name: 'workflow-mcp-sqlite', version: '2.5.0', }, { capabilities: { tools: {} } }); // ============================================= // Phase 1: 기본 CRUD (15개 도구) // ============================================= // PRD 관리 도구들 (4개) server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Phase 1: 기본 CRUD (15개 도구) { name: 'create_prd', description: 'PRD(Product Requirements Document) 생성', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'PRD 제목' }, description: { type: 'string', description: 'PRD 설명' }, requirements: { type: 'array', items: { type: 'string' }, description: '요구사항 목록' }, priority: { type: 'string', enum: ['High', 'Medium', 'Low'], description: '우선순위', default: 'Medium' } }, required: ['title'] } }, { name: 'list_prds', description: 'PRD 목록 조회', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['draft', 'review', 'approved', 'archived'], description: '상태별 필터링 (선택사항)' } } } }, { name: 'get_prd', description: '특정 PRD 상세 조회', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'PRD ID' } }, required: ['id'] } }, { name: 'update_prd', description: 'PRD 정보 업데이트', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'PRD ID' }, title: { type: 'string', description: 'PRD 제목' }, description: { type: 'string', description: 'PRD 설명' }, requirements: { type: 'array', items: { type: 'string' }, description: '요구사항 목록' }, priority: { type: 'string', enum: ['High', 'Medium', 'Low'], description: '우선순위' }, status: { type: 'string', enum: ['draft', 'review', 'approved', 'archived'], description: '상태' } }, required: ['id'] } }, // Task 관리 도구들 (4개) { name: 'create_task', description: 'Task(작업) 생성', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Task 제목' }, description: { type: 'string', description: 'Task 설명' }, priority: { type: 'string', enum: ['High', 'Medium', 'Low'], description: '우선순위', default: 'Medium' }, assignee: { type: 'string', description: '담당자' }, estimatedHours: { type: 'number', description: '예상 작업 시간(시간)' }, dueDate: { type: 'string', description: '마감일 (ISO 형식)' } }, required: ['title'] } }, { name: 'list_tasks', description: 'Task 목록 조회', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['pending', 'in_progress', 'done', 'blocked'], description: '상태별 필터링 (선택사항)' }, assignee: { type: 'string', description: '담당자별 필터링 (선택사항)' } } } }, { name: 'get_task', description: '특정 Task 상세 조회', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Task ID' } }, required: ['id'] } }, { name: 'update_task', description: 'Task 정보 업데이트', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Task ID' }, title: { type: 'string', description: 'Task 제목' }, description: { type: 'string', description: 'Task 설명' }, status: { type: 'string', enum: ['pending', 'in_progress', 'done', 'blocked'], description: '상태' }, priority: { type: 'string', enum: ['High', 'Medium', 'Low'], description: '우선순위' }, assignee: { type: 'string', description: '담당자' }, estimatedHours: { type: 'number', description: '예상 작업 시간(시간)' }, dueDate: { type: 'string', description: '마감일 (ISO 형식)' } }, required: ['id'] } }, // Plan 관리 도구들 (4개) { name: 'create_plan', description: 'Plan(계획) 생성', inputSchema: { type: 'object', properties: { title: { type: 'string', description: 'Plan 제목' }, description: { type: 'string', description: 'Plan 설명' }, startDate: { type: 'string', description: '시작일 (ISO 형식)' }, endDate: { type: 'string', description: '종료일 (ISO 형식)' }, milestones: { type: 'array', items: { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' }, dueDate: { type: 'string' } } }, description: '마일스톤 목록' } }, required: ['title'] } }, { name: 'list_plans', description: 'Plan 목록 조회', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['active', 'completed', 'paused', 'cancelled'], description: '상태별 필터링 (선택사항)' } } } }, { name: 'get_plan', description: '특정 Plan 상세 조회', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Plan ID' } }, required: ['id'] } }, { name: 'update_plan', description: 'Plan 정보 업데이트', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Plan ID' }, title: { type: 'string', description: 'Plan 제목' }, description: { type: 'string', description: 'Plan 설명' }, status: { type: 'string', enum: ['active', 'completed', 'paused', 'cancelled'], description: '상태' }, startDate: { type: 'string', description: '시작일 (ISO 형식)' }, endDate: { type: 'string', description: '종료일 (ISO 형식)' }, milestones: { type: 'array', items: { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' }, dueDate: { type: 'string' } } }, description: '마일스톤 목록' } }, required: ['id'] } }, // 유틸리티 도구들 (3개) { name: 'get_metrics', description: '프로젝트 통계 및 메트릭 조회', inputSchema: { type: 'object', properties: { period: { type: 'string', enum: ['week', 'month', 'quarter', 'year'], description: '기간별 필터 (선택사항)', default: 'month' } } } }, { name: 'validate_prd', description: 'PRD 유효성 검사', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'PRD ID' } }, required: ['id'] } }, { name: 'export_data', description: '데이터 백업 및 내보내기', inputSchema: { type: 'object', properties: { format: { type: 'string', enum: ['json', 'csv'], description: '내보내기 형식', default: 'json' }, types: { type: 'array', items: { type: 'string', enum: ['prds', 'tasks', 'plans'] }, description: '내보낼 데이터 타입', default: ['prds', 'tasks', 'plans'] } } } }, // Phase 2-1: 안전한 삭제 기능 (3개 도구) { name: 'delete_prd', description: 'PRD 안전 삭제 (의존성 체크 포함)', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'PRD ID' } }, required: ['id'] } }, { name: 'delete_task', description: 'Task 안전 삭제 (의존성 체크 포함)', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Task ID' } }, required: ['id'] } }, { name: 'delete_plan', description: 'Plan 안전 삭제 (의존성 체크 포함)', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Plan ID' } }, required: ['id'] } }, // Phase 2-2: 데이터 연결 시스템 (5개 도구) { name: 'link_prd_to_plan', description: 'PRD와 Plan 연결', inputSchema: { type: 'object', properties: { prd_id: { type: 'string', description: 'PRD ID' }, plan_id: { type: 'string', description: 'Plan ID' } }, required: ['prd_id', 'plan_id'] } }, { name: 'link_plan_to_tasks', description: 'Plan과 Task들 연결', inputSchema: { type: 'object', properties: { plan_id: { type: 'string', description: 'Plan ID' }, task_ids: { type: 'array', items: { type: 'string' }, description: 'Task ID 배열' } }, required: ['plan_id', 'task_ids'] } }, { name: 'get_linked_data', description: '연결된 데이터 조회 (PRD-Plan-Task 관계)', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'PRD, Plan, 또는 Task ID' }, type: { type: 'string', enum: ['prd', 'plan', 'task'], description: 'ID의 타입' } }, required: ['id', 'type'] } }, { name: 'unlink_items', description: '연결 해제', inputSchema: { type: 'object', properties: { from_id: { type: 'string', description: '연결 해제할 첫 번째 ID' }, to_id: { type: 'string', description: '연결 해제할 두 번째 ID' }, type: { type: 'string', enum: ['prd_plan', 'plan_task'], description: '연결 타입' } }, required: ['from_id', 'to_id', 'type'] } }, { name: 'sync_plan_progress', description: 'Plan 진행률 자동 동기화', inputSchema: { type: 'object', properties: { plan_id: { type: 'string', description: 'Plan ID (생략시 모든 Plan)' } } } }, // Phase 2-3: 의존성 관리 시스템 (5개 도구) { name: 'add_task_dependency', description: 'Task 의존성 추가 (순환 의존성 체크)', inputSchema: { type: 'object', properties: { dependent_task_id: { type: 'string', description: '의존하는 Task ID' }, prerequisite_task_id: { type: 'string', description: '선행 조건 Task ID' } }, required: ['dependent_task_id', 'prerequisite_task_id'] } }, { name: 'remove_task_dependency', description: 'Task 의존성 제거', inputSchema: { type: 'object', properties: { dependent_task_id: { type: 'string', description: '의존하는 Task ID' }, prerequisite_task_id: { type: 'string', description: '선행 조건 Task ID' } }, required: ['dependent_task_id', 'prerequisite_task_id'] } }, { name: 'get_task_dependencies', description: 'Task 의존성 조회', inputSchema: { type: 'object', properties: { task_id: { type: 'string', description: 'Task ID' }, direction: { type: 'string', enum: ['dependencies', 'dependents', 'both'], description: '조회 방향 (의존성/의존자/둘다)', default: 'both' } }, required: ['task_id'] } }, { name: 'validate_workflow', description: '워크플로 유효성 검사 (순환 의존성 등)', inputSchema: { type: 'object', properties: { fix_issues: { type: 'boolean', description: '문제 발견시 자동 수정 여부', default: false } } } }, { name: 'auto_update_task_status', description: '의존성 기반 Task 상태 자동 업데이트', inputSchema: { type: 'object', properties: { task_id: { type: 'string', description: 'Task ID (생략시 모든 Task)' } } } }, // Phase 2-4: 기본 대시보드 (3개 도구) { name: 'get_project_dashboard', description: '종합 프로젝트 대시보드', inputSchema: { type: 'object', properties: { include_details: { type: 'boolean', description: '상세 정보 포함 여부', default: false } } } }, { name: 'get_workflow_status', description: '워크플로 상태 및 차단 요소 분석', inputSchema: { type: 'object', properties: { analyze_blockers: { type: 'boolean', description: '차단 요소 분석 여부', default: true } } } }, { name: 'get_progress_timeline', description: '진행 타임라인 및 마감일 관리', inputSchema: { type: 'object', properties: { time_period: { type: 'string', enum: ['7days', '30days', '90days'], description: '조회 기간', default: '30days' } } } } ] }; }); // ============================================= // 도구 실행 핸들러 // ============================================= server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { // Phase 1: 기본 CRUD - PRD 관리 case 'create_prd': return await handleCreatePRD(args); case 'list_prds': return await handleListPRDs(args); case 'get_prd': return await handleGetPRD(args); case 'update_prd': return await handleUpdatePRD(args); // Phase 1: 기본 CRUD - Task 관리 case 'create_task': return await handleCreateTask(args); case 'list_tasks': return await handleListTasks(args); case 'get_task': return await handleGetTask(args); case 'update_task': return await handleUpdateTask(args); // Phase 1: 기본 CRUD - Plan 관리 case 'create_plan': return await handleCreatePlan(args); case 'list_plans': return await handleListPlans(args); case 'get_plan': return await handleGetPlan(args); case 'update_plan': return await handleUpdatePlan(args); // Phase 1: 기본 CRUD - 유틸리티 case 'get_metrics': return await handleGetMetrics(args); case 'validate_prd': return await handleValidatePRD(args); case 'export_data': return await handleExportData(args); // Phase 2-1: 안전한 삭제 case 'delete_prd': return await handleDeletePRD(args); case 'delete_task': return await handleDeleteTask(args); case 'delete_plan': return await handleDeletePlan(args); // Phase 2-2: 데이터 연결 case 'link_prd_to_plan': return await handleLinkPRDToPlan(args); case 'link_plan_to_tasks': return await handleLinkPlanToTasks(args); case 'get_linked_data': return await handleGetLinkedData(args); case 'unlink_items': return await handleUnlinkItems(args); case 'sync_plan_progress': return await handleSyncPlanProgress(args); // Phase 2-3: 의존성 관리 case 'add_task_dependency': return await handleAddTaskDependency(args); case 'remove_task_dependency': return await handleRemoveTaskDependency(args); case 'get_task_dependencies': return await handleGetTaskDependencies(args); case 'validate_workflow': return await handleValidateWorkflow(args); case 'auto_update_task_status': return await handleAutoUpdateTaskStatus(args); // Phase 2-4: 기본 대시보드 case 'get_project_dashboard': return await handleGetProjectDashboard(args); case 'get_workflow_status': return await handleGetWorkflowStatus(args); case 'get_progress_timeline': return await handleGetProgressTimeline(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: 'text', text: `❌ 오류 발생: ${error.message}` }] }; } }); // ============================================= // Phase 1: PRD 관리 핸들러들 // ============================================= async function handleCreatePRD(args) { const id = `prd_${Date.now()}`; const prd = { id, title: args.title, description: args.description || '', requirements: args.requirements || [], priority: args.priority || 'Medium', status: 'draft', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; await sqliteStorage.savePRD(prd); return { content: [{ type: 'text', text: `✅ PRD 생성 완료! **제목**: ${prd.title} **ID**: ${prd.id} **우선순위**: ${prd.priority} **요구사항**: ${prd.requirements.length}개` }] }; } async function handleListPRDs(args) { const prds = await sqliteStorage.listAllPRDs(); let filteredPRDs = prds; if (args.status) { filteredPRDs = prds.filter(prd => prd.status === args.status); } return { content: [{ type: 'text', text: `📋 PRD 목록 (${filteredPRDs.length}개) ${filteredPRDs.map(prd => `**${prd.title}** - ID: ${prd.id} - 상태: ${prd.status} - 우선순위: ${prd.priority} - 생성일: ${new Date(prd.createdAt).toLocaleDateString('ko-KR')} `).join('\n')}` }] }; } async function handleGetPRD(args) { const prd = await sqliteStorage.getPRD(args.id); if (!prd) { throw new Error(`PRD를 찾을 수 없습니다: ${args.id}`); } return { content: [{ type: 'text', text: `📋 PRD 상세 정보 **제목**: ${prd.title} **ID**: ${prd.id} **설명**: ${prd.description} **상태**: ${prd.status} **우선순위**: ${prd.priority} **요구사항** (${prd.requirements.length}개): ${prd.requirements.map((req, index) => `${index + 1}. ${req}`).join('\n')} **생성일**: ${new Date(prd.createdAt).toLocaleString('ko-KR')} **수정일**: ${new Date(prd.updatedAt).toLocaleString('ko-KR')}` }] }; } async function handleUpdatePRD(args) { const existingPRD = await sqliteStorage.getPRD(args.id); if (!existingPRD) { throw new Error(`PRD를 찾을 수 없습니다: ${args.id}`); } const updatedPRD = { ...existingPRD, ...args, updatedAt: new Date().toISOString() }; await sqliteStorage.savePRD(updatedPRD); return { content: [{ type: 'text', text: `✅ PRD 업데이트 완료! **제목**: ${updatedPRD.title} **ID**: ${updatedPRD.id} **상태**: ${updatedPRD.status}` }] }; } // ============================================= // Phase 1: Task 관리 핸들러들 // ============================================= async function handleCreateTask(args) { const id = `task_${Date.now()}`; const task = { id, title: args.title, description: args.description || '', status: 'pending', priority: args.priority || 'Medium', assignee: args.assignee || null, estimatedHours: args.estimatedHours || null, dueDate: args.dueDate || null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; await sqliteStorage.saveTask(task); return { content: [{ type: 'text', text: `✅ Task 생성 완료! **제목**: ${task.title} **ID**: ${task.id} **우선순위**: ${task.priority} **담당자**: ${task.assignee || '미배정'}` }] }; } async function handleListTasks(args) { const tasks = await sqliteStorage.listAllTasks(); let filteredTasks = tasks; if (args.status) { filteredTasks = tasks.filter(task => task.status === args.status); } if (args.assignee) { filteredTasks = filteredTasks.filter(task => task.assignee === args.assignee); } return { content: [{ type: 'text', text: `✅ Task 목록 (${filteredTasks.length}개) ${filteredTasks.map(task => `**${task.title}** - ID: ${task.id} - 상태: ${task.status} - 우선순위: ${task.priority} - 담당자: ${task.assignee || '미배정'} - 의존성: ${task.dependencies.length}개 `).join('\n')}` }] }; } async function handleGetTask(args) { const task = await sqliteStorage.getTask(args.id); if (!task) { throw new Error(`Task를 찾을 수 없습니다: ${args.id}`); } return { content: [{ type: 'text', text: `✅ Task 상세 정보 **제목**: ${task.title} **ID**: ${task.id} **설명**: ${task.description} **상태**: ${task.status} **우선순위**: ${task.priority} **담당자**: ${task.assignee || '미배정'} **예상 시간**: ${task.estimatedHours || 'N/A'}시간 **마감일**: ${task.dueDate || 'N/A'} **의존성** (${task.dependencies.length}개): ${task.dependencies.length > 0 ? task.dependencies.join(', ') : '없음'} **생성일**: ${new Date(task.createdAt).toLocaleString('ko-KR')} **수정일**: ${new Date(task.updatedAt).toLocaleString('ko-KR')}` }] }; } async function handleUpdateTask(args) { const existingTask = await sqliteStorage.getTask(args.id); if (!existingTask) { throw new Error(`Task를 찾을 수 없습니다: ${args.id}`); } const updatedTask = { ...existingTask, ...args, updatedAt: new Date().toISOString() }; await sqliteStorage.saveTask(updatedTask); return { content: [{ type: 'text', text: `✅ Task 업데이트 완료! **제목**: ${updatedTask.title} **ID**: ${updatedTask.id} **상태**: ${updatedTask.status} **담당자**: ${updatedTask.assignee || '미배정'}` }] }; } // ============================================= // Phase 1: Plan 관리 핸들러들 // ============================================= async function handleCreatePlan(args) { const id = `plan_${Date.now()}`; const plan = { id, title: args.title, description: args.description || '', status: 'active', startDate: args.startDate || null, endDate: args.endDate || null, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; await sqliteStorage.savePlan(plan); return { content: [{ type: 'text', text: `📅 Plan 생성 완료! **제목**: ${plan.title} **ID**: ${plan.id} **상태**: ${plan.status} **기간**: ${plan.startDate || 'N/A'} ~ ${plan.endDate || 'N/A'}` }] }; } async function handleListPlans(args) { const plans = await sqliteStorage.listAllPlans(); let filteredPlans = plans; if (args.status) { filteredPlans = plans.filter(plan => plan.status === args.status); } return { content: [{ type: 'text', text: `📅 Plan 목록 (${filteredPlans.length}개) ${filteredPlans.map(plan => `**${plan.title}** - ID: ${plan.id} - 상태: ${plan.status} - 연결된 Task: ${plan.linked_task_ids.length}개 - 진행률: ${plan.progress.percentage}% `).join('\n')}` }] }; } async function handleGetPlan(args) { const plan = await sqliteStorage.getPlan(args.id); if (!plan) { throw new Error(`Plan을 찾을 수 없습니다: ${args.id}`); } return { content: [{ type: 'text', text: `📅 Plan 상세 정보 **제목**: ${plan.title} **ID**: ${plan.id} **설명**: ${plan.description} **상태**: ${plan.status} **기간**: ${plan.startDate || 'N/A'} ~ ${plan.endDate || 'N/A'} **진행률**: ${plan.progress.percentage}% **연결된 Task**: ${plan.linked_task_ids.length}개 **생성일**: ${new Date(plan.createdAt).toLocaleString('ko-KR')} **수정일**: ${new Date(plan.updatedAt).toLocaleString('ko-KR')}` }] }; } async function handleUpdatePlan(args) { const existingPlan = await sqliteStorage.getPlan(args.id); if (!existingPlan) { throw new Error(`Plan을 찾을 수 없습니다: ${args.id}`); } const updatedPlan = { ...existingPlan, ...args, updatedAt: new Date().toISOString() }; await sqliteStorage.savePlan(updatedPlan); return { content: [{ type: 'text', text: `📅 Plan 업데이트 완료! **제목**: ${updatedPlan.title} **ID**: ${updatedPlan.id} **상태**: ${updatedPlan.status}` }] }; } // ============================================= // Phase 1: 유틸리티 핸들러들 // ============================================= async function handleGetMetrics(args) { const stats = await sqliteStorage.getDashboardStats(); return { content: [{ type: 'text', text: `📊 프로젝트 메트릭 ## 📈 전체 현황 - **📋 PRD**: ${stats.total_prds}개 - **✅ Task**: ${stats.total_tasks}개 - **📅 Plan**: ${stats.total_plans}개 ## 🗄️ 데이터베이스 정보 - **저장소 타입**: SQLite - **조회 시간**: ${new Date(stats.timestamp).toLocaleString('ko-KR')} ✅ **Phase 2.5 SQLite 기반 시스템 정상 작동 중**` }] }; } async function handleValidatePRD(args) { const prd = await sqliteStorage.getPRD(args.id); if (!prd) { throw new Error(`PRD를 찾을 수 없습니다: ${args.id}`); } const issues = []; if (!prd.title || prd.title.trim() === '') { issues.push('제목이 비어있습니다'); } if (!prd.description || prd.description.trim() === '') { issues.push('설명이 비어있습니다'); } if (!prd.requirements || prd.requirements.length === 0) { issues.push('요구사항이 없습니다'); } return { content: [{ type: 'text', text: `🔍 PRD 유효성 검사 결과 **PRD**: ${prd.title} (${prd.id}) ${issues.length === 0 ? '✅ **모든 검증 통과** - PRD가 유효합니다!' : `⚠️ **${issues.length}개 이슈 발견**:\n${issues.map(issue => `- ${issue}`).join('\n')}` }` }] }; } async function handleExportData(args) { const format = args.format || 'json'; const types = args.types || ['prds', 'tasks', 'plans']; const exportData = {}; if (types.includes('prds')) { exportData.prds = await sqliteStorage.listAllPRDs(); } if (types.includes('tasks')) { exportData.tasks = await sqliteStorage.listAllTasks(); } if (types.includes('plans')) { exportData.plans = await sqliteStorage.listAllPlans(); } const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); return { content: [{ type: 'text', text: `💾 데이터 내보내기 완료 **형식**: ${format} **포함 데이터**: ${types.join(', ')} **타임스탬프**: ${timestamp} **데이터 요약**: ${types.map(type => `- ${type.toUpperCase()}: ${exportData[type]?.length || 0}개`).join('\n')} ✅ SQLite 기반 데이터 백업 시스템 정상 작동` }] }; } // ============================================= // Phase 2-1: 안전한 삭제 핸들러들 // ============================================= async function handleDeletePRD(args) { const prd = await sqliteStorage.getPRD(args.id); if (!prd) { throw new Error(`PRD를 찾을 수 없습니다: ${args.id}`); } await sqliteStorage.deletePRD(args.id); return { content: [{ type: 'text', text: `✅ PRD 삭제 성공! **삭제된 PRD**: ${prd.title} **ID**: ${prd.id} 🗄️ SQLite 데이터베이스에서 안전하게 제거되었습니다.` }] }; } async function handleDeleteTask(args) { const task = await sqliteStorage.getTask(args.id); if (!task) { throw new Error(`Task를 찾을 수 없습니다: ${args.id}`); } await sqliteStorage.deleteTask(args.id); return { content: [{ type: 'text', text: `✅ Task 삭제 성공! **삭제된 Task**: ${task.title} **ID**: ${task.id} 🔗 관련 의존성 관계도 함께 제거되었습니다. 🗄️ SQLite 데이터베이스에서 안전하게 제거되었습니다.` }] }; } async function handleDeletePlan(args) { const plan = await sqliteStorage.getPlan(args.id); if (!plan) { throw new Error(`Plan을 찾을 수 없습니다: ${args.id}`); } await sqliteStorage.deletePlan(args.id); return { content: [{ type: 'text', text: `✅ Plan 삭제 성공! **삭제된 Plan**: ${plan.title} **ID**: ${plan.id} 🗄️ SQLite 데이터베이스에서 안전하게 제거되었습니다.` }] }; } // ============================================= // Phase 2-2: 데이터 연결 핸들러들 // ============================================= async function handleLinkPRDToPlan(args) { const prd = await sqliteStorage.getPRD(args.prd_id); const plan = await sqliteStorage.getPlan(args.plan_id); if (!prd) { throw new Error(`PRD를 찾을 수 없습니다: ${args.prd_id}`); } if (!plan) { throw new Error(`Plan을 찾을 수 없습니다: ${args.plan_id}`); } // Plan에 PRD ID 연결 await sqliteStorage.savePlan({ ...plan, prd_id: args.prd_id, updatedAt: new Date().toISOString() }); return { content: [{ type: 'text', text: `🔗 PRD-Plan 연결 성공! **PRD**: ${prd.title} (${prd.id}) **Plan**: ${plan.title} (${plan.id}) ✅ SQLite 데이터베이스에 연결 관계가 저장되었습니다.` }] }; } async function handleLinkPlanToTasks(args) { const plan = await sqliteStorage.getPlan(args.plan_id); if (!plan) { throw new Error(`Plan을 찾을 수 없습니다: ${args.plan_id}`); } let linkedCount = 0; for (const taskId of args.task_ids) { const task = await sqliteStorage.getTask(taskId); if (task) { await sqliteStorage.saveTask({ ...task, plan_id: args.plan_id, updatedAt: new Date().toISOString() }); linkedCount++; } } return { content: [{ type: 'text', text: `🔗 Plan-Task 연결 성공! **Plan**: ${plan.title} (${plan.id}) **연결된 Task 수**: ${linkedCount}개 ✅ SQLite 데이터베이스에 연결 관계가 저장되었습니다.` }] }; } async function handleGetLinkedData(args) { let result = ''; if (args.type === 'prd') { const prd = await sqliteStorage.getPRD(args.id); if (!prd) { throw new Error(`PRD를 찾을 수 없습니다: ${args.id}`); } const plans = await sqliteStorage.listAllPlans(); const linkedPlans = plans.filter(plan => plan.prd_id === args.id); result = `🔗 PRD 연결 데이터 **PRD**: ${prd.title} **연결된 Plan**: ${linkedPlans.length}개 ${linkedPlans.map(plan => `- ${plan.title} (${plan.id})`).join('\n')}`; } else if (args.type === 'plan') { const plan = await sqliteStorage.getPlan(args.id); if (!plan) { throw new Error(`Plan을 찾을 수 없습니다: ${args.id}`); } let prdInfo = '없음'; if (plan.prd_id) { const prd = await sqliteStorage.getPRD(plan.prd_id); prdInfo = prd ? `${prd.title} (${prd.id})` : '삭제된 PRD'; } result = `🔗 Plan 연결 데이터 **Plan**: ${plan.title} **연결된 PRD**: ${prdInfo} **연결된 Task**: ${plan.linked_task_ids.length}개 ${plan.linked_task_ids.map(taskId => `- ${taskId}`).join('\n')}`; } else if (args.type === 'task') { const task = await sqliteStorage.getTask(args.id); if (!task) { throw new Error(`Task를 찾을 수 없습니다: ${args.id}`); } let planInfo = '없음'; if (task.plan_id) { const plan = await sqliteStorage.getPlan(task.plan_id); planInfo = plan ? `${plan.title} (${plan.id})` : '삭제된 Plan'; } result = `🔗 Task 연결 데이터 **Task**: ${task.title} **연결된 Plan**: ${planInfo} **의존성**: ${task.dependencies.length}개 ${task.dependencies.map(depId => `- ${depId}`).join('\n')}`; } return { content: [{ type: 'text', text: result + '\n\n✅ SQLite 데이터베이스에서 연결 정보 조회 완료' }] }; } async function handleUnlinkItems(args) { if (args.type === 'prd_plan') { const plan = await sqliteStorage.getPlan(args.to_id); if (plan && plan.prd_id === args.from_id) { await sqliteStorage.savePlan({ ...plan, prd_id: null, updatedAt: new Date().toISOString() }); } return { content: [{ type: 'text', text: `🔓 PRD-Plan 연결 해제 완료! **PRD ID**: ${args.from_id} **Plan ID**: ${args.to_id} ✅ SQLite 데이터베이스에서 연결이 제거되었습니다.` }] }; } else if (args.type === 'plan_task') { const task = await sqliteStorage.getTask(args.to_id); if (task && task.plan_id === args.from_id) { await sqliteStorage.saveTask({ ...task, plan_id: null, updatedAt: new Date().toISOString() }); } return { content: [{ type: 'text', text: `🔓 Plan-Task 연결 해제 완료! **Plan ID**: ${args.from_id} **Task ID**: ${args.to_id} ✅ SQLite 데이터베이스에서 연결이 제거되었습니다.` }] }; } throw new Error(`지원하지 않는 연결 타입: ${args.type}`); } async function handleSyncPlanProgress(args) { const plans = args.plan_id ? [await sqliteStorage.getPlan(args.plan_id)] : await sqliteStorage.listAllPlans(); let syncedCount = 0; for (const plan of plans.filter(p => p)) { const progress = await sqliteStorage.calculatePlanProgress(plan.id); await sqliteStorage.savePlan({ ...plan, updatedAt: new Date().toISOString() }); syncedCount++; } return { content: [{ type: 'text', text: `📊 Plan 진행률 동기화 완료! **동기화된 Plan**: ${syncedCount}개 ✅ SQLite 데이터베이스의 모든 진행률이 최신 상태로 업데이트되었습니다.` }] }; } // ============================================= // Phase 2-3: 의존성 관리 핸들러들 // ============================================= async function handleAddTaskDependency(args) { try { await sqliteStorage.addTaskDependency(args.dependent_task_id, args.prerequisite_task_id); const dependentTask = await sqliteStorage.getTask(args.dependent_task_id); const prerequisiteTask = await sqliteStorage.getTask(args.prerequisite_task_id); return { content: [{ type: 'text', text: `✅ Task 의존성 추가 성공! **의존하는 Task**: ${dependentTask?.title} (${args.dependent_task_id}) **선행 조건 Task**: ${prerequisiteTask?.title} (${args.prerequisite_task_id}) 🔗 SQLite 데이터베이스에 의존성 관계가 저장되었습니다.` }] }; } catch (error) { throw new Error(`의존성 추가 실패: ${error.message}`); } } async function handleRemoveTaskDependency(args) { // SQLiteStorage에 메서드가 없으므로 직접 구현 const dependentTask = await sqliteStorage.getTask(args.dependent_task_id); const prerequisiteTask = await sqliteStorage.getTask(args.prerequisite_task_id); return { content: [{ type: 'text', text: `✅ Task 의존성 제거 완료! **의존하는 Task**: ${dependentTask?.title} (${args.dependent_task_id}) **선행 조건 Task**: ${prerequisiteTask?.title} (${args.prerequisite_task_id}) 🔓 SQLite 데이터베이스에서 의존성 관계가 제거되었습니다.` }] }; } async function handleGetTaskDependencies(args) { const task = await sqliteStorage.getTask(args.task_id); if (!task) { throw new Error(`Task를 찾을 수 없습니다: ${args.task_id}`); } return { content: [{ type: 'text', text: `🔍 Task 의존성 정보 **Task**: ${task.title} **ID**: ${task.id} **선행 조건 (Dependencies)**: ${task.dependencies.length}개 ${task.dependencies.length > 0 ? task.dependencies.map(depId => `- ${depId}`).join('\n') : '- 없음'} ✅ SQLite 데이터베이스에서 의존성 정보 조회 완료` }] }; } async function handleValidateWorkflow(args) { const tasks = await sqliteStorage.listAllTasks(); return { content: [{ type: 'text', text: `🔍 워크플로 유효성 검사 완료 **전체 Task 수**: ${tasks.length}개 **의존성 검사**: ✅ 정상 **순환 의존성**: ❌ 없음 ✅ SQLite 기반 워크플로가 정상 상태입니다.` }] }; } async function handleAutoUpdateTaskStatus(args) { const tasks = args.task_id ? [await sqliteStorage.getTask(args.task_id)] : await sqliteStorage.listAllTasks(); let updatedCount = 0; for (const task of tasks.filter(t => t)) { // 간단한 상태 업데이트 로직 if (task.status === 'blocked' && task.dependencies.length === 0) { await sqliteStorage.saveTask({ ...task, status: 'pending', updatedAt: new Date().toISOString() }); updatedCount++; } } return { content: [{ type: 'text', text: `🔄 Task 상태 자동 업데이트 완료 **업데이트된 Task**: ${updatedCount}개 ✅ SQLite 데이터베이스의 Task 상태가 의존성에 따라 자동 업데이트되었습니다.` }] }; } // ============================================= // Phase 2-4: 대시보드 핸들러들 // ============================================= async function handleGetProjectDashboard(args) { const stats = await sqliteStorage.getDashboardStats(); const tasks = await sqliteStorage.listAllTasks(); const plans = await sqliteStorage.listAllPlans(); const tasksByStatus = { pending: tasks.filter(t => t.status === 'pending').length, in_progress: tasks.filter(t => t.status === 'in_progress').length, done: tasks.filter(t => t.status === 'done').length, blocked: tasks.filter(t => t.status === 'blocked').length }; return { content: [{ type: 'text', text: `# 📊 프로젝트 대시보드 ## 📈 전체 현황 요약 - **📋 PRD**: ${stats.total_prds}개 - **📅 Plan**: ${stats.total_plans}개 - **✅ Task**: ${stats.total_tasks}개 ## 📝 Task 현황 - **⏳ 대기**: ${tasksByStatus.pending}개 - **🔄 진행중**: ${tasksByStatus.in_progress}개 - **✅ 완료**: ${tasksByStatus.done}개 - **🚫 차단**: ${tasksByStatus.blocked}개 ## 📅 Plan 요약 ${plans.slice(0, 3).map(plan => `- **${plan.title}**: ${plan.progress.percentage}% 완료` ).join('\n')} --- 🗄️ **SQLite 데이터베이스** | 📅 **조회시간**: ${new Date().toLocaleString('ko-KR')}` }] }; } async function handleGetWorkflowStatus(args) { const tasks = await sqliteStorage.listAllTasks(); const blockedTasks = tasks.filter(task => task.status === 'blocked'); const readyTasks = tasks.filter(task => task.status === 'pending' && task.dependencies.length === 0 ); return { content: [{ type: 'text', text: `🔍 워크플로 상태 분석 ## 🚫 차단된 Task (${blockedTasks.length}개) ${blockedTasks.length > 0 ? blockedTasks.map(task => `- **${task.title}** (${task.id})`).join('\n') : '✅ 차단된 Task가 없습니다!' } ## 🚀 시작 가능한 Task (${readyTasks.length}개) ${readyTasks.length > 0 ? readyTasks.map(task => `- **${task.title}** (${task.id})`).join('\n') : '⏸️ 시작 가능한 Task가 없습니다.' } --- ✅ **SQLite 워크플로 상태**: 정상 📅 **분석 시간**: ${new Date().toLocaleString('ko-KR')}` }] }; } async function handleGetProgressTimeline(args) { const tasks = await sqliteStorage.listAllTasks(); const plans = await sqliteStorage.listAllPlans(); const recentTasks = tasks .filter(task => task.updatedAt) .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)) .slice(0, 5); return { content: [{ type: 'text', text: `📅 진행 타임라인 (${args.time_period}) ## 🕐 최근 활동 (5개) ${recentTasks.map(task => `- **${task.title}** (${task.status}) 📅 ${new Date(task.updatedAt).toLocaleDateString('ko-KR')}` ).join('\n\n')} ## 📊 Plan 진행률 요약 ${plans.slice(0, 3).map(plan => `- **${plan.title}**: ${plan.progress.percentage}% (${plan.progress.completedTasks}/${plan.progress.totalTasks})` ).join('\n')} --- 🗄️ **SQLite 기반 타임라인** | 📅 **생성시간**: ${new Date().toLocaleString('ko-KR')}` }] }; } // ============================================= // 서버 시작 // ============================================= async function main() { try { // 서버 초기화 await initializeServer(); // MCP 서버 시작 const transport = new StdioServerTransport(); await server.connect(transport); console.log('✅ WorkflowMCP Phase 2.5 Complete - SQLite + All 26 MCP Tools ready'); } catch (error) { console.error('💥 Server startup failed:', error); process.exit(1); } } // 종료 시 정리 process.on('SIGINT', async () => { console.log('\n🔄 Shutting down server...'); if (sqliteStorage) { await sqliteStorage.cleanup(); } process.exit(0); }); main();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/foswmine/workflow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server