UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

493 lines • 22.1 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { readFileSync } from 'fs'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; // Core 12-Factor Infrastructure import { ToolRegistry, ToolExecutor } from './core/tool-framework.js'; import { SchemaValidator } from './core/validation.js'; import { getSQLiteManager, ensureDatabaseReady } from './storage/sqlite-manager.js'; import { convertToolResultToMCP } from './core/mcp-adapter.js'; // New 12-Factor Modules import { setupAgileManagementTools } from './modules/agile-management/tools.js'; import { setupKanbanTools } from './modules/kanban/tools.js'; import { setupMemoryManagementTools } from './modules/memory-management/tools.js'; import { setupADRTools } from './modules/adr-management/tools.js'; import { setupDevelopmentTools } from './modules/development/tools.js'; import { setupWorkspaceTools } from './modules/workspace/tools.js'; // import { setupHumanInteractionTools } from './modules/human-interaction/index.js'; // TODO: Migrate this module import { setupLocalAITools as setupLocalAIToolsNew } from './modules/local-ai/tools.js'; import { setupDocumentationTools as setupDocumentationToolsNew } from './modules/documentation/tools.js'; import { setupProductRequirementsTools } from './modules/product-requirements/tools.js'; import { setupRAGRetrievalTools } from './modules/rag-retrieval/tools.js'; import { setupProcessAutomationTools } from './modules/process-automation/tools.js'; // Legacy modules (to be migrated) // import { setupProjectInitTools } from './modules/project-init/index.js'; // import { setupCodeIntelligenceTools } from './modules/code-intelligence/index.js'; // Module not fully implemented // import { setupLocalAITools } from './modules/local-ai/index.js'; // Duplicate - using tools.js import import { setupDeveloperWorkflowTools } from './modules/developer-workflow/tools.js'; // import { setupDocumentationTools } from './modules/documentation/index.js'; // Duplicate - using tools.js import import { setupDataManagementTools } from './modules/data-management/tools.js'; // Dashboard and monitoring import { DashboardServer } from './web-dashboard/server.js'; import { PerformanceMonitor } from './utils/performance-monitor.js'; import { SecurityManager } from './utils/security-manager.js'; import { ErrorHandler } from './utils/error-handler.js'; // Get version from package.json const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8')); const VERSION = packageJson.version; /** * Atlas MCP Server - 12-Factor Implementation * * Implements all 12 factors for enterprise-grade MCP servers: * 1. Separation of Concerns - Modular architecture * 2. Deterministic Execution - Structured outputs * 3. Stateless Processes - RequestContext pattern * 4. Structured Outputs - JSON Schema validation * 5. Contextual Memory - SQLite persistence * 6. Configuration as Code - Environment variables * 7. Contact Humans - Approval workflows * 8. Capabilities-based Authorization - Security layer * 9. Error Self-Healing - Structured error handling * 10. Performance Observability - Monitoring and metrics * 11. Request Context - Tracing and logging * 12. Production Infrastructure - Deployment ready */ export class AtlasServer { server; toolRegistry; toolExecutor; schemaValidator; sqliteManager; dashboardServer; performanceMonitor; securityManager; errorHandler; constructor() { this.performanceMonitor = PerformanceMonitor.getInstance(); this.securityManager = SecurityManager.getInstance(); this.errorHandler = new ErrorHandler(); this.sqliteManager = getSQLiteManager; // Initialize 12-factor infrastructure this.schemaValidator = new SchemaValidator(); this.toolRegistry = new ToolRegistry(); this.toolExecutor = new ToolExecutor(this.toolRegistry); this.server = new Server({ name: 'atlas', version: VERSION, }, { capabilities: { resources: {}, tools: {}, prompts: {}, }, }); this.setupHandlers(); } /** * Initialize the server and register all modules */ async initialize() { try { console.error('šŸš€ Initializing Atlas MCP Server (12-Factor Architecture)...'); // Initialize SQLite database with automatic migration await this.sqliteManager().initialize(); console.error('āœ… Database initialized with migration support'); // Register 12-factor compliant modules await this.registerNewModules(); // Register legacy modules (to be migrated) await this.registerLegacyModules(); // Initialize dashboard await this.initializeDashboard(); // Start performance monitoring this.performanceMonitor.startMonitoring(); console.error(`āœ… Atlas Server v${VERSION} initialized successfully`); console.error(`šŸ“Š Registered ${this.toolRegistry.getToolCount()} tools across ${this.toolRegistry.getModuleCount()} modules`); } catch (error) { console.error('āŒ Failed to initialize Atlas Server:', error); throw error; } } /** * Register new 12-factor compliant modules */ async registerNewModules() { const modules = [ // { name: 'human-interaction', setup: setupHumanInteractionTools }, // TODO: Migrate this module { name: 'agile-management', setup: setupAgileManagementTools }, { name: 'kanban', setup: setupKanbanTools }, { name: 'memory-management', setup: setupMemoryManagementTools }, { name: 'adr-management', setup: setupADRTools }, { name: 'development', setup: setupDevelopmentTools }, { name: 'workspace', setup: setupWorkspaceTools }, { name: 'local-ai', setup: setupLocalAIToolsNew }, { name: 'documentation', setup: setupDocumentationToolsNew }, { name: 'product-requirements', setup: setupProductRequirementsTools }, { name: 'rag-retrieval', setup: setupRAGRetrievalTools }, { name: 'process-automation', setup: setupProcessAutomationTools }, { name: 'developer-workflow', setup: setupDeveloperWorkflowTools }, { name: 'data-management', setup: setupDataManagementTools }, ]; for (const module of modules) { try { const registration = await module.setup(); this.toolRegistry.registerModule(registration); console.error(`āœ… Registered ${module.name} module (${registration.tools.length} tools)`); } catch (error) { console.error(`āŒ Failed to register ${module.name} module:`, error); throw error; } } } /** * Register legacy modules (to be gradually migrated) */ async registerLegacyModules() { const legacyModules = [ // { name: 'project-init', setup: () => setupProjectInitTools(this.server) }, // { name: 'code-intelligence', setup: () => setupCodeIntelligenceTools(this.server) }, // Module not implemented // All modules have been migrated to 12-factor pattern ]; for (const module of legacyModules) { try { await module.setup(); console.error(`āœ… Registered legacy ${module.name} module`); } catch (error) { console.warn(`āš ļø Failed to register legacy ${module.name} module:`, error); // Continue with other modules } } } /** * Find an available port starting from the given port number */ async findAvailablePort(startPort) { const net = await import('net'); return new Promise((resolve, reject) => { const server = net.createServer(); server.listen(startPort, () => { const port = server.address()?.port; server.close(() => { if (port) { resolve(port); } else { reject(new Error('Failed to get port')); } }); }); server.on('error', (err) => { if (err.code === 'EADDRINUSE') { // Try next port this.findAvailablePort(startPort + 1).then(resolve).catch(reject); } else { reject(err); } }); }); } /** * Initialize the web dashboard */ async initializeDashboard() { try { const dashboardEnabled = process.env.ATLAS_DASHBOARD_ENABLED !== 'false'; // Use dynamic port selection if not specified let dashboardPort; if (process.env.ATLAS_DASHBOARD_PORT) { dashboardPort = parseInt(process.env.ATLAS_DASHBOARD_PORT); } else { // Use different port ranges for dev vs production const isDevelopment = process.env.NODE_ENV === 'development' || process.argv[0].includes('tsx'); const startPort = isDevelopment ? 3100 : 3001; // Dev uses 3100+, production uses 3001+ dashboardPort = await this.findAvailablePort(startPort); } if (dashboardEnabled) { // Create an agileManager with methods needed by the dashboard const agileManager = { // Velocity calculation for dashboard charts getVelocityData: async (options) => { try { const db = await ensureDatabaseReady(); const limit = options?.sprints || 5; // Get completed sprints const sprintsResult = await db.query(`SELECT * FROM agile_sprints WHERE status = 'completed' ORDER BY end_date DESC LIMIT ?`, [limit]); if (!sprintsResult.success || !sprintsResult.data) { return null; } const velocityData = { labels: [], datasets: [{ label: 'Velocity', data: [], backgroundColor: 'rgba(59, 130, 246, 0.5)', borderColor: 'rgb(59, 130, 246)', borderWidth: 2 }] }; for (const sprint of sprintsResult.data.reverse()) { velocityData.labels.push(sprint.name); velocityData.datasets[0].data.push(sprint.story_points_completed || 0); } return velocityData; } catch (error) { console.error('Error calculating velocity:', error); return null; } }, // Burndown data for dashboard charts getBurndownData: async (sprintId) => { try { const db = await ensureDatabaseReady(); // Get sprint details const sprintResult = await db.get('SELECT * FROM agile_sprints WHERE id = ?', [sprintId]); if (!sprintResult.success || !sprintResult.data) { return null; } const sprint = sprintResult.data; const totalPoints = sprint.story_points_planned || 0; const completedPoints = sprint.story_points_completed || 0; // Create simple burndown data // In a real implementation, this would track daily progress const burndownData = { labels: ['Start', 'Current', 'End'], datasets: [{ label: 'Ideal', data: [totalPoints, totalPoints / 2, 0], borderColor: 'rgba(156, 163, 175, 1)', borderDash: [5, 5], fill: false }, { label: 'Actual', data: [totalPoints, totalPoints - completedPoints, null], borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true }] }; return burndownData; } catch (error) { console.error('Error getting burndown data:', error); return null; } } }; this.dashboardServer = new DashboardServer({ enabled: true, port: dashboardPort, host: 'localhost', features: { performance: true, security: true, agile: true, errors: true }, realTimeUpdates: true, exportEnabled: true }, this.performanceMonitor, this.securityManager, this.errorHandler, agileManager); await this.dashboardServer.start(); console.error(`āœ… Dashboard server started on port ${dashboardPort}`); } else { console.error('šŸ“Š Dashboard disabled via configuration'); } } catch (error) { console.warn('āš ļø Dashboard initialization failed:', error); // Dashboard failure shouldn't break the main server } } /** * Setup MCP request handlers */ setupHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: this.toolRegistry.getToolManifest() }; }); // Execute tools using the new framework this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const startTime = Date.now(); const { name, arguments: args } = request.params; try { // Create request context const context = { toolName: name, requestId: this.generateRequestId(), timestamp: Date.now(), userId: this.extractUserId(request), projectId: this.extractProjectId(request), db: this.sqliteManager(), metadata: {} }; // Log request console.error(`šŸ”§ Executing tool: ${name} (${context.requestId})`); // Execute tool using the framework const result = await this.toolExecutor.execute(name, args || {}, context); // Log performance const duration = Date.now() - startTime; this.performanceMonitor.recordToolExecution(name, duration); // Convert the result to MCP format using the adapter const mcpResponse = convertToolResultToMCP(result, name); if (result.success) { console.error(`āœ… Tool ${name} completed in ${duration}ms`); } else { console.error(`āŒ Tool ${name} failed:`, result.error); } return mcpResponse; } catch (error) { const duration = Date.now() - startTime; console.error(`šŸ’„ Tool ${name} crashed after ${duration}ms:`, error); return { content: [ { type: 'text', text: `Internal error executing ${name}: ${error instanceof Error ? error.message : 'Unknown error'}`, } ], isError: true }; } }); // List resources this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [ { uri: 'atlas://dashboard', name: 'Atlas Dashboard', description: 'Web-based project management dashboard', mimeType: 'text/html', }, { uri: 'atlas://database/stats', name: 'Database Statistics', description: 'SQLite database statistics and health', mimeType: 'application/json', }, { uri: 'atlas://tools/manifest', name: 'Tools Manifest', description: 'List of all available tools and their schemas', mimeType: 'application/json', } ] })); // Read resources this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; switch (uri) { case 'atlas://dashboard': return { contents: [ { uri, mimeType: 'text/html', text: `Dashboard available at: ${process.env.ATLAS_DASHBOARD_URL || 'http://localhost:3001'}` } ] }; case 'atlas://database/stats': const stats = await this.sqliteManager().getStats(); return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(stats.data, null, 2) } ] }; case 'atlas://tools/manifest': return { contents: [ { uri, mimeType: 'application/json', text: JSON.stringify(this.toolRegistry.getToolManifest(), null, 2) } ] }; default: throw new Error(`Unknown resource: ${uri}`); } }); } /** * Start the server */ async start() { await this.initialize(); const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('🌟 Atlas MCP Server is running with 12-factor architecture'); } /** * Stop the server */ async stop() { try { await this.sqliteManager().close(); if (this.dashboardServer) { await this.dashboardServer.stop(); } this.performanceMonitor.stopMonitoring(); console.error('šŸ‘‹ Atlas Server stopped gracefully'); } catch (error) { console.error('āŒ Error stopping server:', error); } } /** * Utility methods */ generateRequestId() { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } extractUserId(request) { // Extract user ID from request metadata if available return request.meta?.userId || process.env.ATLAS_DEFAULT_USER_ID || undefined; } extractProjectId(request) { // Extract project ID from request metadata if available return request.meta?.projectId || process.env.ATLAS_PROJECT_ID || 'default'; } } // Start the server if this file is run directly if (import.meta.url === `file://${process.argv[1]}`) { const server = new AtlasServer(); // Graceful shutdown process.on('SIGINT', async () => { console.error('\nšŸ“” Received SIGINT, shutting down gracefully...'); await server.stop(); process.exit(0); }); process.on('SIGTERM', async () => { console.error('\nšŸ“” Received SIGTERM, shutting down gracefully...'); await server.stop(); process.exit(0); }); // Start the server server.start().catch(error => { console.error('šŸ’„ Failed to start Atlas Server:', error); process.exit(1); }); } //# sourceMappingURL=server.js.map