claude-code-emacs-mcp-server
Version:
MCP server for Claude Code Emacs integration
197 lines • 7.84 kB
JavaScript
;
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