UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

187 lines (186 loc) • 9.42 kB
#!/usr/bin/env node import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import express from "express"; import cors from "cors"; import dotenv from "dotenv"; import path from 'path'; import { fileURLToPath } from 'url'; import chalk from 'chalk'; import ora from 'ora'; import logger, { registerShutdownCallback } from "./logger.js"; import { initializeToolEmbeddings } from './services/routing/embeddingStore.js'; import { OpenRouterConfigManager } from './utils/openrouter-config-manager.js'; import { ToolRegistry } from './services/routing/toolRegistry.js'; import { transportManager } from './services/transport-manager/index.js'; import { PortAllocator } from './utils/port-allocator.js'; import { setupWizard } from './setup-wizard.js'; import { UnifiedSecurityConfigManager } from './tools/vibe-task-manager/security/unified-security-config.js'; const isMessageHandlingTransport = (t) => t !== null && typeof t === 'object' && 'handlePostMessage' in t && typeof t.handlePostMessage === 'function'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const envPath = path.resolve(__dirname, '../.env'); const args = process.argv.slice(2); const useSSE = args.includes('--sse'); const showHelp = args.includes('--help') || args.includes('-h'); const runSetup = args.includes('--setup') || args.includes('--reconfigure'); if (showHelp) { console.log(chalk.cyan.bold('\nšŸ¤– Vibe Coder MCP Server\n')); console.log(chalk.white('Usage: vibe-coder-mcp [options]\n')); console.log(chalk.yellow('Options:')); console.log(chalk.green(' --help, -h ') + chalk.gray('Show this help message')); console.log(chalk.green(' --setup ') + chalk.gray('Run the setup wizard')); console.log(chalk.green(' --reconfigure ') + chalk.gray('Reconfigure settings')); console.log(chalk.green(' --sse ') + chalk.gray('Use Server-Sent Events transport')); console.log(chalk.green(' --port <number> ') + chalk.gray('Specify port for SSE mode (default: 3000)')); console.log('\n' + chalk.gray('For more information: https://github.com/freshtechbro/Vibe-Coder-MCP')); process.exit(0); } async function initialize() { if (runSetup || await setupWizard.isFirstRun()) { console.log(chalk.cyan.bold('\nšŸš€ Vibe Coder MCP Setup\n')); const success = await setupWizard.run(); if (!success) { console.log(chalk.red('\nāŒ Setup failed. Exiting...')); process.exit(1); } console.log(chalk.green('\nāœ… Setup complete! Starting server...\n')); } const dotenvResult = dotenv.config({ path: envPath }); if (dotenvResult.error) { logger.warn({ err: dotenvResult.error, path: envPath }, `Could not load .env file from explicit path.`); } else { logger.info({ path: envPath, loaded: dotenvResult.parsed ? Object.keys(dotenvResult.parsed) : [] }, `Loaded environment variables from .env file.`); } const spinner = ora({ text: 'Starting Vibe Coder MCP Server...', color: 'cyan' }).start(); try { await startServer(spinner); } catch (error) { spinner.fail('Failed to start server'); logger.error({ err: error }, 'Server startup failed'); console.log(chalk.red('\nāŒ Server startup failed. Check logs for details.')); process.exit(1); } } async function startServer(spinner) { const configManager = OpenRouterConfigManager.getInstance(); await configManager.initialize(); const openRouterConfig = await configManager.getOpenRouterConfig(); ToolRegistry.getInstance(openRouterConfig); const securityManager = UnifiedSecurityConfigManager.getInstance(); securityManager.initializeFromMCPConfig(openRouterConfig); logger.info('Unified Security Configuration initialized with centralized config'); try { await initializeToolEmbeddings(); logger.info('Tool embeddings initialized'); } catch (error) { logger.error({ err: error }, 'Failed to initialize tool embeddings'); } const { createServer } = await import("./server.js"); const mcpServer = createServer(openRouterConfig); async function main(mcpServer) { try { if (useSSE) { const app = express(); app.use(cors()); app.use(express.json()); const allocatedSsePort = transportManager.getServicePort('sse'); const port = allocatedSsePort || (process.env.SSE_PORT ? parseInt(process.env.SSE_PORT) : undefined) || (process.env.PORT ? parseInt(process.env.PORT) : 3000); app.get('/health', (req, res) => { res.status(200).json({ status: 'ok' }); }); app.get('/sse', (req, res) => { const sessionId = req.query.sessionId || `sse-${Math.random().toString(36).substring(2)}`; const transport = new SSEServerTransport('/messages', res); req.sessionId = sessionId; logger.info({ sessionId, transportSessionId: transport.sessionId }, 'Established SSE connection'); mcpServer.connect(transport).catch((error) => { logger.error({ err: error }, 'Failed to connect transport'); }); }); app.post('/messages', async (req, res) => { if (!req.body) { return res.status(400).json({ error: 'Invalid request body' }); } try { const sessionId = req.query.sessionId || req.body.session_id; if (!sessionId) { return res.status(400).json({ error: 'Missing session ID. Establish an SSE connection first.' }); } const transport = mcpServer.server.transport; if (!transport) { return res.status(400).json({ error: 'No active SSE connection' }); } if (isMessageHandlingTransport(transport)) { const context = { sessionId, transportType: sessionId === 'stdio-session' ? 'stdio' : 'sse', timestamp: Date.now() }; await transport.handlePostMessage(req, res, context); } else { logger.error('Active transport does not support handlePostMessage or is not defined.'); if (!res.headersSent) { res.status(500).json({ error: 'Transport does not support POST messages' }); } } } catch (error) { logger.error({ err: error }, 'Error handling POST message'); if (!res.headersSent) { res.status(500).json({ error: 'Internal server error' }); } } }); const server = app.listen(port, () => { spinner.succeed(`Vibe Coder MCP Server running on port ${port} (SSE mode)`); console.log(chalk.green(`\nāœ… Server ready at: `) + chalk.cyan(`http://localhost:${port}`)); console.log(chalk.gray('Health check: ') + chalk.cyan(`http://localhost:${port}/health`)); console.log(chalk.gray('SSE endpoint: ') + chalk.cyan(`http://localhost:${port}/sse\n`)); }); registerShutdownCallback(async () => { server.close(); await PortAllocator.cleanupOrphanedPorts(); }); } else { const transport = new StdioServerTransport(); await mcpServer.connect(transport); spinner.succeed('Vibe Coder MCP Server running (stdio mode)'); console.log(chalk.green('\nāœ… Server ready!')); console.log(chalk.gray('Connected via stdio transport')); console.log(chalk.gray('Ready to receive requests from MCP client\n')); } } catch (error) { spinner.fail('Server startup failed'); logger.error({ err: error }, 'Failed to start MCP server'); throw error; } } await main(mcpServer); } process.on('uncaughtException', (error) => { logger.error({ err: error }, 'Uncaught exception'); console.error(chalk.red('\nāŒ Uncaught exception:'), error); process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { logger.error({ err: reason, promise }, 'Unhandled rejection'); console.error(chalk.red('\nāŒ Unhandled rejection:'), reason); process.exit(1); }); initialize().catch((error) => { logger.error({ err: error }, 'Failed to initialize'); console.error(chalk.red('\nāŒ Initialization failed:'), error); process.exit(1); });