mcpdog
Version:
MCPDog - Universal MCP Server Manager with Web Interface
199 lines • 6.51 kB
JavaScript
/**
* 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