#!/usr/bin/env node
/**
* OnCall Runbook MCP Server - Simplified Version
* Compatible with Node.js 14+
*/
import { createFsAdapter } from './src/adapters/fsio.mjs';
import { loadConfig } from './src/adapters/config.mjs';
import { createIndexer } from './src/services/indexer.mjs';
import { createSearchService } from './src/services/searchService.mjs';
import { createChecklistService } from './src/services/checklistService.mjs';
import { createCommandExtractService } from './src/services/commandExtractService.mjs';
import { createAnswerService } from './src/services/answerService.mjs';
import { createHandoffService } from './src/services/handoffService.mjs';
import { createPostmortemService } from './src/services/postmortemService.mjs';
import { createSearchTool } from './src/mcp/tools/search.mjs';
import { createReadTool } from './src/mcp/tools/read.mjs';
import { createChecklistTool } from './src/mcp/tools/checklist.mjs';
import { createCommandsTool } from './src/mcp/tools/commands.mjs';
import { createAnswerTool } from './src/mcp/tools/answer.mjs';
import { createHandoffTool } from './src/mcp/tools/handoff.mjs';
import { createPostmortemTool } from './src/mcp/tools/postmortem.mjs';
import { log } from './src/utils/logger.mjs';
class SimpleMCPServer {
constructor() {
this.tools = new Map();
this.initialized = false;
}
async initialize() {
try {
log('server.init', { status: 'starting' });
// Load configuration
const config = loadConfig();
log('server.config', { root: config.root });
// Create adapters
const fsAdapter = createFsAdapter(config.root);
// Build index
const indexer = createIndexer({ fsAdapter, config, logger: { log } });
const index = await indexer.buildIndex();
log('server.index', {
documents: index.documents.length,
chunks: index.chunks.length
});
// Create services
const searchService = createSearchService({ index, config, logger: { log } });
const checklistService = createChecklistService({ logger: { log } });
const commandExtractService = createCommandExtractService({ logger: { log } });
const answerService = createAnswerService({
searchService,
checklistService,
commandExtractService,
logger: { log }
});
const handoffService = createHandoffService({ logger: { log } });
const postmortemService = createPostmortemService({ logger: { log } });
// Create tools
const tools = [];
try {
tools.push(createSearchTool({ searchService, logger: { log } }));
console.log('✓ Search tool created');
} catch (error) {
console.error('✗ Search tool failed:', error.message);
}
try {
tools.push(createReadTool({ index, fsAdapter, logger: { log } }));
console.log('✓ Read tool created');
} catch (error) {
console.error('✗ Read tool failed:', error.message);
}
try {
tools.push(createChecklistTool({ checklistService, searchService, index, logger: { log } }));
console.log('✓ Checklist tool created');
} catch (error) {
console.error('✗ Checklist tool failed:', error.message);
}
try {
tools.push(createCommandsTool({ commandExtractService, searchService, index, logger: { log } }));
console.log('✓ Commands tool created');
} catch (error) {
console.error('✗ Commands tool failed:', error.message);
}
try {
tools.push(createAnswerTool({ answerService, logger: { log } }));
console.log('✓ Answer tool created');
} catch (error) {
console.error('✗ Answer tool failed:', error.message);
}
try {
tools.push(createHandoffTool({ handoffService, logger: { log } }));
console.log('✓ Handoff tool created');
} catch (error) {
console.error('✗ Handoff tool failed:', error.message);
}
try {
tools.push(createPostmortemTool({ postmortemService, logger: { log } }));
console.log('✓ Postmortem tool created');
} catch (error) {
console.error('✗ Postmortem tool failed:', error.message);
}
// Register tools
for (const tool of tools) {
if (!tool || !tool.definition || !tool.definition.name) {
console.error('Invalid tool:', tool);
continue;
}
this.tools.set(tool.definition.name, tool);
log('server.tool.registered', { name: tool.definition.name });
}
this.initialized = true;
log('server.init', { status: 'ready', tools: tools.length });
} catch (error) {
log('server.init', { status: 'failed', error: error.message });
console.error('Detailed error:', error);
throw error;
}
}
async handleMessage(message) {
try {
const request = JSON.parse(message);
if (request.method === 'tools/list') {
const toolList = Array.from(this.tools.values()).map(tool => tool.definition);
return {
jsonrpc: "2.0",
id: request.id,
result: { tools: toolList }
};
}
if (request.method === 'tools/call') {
const { name, arguments: args } = request.params;
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`Tool not found: ${name}`);
}
const result = await tool.handler(args);
return {
jsonrpc: "2.0",
id: request.id,
result: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }
};
}
// Handle initialize
if (request.method === 'initialize') {
return {
jsonrpc: "2.0",
id: request.id,
result: {
protocolVersion: "2024-11-05",
capabilities: {
tools: {}
},
serverInfo: {
name: "oncall-runbook-server",
version: "1.0.0"
}
}
};
}
throw new Error(`Unknown method: ${request.method}`);
} catch (error) {
return {
jsonrpc: "2.0",
id: request.id || null,
error: {
code: -32603,
message: error.message
}
};
}
}
async run() {
// Handle stdio communication
process.stdin.setEncoding('utf8');
let buffer = '';
process.stdin.on('data', async (chunk) => {
buffer += chunk;
// Process complete messages (separated by newlines)
const lines = buffer.split('\n');
buffer = lines.pop(); // Keep incomplete line in buffer
for (const line of lines) {
if (line.trim()) {
try {
const response = await this.handleMessage(line);
process.stdout.write(JSON.stringify(response) + '\n');
} catch (error) {
log('server.error', { error: error.message });
}
}
}
});
process.stdin.on('end', () => {
log('server.shutdown', { reason: 'stdin_closed' });
process.exit(0);
});
// Handle process signals
process.on('SIGINT', () => {
log('server.shutdown', { signal: 'SIGINT' });
process.exit(0);
});
log('server.started', { transport: 'stdio' });
}
}
// Main execution
async function main() {
const server = new SimpleMCPServer();
await server.initialize();
await server.run();
}
if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/'))) {
main().catch((error) => {
console.error('Server failed to start:', error);
process.exit(1);
});
}
export { SimpleMCPServer };