#!/usr/bin/env node
/**
* Komodo MCP Server
*
* Model Context Protocol (MCP) server implementation for Komodo IDE integration.
* Provides 60+ tools across 6 modules for chat, conversation, file, project, agent, and task 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 { chatTools } from './tools/chat.js';
import { conversationTools } from './tools/conversation.js';
import { fileTools } from './tools/file.js';
import { projectTools } from './tools/project.js';
import { agentTools } from './tools/agent.js';
import { taskTools } from './tools/task.js';
import { readTools } from './tools/read.js';
import { createLogger } from './utils/logger.js';
const logger = createLogger('server');
interface ToolHandler {
name: string;
description: string;
inputSchema: Record<string, unknown>;
handler: (args: Record<string, unknown>) => Promise<unknown>;
}
class KomodoMCPServer {
private server: Server;
private tools: 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.registerTools();
this.setupHandlers();
}
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');
});
}
private registerTools(): void {
const allTools: ToolHandler[] = [
...chatTools,
...conversationTools,
...fileTools,
...projectTools,
...agentTools,
...taskTools,
...readTools,
];
logger.info(`Registering ${allTools.length} tools across 7 modules`);
for (const tool of allTools) {
if (this.tools.has(tool.name)) {
logger.warn(`Duplicate tool name detected: ${tool.name}`);
continue;
}
this.tools.set(tool.name, tool);
}
logger.info('Tool registration complete', {
chat: chatTools.length,
conversation: conversationTools.length,
file: fileTools.length,
project: projectTools.length,
agent: agentTools.length,
task: taskTools.length,
read: readTools.length,
total: this.tools.size,
});
}
private setupHandlers(): void {
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools: Tool[] = Array.from(this.tools.values()).map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema,
}));
logger.debug(`Listing ${tools.length} tools`);
return { tools };
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.debug(`Tool called: ${name}`, { args });
const tool = this.tools.get(name);
if (!tool) {
logger.error(`Tool not found: ${name}`);
throw new Error(`Unknown tool: ${name}`);
}
try {
const result = await tool.handler(args ?? {});
logger.debug(`Tool completed: ${name}`, {
success: true,
resultType: typeof result
});
return {
content: [
{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
logger.error(`Tool execution failed: ${name}`, 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,
};
}
});
}
async start(): Promise<void> {
logger.info('Starting Komodo MCP Server', {
version: '1.0.0',
toolCount: this.tools.size,
nodeVersion: process.version,
platform: process.platform,
});
const transport = new StdioServerTransport();
await this.server.connect(transport);
logger.info('Server started successfully on stdio transport');
}
private async shutdown(signal: string): Promise<void> {
if (this.isShuttingDown) {
return;
}
this.isShuttingDown = true;
logger.info(`Shutting down server (${signal})`);
try {
await this.server.close();
logger.info('Server closed successfully');
process.exit(0);
} catch (error) {
logger.error('Error during shutdown:', error);
process.exit(1);
}
}
}
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();