UNPKG

claude-code-emacs-mcp-server

Version:

MCP server for Claude Code Emacs integration

197 lines 7.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EmacsBridge = void 0; const ws_1 = require("ws"); const events_1 = require("events"); class EmacsBridge extends events_1.EventEmitter { wss; clients = new Map(); sessionId; pendingRequests = new Map(); requestId = 0; log; onNotification; constructor(logger) { super(); this.log = logger || (() => { }); } async start(port = 0, sessionId) { this.sessionId = sessionId; return new Promise((resolve, reject) => { try { this.wss = new ws_1.WebSocketServer({ port, verifyClient: (info, cb) => { try { this.log(`WebSocket upgrade request - origin: ${info.origin}, url: ${info.req.url}, headers: ${JSON.stringify(info.req.headers)}`); // Accept all connections for now cb(true); } catch (error) { this.log(`Error in verifyClient: ${error}`); cb(false, 400, 'Bad Request'); } } }); this.wss.on('connection', (ws, req) => { this.log(`WebSocket connection attempt - URL: ${req.url}, headers: ${JSON.stringify(req.headers)}`); try { const url = new URL(req.url || '', `http://${req.headers.host}`); const clientSessionId = decodeURIComponent(url.searchParams.get('session') || 'default'); this.log(`Emacs connected for session: ${clientSessionId}`); this.clients.set(clientSessionId, ws); ws.on('message', (data) => { try { const message = JSON.parse(data.toString()); this.handleMessage(ws, message); } catch (error) { this.log(`Invalid message: ${error}`); } }); ws.on('close', () => { this.log(`Emacs disconnected for session: ${clientSessionId}`); this.clients.delete(clientSessionId); }); ws.on('error', (error) => { this.log(`WebSocket error: ${error}`); }); } catch (error) { this.log(`Error handling WebSocket connection: ${error}`); ws.close(1002, 'Invalid request'); } }); this.wss.on('listening', () => { const assignedPort = this.wss.address().port; this.log(`Emacs bridge listening on port ${assignedPort}`); resolve(assignedPort); }); this.wss.on('error', (error) => { this.log(`WebSocketServer error: ${error}`); reject(error); }); // Add additional error handling this.wss.on('headers', (headers, req) => { this.log(`WebSocket headers event - URL: ${req.url}`); }); } catch (error) { this.log(`Failed to create WebSocketServer: ${error}`); reject(error); } }); } async stop() { if (this.wss) { this.clients.forEach((client) => client.close()); this.clients.clear(); return new Promise((resolve) => { this.wss.close(() => resolve()); }); } } handleMessage(ws, message) { // Handle ping message if ('type' in message && message.type === 'ping') { // Respond with pong ws.send(JSON.stringify({ type: 'pong' })); return; } // Handle JSON-RPC response if ('id' in message && ('result' in message || 'error' in message)) { const pending = this.pendingRequests.get(message.id); if (pending) { this.pendingRequests.delete(message.id); if ('error' in message) { this.log(`Emacs Response Error: id=${message.id}, error=${JSON.stringify(message.error)}`); pending.reject(new Error(message.error.message)); } else { this.log(`Emacs Response: id=${message.id}, result=${JSON.stringify(message.result)}`); pending.resolve(message.result); } } } // Handle JSON-RPC request/notification from Emacs else if ('method' in message) { // If no id, it's a notification if (!('id' in message)) { this.handleNotification(message.method, message.params); } else { // Request from Emacs (currently not supported) this.sendResponse(ws, message.id, null, { code: -32601, message: 'Method not found' }); } } } sendResponse(ws, id, result, error) { const response = { jsonrpc: '2.0', id }; if (error) { response.error = error; } else { response.result = result; } ws.send(JSON.stringify(response) + '\n'); } async request(method, params) { const sessionClient = this.sessionId ? this.clients.get(this.sessionId) : null; const client = sessionClient || Array.from(this.clients.values())[0]; if (!client) { this.log(`Request failed: No Emacs client connected for session ${this.sessionId}`); throw new Error('No Emacs client connected'); } const id = ++this.requestId; const request = { jsonrpc: '2.0', id, method, params }; return new Promise((resolve, reject) => { this.pendingRequests.set(id, { resolve, reject }); this.log(`Emacs Request: ${method} with params: ${JSON.stringify(params)}`); client.send(JSON.stringify(request) + '\n', (error) => { if (error) { this.pendingRequests.delete(id); this.log(`Emacs Request Error: ${method} - ${error}`); reject(error); } }); // Timeout after 30 seconds setTimeout(() => { if (this.pendingRequests.has(id)) { this.pendingRequests.delete(id); this.log(`Emacs Request Timeout: ${method} (id=${id}) after 30 seconds`); reject(new Error(`Request timeout: ${method}`)); } }, 30000); }); } isConnected() { return this.clients.size > 0; } async sendRequest(method, params) { return this.request(method, params); } handleNotification(method, params) { this.log(`Received notification from Emacs: ${method}`); if (this.onNotification) { this.onNotification(method, params); } // Emit event for the notification this.emit('notification', method, params); } setNotificationHandler(handler) { this.onNotification = handler; } } exports.EmacsBridge = EmacsBridge; //# sourceMappingURL=emacs-bridge.js.map