UNPKG

@vfarcic/dot-ai

Version:

AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance

251 lines (250 loc) 13.1 kB
#!/usr/bin/env node "use strict"; /** * MCP Server Entry Point for DevOps AI Toolkit * * This server exposes DevOps AI Toolkit functionality through the Model Context Protocol, * enabling AI assistants to interact with Kubernetes deployment capabilities. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const mcp_js_1 = require("../interfaces/mcp.js"); const index_js_1 = require("../core/index.js"); const index_js_2 = require("../core/tracing/index.js"); const index_js_3 = require("../core/telemetry/index.js"); const plugin_registry_js_1 = require("../core/plugin-registry.js"); const mcp_client_registry_js_1 = require("../core/mcp-client-registry.js"); const mcp_client_manager_js_1 = require("../core/mcp-client-manager.js"); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const plugin_manager_js_1 = require("../core/plugin-manager.js"); const error_handling_js_1 = require("../core/error-handling.js"); // Track server start time for uptime calculation const serverStartTime = Date.now(); /** * Get Kubernetes version (non-blocking, returns undefined if unavailable) */ async function main() { try { // Initialize OpenTelemetry tracing (must happen before HTTP server starts) const tracer = (0, index_js_2.getTracer)(); if (tracer.isEnabled()) { process.stderr.write('OpenTelemetry tracing enabled\n'); // Force initialization now to enable auto-instrumentation tracer.initialize(); } // Validate required environment variables process.stderr.write('Validating MCP server configuration...\n'); // Check session directory configuration const sessionDir = process.env.DOT_AI_SESSION_DIR || './tmp/sessions'; process.stderr.write(`Using session directory: ${sessionDir}\n`); if (!process.env.DOT_AI_SESSION_DIR) { process.stderr.write('INFO: DOT_AI_SESSION_DIR not set, using default: ./tmp/sessions\n'); process.stderr.write('For custom session directory, set DOT_AI_SESSION_DIR environment variable\n'); } // Validate session directory exists and is writable try { const fs = await Promise.resolve().then(() => __importStar(require('fs'))); const path = await Promise.resolve().then(() => __importStar(require('path'))); // Check if directory exists if (!fs.existsSync(sessionDir)) { process.stderr.write(`FATAL: Session directory does not exist: ${sessionDir}\n`); process.stderr.write('Solution: Create the directory or update DOT_AI_SESSION_DIR\n'); process.exit(1); } // Check if it's actually a directory const stat = fs.statSync(sessionDir); if (!stat.isDirectory()) { process.stderr.write(`FATAL: Session directory path is not a directory: ${sessionDir}\n`); process.stderr.write('Solution: Use a valid directory path in DOT_AI_SESSION_DIR\n'); process.exit(1); } // Test write permissions const testFile = path.join(sessionDir, '.mcp-test-write'); try { fs.writeFileSync(testFile, 'test'); fs.unlinkSync(testFile); process.stderr.write(`Session directory validated: ${sessionDir}\n`); } catch { process.stderr.write(`FATAL: Session directory is not writable: ${sessionDir}\n`); process.stderr.write('Solution: Fix directory permissions or use a different directory\n'); process.exit(1); } } catch (error) { process.stderr.write(`FATAL: Session directory validation failed: ${error}\n`); process.exit(1); } // Initialize DotAI - it will read KUBECONFIG and AI provider configuration from environment const dotAI = new index_js_1.DotAI(); // Initialize without cluster connection (lazy connection) process.stderr.write('Initializing DevOps AI Toolkit...\n'); try { await dotAI.initializeWithoutCluster(); process.stderr.write('DevOps AI Toolkit initialized successfully\n'); process.stderr.write('Cluster connectivity will be checked when needed by individual tools\n'); } catch (initError) { process.stderr.write(`FATAL: Failed to initialize DevOps AI Toolkit: ${initError}\n`); process.exit(1); } // Load version dynamically from package.json const packageJson = JSON.parse((0, fs_1.readFileSync)(path_1.default.join(__dirname, '../../package.json'), 'utf8')); // Initialize plugin discovery (PRD #343) const pluginLogger = new error_handling_js_1.ConsoleLogger('PluginManager'); const pluginManager = new plugin_manager_js_1.PluginManager(pluginLogger); const pluginConfigs = plugin_manager_js_1.PluginManager.parsePluginConfig(); if (pluginConfigs.length > 0) { process.stderr.write(`Discovering ${pluginConfigs.length} plugin(s)...\n`); try { await pluginManager.discoverPlugins(pluginConfigs); const stats = pluginManager.getStats(); const pending = pluginManager.getPendingPlugins(); process.stderr.write(`Plugin discovery complete: ${stats.pluginCount} plugin(s), ${stats.toolCount} tool(s)\n`); if (pending.length > 0) { process.stderr.write(`Plugins pending background discovery: ${pending.join(', ')}\n`); } } catch (error) { // Non-required plugin failures are warnings, required failures throw if (error instanceof Error && error.name === 'PluginDiscoveryError') { process.stderr.write(`FATAL: Required plugin discovery failed: ${error.message}\n`); process.exit(1); } process.stderr.write(`Plugin discovery warning: ${error}\n`); } // Start background discovery for any plugins that failed initial discovery // They will be retried every 30 seconds for up to 10 minutes if (pluginManager.getPendingPlugins().length > 0) { pluginManager.setOnPluginDiscovered((plugin) => { process.stderr.write(`Background discovery: Plugin '${plugin.name}' now available with ${plugin.tools.length} tool(s)\n`); // Note: Tools are automatically registered via pluginManager's internal maps // The version tool will reflect the updated plugin status }); pluginManager.startBackgroundDiscovery(); } } else { process.stderr.write('No plugins configured (mount plugins.json at /etc/dot-ai/plugins.json to enable)\n'); } // PRD #359: Initialize unified plugin registry for all plugin tool invocations // This replaces scattered plugin manager passing (telemetry, vector tools, etc.) if (pluginConfigs.length > 0) { (0, plugin_registry_js_1.initializePluginRegistry)(pluginManager); } // PRD #358: Initialize MCP client for connecting to external MCP servers const mcpClientLogger = new error_handling_js_1.ConsoleLogger('McpClientManager'); const mcpClientManager = new mcp_client_manager_js_1.McpClientManager(mcpClientLogger); const mcpServerConfigs = mcp_client_manager_js_1.McpClientManager.parseMcpServerConfig(); if (mcpServerConfigs.length > 0) { process.stderr.write(`Discovering ${mcpServerConfigs.length} MCP server(s)...\n`); try { await mcpClientManager.discoverMcpServers(mcpServerConfigs); const stats = mcpClientManager.getStats(); process.stderr.write(`MCP server discovery complete: ${stats.serverCount} server(s), ${stats.toolCount} tool(s)\n`); } catch (error) { // Fail-fast: in Kubernetes, crash + backoff is easier to detect than a Running // pod silently missing MCP tools. Config errors (bad endpoint, missing auth) // surface immediately via CrashLoopBackOff events and alerts. process.stderr.write(`FATAL: MCP server discovery failed: ${error instanceof Error ? error.message : String(error)}\n`); process.exit(1); } (0, mcp_client_registry_js_1.initializeMcpClientRegistry)(mcpClientManager); } else { process.stderr.write('No MCP servers configured (mount mcp-servers.json at /etc/dot-ai-mcp/mcp-servers.json to enable)\n'); } // Create and configure MCP server const mcpServer = new mcp_js_1.MCPServer(dotAI, { name: 'dot-ai', version: packageJson.version, description: 'Universal Kubernetes application deployment agent with AI-powered orchestration', author: 'Viktor Farcic', pluginManager: pluginConfigs.length > 0 ? pluginManager : undefined }); // Start the MCP server (HTTP transport only) process.stderr.write('Starting DevOps AI Toolkit MCP server...\n'); await mcpServer.start(); process.stderr.write('DevOps AI Toolkit MCP server started successfully\n'); // Track server start telemetry (non-blocking) // PRD #343: K8s version obtained via plugin at runtime, not at startup // PRD #345: Deployment method tracking removed (Kubernetes-only) (0, index_js_3.getTelemetry)().trackServerStart(); // Handle graceful shutdown const gracefulShutdown = async (signal) => { process.stderr.write(`Shutting down DevOps AI Toolkit MCP server (${signal})...\n`); // Stop background plugin discovery if active pluginManager.stopBackgroundDiscovery(); // Stop server first to drain in-flight requests before closing shared clients await mcpServer.stop(); // Close MCP client connections (PRD #358) await mcpClientManager.close(); // Track server stop telemetry const uptimeSeconds = Math.floor((Date.now() - serverStartTime) / 1000); (0, index_js_3.getTelemetry)().trackServerStop(uptimeSeconds); await (0, index_js_2.shutdownTracer)(); // Flush telemetry events before exit await (0, index_js_3.shutdownTelemetry)(); process.exit(0); }; process.on('SIGINT', () => gracefulShutdown('SIGINT')); process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // HTTP server keeps the process alive process.stderr.write('HTTP server active - server will run until terminated\n'); } catch (error) { process.stderr.write(`Failed to start DevOps AI Toolkit MCP server: ${error}\n`); process.exit(1); } } // Handle uncaught exceptions process.on('uncaughtException', (error) => { process.stderr.write(`Uncaught exception in MCP server: ${error}\n`); process.exit(1); }); process.on('unhandledRejection', (reason) => { process.stderr.write(`Unhandled rejection in MCP server: ${reason}\n`); process.exit(1); }); // Start the server main().catch((error) => { process.stderr.write(`Fatal error starting MCP server: ${error}\n`); process.exit(1); });