feat: implement keep-alive functionality for SSE connections (#166)

Co-authored-by: samanhappy@qq.com <my6051199>
This commit is contained in:
samanhappy
2025-06-07 20:41:51 +08:00
committed by GitHub
parent 56c6447469
commit d9cbc5381a
4 changed files with 55 additions and 1 deletions

View File

@@ -20,6 +20,6 @@
} }
], ],
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
"no-undef": "off", "no-undef": "off"
} }
} }

View File

@@ -107,6 +107,11 @@ export const createServer = async (req: Request, res: Response): Promise<void> =
return; return;
} }
// Set default keep-alive interval for SSE servers if not specified
if ((config.type === 'sse' || (!config.type && config.url)) && !config.keepAliveInterval) {
config.keepAliveInterval = 60000; // Default 60 seconds for SSE servers
}
const result = await addServer(name, config); const result = await addServer(name, config);
if (result.success) { if (result.success) {
notifyToolChanged(); notifyToolChanged();
@@ -224,6 +229,11 @@ export const updateServer = async (req: Request, res: Response): Promise<void> =
return; return;
} }
// Set default keep-alive interval for SSE servers if not specified
if ((config.type === 'sse' || (!config.type && config.url)) && !config.keepAliveInterval) {
config.keepAliveInterval = 60000; // Default 60 seconds for SSE servers
}
const result = await updateMcpServer(name, config); const result = await updateMcpServer(name, config);
if (result.success) { if (result.success) {
notifyToolChanged(); notifyToolChanged();

View File

@@ -13,6 +13,38 @@ import { saveToolsAsVectorEmbeddings, searchToolsByVector } from './vectorSearch
const servers: { [sessionId: string]: Server } = {}; const servers: { [sessionId: string]: Server } = {};
// Helper function to set up keep-alive ping for SSE connections
const setupKeepAlive = (serverInfo: ServerInfo, serverConfig: ServerConfig): void => {
// Only set up keep-alive for SSE connections
if (!(serverInfo.transport instanceof SSEClientTransport)) {
return;
}
// Clear any existing interval first
if (serverInfo.keepAliveIntervalId) {
clearInterval(serverInfo.keepAliveIntervalId);
}
// Use configured interval or default to 60 seconds for SSE
const interval = serverConfig.keepAliveInterval || 60000;
serverInfo.keepAliveIntervalId = setInterval(async () => {
try {
if (serverInfo.client && serverInfo.status === 'connected') {
await serverInfo.client.ping();
console.log(`Keep-alive ping successful for server: ${serverInfo.name}`);
}
} catch (error) {
console.warn(`Keep-alive ping failed for server ${serverInfo.name}:`, error);
// TODO Consider handling reconnection logic here if needed
}
}, interval);
console.log(
`Keep-alive ping set up for server ${serverInfo.name} with interval ${interval / 1000} seconds`,
);
};
export const initUpstreamServers = async (): Promise<void> => { export const initUpstreamServers = async (): Promise<void> => {
await registerAllTools(true); await registerAllTools(true);
}; };
@@ -210,6 +242,9 @@ export const initializeClientsFromSettings = (isInit: boolean): ServerInfo[] =>
serverInfo.status = 'connected'; serverInfo.status = 'connected';
serverInfo.error = null; serverInfo.error = null;
// Set up keep-alive ping for SSE connections
setupKeepAlive(serverInfo, conf);
// Save tools as vector embeddings for search // Save tools as vector embeddings for search
saveToolsAsVectorEmbeddings(name, serverInfo.tools); saveToolsAsVectorEmbeddings(name, serverInfo.tools);
}) })
@@ -389,6 +424,13 @@ export const updateMcpServer = async (
function closeServer(name: string) { function closeServer(name: string) {
const serverInfo = serverInfos.find((serverInfo) => serverInfo.name === name); const serverInfo = serverInfos.find((serverInfo) => serverInfo.name === name);
if (serverInfo && serverInfo.client && serverInfo.transport) { if (serverInfo && serverInfo.client && serverInfo.transport) {
// Clear keep-alive interval if exists
if (serverInfo.keepAliveIntervalId) {
clearInterval(serverInfo.keepAliveIntervalId);
serverInfo.keepAliveIntervalId = undefined;
console.log(`Cleared keep-alive interval for server: ${serverInfo.name}`);
}
serverInfo.client.close(); serverInfo.client.close();
serverInfo.transport.close(); serverInfo.transport.close();
console.log(`Closed client and transport for server: ${serverInfo.name}`); console.log(`Closed client and transport for server: ${serverInfo.name}`);

View File

@@ -105,6 +105,7 @@ export interface ServerConfig {
env?: Record<string, string>; // Environment variables env?: Record<string, string>; // Environment variables
headers?: Record<string, string>; // HTTP headers for SSE/streamable-http servers headers?: Record<string, string>; // HTTP headers for SSE/streamable-http servers
enabled?: boolean; // Flag to enable/disable the server enabled?: boolean; // Flag to enable/disable the server
keepAliveInterval?: number; // Keep-alive ping interval in milliseconds (default: 60000ms for SSE servers)
tools?: Record<string, { enabled: boolean; description?: string }>; // Tool-specific configurations with enable/disable state and custom descriptions tools?: Record<string, { enabled: boolean; description?: string }>; // Tool-specific configurations with enable/disable state and custom descriptions
} }
@@ -118,6 +119,7 @@ export interface ServerInfo {
transport?: SSEClientTransport | StdioClientTransport | StreamableHTTPClientTransport; // Transport mechanism used transport?: SSEClientTransport | StdioClientTransport | StreamableHTTPClientTransport; // Transport mechanism used
createTime: number; // Timestamp of when the server was created createTime: number; // Timestamp of when the server was created
enabled?: boolean; // Flag to indicate if the server is enabled enabled?: boolean; // Flag to indicate if the server is enabled
keepAliveIntervalId?: NodeJS.Timeout; // Timer ID for keep-alive ping interval
} }
// Details about a tool available on the server // Details about a tool available on the server