@pluggedin/pluggedin-mcp-proxy
Version:
Unified MCP proxy that aggregates all your MCP servers (STDIO, SSE, Streamable HTTP) into one powerful interface. Access any tool through a single connection, search across unified documents with built-in RAG, and receive notifications from any model. Tes
213 lines (212 loc) • 6.73 kB
JavaScript
/**
* Connection State Manager for Lazy Tool Loading
*
* Manages connection-scoped tool enablement state.
* Tools are enabled per connection and cleared on disconnect.
* This is NOT persistent storage - state is lost on server restart.
*/
import { debugLog, debugError } from './debug-log.js';
class ConnectionManager {
connections = new Map();
cleanupInterval = null;
CLEANUP_INTERVAL_MS = 60000; // 1 minute
CONNECTION_TIMEOUT_MS = 3600000; // 1 hour
constructor() {
this.startCleanupInterval();
}
/**
* Initialize a new connection or get existing one
*/
initConnection(connectionId, profileUuid) {
let state = this.connections.get(connectionId);
if (!state) {
state = {
connectionId,
profileUuid,
enabledTools: new Set(),
serverTools: new Map(),
createdAt: new Date(),
lastAccessed: new Date(),
};
this.connections.set(connectionId, state);
debugLog(`[ConnectionManager] Initialized connection: ${connectionId}`);
}
else {
state.lastAccessed = new Date();
}
return state;
}
/**
* Get connection state
*/
getConnection(connectionId) {
const state = this.connections.get(connectionId);
if (state) {
state.lastAccessed = new Date();
}
return state;
}
/**
* Enable tools for a connection
*/
enableTools(connectionId, toolNames, serverUuid) {
const state = this.connections.get(connectionId);
if (!state) {
debugError(`[ConnectionManager] Connection not found: ${connectionId}`);
return false;
}
// Add tools to enabled set
toolNames.forEach(tool => {
state.enabledTools.add(tool);
// Track which server the tool belongs to
if (serverUuid) {
if (!state.serverTools.has(serverUuid)) {
state.serverTools.set(serverUuid, new Set());
}
state.serverTools.get(serverUuid).add(tool);
}
});
state.lastAccessed = new Date();
debugLog(`[ConnectionManager] Enabled ${toolNames.length} tools for connection ${connectionId}`);
return true;
}
/**
* Disable tools for a connection
*/
disableTools(connectionId, toolNames) {
const state = this.connections.get(connectionId);
if (!state) {
return false;
}
toolNames.forEach(tool => {
state.enabledTools.delete(tool);
// Remove from server tracking
state.serverTools.forEach((tools) => {
tools.delete(tool);
});
});
state.lastAccessed = new Date();
return true;
}
/**
* Get enabled tools for a connection
*/
getEnabledTools(connectionId) {
const state = this.connections.get(connectionId);
if (!state) {
return [];
}
state.lastAccessed = new Date();
return Array.from(state.enabledTools);
}
/**
* Check if a tool is enabled for a connection
*/
isToolEnabled(connectionId, toolName) {
const state = this.connections.get(connectionId);
if (!state) {
return false;
}
state.lastAccessed = new Date();
return state.enabledTools.has(toolName);
}
/**
* Clear connection state
*/
clearConnection(connectionId) {
if (this.connections.delete(connectionId)) {
debugLog(`[ConnectionManager] Cleared connection: ${connectionId}`);
}
}
/**
* Get connection ID from request context
* Handles different transport types
*/
getConnectionId(request) {
// For STDIO transport, use process ID
if (!request.transport || request.transport === 'stdio') {
return `stdio_${process.pid}`;
}
// For HTTP/SSE transports, check headers
if (request.headers) {
// Check for session header
const sessionId = request.headers['x-mcp-session-id'];
if (sessionId) {
return `http_${sessionId}`;
}
// Check for connection ID header
const connectionId = request.headers['x-mcp-connection-id'];
if (connectionId) {
return connectionId;
}
}
// For WebSocket, use socket ID
if (request.socketId) {
return `ws_${request.socketId}`;
}
// Fallback to timestamp-based ID
return `unknown_${Date.now()}`;
}
/**
* Start cleanup interval to remove stale connections
*/
startCleanupInterval() {
this.cleanupInterval = setInterval(() => {
this.cleanupStaleConnections();
}, this.CLEANUP_INTERVAL_MS);
}
/**
* Clean up stale connections
*/
cleanupStaleConnections() {
const now = Date.now();
const staleConnectionIds = [];
this.connections.forEach((state, connectionId) => {
const age = now - state.lastAccessed.getTime();
if (age > this.CONNECTION_TIMEOUT_MS) {
staleConnectionIds.push(connectionId);
}
});
staleConnectionIds.forEach(id => {
this.clearConnection(id);
});
if (staleConnectionIds.length > 0) {
debugLog(`[ConnectionManager] Cleaned up ${staleConnectionIds.length} stale connections`);
}
}
/**
* Shutdown the connection manager
*/
shutdown() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = null;
}
this.connections.clear();
debugLog('[ConnectionManager] Shutdown complete');
}
/**
* Get statistics about connections
*/
getStats() {
const now = Date.now();
let totalEnabledTools = 0;
const connectionDetails = [];
this.connections.forEach((state, connectionId) => {
const toolCount = state.enabledTools.size;
totalEnabledTools += toolCount;
connectionDetails.push({
connectionId,
toolCount,
age: now - state.createdAt.getTime(),
});
});
return {
totalConnections: this.connections.size,
totalEnabledTools,
connectionDetails,
};
}
}
// Export singleton instance
export const connectionManager = new ConnectionManager();