🔌 fix: Reuse Undici Agents Per Transport and Close on Disconnect (#11935)

* fix: error handling for transient HTTP request failures in MCP connection

- Added specific handling for the "fetch failed" TypeError, indicating that the request was aborted likely due to a timeout, while the connection remains usable.
- Updated the error message to provide clearer context for users regarding the transient nature of the error.

* refactor: MCPConnection with Agent Lifecycle Management

- Introduced an array to manage undici Agents, ensuring they are reused across requests and properly closed during disconnection.
- Updated the custom fetch and SSE connection methods to utilize the new Agent management system.
- Implemented error handling for SSE 404 responses based on session presence, improving connection stability.
- Added integration tests to validate the Agent lifecycle, ensuring agents are reused and closed correctly.

* fix: enhance error handling and connection management in MCPConnection

- Updated SSE connection timeout handling to use nullish coalescing for better defaulting.
- Improved the connection closure process by ensuring agents are properly closed and errors are logged non-fatally.
- Added tests to validate handling of "fetch failed" errors, marking them as transient and providing clearer messaging for users.

* fix: update timeout handling in MCPConnection for improved defaulting

- Changed timeout handling in MCPConnection to use logical OR instead of nullish coalescing for better default value assignment.
- Ensured consistent timeout behavior for both standard and SSE connections, enhancing reliability in connection management.
This commit is contained in:
Danny Avila 2026-02-24 19:06:06 -05:00 committed by GitHub
parent 3d7e26382e
commit 8c3c326440
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 625 additions and 20 deletions

View file

@ -290,6 +290,16 @@ describe('extractSSEErrorMessage', () => {
};
}
if (rawMessage === 'fetch failed') {
return {
message:
'fetch failed (request aborted, likely after a timeout — connection may still be usable)',
code,
isProxyHint: false,
isTransient: true,
};
}
return {
message: rawMessage,
code,
@ -528,4 +538,24 @@ describe('extractSSEErrorMessage', () => {
expect(result.isTransient).toBe(false);
});
});
describe('fetch failed errors', () => {
it('should detect "fetch failed" as transient', () => {
const error = { message: 'fetch failed' };
const result = extractSSEErrorMessage(error);
expect(result.message).toContain('fetch failed');
expect(result.message).toContain('request aborted');
expect(result.isProxyHint).toBe(false);
expect(result.isTransient).toBe(true);
});
it('should not match "fetch failed" as a substring in a longer message', () => {
const error = { message: 'Something fetch failed to do' };
const result = extractSSEErrorMessage(error);
expect(result.message).toBe('Something fetch failed to do');
expect(result.isTransient).toBe(false);
});
});
});