UNPKG

@jmkim85/dev-flow-mcp

Version:

MCP-based Dev Flow - AI-powered development workflow management with 13 essential tools for TDD and context management

223 lines 10 kB
#!/usr/bin/env node /** * Dev Flow MCP Server * Replaces the entire FastAPI + SQLite + React system with 6 simple tools */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { initializeComponents, logDebugInfo, logServerInfo } from './globals.js'; import { tools } from './tool-definitions.js'; import { getNextTask, startTask, checkProgress, saveCheckpoint, recordMistake, completeTask } from './core-tool-handlers.js'; import { createContextFrame, addContextFact, getContext } from './context-tool-handlers.js'; import { loadWorkflow, advanceStage } from './workflow-tool-handlers.js'; import { cleanTempFilesTool, initializeProjectFile, createTaskStructure, initProject } from './project-tool-handlers.js'; import { promises as fs } from 'fs'; import { join } from 'path'; import { getServerVersion } from './version-manager.js'; // Smart project root detection async function detectProjectRoot() { console.error('🔍 Starting project root detection...'); // 1. Check environment variable (highest priority) if (process.env.DEV_FLOW_PROJECT_ROOT) { console.error(`✅ Using DEV_FLOW_PROJECT_ROOT: ${process.env.DEV_FLOW_PROJECT_ROOT}`); return process.env.DEV_FLOW_PROJECT_ROOT; } // 2. Check VSCode workspace environment variables (most reliable) if (process.env.VSCODE_WORKSPACE_FOLDER) { console.error(`✅ Using VSCODE_WORKSPACE_FOLDER: ${process.env.VSCODE_WORKSPACE_FOLDER}`); return process.env.VSCODE_WORKSPACE_FOLDER; } // 3. Try current working directory first (often more specific than VSCODE_CWD) const cwd = process.cwd(); console.error(`🔍 Checking current working directory: ${cwd}`); const projectRootFromCwd = await findProjectRoot(cwd); if (projectRootFromCwd) { console.error(`✅ Found project root from cwd: ${projectRootFromCwd}`); // If we have VSCODE_CWD, compare which one is more specific if (process.env.VSCODE_CWD) { const vscodeCwd = process.env.VSCODE_CWD; console.error(`🔍 Comparing with VSCODE_CWD: ${vscodeCwd}`); // If current directory is a subdirectory of VSCODE_CWD, prefer current directory if (cwd.startsWith(vscodeCwd) && cwd !== vscodeCwd) { console.error(`✅ Current directory is more specific than VSCODE_CWD, using: ${projectRootFromCwd}`); return projectRootFromCwd; } // If VSCODE_CWD is a subdirectory of current directory, check if it has project markers if (vscodeCwd.startsWith(cwd)) { const projectRootFromVscode = await findProjectRoot(vscodeCwd); if (projectRootFromVscode) { console.error(`✅ Found project root from VSCODE_CWD: ${projectRootFromVscode}`); return projectRootFromVscode; } } } return projectRootFromCwd; } // 4. Fall back to VSCODE_CWD if current directory doesn't have project markers if (process.env.VSCODE_CWD) { console.error(`🔍 Checking VSCODE_CWD: ${process.env.VSCODE_CWD}`); const projectRootFromVscode = await findProjectRoot(process.env.VSCODE_CWD); if (projectRootFromVscode) { console.error(`✅ Found project root from VSCODE_CWD: ${projectRootFromVscode}`); return projectRootFromVscode; } else { console.error(`⚠️ No project markers found in VSCODE_CWD, using as-is: ${process.env.VSCODE_CWD}`); return process.env.VSCODE_CWD; } } // 5. SSH environment: Check for VSCode Remote SSH specific variables if (process.env.SSH_CLIENT || process.env.SSH_CONNECTION) { console.error('🔗 SSH environment detected, checking VSCode Remote variables...'); // VSCode Remote SSH often sets these if (process.env.VSCODE_IPC_HOOK_CLI) { console.error('📡 VSCode Remote SSH detected'); } // In SSH, VSCode might set the workspace in different variables const remoteWorkspace = process.env.VSCODE_WORKSPACE || process.env.REMOTE_CONTAINERS_IPC; if (remoteWorkspace) { console.error(`✅ Using remote workspace: ${remoteWorkspace}`); return remoteWorkspace; } } // 6. Last resort: use current working directory console.error(`⚠️ No project root found, using current directory: ${cwd}`); return cwd; } // Find project root by looking for common project markers async function findProjectRoot(startPath) { const projectMarkers = [ '.devflow', // Existing Dev Flow project (highest priority) '.git', // Git repository 'package.json', // Node.js 'requirements.txt', // Python 'pyproject.toml', // Modern Python 'Cargo.toml', // Rust 'go.mod', // Go 'pom.xml', // Java Maven 'build.gradle', // Java Gradle 'composer.json', // PHP 'Gemfile', // Ruby 'mix.exs', // Elixir 'deno.json', // Deno 'tsconfig.json', // TypeScript '.vscode', // VSCode workspace '.idea' // IntelliJ workspace ]; let currentPath = startPath; let searchDepth = 0; const maxDepth = 10; // Prevent infinite loops console.error(`🔍 Searching for project markers in: ${currentPath}`); while (currentPath !== '/' && currentPath !== '' && !currentPath.match(/^[A-Z]:\\?$/) && searchDepth < maxDepth) { console.error(`📁 Checking directory: ${currentPath}`); for (const marker of projectMarkers) { try { const markerPath = join(currentPath, marker); await fs.access(markerPath); console.error(`✅ Found project marker '${marker}' at: ${currentPath}`); return currentPath; // Found a project marker } catch { // Continue searching } } const parentPath = join(currentPath, '..'); if (parentPath === currentPath) break; // Reached root currentPath = parentPath; searchDepth++; } console.error(`❌ No project markers found after searching ${searchDepth} directories`); return null; // No project root found } // Simplified Dev Flow MCP - Essential Tools // Create server with dynamic version let server; async function createServer() { const version = await getServerVersion(); return new Server({ name: "dev-flow", version: version, // Dynamic version from package.json }, { capabilities: { tools: {}, resources: {}, }, }); } // Tool implementations // Removed duplicate functions - using the ones defined above // Removed unused functions for simplification // Set up request handlers (will be called after server is created) function setupRequestHandlers(serverInstance) { serverInstance.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); serverInstance.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case "get_next_task": return await getNextTask(); case "start_task": return await startTask(args?.task_id, args?.stage); case "check_progress": return await checkProgress(args?.run_tests); case "save_checkpoint": return await saveCheckpoint(args?.message, args?.files); case "record_mistake": return await recordMistake(args?.pattern, args?.context, args?.solution); case "complete_task": return await completeTask(args?.summary); // Simplified v2.1 context tools case "create_context_frame": return await createContextFrame(args?.task_id, args?.stage, args?.summary); case "add_context_fact": return await addContextFact(args?.fact, args?.is_global); case "get_context": return await getContext(); case "load_workflow": return await loadWorkflow(args?.workflow_id); case "clean_temp_files": return await cleanTempFilesTool(); case "initialize_project_file": return await initializeProjectFile(args?.template_type); case "init_project": return await initProject({ project_name: args?.project_name, project_type: args?.project_type, workflow_type: args?.workflow_type, include_templates: args?.include_templates }); case "advance_stage": return await advanceStage(args?.summary); case "create_task_structure": return await createTaskStructure(args?.task_id, args?.task_title); default: throw new Error(`Unknown tool: ${name}`); } }); } // Start the server async function main() { // Debug environment variables logDebugInfo(); // Detect project root const projectRoot = await detectProjectRoot(); // Initialize components initializeComponents(projectRoot); // Create server with dynamic version server = await createServer(); // Set up request handlers setupRequestHandlers(server); // Start server const transport = new StdioServerTransport(); await server.connect(transport); // Log server info await logServerInfo(tools.length); } main().catch((error) => { console.error("Server error:", error); process.exit(1); }); //# sourceMappingURL=server.js.map