@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
JavaScript
;
/**
* 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);
});