#!/usr/bin/env node
/**
* WorkflowMCP Server - Complete Product Development Lifecycle Management
* Entry point for MCP server handling PRD, Task, and Plan management
*/
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 { PRDManager } from '../dashboard/src/lib/server/PRDManager.js';
import { TaskManager } from '../dashboard/src/lib/server/TaskManager.js';
import { DesignManager } from '../dashboard/src/lib/server/DesignManager.js';
import { ProjectManager } from '../dashboard/src/lib/server/ProjectManager.js';
import { DocumentManager } from './models/DocumentManager.js';
import TestManager from './models/TestManager.js';
import { DevOpsManager } from '../dashboard/src/lib/server/DevOpsManager.js';
import { MetricsCollector } from './utils/MetricsCollector.js';
import { ErrorLogger } from './utils/ErrorLogger.js';
import { ExecutableSpecsManager } from './models/ExecutableSpecsManager.js';
import { KnowledgeBaseManager } from './models/KnowledgeBaseManager.js';
import { CodeTemplatesManager } from './models/CodeTemplatesManager.js';
import { SDDTools } from './sdd-tools.js';
import sqlite3 from 'sqlite3';
import { open } from 'sqlite';
class WorkflowMCPServer {
constructor() {
this.server = new Server(
{
name: 'workflow-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Initialize managers
this.prdManager = new PRDManager();
this.taskManager = new TaskManager();
this.designManager = new DesignManager();
this.projectManager = new ProjectManager();
this.documentManager = new DocumentManager();
this.testManager = new TestManager();
this.devOpsManager = new DevOpsManager();
this.metricsCollector = new MetricsCollector();
this.errorLogger = new ErrorLogger();
// Initialize SDD managers
this.executableSpecsManager = new ExecutableSpecsManager();
this.knowledgeBaseManager = new KnowledgeBaseManager();
this.codeTemplatesManager = new CodeTemplatesManager();
// Initialize SDD tools
this.sddTools = new SDDTools(
this.executableSpecsManager,
this.knowledgeBaseManager,
this.codeTemplatesManager
);
this.setupToolHandlers();
this.setupErrorHandling();
}
setupToolHandlers() {
// PRD Management Tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// PRD Tools
{
name: 'create_prd',
description: 'Create a new Product Requirements Document with structured format',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'PRD title' },
description: { type: 'string', description: 'Brief description' },
requirements: {
type: 'array',
items: { type: 'string' },
description: 'List of functional requirements'
},
priority: {
type: 'string',
enum: ['high', 'medium', 'low'],
description: 'Priority level'
},
status: {
type: 'string',
enum: ['active', 'inactive', 'draft', 'review', 'approved', 'completed'],
description: 'PRD status'
},
project_id: {
type: 'string',
description: 'Related project ID (optional)'
},
acceptance_criteria: {
type: 'array',
items: { type: 'string' },
description: 'List of acceptance criteria'
}
},
required: ['title', 'description', 'requirements']
}
},
{
name: 'list_prds',
description: 'List all existing PRDs with summary information',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['active', 'inactive', 'draft', 'review', 'approved', 'completed'],
description: 'Filter by status (optional)'
},
sort_by: {
type: 'string',
enum: ['created_desc', 'created_asc', 'updated_desc', 'updated_asc', 'title_asc', 'title_desc'],
description: 'Sort order (optional)'
},
project_id: {
type: 'string',
description: 'Filter by project ID (optional)'
}
}
}
},
{
name: 'get_prd',
description: 'Retrieve detailed PRD information by ID',
inputSchema: {
type: 'object',
properties: {
prd_id: { type: 'string', description: 'PRD unique identifier' }
},
required: ['prd_id']
}
},
{
name: 'update_prd',
description: 'Update existing PRD with new information',
inputSchema: {
type: 'object',
properties: {
prd_id: { type: 'string', description: 'PRD unique identifier' },
updates: {
type: 'object',
description: 'Fields to update (title, description, requirements, etc.)'
}
},
required: ['prd_id', 'updates']
}
},
// Design Management Tools
{
name: 'create_design',
description: 'Create a new design document with structured format',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Design title' },
description: { type: 'string', description: 'Design description' },
design_type: {
type: 'string',
enum: ['system', 'architecture', 'ui_ux', 'database', 'api'],
description: 'Type of design'
},
requirement_id: { type: 'string', description: 'Related PRD/requirement ID (optional)' },
details: { type: 'string', description: 'Detailed design specifications' },
priority: {
type: 'string',
enum: ['High', 'Medium', 'Low'],
description: 'Priority level'
}
},
required: ['title', 'description', 'design_type']
}
},
{
name: 'list_designs',
description: 'List all existing designs with summary information',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['draft', 'review', 'approved', 'implemented'],
description: 'Filter by status (optional)'
},
design_type: {
type: 'string',
enum: ['system', 'architecture', 'ui_ux', 'database', 'api'],
description: 'Filter by design type (optional)'
},
sort_by: {
type: 'string',
enum: ['updated_desc', 'updated_asc', 'created_desc', 'created_asc', 'title_asc', 'title_desc'],
description: 'Sort order (optional)',
default: 'updated_desc'
}
}
}
},
{
name: 'get_design',
description: 'Retrieve detailed design information by ID',
inputSchema: {
type: 'object',
properties: {
design_id: { type: 'string', description: 'Design unique identifier' }
},
required: ['design_id']
}
},
{
name: 'update_design',
description: 'Update existing design with new information',
inputSchema: {
type: 'object',
properties: {
design_id: { type: 'string', description: 'Design unique identifier' },
updates: {
type: 'object',
description: 'Fields to update (title, description, status, etc.)'
}
},
required: ['design_id', 'updates']
}
},
{
name: 'delete_design',
description: 'Delete a design and its related data',
inputSchema: {
type: 'object',
properties: {
design_id: { type: 'string', description: 'Design unique identifier' }
},
required: ['design_id']
}
},
// Task Management Tools
{
name: 'create_task',
description: 'Create a new task with structured format',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Task title' },
description: { type: 'string', description: 'Task description' },
priority: {
type: 'string',
enum: ['High', 'Medium', 'Low'],
description: 'Priority level'
},
status: {
type: 'string',
enum: ['pending', 'in_progress', 'done', 'blocked'],
description: 'Task status'
},
assignee: { type: 'string', description: 'Task assignee' },
estimated_hours: { type: 'number', description: 'Estimated hours to complete' },
due_date: { type: 'string', description: 'Due date (ISO string)' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Task tags'
},
notes: { type: 'string', description: 'Additional notes' },
details: { type: 'string', description: 'Detailed task specifications' }
},
required: ['title', 'description']
}
},
{
name: 'list_tasks',
description: 'List all existing tasks with summary information',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['pending', 'in_progress', 'done', 'blocked'],
description: 'Filter by status (optional)'
},
assignee: { type: 'string', description: 'Filter by assignee (optional)' },
sort_by: {
type: 'string',
enum: ['updated_desc', 'updated_asc', 'created_desc', 'created_asc', 'title_asc', 'title_desc'],
description: 'Sort order (optional)',
default: 'updated_desc'
}
}
}
},
{
name: 'get_task',
description: 'Retrieve detailed task information by ID',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'string', description: 'Task unique identifier' }
},
required: ['task_id']
}
},
{
name: 'update_task',
description: 'Update existing task with new information',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'string', description: 'Task unique identifier' },
updates: {
type: 'object',
description: 'Fields to update (title, description, status, priority, etc.)'
}
},
required: ['task_id', 'updates']
}
},
{
name: 'delete_task',
description: 'Delete a task and its related data',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'string', description: 'Task unique identifier' }
},
required: ['task_id']
}
},
// Document Management Tools
{
name: 'create_document',
description: 'Create a new document and store in SQLite database',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Document title' },
content: { type: 'string', description: 'Document content (Markdown format)' },
doc_type: {
type: 'string',
enum: ['test_guide', 'test_results', 'analysis', 'report', 'checklist', 'specification', 'meeting_notes', 'decision_log'],
description: 'Document type'
},
category: { type: 'string', description: 'Category (e.g., phase_2.6, testing)' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Tag list'
},
summary: { type: 'string', description: 'Document summary' },
linked_entity_type: {
type: 'string',
enum: ['prd', 'task', 'plan'],
description: 'Entity type to link (optional)'
},
linked_entity_id: { type: 'string', description: 'Entity ID to link (optional)' },
link_type: {
type: 'string',
enum: ['specification', 'test_plan', 'result', 'analysis', 'notes'],
description: 'Link type (optional)'
}
},
required: ['title', 'content', 'doc_type']
}
},
{
name: 'search_documents',
description: 'Search documents using full-text search',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
doc_type: {
type: 'string',
enum: ['test_guide', 'test_results', 'analysis', 'report', 'checklist', 'specification', 'meeting_notes', 'decision_log'],
description: 'Filter by document type (optional)'
},
category: { type: 'string', description: 'Filter by category (optional)' },
limit: { type: 'integer', default: 10, description: 'Limit results' }
},
required: ['query']
}
},
{
name: 'get_document',
description: 'Get specific document by ID',
inputSchema: {
type: 'object',
properties: {
id: { type: 'integer', description: 'Document ID' }
},
required: ['id']
}
},
{
name: 'update_document',
description: 'Update existing document',
inputSchema: {
type: 'object',
properties: {
id: { type: 'integer', description: 'Document ID' },
title: { type: 'string', description: 'Document title' },
content: { type: 'string', description: 'Document content' },
summary: { type: 'string', description: 'Document summary' },
status: {
type: 'string',
enum: ['draft', 'review', 'approved', 'archived'],
description: 'Document status'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Tag list'
}
},
required: ['id']
}
},
{
name: 'delete_document',
description: 'Delete document',
inputSchema: {
type: 'object',
properties: {
id: { type: 'integer', description: 'Document ID' }
},
required: ['id']
}
},
{
name: 'list_documents',
description: 'List documents with optional filters',
inputSchema: {
type: 'object',
properties: {
doc_type: {
type: 'string',
enum: ['test_guide', 'test_results', 'analysis', 'report', 'checklist', 'specification', 'meeting_notes', 'decision_log'],
description: 'Filter by document type'
},
category: { type: 'string', description: 'Filter by category' },
status: {
type: 'string',
enum: ['draft', 'review', 'approved', 'archived'],
description: 'Filter by status'
},
limit: { type: 'integer', default: 20, description: 'Limit results' }
}
}
},
{
name: 'link_document',
description: 'Link document to PRD, Task, or Plan',
inputSchema: {
type: 'object',
properties: {
document_id: { type: 'integer', description: 'Document ID' },
entity_type: {
type: 'string',
enum: ['prd', 'task', 'plan'],
description: 'Entity type to link'
},
entity_id: { type: 'string', description: 'Entity ID to link' },
link_type: {
type: 'string',
enum: ['specification', 'test_plan', 'result', 'analysis', 'notes'],
default: 'notes',
description: 'Link type'
}
},
required: ['document_id', 'entity_type', 'entity_id']
}
},
{
name: 'get_document_categories',
description: 'Get existing document categories, types and tags for selection',
inputSchema: {
type: 'object',
properties: {},
required: []
}
},
// PRD Document Management Tools
{
name: 'create_prd_document',
description: 'Create detailed document for PRD and link automatically',
inputSchema: {
type: 'object',
properties: {
prd_id: { type: 'string', description: 'PRD ID to link to' },
title: { type: 'string', description: 'Document title' },
content: { type: 'string', description: 'Detailed PRD content' },
doc_type: {
type: 'string',
enum: ['specification', 'analysis', 'report'],
default: 'specification',
description: 'Document type'
},
summary: { type: 'string', description: 'Brief summary (optional)' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Tag list (optional)'
}
},
required: ['prd_id', 'title', 'content']
}
},
{
name: 'get_prd_documents',
description: 'Get all documents linked to a PRD',
inputSchema: {
type: 'object',
properties: {
prd_id: { type: 'string', description: 'PRD ID' },
doc_type: {
type: 'string',
enum: ['specification', 'test_plan', 'result', 'analysis', 'notes'],
description: 'Filter by document type (optional)'
}
},
required: ['prd_id']
}
},
{
name: 'search_prd_documents',
description: 'Search documents within specific PRD',
inputSchema: {
type: 'object',
properties: {
prd_id: { type: 'string', description: 'PRD ID to search within' },
query: { type: 'string', description: 'Search query' },
limit: { type: 'integer', default: 10, description: 'Limit results' }
},
required: ['prd_id', 'query']
}
},
{
name: 'update_prd_document',
description: 'Update document content linked to PRD',
inputSchema: {
type: 'object',
properties: {
document_id: { type: 'integer', description: 'Document ID' },
title: { type: 'string', description: 'Updated title (optional)' },
content: { type: 'string', description: 'Updated content (optional)' },
summary: { type: 'string', description: 'Updated summary (optional)' },
status: {
type: 'string',
enum: ['draft', 'review', 'approved', 'archived'],
description: 'Updated status (optional)'
}
},
required: ['document_id']
}
},
{
name: 'link_document_to_prd',
description: 'Link existing document to PRD',
inputSchema: {
type: 'object',
properties: {
document_id: { type: 'integer', description: 'Document ID' },
prd_id: { type: 'string', description: 'PRD ID' },
link_type: {
type: 'string',
enum: ['specification', 'test_plan', 'result', 'analysis', 'notes'],
default: 'specification',
description: 'Link type'
}
},
required: ['document_id', 'prd_id']
}
},
// Document Relations Tools
{
name: 'create_document_relation',
description: 'Create a relationship between two documents',
inputSchema: {
type: 'object',
properties: {
parent_doc_id: { type: 'integer', description: 'Parent document ID' },
child_doc_id: { type: 'integer', description: 'Child document ID' },
relation_type: {
type: 'string',
enum: ['referenced_by', 'contains', 'derived_from', 'replaces'],
default: 'referenced_by',
description: 'Type of relationship'
},
notes: { type: 'string', description: 'Optional notes about the relationship' }
},
required: ['parent_doc_id', 'child_doc_id']
}
},
{
name: 'get_document_relations',
description: 'Get all relationships for a specific document',
inputSchema: {
type: 'object',
properties: {
document_id: { type: 'integer', description: 'Document ID' }
},
required: ['document_id']
}
},
{
name: 'remove_document_relation',
description: 'Remove a relationship between two documents',
inputSchema: {
type: 'object',
properties: {
parent_doc_id: { type: 'integer', description: 'Parent document ID' },
child_doc_id: { type: 'integer', description: 'Child document ID' },
relation_type: {
type: 'string',
enum: ['referenced_by', 'contains', 'derived_from', 'replaces'],
description: 'Type of relationship to remove'
}
},
required: ['parent_doc_id', 'child_doc_id', 'relation_type']
}
},
{
name: 'remove_document_link',
description: 'Remove a link between document and entity (PRD, Task, Plan)',
inputSchema: {
type: 'object',
properties: {
document_id: { type: 'integer', description: 'Document ID' },
entity_type: {
type: 'string',
enum: ['prd', 'task', 'plan'],
description: 'Type of entity to unlink'
},
entity_id: { type: 'string', description: 'Entity ID to unlink' }
},
required: ['document_id', 'entity_type', 'entity_id']
}
},
{
name: 'get_document_links',
description: 'Get all entity links for a specific document',
inputSchema: {
type: 'object',
properties: {
document_id: { type: 'integer', description: 'Document ID' }
},
required: ['document_id']
}
},
// Test Management Tools
{
name: 'create_test_case',
description: 'Create a new test case with structured format',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Test case title' },
description: { type: 'string', description: 'Test case description' },
type: {
type: 'string',
enum: ['unit', 'integration', 'system', 'acceptance', 'regression'],
description: 'Test case type'
},
status: {
type: 'string',
enum: ['draft', 'ready', 'active', 'deprecated'],
description: 'Test case status'
},
priority: {
type: 'string',
enum: ['High', 'Medium', 'Low'],
description: 'Priority level'
},
task_id: { type: 'string', description: 'Related task ID (optional)' },
design_id: { type: 'string', description: 'Related design ID (optional)' },
prd_id: { type: 'string', description: 'Related PRD ID (optional)' },
preconditions: { type: 'string', description: 'Test preconditions' },
test_steps: {
type: 'array',
items: { type: 'string' },
description: 'Test execution steps'
},
expected_result: { type: 'string', description: 'Expected test result' },
test_data: { type: 'object', description: 'Test data (JSON object)' },
estimated_duration: { type: 'number', description: 'Estimated duration in minutes' },
complexity: {
type: 'string',
enum: ['Low', 'Medium', 'High'],
description: 'Test complexity'
},
automation_status: {
type: 'string',
enum: ['manual', 'automated', 'semi_automated'],
description: 'Automation status'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Test case tags'
}
},
required: ['title', 'description']
}
},
{
name: 'list_test_cases',
description: 'List all test cases with optional filtering',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['draft', 'ready', 'active', 'deprecated'],
description: 'Filter by status (optional)'
},
type: {
type: 'string',
enum: ['unit', 'integration', 'system', 'acceptance', 'regression'],
description: 'Filter by type (optional)'
},
priority: {
type: 'string',
enum: ['High', 'Medium', 'Low'],
description: 'Filter by priority (optional)'
},
task_id: { type: 'string', description: 'Filter by task ID (optional)' },
design_id: { type: 'string', description: 'Filter by design ID (optional)' },
prd_id: { type: 'string', description: 'Filter by PRD ID (optional)' },
sort_by: {
type: 'string',
enum: ['updated_desc', 'updated_asc', 'created_desc', 'created_asc', 'title_asc', 'title_desc'],
description: 'Sort order (optional)',
default: 'updated_desc'
}
}
}
},
{
name: 'get_test_case',
description: 'Retrieve detailed test case information by ID',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' }
},
required: ['test_case_id']
}
},
{
name: 'update_test_case',
description: 'Update existing test case with new information',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' },
updates: {
type: 'object',
description: 'Fields to update (title, description, status, etc.)'
}
},
required: ['test_case_id', 'updates']
}
},
{
name: 'delete_test_case',
description: 'Delete a test case and its execution history',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' }
},
required: ['test_case_id']
}
},
{
name: 'execute_test_case',
description: 'Execute a test case and record the results',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' },
status: {
type: 'string',
enum: ['pass', 'fail', 'blocked', 'skipped', 'pending'],
description: 'Execution result status'
},
executed_by: { type: 'string', description: 'Person who executed the test' },
environment: {
type: 'string',
enum: ['development', 'staging', 'production'],
description: 'Test environment'
},
actual_result: { type: 'string', description: 'Actual test result' },
notes: { type: 'string', description: 'Execution notes' },
defects_found: {
type: 'array',
items: { type: 'string' },
description: 'List of defect IDs found'
},
actual_duration: { type: 'number', description: 'Actual duration in minutes' },
started_at: { type: 'string', description: 'Start time (ISO string)' },
completed_at: { type: 'string', description: 'Completion time (ISO string)' },
attachments: {
type: 'array',
items: { type: 'string' },
description: 'List of attachment URLs/paths'
}
},
required: ['test_case_id', 'status']
}
},
{
name: 'get_test_executions',
description: 'Get test execution history for a test case',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' },
environment: {
type: 'string',
enum: ['development', 'staging', 'production'],
description: 'Filter by environment (optional)'
},
limit: { type: 'number', description: 'Limit number of results (optional)' }
},
required: ['test_case_id']
}
},
{
name: 'get_test_summary',
description: 'Get comprehensive test summary report with statistics',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'get_test_coverage',
description: 'Get test coverage analysis by task/design/PRD',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'link_test_to_task',
description: 'Link a test case to a specific task',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' },
task_id: { type: 'string', description: 'Task unique identifier' }
},
required: ['test_case_id', 'task_id']
}
},
{
name: 'get_test_connections',
description: 'Get all connections for a specific test case (tasks, designs, requirements)',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' }
},
required: ['test_case_id']
}
},
{
name: 'add_test_connection',
description: 'Add a new connection between a test case and another entity',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' },
entity_type: {
type: 'string',
enum: ['task', 'design', 'prd'],
description: 'Type of entity to connect'
},
entity_id: { type: 'string', description: 'Entity unique identifier' }
},
required: ['test_case_id', 'entity_type', 'entity_id']
}
},
{
name: 'remove_test_connection',
description: 'Remove a connection between a test case and another entity',
inputSchema: {
type: 'object',
properties: {
test_case_id: { type: 'string', description: 'Test case unique identifier' },
entity_type: {
type: 'string',
enum: ['task', 'design', 'prd'],
description: 'Type of entity to disconnect'
},
entity_id: { type: 'string', description: 'Entity unique identifier' }
},
required: ['test_case_id', 'entity_type', 'entity_id']
}
},
{
name: 'get_task_connections',
description: 'Get all connections for a specific task (PRDs, designs, documents, tests)',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'string', description: 'Task unique identifier' }
},
required: ['task_id']
}
},
{
name: 'add_task_connection',
description: 'Add a new connection between a task and another entity',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'string', description: 'Task unique identifier' },
entity_type: {
type: 'string',
enum: ['prd', 'design', 'document', 'test'],
description: 'Type of entity to connect'
},
entity_id: { type: 'string', description: 'ID of the entity to connect' },
connection_type: {
type: 'string',
enum: ['related', 'dependent', 'blocking', 'reference'],
description: 'Type of connection',
default: 'related'
}
},
required: ['task_id', 'entity_type', 'entity_id']
}
},
{
name: 'remove_task_connection',
description: 'Remove a connection between a task and another entity',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'string', description: 'Task unique identifier' },
entity_type: {
type: 'string',
enum: ['prd', 'design', 'document', 'test'],
description: 'Type of entity to disconnect'
},
entity_id: { type: 'string', description: 'ID of the entity to disconnect' }
},
required: ['task_id', 'entity_type', 'entity_id']
}
},
// Project Management Tools
{
name: 'create_project',
description: 'Create a new project with structured format',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Project name' },
description: { type: 'string', description: 'Project description' },
status: {
type: 'string',
enum: ['planning', 'active', 'on_hold', 'completed'],
description: 'Project status'
},
priority: {
type: 'string',
enum: ['High', 'Medium', 'Low'],
description: 'Priority level'
},
manager: { type: 'string', description: 'Project manager' },
start_date: { type: 'string', description: 'Start date (ISO string)' },
end_date: { type: 'string', description: 'End date (ISO string)' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Project tags'
},
notes: { type: 'string', description: 'Project notes' }
},
required: ['name']
}
},
{
name: 'list_projects',
description: 'List all projects with filtering and sorting',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['planning', 'active', 'on_hold', 'completed'],
description: 'Filter by status (optional)'
},
priority: {
type: 'string',
enum: ['High', 'Medium', 'Low'],
description: 'Filter by priority (optional)'
},
sort_by: {
type: 'string',
enum: ['name', 'updated_desc', 'created_desc', 'priority'],
description: 'Sort order (optional)'
}
}
}
},
{
name: 'get_project',
description: 'Get detailed project information by ID',
inputSchema: {
type: 'object',
properties: {
project_id: { type: 'string', description: 'Project unique identifier' }
},
required: ['project_id']
}
},
{
name: 'update_project',
description: 'Update existing project with new information',
inputSchema: {
type: 'object',
properties: {
project_id: { type: 'string', description: 'Project unique identifier' },
updates: {
type: 'object',
description: 'Fields to update (name, description, status, etc.)'
}
},
required: ['project_id', 'updates']
}
},
{
name: 'delete_project',
description: 'Delete a project (only if no related data exists)',
inputSchema: {
type: 'object',
properties: {
project_id: { type: 'string', description: 'Project unique identifier' }
},
required: ['project_id']
}
},
{
name: 'get_project_analytics',
description: 'Get comprehensive project analytics and progress metrics',
inputSchema: {
type: 'object',
properties: {
project_id: { type: 'string', description: 'Project unique identifier' }
},
required: ['project_id']
}
},
// Environment Management Tools
{
name: 'create_environment',
description: 'Create a new environment with configuration',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Environment name' },
environment_type: {
type: 'string',
enum: ['development', 'staging', 'production', 'testing'],
description: 'Type of environment'
},
description: { type: 'string', description: 'Environment description' },
url: { type: 'string', description: 'Environment URL' },
status: {
type: 'string',
enum: ['active', 'inactive', 'maintenance'],
description: 'Environment status'
},
project_id: { type: 'string', description: 'Related project ID (optional)' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Environment tags'
}
},
required: ['name', 'environment_type']
}
},
{
name: 'list_environments',
description: 'List all environments with optional filtering',
inputSchema: {
type: 'object',
properties: {
environment_type: {
type: 'string',
enum: ['development', 'staging', 'production', 'testing'],
description: 'Filter by environment type (optional)'
},
status: {
type: 'string',
enum: ['active', 'inactive', 'maintenance'],
description: 'Filter by status (optional)'
},
project_id: { type: 'string', description: 'Filter by project ID (optional)' }
}
}
},
{
name: 'get_environment_status',
description: 'Get detailed status and health information for an environment',
inputSchema: {
type: 'object',
properties: {
environment_id: { type: 'string', description: 'Environment unique identifier' }
},
required: ['environment_id']
}
},
{
name: 'update_environment',
description: 'Update environment configuration and status',
inputSchema: {
type: 'object',
properties: {
environment_id: { type: 'string', description: 'Environment unique identifier' },
updates: {
type: 'object',
description: 'Fields to update (name, description, status, tags, etc.)'
}
},
required: ['environment_id', 'updates']
}
},
// Deployment Management Tools
{
name: 'create_deployment',
description: 'Create a new deployment record with structured format',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Deployment title' },
description: { type: 'string', description: 'Deployment description' },
environment_id: { type: 'string', description: 'Target environment ID' },
version: { type: 'string', description: 'Application/package version' },
deployment_type: {
type: 'string',
enum: ['blue_green', 'rolling', 'canary', 'hotfix'],
description: 'Deployment strategy type'
},
status: {
type: 'string',
enum: ['planned', 'in_progress', 'completed', 'failed', 'rolled_back'],
description: 'Deployment status'
},
deployment_config: {
type: 'object',
description: 'Deployment configuration (JSON object)'
},
scheduled_at: { type: 'string', description: 'Scheduled deployment time (ISO string)' },
rollback_version: { type: 'string', description: 'Rollback version if needed' },
project_id: { type: 'string', description: 'Related project ID (optional)' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Deployment tags'
},
notes: { type: 'string', description: 'Additional deployment notes' }
},
required: ['title', 'description', 'environment_id', 'version']
}
},
{
name: 'list_deployments',
description: 'List all deployments with optional filtering',
inputSchema: {
type: 'object',
properties: {
environment_id: { type: 'string', description: 'Filter by environment ID (optional)' },
status: {
type: 'string',
enum: ['planned', 'in_progress', 'completed', 'failed', 'rolled_back'],
description: 'Filter by status (optional)'
},
deployment_type: {
type: 'string',
enum: ['blue_green', 'rolling', 'canary', 'hotfix'],
description: 'Filter by deployment type (optional)'
},
project_id: { type: 'string', description: 'Filter by project ID (optional)' },
sort_by: {
type: 'string',
enum: ['scheduled_desc', 'scheduled_asc', 'created_desc', 'created_asc', 'status'],
description: 'Sort order (optional)'
}
}
}
},
{
name: 'get_deployment',
description: 'Retrieve detailed deployment information by ID',
inputSchema: {
type: 'object',
properties: {
deployment_id: { type: 'string', description: 'Deployment unique identifier' }
},
required: ['deployment_id']
}
},
{
name: 'update_deployment',
description: 'Update existing deployment with new information',
inputSchema: {
type: 'object',
properties: {
deployment_id: { type: 'string', description: 'Deployment unique identifier' },
updates: {
type: 'object',
description: 'Fields to update (title, description, status, deployment_type, etc.)',
properties: {
title: { type: 'string', description: 'Deployment title' },
description: { type: 'string', description: 'Deployment description' },
status: {
type: 'string',
enum: ['planned', 'in_progress', 'completed', 'failed', 'rolled_back'],
description: 'Deployment status'
},
deployment_type: {
type: 'string',
enum: ['blue_green', 'rolling', 'canary', 'hotfix'],
description: 'Deployment strategy type'
},
version: { type: 'string', description: 'Application/package version' },
deployment_config: {
type: 'object',
description: 'Deployment configuration (JSON object)'
},
scheduled_at: { type: 'string', description: 'Scheduled deployment time (ISO string)' },
rollback_version: { type: 'string', description: 'Rollback version if needed' },
notes: { type: 'string', description: 'Additional deployment notes' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Deployment tags'
}
}
}
},
required: ['deployment_id', 'updates']
}
},
// Incident Management Tools
{
name: 'create_incident',
description: 'Create a new incident record for tracking operational issues',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Incident title' },
description: { type: 'string', description: 'Detailed incident description' },
severity: {
type: 'string',
enum: ['critical', 'high', 'medium', 'low'],
description: 'Incident severity level'
},
incident_type: {
type: 'string',
enum: ['outage', 'performance', 'security', 'data', 'deployment'],
description: 'Type of incident'
},
status: {
type: 'string',
enum: ['open', 'investigating', 'identified', 'monitoring', 'resolved'],
description: 'Incident status'
},
affected_services: {
type: 'array',
items: { type: 'string' },
description: 'List of affected services'
},
environment_id: { type: 'string', description: 'Affected environment ID (optional)' },
project_id: { type: 'string', description: 'Related project ID (optional)' },
tags: {
type: 'array',
items: { type: 'string' },
description: 'Incident tags'
}
},
required: ['title', 'description', 'severity', 'incident_type']
}
},
{
name: 'list_incidents',
description: 'List all incidents with optional filtering and sorting',
inputSchema: {
type: 'object',
properties: {
severity: {
type: 'string',
enum: ['critical', 'high', 'medium', 'low'],
description: 'Filter by severity (optional)'
},
status: {
type: 'string',
enum: ['open', 'investigating', 'identified', 'monitoring', 'resolved'],
description: 'Filter by status (optional)'
},
incident_type: {
type: 'string',
enum: ['outage', 'performance', 'security', 'data', 'deployment'],
description: 'Filter by incident type (optional)'
},
environment_id: { type: 'string', description: 'Filter by environment ID (optional)' },
sort_by: {
type: 'string',
enum: ['created_desc', 'created_asc', 'updated_desc', 'severity', 'status'],
description: 'Sort order (optional)'
}
}
}
},
// System Health Tools
{
name: 'get_system_health',
description: 'Get comprehensive system health status and metrics',
inputSchema: {
type: 'object',
properties: {
environment_id: { type: 'string', description: 'Environment ID (optional - if not provided, gets all environments)' },
include_details: {
type: 'boolean',
description: 'Include detailed metrics (default: false)'
}
}
}
},
// =============================================
// Claude Collaboration Tools (Phase 3.0)
// =============================================
// Agent Session Management
{
name: 'start_agent_session',
description: 'Start a new agent session (developer or supervisor)',
inputSchema: {
type: 'object',
properties: {
agent_type: {
type: 'string',
enum: ['developer', 'supervisor'],
description: 'Type of agent session to start'
},
agent_name: {
type: 'string',
description: 'Optional display name for the agent'
},
project_id: {
type: 'string',
description: 'Project ID this session is working on (optional)'
}
},
required: ['agent_type']
}
},
{
name: 'update_agent_status',
description: 'Update current agent session status and activity',
inputSchema: {
type: 'object',
properties: {
session_id: { type: 'string', description: 'Agent session ID' },
status: {
type: 'string',
enum: ['active', 'idle', 'offline'],
description: 'New status'
},
current_activity: { type: 'string', description: 'What the agent is currently doing' },
current_task_id: { type: 'string', description: 'Task ID currently being worked on' },
progress_notes: { type: 'string', description: 'Latest progress notes' }
},
required: ['session_id']
}
},
{
name: 'get_active_sessions',
description: 'Get all currently active agent sessions',
inputSchema: {
type: 'object',
properties: {
agent_type: {
type: 'string',
enum: ['developer', 'supervisor'],
description: 'Filter by agent type (optional)'
}
}
}
},
// Real-time Communication
{
name: 'send_message',
description: 'Send message to another agent or broadcast',
inputSchema: {
type: 'object',
properties: {
from_session_id: { type: 'string', description: 'Sender session ID' },
to_session_id: { type: 'string', description: 'Recipient session ID (null for broadcast)' },
message_type: {
type: 'string',
enum: ['progress_update', 'question', 'feedback', 'approval_request', 'direction_change', 'task_assignment', 'status_alert'],
description: 'Type of message'
},
subject: { type: 'string', description: 'Brief subject line' },
content: { type: 'string', description: 'Main message content' },
priority: {
type: 'string',
enum: ['urgent', 'high', 'normal', 'low'],
description: 'Message priority'
},
related_task_id: { type: 'string', description: 'Related task ID (optional)' },
response_required: { type: 'boolean', description: 'Whether response is required' }
},
required: ['from_session_id', 'message_type', 'content']
}
},
{
name: 'get_messages',
description: 'Get messages for current session',
inputSchema: {
type: 'object',
properties: {
session_id: { type: 'string', description: 'Session ID to get messages for' },
unread_only: { type: 'boolean', description: 'Only return unread messages' },
limit: { type: 'number', description: 'Limit number of messages returned' }
},
required: ['session_id']
}
},
{
name: 'mark_message_read',
description: 'Mark message as read',
inputSchema: {
type: 'object',
properties: {
message_id: { type: 'number', description: 'Message ID to mark as read' },
session_id: { type: 'string', description: 'Session ID marking the message as read' }
},
required: ['message_id', 'session_id']
}
},
// Supervisor Intervention Tools
{
name: 'send_intervention',
description: 'Supervisor sends intervention to developer',
inputSchema: {
type: 'object',
properties: {
supervisor_session_id: { type: 'string', description: 'Supervisor session ID' },
developer_session_id: { type: 'string', description: 'Target developer session ID' },
intervention_type: {
type: 'string',
enum: ['feedback', 'direction_change', 'approval', 'rejection', 'task_reassignment', 'priority_change', 'blocking', 'unblocking'],
description: 'Type of intervention'
},
title: { type: 'string', description: 'Brief intervention title' },
message: { type: 'string', description: 'Detailed intervention message' },
related_task_id: { type: 'string', description: 'Related task ID (optional)' },
priority: {
type: 'string',
enum: ['critical', 'urgent', 'normal', 'advisory'],
description: 'Intervention priority'
},
requires_immediate_action: { type: 'boolean', description: 'Whether immediate action is required' }
},
required: ['supervisor_session_id', 'developer_session_id', 'intervention_type', 'title', 'message']
}
},
{
name: 'get_pending_interventions',
description: 'Get pending interventions for developer',
inputSchema: {
type: 'object',
properties: {
developer_session_id: { type: 'string', description: 'Developer session ID' },
acknowledged_only: { type: 'boolean', description: 'Only return unacknowledged interventions' }
},
required: ['developer_session_id']
}
},
{
name: 'acknowledge_intervention',
description: 'Developer acknowledges supervisor intervention',
inputSchema: {
type: 'object',
properties: {
intervention_id: { type: 'number', description: 'Intervention ID to acknowledge' },
developer_session_id: { type: 'string', description: 'Developer session ID' },
response: { type: 'string', description: 'Developer response to intervention' }
},
required: ['intervention_id', 'developer_session_id']
}
},
// Progress Monitoring
{
name: 'update_task_progress',
description: 'Update detailed task progress with snapshot',
inputSchema: {
type: 'object',
properties: {
task_id: { type: 'string', description: 'Task ID being updated' },
agent_session_id: { type: 'string', description: 'Agent session making the update' },
progress_percentage: { type: 'number', minimum: 0, maximum: 100, description: 'Progress percentage (0-100)' },
work_description: { type: 'string', description: 'Description of work completed' },
files_modified: { type: 'array', items: { type: 'string' }, description: 'Array of file paths modified' },
confidence_level: { type: 'number', minimum: 1, maximum: 10, description: 'Confidence level (1-10 scale)' },
needs_review: { type: 'boolean', description: 'Whether work needs supervisor review' }
},
required: ['task_id', 'agent_session_id', 'work_description']
}
},
{
name: 'monitor_active_development',
description: 'Get real-time view of active development sessions',
inputSchema: {
type: 'object',
properties: {
project_id: { type: 'string', description: 'Filter by project ID (optional)' }
}
}
},
// Approval Workflows
{
name: 'request_approval',
description: 'Request approval for work or decision',
inputSchema: {
type: 'object',
properties: {
requester_session_id: { type: 'string', description: 'Session ID requesting approval' },
workflow_type: {
type: 'string',
enum: ['task_completion', 'code_change', 'architecture_decision', 'deployment_approval', 'requirement_change'],
description: 'Type of approval workflow'
},
title: { type: 'string', description: 'Approval request title' },
description: { type: 'string', description: 'Detailed description of what needs approval' },
related_task_id: { type: 'string', description: 'Related task ID (optional)' }
},
required: ['requester_session_id', 'workflow_type', 'title', 'description']
}
},
{
name: 'get_pending_approvals',
description: 'Get pending approval requests',
inputSchema: {
type: 'object',
properties: {
approver_session_id: { type: 'string', description: 'Approver session ID (optional)' },
workflow_type: {
type: 'string',
enum: ['task_completion', 'code_change', 'architecture_decision', 'deployment_approval', 'requirement_change'],
description: 'Filter by workflow type (optional)'
}
}
}
},
{
name: 'approve_request',
description: 'Approve a pending request',
inputSchema: {
type: 'object',
properties: {
approval_id: { type: 'number', description: 'Approval workflow ID' },
approver_session_id: { type: 'string', description: 'Approver session ID' }
},
required: ['approval_id', 'approver_session_id']
}
},
// Collaboration Analytics
{
name: 'get_collaboration_dashboard',
description: 'Get comprehensive collaboration dashboard data',
inputSchema: {
type: 'object',
properties: {
project_id: { type: 'string', description: 'Filter by project ID (optional)' },
time_range: {
type: 'string',
enum: ['1hour', '6hours', '1day', '1week'],
description: 'Time range for analytics'
}
}
}
},
// SDD (Spec-Driven Development) Tools
...this.sddTools.getToolDefinitions()
]
};
});
// Tool execution handler
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const startTime = Date.now();
let result;
switch (name) {
case 'create_prd':
result = await this.prdManager.createPRD(args);
break;
case 'list_prds':
result = await this.prdManager.listPRDs(args.status, args.sort_by, args.project_id);
break;
case 'get_prd':
result = await this.prdManager.getPRD(args.prd_id);
break;
case 'update_prd':
result = await this.prdManager.updatePRD(args.prd_id, args.updates);
break;
// Design management cases
case 'create_design':
result = await this.designManager.createDesign(args);
break;
case 'list_designs':
result = await this.designManager.listDesigns(args.status, args.design_type, args.sort_by);
break;
case 'get_design':
result = await this.designManager.getDesign(args.design_id);
break;
case 'update_design':
result = await this.designManager.updateDesign(args.design_id, args.updates);
break;
case 'delete_design':
result = await this.designManager.deleteDesign(args.design_id);
break;
// Task management cases
case 'create_task':
result = await this.taskManager.createTask(args);
break;
case 'list_tasks':
result = await this.taskManager.listTasks(args.status, args.assignee, args.sort_by);
break;
case 'get_task':
result = await this.taskManager.getTask(args.task_id);
break;
case 'update_task':
result = await this.taskManager.updateTask(args.task_id, args.updates);
break;
case 'delete_task':
result = await this.taskManager.deleteTask(args.task_id);
break;
// Test management cases
case 'create_test_case':
result = await this.testManager.createTestCase(args);
break;
case 'list_test_cases':
result = await this.testManager.listTestCases(args);
break;
case 'get_test_case':
result = await this.testManager.getTestCase(args.test_case_id);
break;
case 'update_test_case':
result = await this.testManager.updateTestCase(args.test_case_id, args.updates);
break;
case 'delete_test_case':
result = await this.testManager.deleteTestCase(args.test_case_id);
break;
case 'execute_test_case':
result = await this.testManager.executeTestCase(args.test_case_id, args);
break;
case 'get_test_executions':
result = await this.testManager.getTestExecutions(args.test_case_id, args);
break;
case 'get_test_summary':
result = await this.testManager.getTestSummary(args);
break;
case 'get_test_coverage':
result = await this.testManager.getTestCoverage(args);
break;
case 'link_test_to_task':
result = await this.testManager.linkTestToTask(args.test_case_id, args.task_id);
break;
case 'get_test_connections':
result = await this.getTestConnections(args.test_case_id);
break;
case 'add_test_connection':
result = await this.addTestConnection(args.test_case_id, args.entity_type, args.entity_id);
break;
case 'remove_test_connection':
result = await this.removeTestConnection(args.test_case_id, args.entity_type, args.entity_id);
break;
case 'get_task_connections':
result = await this.getTaskConnections(args.task_id);
break;
case 'add_task_connection':
result = await this.addTaskConnection(args.task_id, args.entity_type, args.entity_id, args.connection_type);
break;
case 'remove_task_connection':
result = await this.removeTaskConnection(args.task_id, args.entity_type, args.entity_id);
break;
// Project management cases
case 'create_project':
result = await this.projectManager.createProject(args);
break;
case 'list_projects':
result = await this.projectManager.listProjects(args.status, args.sort_by);
break;
case 'get_project':
result = await this.projectManager.getProject(args.project_id);
break;
case 'update_project':
result = await this.projectManager.updateProject(args.project_id, args.updates);
break;
case 'delete_project':
result = await this.projectManager.deleteProject(args.project_id);
break;
case 'get_project_analytics':
// get_project already returns analytics, so we can use the same method
result = await this.projectManager.getProject(args.project_id);
if (result.success) {
result = result.analytics || result;
}
break;
// DevOps Environment management cases
case 'create_environment':
result = await this.devOpsManager.createEnvironment(args);
break;
case 'list_environments':
result = await this.devOpsManager.listEnvironments(args.environment_type, args.status, args.project_id);
break;
case 'get_environment_status':
result = await this.devOpsManager.getEnvironmentStatus(args.environment_id);
break;
case 'update_environment':
result = await this.devOpsManager.updateEnvironment(args.environment_id, args.updates);
break;
// DevOps Deployment management cases
case 'create_deployment':
result = await this.devOpsManager.createDeployment(args);
break;
case 'list_deployments':
result = await this.devOpsManager.listDeployments(args.environment_id, args.status, args.deployment_type, args.project_id, args.sort_by);
break;
case 'get_deployment':
result = await this.devOpsManager.getDeployment(args.deployment_id);
break;
case 'update_deployment':
result = await this.devOpsManager.updateDeployment(args.deployment_id, args.updates);
break;
// DevOps Incident management cases
case 'create_incident':
result = await this.devOpsManager.createIncident(args);
break;
case 'list_incidents':
result = await this.devOpsManager.listIncidents(args.severity, args.status, args.incident_type, args.environment_id, args.sort_by);
break;
// DevOps System health cases
case 'get_system_health':
result = await this.devOpsManager.getSystemHealth(args.environment_id, args.include_details);
break;
// Document management cases
case 'create_document':
result = await this.documentManager.createDocument(args);
break;
case 'search_documents':
result = await this.documentManager.searchDocuments(args);
break;
case 'get_document':
result = await this.documentManager.getDocument(args.id);
break;
case 'update_document':
result = await this.documentManager.updateDocument(args.id, args);
break;
case 'delete_document':
result = await this.documentManager.deleteDocument(args.id);
break;
case 'list_documents':
result = await this.documentManager.listDocuments(args);
break;
case 'link_document':
result = await this.documentManager.linkDocument(args.document_id, args.entity_type, args.entity_id, args.link_type);
break;
case 'get_document_categories':
console.log('π MCP Handler: get_document_categories case executed');
result = await this.documentManager.getDocumentCategories();
console.log('DEBUG - get_document_categories result:', JSON.stringify({
doc_types_count: result.doc_types.length,
categories_count: result.categories.length,
total: result.total_documents,
first_doc_type: result.doc_types[0],
first_category: result.categories[0]
}, null, 2));
break;
// PRD Document management cases
case 'create_prd_document':
result = await this.createPRDDocument(args);
break;
case 'get_prd_documents':
result = await this.getPRDDocuments(args.prd_id, args.doc_type);
break;
case 'search_prd_documents':
result = await this.searchPRDDocuments(args.prd_id, args.query, args.limit);
break;
case 'update_prd_document':
result = await this.updatePRDDocument(args.document_id, args);
break;
case 'link_document_to_prd':
result = await this.linkDocumentToPRD(args.document_id, args.prd_id, args.link_type);
break;
// Document Relations Cases
case 'create_document_relation':
result = await this.documentManager.createDocumentRelation(args.parent_doc_id, args.child_doc_id, args.relation_type, args.notes);
break;
case 'get_document_relations':
result = await this.documentManager.getDocumentRelations(args.document_id);
break;
case 'remove_document_relation':
result = await this.documentManager.removeDocumentRelation(args.parent_doc_id, args.child_doc_id, args.relation_type);
break;
case 'remove_document_link':
result = await this.documentManager.removeDocumentLink(args.document_id, args.entity_type, args.entity_id);
break;
case 'get_document_links':
result = await this.documentManager.getDocumentLinks(args.document_id);
break;
// SDD (Spec-Driven Development) Tools
case 'create_executable_spec':
case 'list_executable_specs':
case 'get_executable_spec':
case 'update_executable_spec':
case 'delete_executable_spec':
case 'search_executable_specs':
case 'get_spec_hierarchy':
case 'link_specs':
case 'unlink_specs':
case 'validate_spec':
case 'create_knowledge_entry':
case 'list_knowledge_entries':
case 'search_knowledge_base':
case 'get_knowledge_entry':
case 'update_knowledge_entry':
case 'delete_knowledge_entry':
case 'get_applicable_knowledge':
case 'track_knowledge_usage':
case 'validate_knowledge':
case 'create_code_template':
case 'list_code_templates':
case 'search_code_templates':
case 'get_code_template':
case 'update_code_template':
case 'delete_code_template':
case 'generate_code':
case 'rate_template':
case 'get_template_statistics':
result = await this.sddTools.handleToolCall(name, args);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
// λ©νΈλ¦ μμ§
const duration = Date.now() - startTime;
this.metricsCollector.recordToolUsage(name, duration, 'success');
// λ¬Έμ κ΄λ ¨ μμ
μ κ²½μ° μ¬μ©μ μΉνμ μΈ μλ΅ ν¬λ§·
if (name.includes('document')) {
return {
content: [
{
type: 'text',
text: this.formatDocumentResponse(name, result)
}
]
};
}
return {
content: [
{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}
]
};
} catch (error) {
// μλ¬ λ‘κΉ
this.errorLogger.logError(name, error.message, args);
this.metricsCollector.recordToolUsage(name, 0, 'error');
return {
content: [
{
type: 'text',
text: `Error executing ${name}: ${error.message}`
}
],
isError: true
};
}
});
}
formatDocumentResponse(toolName, result) {
switch (toolName) {
case 'create_document':
return `β
λ¬Έμκ° μμ±λμμ΅λλ€!
**λ¬Έμ ID**: ${result.id}
**μ λͺ©**: ${result.title}
**μ ν**: ${result.doc_type}
**μΉ΄ν
κ³ λ¦¬**: ${result.category || 'μμ'}
**νκ·Έ**: ${result.tags.length > 0 ? result.tags.join(', ') : 'μμ'}
${result.linked ? `**μ°κ²°λ¨**: ${result.linked.type} #${result.linked.id} (${result.linked.link_type})` : ''}
π **κ²μ μΈλ±μ±**: μλμΌλ‘ μ λ¬Έ κ²μ μΈλ±μ€μ μΆκ°λ¨`;
case 'search_documents':
if (result.length === 0) {
return `π κ²μ κ²°κ³Όκ° μμ΅λλ€.
π‘ **κ²μ ν**:
- λ€λ₯Έ ν€μλ μλ
- νν° μ‘°κ±΄ μν
- μ 체 λ¬Έμ λͺ©λ‘: \`list_documents\` μ¬μ©`;
}
return `π κ²μ κ²°κ³Ό (${result.length}κ°)
${result.map(doc => `
**[${doc.id}] ${doc.title}**
π μ ν: ${doc.doc_type} | π μΉ΄ν
κ³ λ¦¬: ${doc.category || 'μμ'}
π
μμ±: ${new Date(doc.created_at).toLocaleDateString('ko-KR')}
π μμ½: ${doc.summary || 'μμ'}
π λ°κ²¬: ${doc.snippet}
---`).join('\n')}
π‘ νΉμ λ¬Έμ 보기: \`get_document\` μ¬μ©`;
case 'get_document':
if (!result) {
return `β λ¬Έμλ₯Ό μ°Ύμ μ μμ΅λλ€.`;
}
return `π **${result.title}**
**λ¬Έμ ID**: ${result.id}
**μ ν**: ${result.doc_type}
**μΉ΄ν
κ³ λ¦¬**: ${result.category || 'μμ'}
**μν**: ${result.status}
**νκ·Έ**: ${result.tags.join(', ') || 'μμ'}
**μμ±μΌ**: ${new Date(result.created_at).toLocaleString('ko-KR')}
**μμ μΌ**: ${new Date(result.updated_at).toLocaleString('ko-KR')}
**μμ±μ**: ${result.created_by}
**λ²μ **: ${result.version}
${result.summary ? `**μμ½**: ${result.summary}\n` : ''}
${result.links.length > 0 ? `**μ°κ²°λ νλͺ©**:\n${result.links.map(link => `- ${link.type} #${link.id} (${link.linkType})`).join('\n')}\n` : ''}
---
${result.content}`;
case 'list_documents':
return `π λ¬Έμ λͺ©λ‘ (${result.length}κ°)
${result.map(doc => `
**[${doc.id}] ${doc.title}**
π ${doc.doc_type} | π ${doc.category || 'μμ'} | π·οΈ ${doc.status}
π
${new Date(doc.updated_at).toLocaleDateString('ko-KR')}
π μ°κ²°: ${doc.linked_entities_count}κ°
π ${doc.summary || 'μμ½ μμ'}
---`).join('\n')}
π‘ **μ¬μ©λ²**:
- λ¬Έμ 보기: \`get_document\` μ¬μ©
- κ²μ: \`search_documents\` μ¬μ©`;
case 'update_document':
return `β
λ¬Έμ ID ${result.id}κ° μ±κ³΅μ μΌλ‘ μ
λ°μ΄νΈλμμ΅λλ€.
**μ
λ°μ΄νΈλ νλ**: ${result.updated_fields.join(', ')}
π **λ²μ **: μλ μ¦κ°
π
**μμ μκ°**: ${new Date().toLocaleString('ko-KR')}`;
case 'delete_document':
return `ποΈ λ¬Έμκ° μμ λμμ΅λλ€.
**λ¬Έμ ID**: ${result.id}
**μ λͺ©**: ${result.title}
β οΈ **μ£Όμ**: μ°κ²°λ λ§ν¬μ κ΄κ³λ ν¨κ» μμ λμμ΅λλ€.`;
case 'link_document':
return `π μ°κ²°μ΄ μμ±λμμ΅λλ€!
**λ¬Έμ**: [${result.document_id}] ${result.document_title}
**μ°κ²° λμ**: ${result.entity_type} #${result.entity_id}
**λ§ν¬ μ ν**: ${result.link_type}
**μν**: ${result.is_new ? 'μλ‘ μμ±λ¨' : 'μ΄λ―Έ μ‘΄μ¬ν¨'}`;
case 'get_document_categories':
return `π λ¬Έμ λΆλ₯ μ 보 (μ΄ ${result.total_documents}κ° λ¬Έμ)
## π λ¬Έμ μ ν (doc_type)
${result.doc_types.map(item => `- **${item.value}** (${item.count}κ°)`).join('\n')}
## π μΉ΄ν
κ³ λ¦¬ (category)
${result.categories.map(item => `- **${item.value}** (${item.count}κ°)`).join('\n')}
## π·οΈ νκ·Έ (tags)
${result.tags.slice(0, 20).map(item => `- **${item.value}** (${item.count}κ°)`).join('\n')}
${result.tags.length > 20 ? `\n... μΈ ${result.tags.length - 20}κ° νκ·Έ` : ''}
**μ¬μ©λ²**: μ λͺ©λ‘μμ κΈ°μ‘΄ λΆλ₯λ₯Ό μ ννκ±°λ μλ‘μ΄ λΆλ₯λ₯Ό μμ±νμΈμ.`;
case 'create_prd_document':
return `π PRD μμΈ λ¬Έμκ° μμ±λμμ΅λλ€!
**λ¬Έμ ID**: ${result.document.id}
**μ λͺ©**: ${result.document.title}
**PRD**: ${result.prd_title}
**μ ν**: ${result.document.doc_type}
**μΉ΄ν
κ³ λ¦¬**: prd-${result.document.category}
β
${result.message}`;
case 'get_prd_documents':
if (result.documents.length === 0) {
return `π PRD "${result.prd_title}" μ°κ²° λ¬Έμκ° μμ΅λλ€.
π‘ **μ μ**:
- \`create_prd_document\`λ‘ μ λ¬Έμ μμ±
- \`link_document_to_prd\`λ‘ κΈ°μ‘΄ λ¬Έμ μ°κ²°`;
}
return `π PRD "${result.prd_title}" μ°κ²° λ¬Έμ λͺ©λ‘ (${result.total}κ°)
${result.documents.map(doc =>
`**[${doc.id}]** ${doc.title}
π ${doc.doc_type} | π ${doc.link_type} | π ${doc.status}
${doc.summary || 'μμ½ μμ'}
π
${new Date(doc.updated_at).toLocaleString('ko-KR')}`
).join('\n\n')}`;
case 'search_prd_documents':
if (result.documents.length === 0) {
return `π PRD "${result.prd_title}"μμ "${result.query}" κ²μ κ²°κ³Όκ° μμ΅λλ€.
π‘ **κ²μ ν**:
- λ€λ₯Έ ν€μλ μλ
- \`get_prd_documents\`λ‘ μ 체 λ¬Έμ νμΈ`;
}
return `π PRD "${result.prd_title}"μμ "${result.query}" κ²μ κ²°κ³Ό (${result.total}건)
${result.documents.map(doc =>
`**[${doc.id}]** ${doc.title}
π ${doc.doc_type} | π ${doc.link_type}
${doc.content_snippet || doc.summary || ''}
π
${new Date(doc.updated_at).toLocaleString('ko-KR')}`
).join('\n\n')}`;
case 'update_prd_document':
return `βοΈ PRD λ¬Έμ μ
λ°μ΄νΈ μλ£!
**λ¬Έμ**: [${result.document.id}] ${result.document.title}
**PRD**: ${result.prd_title}
**μ
λ°μ΄νΈ μκ°**: ${new Date().toLocaleString('ko-KR')}
β
${result.message}`;
case 'link_document_to_prd':
return `π PRD-λ¬Έμ μ°κ²° μλ£!
**λ¬Έμ**: ${result.document_title}
**PRD**: ${result.prd_title}
**λ§ν¬ μ ν**: ${result.link_type}
β
${result.message}`;
// Document Relations Response Formatters
case 'create_document_relation':
if (result.success) {
return `π λ¬Έμ κ΄κ³ μμ± μλ£!
**λΆλͺ¨ λ¬Έμ**: [${result.parent_doc.id}] ${result.parent_doc.title}
**μμ λ¬Έμ**: [${result.child_doc.id}] ${result.child_doc.title}
**κ΄κ³ μ ν**: ${result.relation_type}
${result.notes ? `**μ€λͺ
**: ${result.notes}` : ''}
β
${result.message}`;
} else {
return `β οΈ λ¬Έμ κ΄κ³ μμ± μ€ν¨
**μ€λ₯**: ${result.error}
**λΆλͺ¨ λ¬Έμ**: [${result.parent_doc.id}] ${result.parent_doc.title}
**μμ λ¬Έμ**: [${result.child_doc.id}] ${result.child_doc.title}
**κ΄κ³ μ ν**: ${result.relation_type}`;
}
case 'get_document_relations':
let relationsText = `π λ¬Έμ "${result.document_title}" κ΄κ³ μ 보 (μ΄ ${result.total_relations}κ°)
`;
if (result.parent_relations.length > 0) {
relationsText += `πΌ **λΆλͺ¨ λ¬Έμλ€** (${result.parent_relations.length}κ°):
${result.parent_relations.map(rel =>
`- [${rel.parent_id}] ${rel.parent_title} (${rel.relation_type})${rel.notes ? ` - ${rel.notes}` : ''}`
).join('\n')}
`;
}
if (result.child_relations.length > 0) {
relationsText += `π½ **μμ λ¬Έμλ€** (${result.child_relations.length}κ°):
${result.child_relations.map(rel =>
`- [${rel.child_id}] ${rel.child_title} (${rel.relation_type})${rel.notes ? ` - ${rel.notes}` : ''}`
).join('\n')}
`;
}
if (result.total_relations === 0) {
relationsText += `π κ΄λ ¨ λ¬Έμκ° μμ΅λλ€.
π‘ λ€μ λκ΅¬λ‘ κ΄κ³λ₯Ό λ§λ€ μ μμ΅λλ€:
- \`create_document_relation\`μΌλ‘ λ¬Έμ κ° κ΄κ³ μμ±`;
}
return relationsText;
case 'remove_document_relation':
if (result.success) {
return `ποΈ λ¬Έμ κ΄κ³ μμ μλ£!
**λΆλͺ¨**: ${result.parent_title}
**μμ**: ${result.child_title}
**κ΄κ³ μ ν**: ${result.relation_type}
β
${result.message}`;
} else {
return `β οΈ λ¬Έμ κ΄κ³ μμ μ€ν¨
**μ€λ₯**: ${result.error}
**λΆλͺ¨ λ¬Έμ ID**: ${result.parent_doc_id}
**μμ λ¬Έμ ID**: ${result.child_doc_id}
**κ΄κ³ μ ν**: ${result.relation_type}`;
}
case 'remove_document_link':
if (result.success) {
return `π λ¬Έμ λ§ν¬ μμ μλ£!
**λ¬Έμ**: ${result.document_title}
**μν°ν°**: ${result.entity_type}:${result.entity_id}
β
${result.message}`;
} else {
return `β οΈ λ¬Έμ λ§ν¬ μμ μ€ν¨
**μ€λ₯**: ${result.error}
**λ¬Έμ ID**: ${result.document_id}
**μν°ν°**: ${result.entity_type}:${result.entity_id}`;
}
case 'get_document_links':
return `π λ¬Έμ "${result.document_title}" μν°ν° λ§ν¬ (μ΄ ${result.total_links}κ°)
${result.links.length > 0 ?
result.links.map(link =>
`π **${link.linked_entity_type.toUpperCase()}**: ${link.linked_entity_id} (${link.link_type}) - ${new Date(link.created_at).toLocaleDateString('ko-KR')}`
).join('\n')
:
`π μ°κ²°λ μν°ν°κ° μμ΅λλ€.
π‘ λ€μ λκ΅¬λ‘ μ°κ²°ν μ μμ΅λλ€:
- \`link_document\`λ‘ PRD, Task, Planκ³Ό μ°κ²°`
}`;
case 'get_task_connections':
const { connections } = result;
let connectionsText = `π μμ
"${connections.task_title}" μ°κ²° μ 보 (μ΄ ${result.total_connections}κ°)
`;
if (connections.connected_prds.length > 0) {
connectionsText += `π **μ°κ²°λ μꡬμ¬ν** (${connections.connected_prds.length}κ°):
${connections.connected_prds.map(prd =>
` β’ [${prd.id}] ${prd.title}
π ${prd.status} | π― ${prd.priority} | π ${prd.connection_type}
${prd.description ? `π ${prd.description.substring(0, 100)}${prd.description.length > 100 ? '...' : ''}` : ''}`
).join('\n\n')}
`;
}
if (connections.connected_designs.length > 0) {
connectionsText += `ποΈ **μ°κ²°λ μ€κ³** (${connections.connected_designs.length}κ°):
${connections.connected_designs.map(design =>
` β’ [${design.id}] ${design.title}
π§ ${design.design_type} | π ${design.status} | π― ${design.priority} | π ${design.connection_type}
${design.description ? `π ${design.description.substring(0, 100)}${design.description.length > 100 ? '...' : ''}` : ''}`
).join('\n\n')}
`;
}
if (connections.connected_documents.length > 0) {
connectionsText += `π **μ°κ²°λ λ¬Έμ** (${connections.connected_documents.length}κ°):
${connections.connected_documents.map(doc =>
` β’ [${doc.id}] ${doc.title}
π ${doc.doc_type} | π ${doc.status} | π ${doc.connection_type}
${doc.summary ? `π ${doc.summary.substring(0, 100)}${doc.summary.length > 100 ? '...' : ''}` : ''}`
).join('\n\n')}
`;
}
if (connections.connected_tests.length > 0) {
connectionsText += `π§ͺ **μ°κ²°λ ν
μ€νΈ** (${connections.connected_tests.length}κ°):
${connections.connected_tests.map(test =>
` β’ [${test.id}] ${test.title}
π¬ ${test.type} | π ${test.status} | π― ${test.priority} | π ${test.connection_type}
${test.description ? `π ${test.description.substring(0, 100)}${test.description.length > 100 ? '...' : ''}` : ''}`
).join('\n\n')}
`;
}
if (result.total_connections === 0) {
connectionsText += `π‘ **μ°κ²°λ νλͺ©μ΄ μμ΅λλ€**
- \`add_task_connection\`μΌλ‘ PRD, μ€κ³, λ¬Έμ, ν
μ€νΈλ₯Ό μ°κ²°νμΈμ`;
}
return connectionsText;
case 'add_task_connection':
return `π μμ
μ°κ²° μΆκ° μλ£!
**μμ
**: ${result.task_title}
**μ°κ²° νλͺ©**: ${result.entity_type} "${result.entity_title}"
**μ°κ²° μ ν**: ${result.connection_type}
β
${result.message}`;
case 'remove_task_connection':
return `π μμ
μ°κ²° ν΄μ μλ£!
**μμ
**: ${result.task_title}
**ν΄μ νλͺ©**: ${result.entity_type} "${result.entity_title}"
β
${result.message}`;
default:
return JSON.stringify(result, null, 2);
}
}
setupErrorHandling() {
process.on('uncaughtException', (error) => {
this.errorLogger.logError('uncaughtException', error.message, {});
console.error('Uncaught Exception:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
this.errorLogger.logError('unhandledRejection', reason.toString(), { promise });
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
}
// PRD Document Management Methods
async createPRDDocument(args) {
const { prd_id, title, content, doc_type = 'specification', summary, tags = [] } = args;
// λ¨Όμ PRD μ‘΄μ¬ νμΈ
const prd = await this.prdManager.getPRD(prd_id);
if (!prd.success) {
throw new Error(`PRD not found: ${prd_id}`);
}
// λ¬Έμ μμ±
const documentData = {
title,
content,
doc_type,
category: `prd-${prd_id}`,
summary,
tags,
linked_entity_type: 'prd',
linked_entity_id: prd_id,
link_type: 'specification'
};
const result = await this.documentManager.createDocument(documentData);
return {
success: true,
document: result,
prd_title: prd.prd.title,
message: `PRD "${prd.prd.title}"μ μμΈ λ¬Έμ μμ± μλ£`
};
}
async getPRDDocuments(prd_id, doc_type = null) {
// λ
립μ μΈ λ°μ΄ν°λ² μ΄μ€ μ°κ²° μ¬μ© (shared connection λ¬Έμ λ°©μ§)
const db = await open({
filename: './data/workflow.db',
driver: sqlite3.default.Database
});
let query = `
SELECT d.*, dl.link_type, dl.created_at as linked_at
FROM documents d
INNER JOIN document_links dl ON d.id = dl.document_id
WHERE dl.linked_entity_type = 'prd' AND dl.linked_entity_id = ?
`;
const params = [prd_id];
if (doc_type) {
query += ` AND dl.link_type = ?`;
params.push(doc_type);
}
query += ` ORDER BY d.updated_at DESC`;
const documents = await db.all(query, params);
await db.close();
// PRD μ 보λ ν¨κ» μ‘°ν
const prd = await this.prdManager.getPRD(prd_id);
return {
success: true,
prd_id,
prd_title: prd.success ? prd.prd.title : 'Unknown PRD',
documents: documents.map(doc => ({
id: doc.id,
title: doc.title,
doc_type: doc.doc_type,
link_type: doc.link_type,
status: doc.status,
summary: doc.summary,
created_at: doc.created_at,
updated_at: doc.updated_at,
linked_at: doc.linked_at
})),
total: documents.length,
message: `PRD "${prd.success ? prd.prd.title : prd_id}" μ°κ²° λ¬Έμ ${documents.length}κ° μ‘°ν`
};
}
async searchPRDDocuments(prd_id, query, limit = 10) {
// PRD μ 보 λ¨Όμ μ‘°ν
const prd = await this.prdManager.getPRD(prd_id);
// μλ‘μ΄ λ°μ΄ν°λ² μ΄μ€ μ°κ²° μμ± (DocumentManager μΈμ€ν΄μ€λ₯Ό μ¬μ¬μ©νμ§ μμ)
const db = await open({
filename: './data/workflow.db',
driver: sqlite3.default.Database
});
const searchQuery = `
SELECT d.*, dl.link_type,
snippet(documents_fts, 1, '<mark>', '</mark>', '...', 20) as content_snippet
FROM documents d
INNER JOIN document_links dl ON d.id = dl.document_id
INNER JOIN documents_fts ON documents_fts.rowid = d.id
WHERE dl.linked_entity_type = 'prd'
AND dl.linked_entity_id = ?
AND documents_fts MATCH ?
ORDER BY rank
LIMIT ?
`;
const documents = await db.all(searchQuery, [prd_id, query, limit]);
await db.close();
return {
success: true,
prd_id,
prd_title: prd.success ? prd.prd.title : 'Unknown PRD',
query,
documents: documents.map(doc => ({
id: doc.id,
title: doc.title,
doc_type: doc.doc_type,
link_type: doc.link_type,
status: doc.status,
summary: doc.summary,
content_snippet: doc.content_snippet,
updated_at: doc.updated_at
})),
total: documents.length,
message: `PRD "${prd.success ? prd.prd.title : prd_id}"μμ "${query}" κ²μ κ²°κ³Ό ${documents.length}건`
};
}
async updatePRDDocument(document_id, updates) {
// λ¬Έμκ° PRDμ μ°κ²°λμ΄ μλμ§ νμΈ
const db = await open({
filename: './data/workflow.db',
driver: sqlite3.default.Database
});
const linkCheck = await db.get(`
SELECT dl.linked_entity_id as prd_id, d.title
FROM document_links dl
INNER JOIN documents d ON dl.document_id = d.id
WHERE dl.document_id = ? AND dl.linked_entity_type = 'prd'
`, [document_id]);
await db.close();
if (!linkCheck) {
throw new Error('Document not found or not linked to any PRD');
}
// μ§μ λ¬Έμ μ
λ°μ΄νΈ (DocumentManager μ°ν)
const updateDb = await open({
filename: './data/workflow.db',
driver: sqlite3.default.Database
});
let updateFields = [];
let updateValues = [];
if (updates.title) {
updateFields.push('title = ?');
updateValues.push(updates.title);
}
if (updates.content) {
updateFields.push('content = ?');
updateValues.push(updates.content);
}
if (updates.summary) {
updateFields.push('summary = ?');
updateValues.push(updates.summary);
}
if (updates.status) {
updateFields.push('status = ?');
updateValues.push(updates.status);
}
updateFields.push('updated_at = CURRENT_TIMESTAMP');
updateValues.push(document_id);
await updateDb.run(`
UPDATE documents
SET ${updateFields.join(', ')}
WHERE id = ?
`, updateValues);
// μ
λ°μ΄νΈλ λ¬Έμ μ‘°ν
const updatedDoc = await updateDb.get('SELECT * FROM documents WHERE id = ?', [document_id]);
await updateDb.close();
// PRD μ 보 μ‘°ν
const prd = await this.prdManager.getPRD(linkCheck.prd_id);
return {
success: true,
document: updatedDoc,
prd_id: linkCheck.prd_id,
prd_title: prd.success ? prd.prd.title : 'Unknown PRD',
message: `PRD "${prd.success ? prd.prd.title : linkCheck.prd_id}" μ°κ²° λ¬Έμ "${linkCheck.title}" μ
λ°μ΄νΈ μλ£`
};
}
async linkDocumentToPRD(document_id, prd_id, link_type = 'specification') {
// PRD μ‘΄μ¬ νμΈ
const prd = await this.prdManager.getPRD(prd_id);
if (!prd.success) {
throw new Error(`PRD not found: ${prd_id}`);
}
// λ¬Έμ μ‘΄μ¬ νμΈ λ° μ°κ²° μμ± (μ§μ ꡬν)
const db = await open({
filename: './data/workflow.db',
driver: sqlite3.default.Database
});
// λ¬Έμ νμΈ
const document = await db.get('SELECT id, title FROM documents WHERE id = ?', [document_id]);
if (!document) {
await db.close();
throw new Error(`Document not found: ${document_id}`);
}
// μ°κ²° μμ± (μ€λ³΅ νμΈ)
const existingLink = await db.get(`
SELECT id FROM document_links
WHERE document_id = ? AND linked_entity_type = 'prd' AND linked_entity_id = ?
`, [document_id, prd_id]);
if (!existingLink) {
await db.run(`
INSERT INTO document_links (document_id, linked_entity_type, linked_entity_id, link_type)
VALUES (?, 'prd', ?, ?)
`, [document_id, prd_id, link_type]);
}
await db.close();
return {
success: true,
document_title: document.title,
prd_title: prd.prd.title,
link_type,
is_new: !existingLink,
message: `λ¬Έμ "${document.title}"λ₯Ό PRD "${prd.prd.title}"μ μ°κ²° μλ£ (${link_type})`
};
}
// Task Connection Management Methods
async getTaskConnections(task_id) {
const sqlite3 = await import('sqlite3');
const { open } = await import('sqlite');
const db = await open({
filename: './data/workflow.db',
driver: sqlite3.default.Database
});
try {
// μμ
νμΈ
const task = await db.get('SELECT id, title FROM tasks WHERE id = ?', [task_id]);
if (!task) {
throw new Error(`Task not found: ${task_id}`);
}
const connections = {
task_title: task.title,
connected_prds: [],
connected_designs: [],
connected_documents: [],
connected_tests: []
};
// μμ
μ μ 체 μ 보 μ‘°ν (prd_id, design_id ν¬ν¨)
const fullTask = await db.get('SELECT * FROM tasks WHERE id = ?', [task_id]);
// μ°κ²°λ PRD μ‘°ν (task ν
μ΄λΈμ prd_id νλ)
if (fullTask.prd_id) {
const prd = await db.get('SELECT id, title, description, status, priority FROM prds WHERE id = ?', [fullTask.prd_id]);
if (prd) {
connections.connected_prds.push({
id: prd.id,
title: prd.title,
description: prd.description,
status: prd.status,
priority: prd.priority,
connection_type: 'primary'
});
}
}
// μ°κ²°λ μ€κ³ μ‘°ν (task ν
μ΄λΈμ design_id νλ)
if (fullTask.design_id) {
const design = await db.get('SELECT id, title, description, status, priority, design_type FROM designs WHERE id = ?', [fullTask.design_id]);
if (design) {
connections.connected_designs.push({
id: design.id,
title: design.title,
description: design.description,
status: design.status,
priority: design.priority,
design_type: design.design_type,
connection_type: 'primary'
});
}
}
// μ°κ²°λ λ¬Έμ μ‘°ν
const documents = await db.all(`
SELECT d.id, d.title, d.summary, d.doc_type, d.status, dl.link_type
FROM documents d
JOIN document_links dl ON d.id = dl.document_id
WHERE dl.linked_entity_type = 'task' AND dl.linked_entity_id = ?
`, [task_id]);
connections.connected_documents = documents.map(doc => ({
id: doc.id,
title: doc.title,
summary: doc.summary,
doc_type: doc.doc_type,
status: doc.status,
connection_type: doc.link_type || 'related'
}));
// μ°κ²°λ ν
μ€νΈ μ‘°ν
const tests = await db.all(`
SELECT id, title, description, status, type, priority
FROM test_cases
WHERE task_id = ?
`, [task_id]);
connections.connected_tests = tests.map(test => ({
id: test.id,
title: test.title,
description: test.description,
status: test.status,
type: test.type,
priority: test.priority,
connection_type: 'primary'
}));
await db.close();
return {
success: true,
connections,
total_connections: connections.connected_prds.length +
connections.connected_designs.length +
connections.connected_documents.length +
connections.connected_tests.length
};
} catch (error) {
await db.close();
throw error;
}
}
async addTaskConnection(task_id, entity_type, entity_id, connection_type = 'related') {
const sqlite3 = await import('sqlite3');
const { open } = await import('sqlite');
const db = await open({
filename: './data/workflow.db',
driver: sqlite3.default.Database
});
try {
// μμ
νμΈ
const task = await db.get('SELECT id, title FROM tasks WHERE id = ?', [task_id]);
if (!task) {
throw new Error(`Task not found: ${task_id}`);
}
let entity = null;
let tableName = '';
let updateField = '';
// μν°ν° νμ
λ³ μ²λ¦¬
switch (entity_type) {
case 'prd':
tableName = 'prds';
updateField = 'prd_id';
entity = await db.get('SELECT id, title FROM prds WHERE id = ?', [entity_id]);
break;
case 'design':
tableName = 'designs';
updateField = 'design_id';
entity = await db.get('SELECT id, title FROM designs WHERE id = ?', [entity_id]);
break;
case 'document':
tableName = 'documents';
entity = await db.get('SELECT id, title FROM documents WHERE id = ?', [entity_id]);
break;
case 'test':
tableName = 'test_cases';
entity = await db.get('SELECT id, title FROM test_cases WHERE id = ?', [entity_id]);
break;
default:
throw new Error(`Unsupported entity type: ${entity_type}`);
}
if (!entity) {
throw new Error(`${entity_type} not found: ${entity_id}`);
}
// μ°κ²° μ²λ¦¬
if (entity_type === 'prd' || entity_type === 'design') {
// PRDμ Designμ task ν
μ΄λΈμ νλ μ
λ°μ΄νΈ
await db.run(`UPDATE tasks SET ${updateField} = ? WHERE id = ?`, [entity_id, task_id]);
} else if (entity_type === 'document') {
// λ¬Έμλ document_links ν
μ΄λΈ μ¬μ©
const existingLink = await db.get(`
SELECT id FROM document_links
WHERE document_id = ? AND linked_entity_type = 'task' AND linked_entity_id = ?
`, [entity_id, task_id]);
if (!existingLink) {
await db.run(`
INSERT INTO document_links (document_id, linked_entity_type, linked_entity_id, link_type)
VALUES (?, 'task', ?, ?)
`, [entity_id, task_id, connection_type]);
}
} else if (entity_type === 'test') {
// ν
μ€νΈλ test_cases ν
μ΄λΈμ task_id νλ μ
λ°μ΄νΈ
await db.run('UPDATE test_cases SET task_id = ? WHERE id = ?', [task_id, entity_id]);
}
await db.close();
return {
success: true,
task_title: task.title,
entity_title: entity.title,
entity_type,
connection_type,
message: `${entity_type} "${entity.title}"λ₯Ό μμ
"${task.title}"μ μ°κ²° μλ£`
};
} catch (error) {
await db.close();
throw error;
}
}
async removeTaskConnection(task_id, entity_type, entity_id) {
const sqlite3 = await import('sqlite3');
const { open } = await import('sqlite');
const db = await open({
filename: './data/workflow.db',
driver: sqlite3.default.Database
});
try {
// μμ
νμΈ
const task = await db.get('SELECT id, title FROM tasks WHERE id = ?', [task_id]);
if (!task) {
throw new Error(`Task not found: ${task_id}`);
}
let entity = null;
let updateField = '';
// μν°ν° νμ
λ³ μ²λ¦¬
switch (entity_type) {
case 'prd':
updateField = 'prd_id';
entity = await db.get('SELECT id, title FROM prds WHERE id = ?', [entity_id]);
break;
case 'design':
updateField = 'design_id';
entity = await db.get('SELECT id, title FROM designs WHERE id = ?', [entity_id]);
break;
case 'document':
entity = await db.get('SELECT id, title FROM documents WHERE id = ?', [entity_id]);
break;
case 'test':
entity = await db.get('SELECT id, title FROM test_cases WHERE id = ?', [entity_id]);
break;
default:
throw new Error(`Unsupported entity type: ${entity_type}`);
}
if (!entity) {
throw new Error(`${entity_type} not found: ${entity_id}`);
}
// μ°κ²° ν΄μ μ²λ¦¬
if (entity_type === 'prd' || entity_type === 'design') {
// PRDμ Designμ task ν
μ΄λΈμ νλλ₯Ό NULLλ‘ μ€μ
await db.run(`UPDATE tasks SET ${updateField} = NULL WHERE id = ? AND ${updateField} = ?`, [task_id, entity_id]);
} else if (entity_type === 'document') {
// λ¬Έμλ document_links ν
μ΄λΈμμ μμ
await db.run(`
DELETE FROM document_links
WHERE document_id = ? AND linked_entity_type = 'task' AND linked_entity_id = ?
`, [entity_id, task_id]);
} else if (entity_type === 'test') {
// ν
μ€νΈλ test_cases ν
μ΄λΈμ task_id νλλ₯Ό NULLλ‘ μ€μ
await db.run('UPDATE test_cases SET task_id = NULL WHERE id = ? AND task_id = ?', [entity_id, task_id]);
}
await db.close();
return {
success: true,
task_title: task.title,
entity_title: entity.title,
entity_type,
message: `${entity_type} "${entity.title}"μ μμ
"${task.title}" μ°κ²° ν΄μ μλ£`
};
} catch (error) {
await db.close();
throw error;
}
}
// ν
μ€νΈ μ°κ²° κ΄λ¦¬ λ©μλλ€
async getTestConnections(test_case_id) {
try {
const db = await this.database.getDatabase();
// ν
μ€νΈ μΌμ΄μ€ μ 보 κ°μ Έμ€κΈ°
const testCase = await db.get('SELECT * FROM test_cases WHERE id = ?', [test_case_id]);
if (!testCase) {
return { success: false, error: 'Test case not found' };
}
const connections = {
task: null,
design: null,
prd: null
};
// μ°κ²°λ μμ
μ 보
if (testCase.task_id) {
const task = await db.get('SELECT id, title, status, priority FROM tasks WHERE id = ?', [testCase.task_id]);
if (task) {
connections.task = task;
}
}
// μ°κ²°λ μ€κ³ μ 보
if (testCase.design_id) {
const design = await db.get('SELECT id, title, type, status FROM designs WHERE id = ?', [testCase.design_id]);
if (design) {
connections.design = design;
}
}
// μ°κ²°λ μꡬμ¬ν μ 보
if (testCase.prd_id) {
const prd = await db.get('SELECT id, title, status, priority FROM prds WHERE id = ?', [testCase.prd_id]);
if (prd) {
connections.prd = prd;
}
}
return {
success: true,
test_case_id: test_case_id,
test_case_title: testCase.title,
connections: connections
};
} catch (error) {
console.error('Error getting test connections:', error);
return { success: false, error: error.message };
}
}
async addTestConnection(test_case_id, entity_type, entity_id) {
try {
const db = await this.database.getDatabase();
// ν
μ€νΈ μΌμ΄μ€ μ‘΄μ¬ νμΈ
const testCase = await db.get('SELECT id, title FROM test_cases WHERE id = ?', [test_case_id]);
if (!testCase) {
return { success: false, error: 'Test case not found' };
}
// μ°κ²°ν μν°ν° νμΈ λ° νλ κ²°μ
let updateField, entity;
switch (entity_type) {
case 'task':
updateField = 'task_id';
entity = await db.get('SELECT id, title FROM tasks WHERE id = ?', [entity_id]);
break;
case 'design':
updateField = 'design_id';
entity = await db.get('SELECT id, title FROM designs WHERE id = ?', [entity_id]);
break;
case 'prd':
updateField = 'prd_id';
entity = await db.get('SELECT id, title FROM prds WHERE id = ?', [entity_id]);
break;
default:
return { success: false, error: 'Invalid entity type. Must be task, design, or prd' };
}
if (!entity) {
return { success: false, error: `${entity_type.charAt(0).toUpperCase() + entity_type.slice(1)} not found` };
}
// ν
μ€νΈ μΌμ΄μ€ μ
λ°μ΄νΈ
await db.run(`UPDATE test_cases SET ${updateField} = ? WHERE id = ?`, [entity_id, test_case_id]);
return {
success: true,
message: `Test case '${testCase.title}' connected to ${entity_type} '${entity.title}'`
};
} catch (error) {
console.error('Error adding test connection:', error);
return { success: false, error: error.message };
}
}
async removeTestConnection(test_case_id, entity_type, entity_id) {
try {
const db = await this.database.getDatabase();
// ν
μ€νΈ μΌμ΄μ€ μ‘΄μ¬ νμΈ
const testCase = await db.get('SELECT id, title FROM test_cases WHERE id = ?', [test_case_id]);
if (!testCase) {
return { success: false, error: 'Test case not found' };
}
// μ°κ²° ν΄μ ν νλ κ²°μ
let updateField;
switch (entity_type) {
case 'task':
updateField = 'task_id';
break;
case 'design':
updateField = 'design_id';
break;
case 'prd':
updateField = 'prd_id';
break;
default:
return { success: false, error: 'Invalid entity type. Must be task, design, or prd' };
}
// νμ¬ μ°κ²°λ μν°ν° νμΈ
const currentConnectionId = testCase[updateField];
if (currentConnectionId !== entity_id) {
return { success: false, error: `Test case is not connected to this ${entity_type}` };
}
// μ°κ²° ν΄μ
await db.run(`UPDATE test_cases SET ${updateField} = NULL WHERE id = ? AND ${updateField} = ?`, [test_case_id, entity_id]);
return {
success: true,
message: `Test case '${testCase.title}' disconnected from ${entity_type}`
};
} catch (error) {
console.error('Error removing test connection:', error);
return { success: false, error: error.message };
}
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
// console.error('WorkflowMCP Server started successfully');
}
}
// Start server
const server = new WorkflowMCPServer();
server.start().catch((error) => {
console.error('Failed to start server:', error);
process.exit(1);
});
export default WorkflowMCPServer;