#!/usr/bin/env node
/**
* Komodo MCP Server - Integrated Version
*
* Model Context Protocol (MCP) server implementation for Komodo IDE integration.
* Provides 60 tools across 6 modules:
* - Auth (8 tools): Authentication and authorization
* - User (10 tools): User and permission management
* - Read (16 tools): Read-only data retrieval
* - Write (8 tools): Create, update, delete operations
* - Execute (9 tools): Action execution and lifecycle
* - Terminal (9 tools): Terminal session management
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { registry } from './registry.js';
import { integrationManager } from './integration/index.js';
import { errorHandler } from './integration/error-handling.js';
import { createLogger } from './utils/logger.js';
import { KomodoError } from './errors/KomodoError.js';
const logger = createLogger('server');
/**
* Tool handler function type
*/
type ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;
/**
* Komodo MCP Server with integrated registry and error handling
*/
class KomodoMCPServer {
private server: Server;
private toolHandlers: Map<string, ToolHandler> = new Map();
private isShuttingDown = false;
constructor() {
this.server = new Server(
{
name: 'komodo-mcp',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupErrorHandling();
this.registerToolHandlers();
this.setupMCPHandlers();
}
/**
* Setup global error handling
*/
private setupErrorHandling(): void {
this.server.onerror = (error: Error) => {
logger.error('Server error:', error);
};
process.on('SIGINT', () => this.shutdown('SIGINT'));
process.on('SIGTERM', () => this.shutdown('SIGTERM'));
process.on('uncaughtException', (error: Error) => {
logger.error('Uncaught exception:', error);
this.shutdown('uncaughtException');
});
process.on('unhandledRejection', (reason: unknown) => {
logger.error('Unhandled rejection:', reason);
this.shutdown('unhandledRejection');
});
}
/**
* Register tool handlers from registry
*/
private registerToolHandlers(): void {
logger.info('Registering tool handlers from registry');
// Validate registry
const validation = registry.validate();
if (!validation.valid) {
logger.error('Registry validation failed:', validation.errors);
throw new Error('Registry validation failed: ' + validation.errors.join(', '));
}
// Get all tools from registry
const allTools = registry.getAllTools();
logger.info(`Registering ${allTools.length} tools`, registry.getStats());
// For now, register placeholder handlers
// In production, these would call actual implementation modules
for (const tool of allTools) {
this.toolHandlers.set(tool.name, async (args) => {
return this.executeToolWithErrorHandling(tool.name, args);
});
}
logger.info('Tool handler registration complete', {
totalHandlers: this.toolHandlers.size,
});
}
/**
* Execute tool with integrated error handling
*/
private async executeToolWithErrorHandling(
toolName: string,
args: Record<string, unknown>
): Promise<unknown> {
// Check if auth is required
const requiresAuth = registry.requiresAuth(toolName);
if (requiresAuth) {
// In production, validate authentication here
// For now, just log
logger.debug(`Tool ${toolName} requires authentication`);
}
// Execute with error handling
return errorHandler.handle(
async () => {
// Determine module and route to appropriate handler
const module = this.getModuleForTool(toolName);
logger.debug(`Executing tool ${toolName} from module ${module}`);
// In production, this would route to actual implementation
// For now, return placeholder response
return {
success: true,
tool: toolName,
module,
message: 'Tool execution placeholder - implement actual handler',
args,
timestamp: new Date().toISOString(),
};
},
{
module: this.getModuleForTool(toolName),
tool: toolName,
args,
}
);
}
/**
* Get module name for a tool
*/
private getModuleForTool(toolName: string): string {
// Extract module from tool name (komodo_{module}_{operation})
const parts = toolName.split('_');
if (parts.length >= 2 && parts[0] === 'komodo') {
return parts[1];
}
return 'unknown';
}
/**
* Setup MCP protocol handlers
*/
private setupMCPHandlers(): void {
// List tools handler
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = registry.getAllTools();
logger.debug(`Listing ${tools.length} tools`);
return {
tools: tools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
})),
};
});
// Call tool handler
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.debug(`Tool called: ${name}`, { args });
// Check if tool exists
const handler = this.toolHandlers.get(name);
if (!handler) {
logger.error(`Tool not found: ${name}`);
throw new Error(`Unknown tool: ${name}`);
}
try {
// Execute tool handler
const result = await handler(args ?? {});
logger.debug(`Tool completed: ${name}`, {
success: true,
resultType: typeof result,
});
// Format response
return {
content: [
{
type: 'text',
text: typeof result === 'string'
? result
: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
logger.error(`Tool execution failed: ${name}`, error);
// Format error response
if (error instanceof KomodoError) {
const errorResponse = errorHandler.formatErrorResponse(error);
return {
content: [
{
type: 'text',
text: JSON.stringify(errorResponse, null, 2),
},
],
isError: true,
};
}
// Unknown error
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: JSON.stringify({
error: errorMessage,
tool: name,
timestamp: new Date().toISOString(),
}, null, 2),
},
],
isError: true,
};
}
});
}
/**
* Start the MCP server
*/
async start(): Promise<void> {
logger.info('Starting Komodo MCP Server', {
version: '1.0.0',
toolCount: registry.getAllTools().length,
moduleCount: registry.getModuleNames().length,
nodeVersion: process.version,
platform: process.platform,
});
// Initialize integration layer
try {
await integrationManager.initializeAll();
logger.info('Integration layer initialized');
} catch (error) {
logger.error('Failed to initialize integration layer:', error);
throw error;
}
// Connect to transport
const transport = new StdioServerTransport();
await this.server.connect(transport);
logger.info('Server started successfully on stdio transport');
// Log registry stats
logger.info('Registry statistics:', registry.getStats());
}
/**
* Shutdown the server
*/
private async shutdown(signal: string): Promise<void> {
if (this.isShuttingDown) {
return;
}
this.isShuttingDown = true;
logger.info(`Shutting down server (${signal})`);
try {
// Cleanup integration layer
await integrationManager.cleanupAll();
logger.info('Integration layer cleaned up');
// Close server
await this.server.close();
logger.info('Server closed successfully');
process.exit(0);
} catch (error) {
logger.error('Error during shutdown:', error);
process.exit(1);
}
}
}
/**
* Main entry point
*/
async function main(): Promise<void> {
try {
const server = new KomodoMCPServer();
await server.start();
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
}
}
main();