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
JavaScript
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;