@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
493 lines ⢠22.1 kB
JavaScript
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