/**
* 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();