UNPKG

christmas-mcp-copilot-runner

Version:

A Model Copilot Provider (MCP) for safely executing whitelisted system commands across platforms with automatic VS Code setup

297 lines (265 loc) 9.02 kB
const { runCommand } = require('./handlers/runCommand'); /** * Main MCP entry point * Handles incoming requests from the Model Copilot Provider */ class MCPRunner { constructor() { this.commands = { runCommand: runCommand }; } /** * Process an incoming MCP request * @param {Object} request - The MCP request object * @returns {Object} - The response object */ async processRequest(request) { try { const { command, params } = request; if (!command) { return { success: false, error: 'No command specified', timestamp: new Date().toISOString() }; } if (!this.commands[command]) { return { success: false, error: `Unknown command: ${command}`, timestamp: new Date().toISOString() }; } // Execute the requested command const result = await this.commands[command](params || {}); return result; } catch (error) { return { success: false, error: error.message || 'Internal server error', timestamp: new Date().toISOString() }; } } /** * Get available commands * @returns {Array} - List of available commands */ getAvailableCommands() { return Object.keys(this.commands).map(cmd => ({ name: cmd, description: this.getCommandDescription(cmd) })); } /** * Get command description * @param {string} command - Command name * @returns {string} - Command description */ getCommandDescription(command) { const descriptions = { runCommand: 'Safely execute whitelisted system commands' }; return descriptions[command] || 'No description available'; } } // Export for use as a module const mcpRunner = new MCPRunner(); // If running directly, set up basic HTTP server for testing if (require.main === module) { const http = require('http'); const url = require('url'); // Check if running as MCP server (no arguments or --mcp flag) const args = process.argv.slice(2); const isMCPMode = args.length === 0 || args.includes('--mcp'); if (isMCPMode) { // MCP Server mode - handle JSON-RPC over stdio console.error('Christmas MCP Copilot Runner - MCP Server Mode'); // Use stderr for logging console.error('Ready to receive MCP commands via JSON-RPC'); let buffer = ''; // Listen for stdin input (JSON-RPC messages) process.stdin.on('data', async (data) => { buffer += data.toString(); // Process complete JSON-RPC messages (one per line) const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Keep incomplete line in buffer for (const line of lines) { if (line.trim()) { try { const request = JSON.parse(line); // Handle JSON-RPC methods if (request.method === 'initialize') { const response = { jsonrpc: '2.0', id: request.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: { listChanged: false } }, serverInfo: { name: 'christmas-mcp-copilot-runner', version: '1.0.3' } } }; process.stdout.write(JSON.stringify(response) + '\n'); } else if (request.method === 'tools/list') { const response = { jsonrpc: '2.0', id: request.id, result: { tools: [ { name: 'runCommand', description: 'Safely execute whitelisted system commands', inputSchema: { type: 'object', properties: { cmd: { type: 'string', description: 'The command to execute (must start with a whitelisted base command)' } }, required: ['cmd'] } } ] } }; process.stdout.write(JSON.stringify(response) + '\n'); } else if (request.method === 'tools/call') { const toolName = request.params.name; const toolArgs = request.params.arguments; if (toolName === 'runCommand') { const result = await mcpRunner.commands.runCommand(toolArgs); const response = { jsonrpc: '2.0', id: request.id, result: { content: [ { type: 'text', text: JSON.stringify(result, null, 2) } ] } }; process.stdout.write(JSON.stringify(response) + '\n'); } else { const response = { jsonrpc: '2.0', id: request.id, error: { code: -32601, message: `Unknown tool: ${toolName}` } }; process.stdout.write(JSON.stringify(response) + '\n'); } } else { // Unknown method const response = { jsonrpc: '2.0', id: request.id, error: { code: -32601, message: `Method not found: ${request.method}` } }; process.stdout.write(JSON.stringify(response) + '\n'); } } catch (error) { console.error('JSON-RPC parse error:', error.message); const errorResponse = { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }; process.stdout.write(JSON.stringify(errorResponse) + '\n'); } } } }); // Handle process termination gracefully process.on('SIGINT', () => { console.error('MCP Server shutting down...'); process.exit(0); }); return; } // HTTP Server mode for testing const server = http.createServer(async (req, res) => { // Set CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } if (req.method === 'GET') { const parsedUrl = url.parse(req.url, true); if (parsedUrl.pathname === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'healthy', service: 'universal-mcp-runner', timestamp: new Date().toISOString() })); return; } if (parsedUrl.pathname === '/commands') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ commands: mcpRunner.getAvailableCommands(), timestamp: new Date().toISOString() })); return; } } if (req.method === 'POST') { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', async () => { try { const request = JSON.parse(body); const result = await mcpRunner.processRequest(request); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(result)); } catch (error) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: false, error: 'Invalid JSON request', timestamp: new Date().toISOString() })); } }); return; } res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: false, error: 'Not found', timestamp: new Date().toISOString() })); }); const PORT = process.env.PORT || 3000; server.listen(PORT, () => { console.log(`Christmas MCP Copilot Runner HTTP Server listening on port ${PORT}`); console.log(`Health check: http://localhost:${PORT}/health`); console.log(`Available commands: http://localhost:${PORT}/commands`); console.log('Use --mcp flag or no arguments to run in MCP server mode'); }); } module.exports = mcpRunner;