UNPKG

mcpdog

Version:

MCPDog - Universal MCP Server Manager with Web Interface

199 lines 6.51 kB
/** * MCPDog Daemon Client * Used to connect to the daemon and communicate */ import { EventEmitter } from 'events'; import { Socket } from 'net'; export class DaemonClient extends EventEmitter { socket; config; isConnected = false; reconnectTimer; requestCounter = 0; pendingRequests = new Map(); constructor(config) { super(); this.config = { host: 'localhost', port: 9999, reconnect: true, reconnectInterval: 5000, ...config }; this.socket = new Socket(); this.setupSocket(); } setupSocket() { this.socket.on('connect', () => { if (!this.config.silent) { console.log('[DAEMON-CLIENT] Connected to daemon'); } this.isConnected = true; this.clearReconnectTimer(); // Send handshake message this.send({ type: 'handshake', clientType: this.config.clientType }); this.emit('connected'); }); this.socket.on('data', (data) => { const lines = data.toString().split('\n').filter(line => line.trim()); lines.forEach(line => { try { const message = JSON.parse(line); this.handleMessage(message); } catch (error) { if (!this.config.silent) { console.error('[DAEMON-CLIENT] Invalid message:', error); } } }); }); this.socket.on('close', () => { if (!this.config.silent) { console.log('[DAEMON-CLIENT] Disconnected from daemon'); } this.isConnected = false; this.emit('disconnected'); if (this.config.reconnect) { this.scheduleReconnect(); } }); this.socket.on('error', (error) => { if (!this.config.silent) { console.error('[DAEMON-CLIENT] Socket error:', error); } this.emit('error', error); }); } handleMessage(message) { switch (message.type) { case 'welcome': if (!this.config.silent) { console.log(`[DAEMON-CLIENT] Welcome, client ID: ${message.clientId}`); } this.emit('welcome', message); break; case 'handshake-ack': if (!this.config.silent) { console.log('[DAEMON-CLIENT] Handshake acknowledged'); } this.emit('ready', message.serverStatus); break; case 'mcp-response': // MCP request response const responseCallback = this.pendingRequests.get(message.requestId); if (responseCallback) { responseCallback(message.response); this.pendingRequests.delete(message.requestId); } break; case 'mcp-error': // MCP request error const errorCallback = this.pendingRequests.get(message.requestId); if (errorCallback) { errorCallback({ error: message.error }); this.pendingRequests.delete(message.requestId); } break; case 'server-started': case 'server-stopped': case 'routes-updated': case 'tool-called': case 'config-changed': // Forward events this.emit(message.type, message.data); break; case 'status': this.emit('status', message.status); break; case 'tools': this.emit('tools', message.tools); break; default: if (!this.config.silent) { console.warn('[DAEMON-CLIENT] Unknown message type:', message.type); } } } send(message) { if (this.isConnected) { this.socket.write(JSON.stringify(message) + '\n'); } else { if (!this.config.silent) { console.error('[DAEMON-CLIENT] Cannot send message, not connected'); } } } scheduleReconnect() { if (this.reconnectTimer) return; if (!this.config.silent) { console.log(`[DAEMON-CLIENT] Scheduling reconnect in ${this.config.reconnectInterval}ms`); } this.reconnectTimer = setTimeout(() => { if (!this.config.silent) { console.log('[DAEMON-CLIENT] Attempting to reconnect...'); } this.connect(); }, this.config.reconnectInterval); } clearReconnectTimer() { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = undefined; } } // Public API async connect() { return new Promise((resolve, reject) => { const onConnect = () => { this.off('error', onError); resolve(); }; const onError = (error) => { this.off('connected', onConnect); reject(error); }; this.once('connected', onConnect); this.once('error', onError); this.socket.connect(this.config.port, this.config.host); }); } disconnect() { this.config.reconnect = false; this.clearReconnectTimer(); this.socket.end(); } // MCP protocol forwarding async sendMCPRequest(request) { return new Promise((resolve) => { const requestId = `req_${++this.requestCounter}`; this.pendingRequests.set(requestId, resolve); this.send({ type: 'mcp-request', requestId, request }); }); } // Get status getStatus() { this.send({ type: 'get-status' }); } // Get tools list getTools() { this.send({ type: 'get-tools' }); } // Reload configuration reloadConfig() { this.send({ type: 'reload-config' }); } get connected() { return this.isConnected; } } //# sourceMappingURL=daemon-client.js.map