UNPKG

ai-expert-workflow-mcp

Version:

Enhanced AI Expert Workflow MCP Server with structured conversation flow, topic tracking, and optional Task Master integration

268 lines (227 loc) 9.69 kB
#!/usr/bin/env node import { createMCPServer, waitForMcpSdk } from './mcp'; import * as dotenv from 'dotenv'; import * as fs from 'fs'; import * as path from 'path'; // Load environment variables dotenv.config(); // Get package version let packageVersion = '2.1.3'; // Default fallback version try { const packageJsonPath = path.resolve(__dirname, '../package.json'); if (fs.existsSync(packageJsonPath)) { const packageData = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); packageVersion = packageData.version || packageVersion; } } catch (error) { // Silently fallback to default version } // Enable debug mode if DEBUG environment variable is set const isDebugMode = process.env.DEBUG?.includes('mcp') || false; // Helper functions for logging function log(...args: any[]) { if (isDebugMode) { console.log('[AI-EXPERT-MCP]', ...args); } } function logError(...args: any[]) { console.error('[AI-EXPERT-MCP-ERROR]', ...args); } // Import HTTP server modules import * as http from 'http'; import { consultWithExpert, generateExpertDocument } from './utils/aiUtils'; import { saveExpertDocument } from './utils/fileUtils'; async function startHttpServer() { // Load environment variables from .env file dotenv.config(); // Check if OPENROUTER_API_KEY is set if (!process.env.OPENROUTER_API_KEY) { console.error('Error: OPENROUTER_API_KEY is required but not set in environment variables'); console.log('Please set your OpenRouter API key in a .env file or as an environment variable'); process.exit(1); } const port = process.env.PORT || 3000; const server = http.createServer(async (req, res) => { // Set CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); // Handle preflight requests if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } // Only handle POST requests to specific endpoints if (req.method === 'POST') { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', async () => { try { // Parse the request body const data = JSON.parse(body); // Handle different endpoints if (req.url === '/mcp/tools/consultExpert') { const { expertType, userInput } = data; if (!expertType || !userInput) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing required parameters: expertType, userInput' })); return; } const response = await consultWithExpert(expertType, userInput); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ response })); } else if (req.url === '/mcp/tools/generateDocument') { const { expertType, projectDescription, saveForTaskMaster } = data; if (!expertType || !projectDescription) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Missing required parameters: expertType, projectDescription' })); return; } // Read the template const templatePath = `templates/${expertType}-template.md`; let template = ''; try { template = fs.readFileSync(path.join(__dirname, '..', templatePath), 'utf8'); } catch (error) { console.log(`Template not found, using default template for ${expertType}`); template = `# ${expertType.charAt(0).toUpperCase() + expertType.slice(1)} Document\n\n## Overview\n[Overview goes here]\n\n## Details\n[Details go here]`; } // Generate the document const document = await generateExpertDocument(expertType, template, projectDescription); // Save the document const filename = `${expertType}_document.md`; fs.writeFileSync(path.join(process.cwd(), filename), document, 'utf8'); // Save for Task Master if requested let taskMasterPath = ''; if (saveForTaskMaster) { const isInitial = expertType === 'productManager'; taskMasterPath = await saveExpertDocument(document, expertType); } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ document, filePath: filename, taskMasterPath: taskMasterPath || undefined })); } else { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Endpoint not found' })); } } catch (error: any) { console.error('Error processing request:', error); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: `Internal server error: ${error.message || String(error)}` })); } }); } else { res.writeHead(405, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Method not allowed' })); } }); server.listen(port, () => { console.log(`HTTP server running at http://localhost:${port}`); console.log(`Available endpoints:`); console.log(`- POST /mcp/tools/consultExpert`); console.log(`- POST /mcp/tools/generateDocument`); }); return server; } async function main() { let serverInfo; try { // Display version info console.log(`AI Expert Workflow MCP v${packageVersion}`); // Check if OPENROUTER_API_KEY is set if (!process.env.OPENROUTER_API_KEY) { logError('OPENROUTER_API_KEY is required but not set in the environment'); console.log('\nPlease set your OpenRouter API key using one of these methods:'); console.log('1. Create a .env file in your project root with:'); console.log(' OPENROUTER_API_KEY=your_key_here'); console.log('2. Set it as an environment variable:'); console.log(' export OPENROUTER_API_KEY=your_key_here'); console.log('\nYou can get an OpenRouter API key from: https://openrouter.ai/keys'); process.exit(1); } // Log which model we're using console.log(`Using OpenRouter API with model: ${process.env.OPENROUTER_MODEL || 'default model'}`); // Check if we should start HTTP server const args = process.argv.slice(2); if (args.includes('--http')) { console.log('Starting HTTP server mode...'); await startHttpServer(); return; // Don't continue with MCP server } console.log('Starting AI Expert Workflow MCP Server...'); // Wait for MCP SDK to load before creating the server log('Waiting for MCP SDK to be available...'); const isMcpSdkAvailable = await waitForMcpSdk(); if (!isMcpSdkAvailable) { logError('Failed to load MCP SDK after multiple retries.'); logError('This may be due to:'); logError('1. Missing or incompatible MCP SDK installation'); logError('2. Network issues preventing module loading'); logError('3. Permissions issues in the node_modules directory'); process.exit(1); } // Try to create the server log('Creating MCP server...'); const server = createMCPServer(); // If server creation failed if (!server) { logError('Failed to create MCP server. Please check the MCP SDK installation.'); process.exit(1); } console.log('Server created, initializing transport...'); // Use MCP stdio transport try { log('Importing StdioServerTransport...'); const stdioModule = await import('@modelcontextprotocol/sdk/server/stdio.js'); if (!stdioModule || !stdioModule.StdioServerTransport) { throw new Error('StdioServerTransport not found in MCP SDK'); } log('Creating transport...'); const transport = new stdioModule.StdioServerTransport(); serverInfo = { server, transport }; } catch (error) { logError('Error importing or initializing StdioServerTransport:', error); console.log('\nTroubleshooting steps:'); console.log('1. Check that @modelcontextprotocol/sdk is installed correctly:'); console.log(' npm install @modelcontextprotocol/sdk@latest'); console.log('2. Make sure your Node.js version is compatible (v14 or higher)'); console.log('3. If you installed globally, you may need to reinstall:'); console.log(' npm uninstall -g ai-expert-workflow-mcp'); console.log(' npm install -g ai-expert-workflow-mcp'); process.exit(1); } // Connect the server to the transport if (serverInfo) { console.log('Starting server...'); log('Connecting server to transport...'); try { await serverInfo.server.connect(serverInfo.transport); console.log('Server terminated.'); } catch (error) { logError('Error connecting server to transport:', error); process.exit(1); } } } catch (error) { logError('Unhandled error starting server:', error instanceof Error ? error.message : String(error)); if (isDebugMode && error instanceof Error && error.stack) { logError('Stack trace:', error.stack); } process.exit(1); } } // Only run if called directly if (require.main === module) { main().catch(error => { logError('Unhandled error in main:', error); process.exit(1); }); }