@civic/nexus-bridge
Version:
Stdio <-> HTTP/SSE MCP bridge with Civic auth handling
83 lines • 3 kB
JavaScript
/**
* clientDetection.ts
*
* Utility for detecting client disconnection using MCP protocol's ping mechanism.
* This helps bridge detect when the client process has closed even when the stdio
* transport doesn't notify about disconnection.
*/
import { messageFromError } from '../utils.js';
import { logger } from './logger.js';
/**
* Monitors client connection by using MCP ping mechanism
*/
export class ClientConnectionMonitor {
server;
pingInterval = null;
pingFailCount = 0;
isRunning = false;
options;
/**
* Creates a new monitor for detecting client disconnection
*/
constructor(server, options) {
this.server = server;
this.options = {
maxPingFailures: options?.maxPingFailures ?? 3,
pingIntervalMs: options?.pingIntervalMs ?? 10000,
pingTimeoutMs: options?.pingTimeoutMs ?? 5000,
onDisconnection: options?.onDisconnection ?? (() => {
logger.info("Client disconnection detected, but no handler was provided");
})
};
}
/**
* Start monitoring for client disconnection
*/
start() {
// Prevent multiple monitors
if (this.isRunning) {
this.stop();
}
this.isRunning = true;
this.pingFailCount = 0;
// Set up ping interval
this.pingInterval = setInterval(async () => {
try {
logger.debug("[ClientMonitor] Sending ping to detect client connection");
// Use the ping utility built into the MCP protocol
await this.server.ping();
// Reset failure count on successful ping
if (this.pingFailCount > 0) {
logger.info("[ClientMonitor] Client ping succeeded after previous failures");
this.pingFailCount = 0;
}
}
catch (error) {
this.pingFailCount++;
logger.warn(`[ClientMonitor] Client ping failed (${this.pingFailCount}/${this.options.maxPingFailures}):`, messageFromError(error));
// If we've reached the failure threshold, assume client disconnection
if (this.pingFailCount >= this.options.maxPingFailures) {
logger.error("[ClientMonitor] Client appears to be disconnected after multiple ping failures");
this.stop();
// Notify about disconnection
this.options.onDisconnection();
}
}
}, this.options.pingIntervalMs);
// Clean up on process exit
process.once('exit', () => {
this.stop();
});
}
/**
* Stop monitoring for client disconnection
*/
stop() {
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
this.isRunning = false;
}
}
//# sourceMappingURL=clientDetection.js.map