mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 02:39:19 -05:00
Add documentation for transport event handlers fix
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
This commit is contained in:
179
docs/transport-event-handlers-fix.md
Normal file
179
docs/transport-event-handlers-fix.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# Transport Event Handlers Fix
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
|
||||||
|
After adding SSE (Server-Sent Events) or Streamable HTTP protocol servers, the server status did not automatically update when connections failed or closed. The status remained "connected" even when the connection was lost.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
The MCP SDK provides `onclose` and `onerror` event handlers for all transport types (SSE, StreamableHTTP, and stdio), but the MCPHub implementation was not setting up these handlers. This meant that:
|
||||||
|
|
||||||
|
1. When a connection closed unexpectedly, the server status remained "connected"
|
||||||
|
2. When transport errors occurred, the status was not updated
|
||||||
|
3. Users could not see the actual connection state in the dashboard
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Added a `setupTransportEventHandlers()` helper function that:
|
||||||
|
|
||||||
|
1. Sets up `onclose` handler to update status to 'disconnected' when connections close
|
||||||
|
2. Sets up `onerror` handler to update status and capture error messages
|
||||||
|
3. Clears keep-alive ping intervals when connections fail
|
||||||
|
4. Logs connection state changes for debugging
|
||||||
|
|
||||||
|
The handlers are set up in two places:
|
||||||
|
|
||||||
|
1. After successful initial connection in `initializeClientsFromSettings()`
|
||||||
|
2. After reconnection in `callToolWithReconnect()`
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### File: `src/services/mcpService.ts`
|
||||||
|
|
||||||
|
#### New Function: `setupTransportEventHandlers()`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const setupTransportEventHandlers = (serverInfo: ServerInfo): void => {
|
||||||
|
if (!serverInfo.transport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up onclose handler to update status when connection closes
|
||||||
|
serverInfo.transport.onclose = () => {
|
||||||
|
console.log(`Transport closed for server: ${serverInfo.name}`);
|
||||||
|
if (serverInfo.status === 'connected') {
|
||||||
|
serverInfo.status = 'disconnected';
|
||||||
|
serverInfo.error = 'Connection closed';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear keep-alive interval if it exists
|
||||||
|
if (serverInfo.keepAliveIntervalId) {
|
||||||
|
clearInterval(serverInfo.keepAliveIntervalId);
|
||||||
|
serverInfo.keepAliveIntervalId = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up onerror handler to update status on connection errors
|
||||||
|
serverInfo.transport.onerror = (error: Error) => {
|
||||||
|
console.error(`Transport error for server ${serverInfo.name}:`, error);
|
||||||
|
if (serverInfo.status === 'connected') {
|
||||||
|
serverInfo.status = 'disconnected';
|
||||||
|
serverInfo.error = `Transport error: ${error.message}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear keep-alive interval if it exists
|
||||||
|
if (serverInfo.keepAliveIntervalId) {
|
||||||
|
clearInterval(serverInfo.keepAliveIntervalId);
|
||||||
|
serverInfo.keepAliveIntervalId = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`Transport event handlers set up for server: ${serverInfo.name}`);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Integration Points
|
||||||
|
|
||||||
|
1. **Initial Connection** - Added call after successful connection:
|
||||||
|
```typescript
|
||||||
|
if (!dataError) {
|
||||||
|
serverInfo.status = 'connected';
|
||||||
|
serverInfo.error = null;
|
||||||
|
|
||||||
|
// Set up transport event handlers for connection monitoring
|
||||||
|
setupTransportEventHandlers(serverInfo);
|
||||||
|
|
||||||
|
// Set up keep-alive ping for SSE connections
|
||||||
|
setupKeepAlive(serverInfo, expandedConf);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Reconnection** - Added call after reconnection succeeds:
|
||||||
|
```typescript
|
||||||
|
// Update server info with new client and transport
|
||||||
|
serverInfo.client = client;
|
||||||
|
serverInfo.transport = newTransport;
|
||||||
|
serverInfo.status = 'connected';
|
||||||
|
|
||||||
|
// Set up transport event handlers for the new connection
|
||||||
|
setupTransportEventHandlers(serverInfo);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Automated Tests
|
||||||
|
|
||||||
|
All 169 existing tests pass, including:
|
||||||
|
- Integration tests for SSE transport (`tests/integration/sse-service-real-client.test.ts`)
|
||||||
|
- Integration tests for StreamableHTTP transport
|
||||||
|
- Unit tests for MCP service functionality
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
|
||||||
|
To manually test the fix:
|
||||||
|
|
||||||
|
1. **Add an SSE server** to `mcp_settings.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"test-sse-server": {
|
||||||
|
"type": "sse",
|
||||||
|
"url": "http://localhost:9999/sse",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Start MCPHub**: `pnpm dev`
|
||||||
|
|
||||||
|
3. **Observe the behavior**:
|
||||||
|
- Server will initially show as "connecting"
|
||||||
|
- When connection fails (port 9999 not available), status will update to "disconnected"
|
||||||
|
- Error message will show: "Transport error: ..." or "Connection closed"
|
||||||
|
|
||||||
|
4. **Test connection recovery**:
|
||||||
|
- Start an MCP server on the configured URL
|
||||||
|
- The status should update to "connected" when available
|
||||||
|
- Stop the MCP server
|
||||||
|
- The status should update back to "disconnected"
|
||||||
|
|
||||||
|
### StreamableHTTP Testing
|
||||||
|
|
||||||
|
1. **Add a StreamableHTTP server** to `mcp_settings.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"test-http-server": {
|
||||||
|
"type": "streamable-http",
|
||||||
|
"url": "http://localhost:9999/mcp",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Follow the same testing steps as SSE
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Accurate Status**: Server status now reflects actual connection state
|
||||||
|
2. **Better UX**: Users can see when connections fail in real-time
|
||||||
|
3. **Debugging**: Error messages help diagnose connection issues
|
||||||
|
4. **Resource Management**: Keep-alive intervals are properly cleaned up on connection failures
|
||||||
|
5. **Consistent Behavior**: All transport types (SSE, StreamableHTTP, stdio) now have proper event handling
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
- **Backwards Compatible**: No breaking changes to existing functionality
|
||||||
|
- **SDK Version**: Works with `@modelcontextprotocol/sdk` v1.20.2+
|
||||||
|
- **Node.js**: Compatible with all supported Node.js versions
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
Potential improvements for the future:
|
||||||
|
|
||||||
|
1. Add automatic reconnection logic for transient failures
|
||||||
|
2. Add connection health metrics (uptime, error count)
|
||||||
|
3. Emit events for UI notifications when status changes
|
||||||
|
4. Add configurable retry strategies per server
|
||||||
Reference in New Issue
Block a user