Skip to content

Commit 8c3c326

Browse files
authored
🔌 fix: Reuse Undici Agents Per Transport and Close on Disconnect (danny-avila#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.
1 parent 3d7e263 commit 8c3c326

3 files changed

Lines changed: 625 additions & 20 deletions

File tree

‎packages/api/src/mcp/__tests__/MCPConnection.test.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,16 @@ describe('extractSSEErrorMessage', () => {
290290
};
291291
}
292292

293+
if (rawMessage === 'fetch failed') {
294+
return {
295+
message:
296+
'fetch failed (request aborted, likely after a timeout — connection may still be usable)',
297+
code,
298+
isProxyHint: false,
299+
isTransient: true,
300+
};
301+
}
302+
293303
return {
294304
message: rawMessage,
295305
code,
@@ -528,4 +538,24 @@ describe('extractSSEErrorMessage', () => {
528538
expect(result.isTransient).toBe(false);
529539
});
530540
});
541+
542+
describe('fetch failed errors', () => {
543+
it('should detect "fetch failed" as transient', () => {
544+
const error = { message: 'fetch failed' };
545+
const result = extractSSEErrorMessage(error);
546+
547+
expect(result.message).toContain('fetch failed');
548+
expect(result.message).toContain('request aborted');
549+
expect(result.isProxyHint).toBe(false);
550+
expect(result.isTransient).toBe(true);
551+
});
552+
553+
it('should not match "fetch failed" as a substring in a longer message', () => {
554+
const error = { message: 'Something fetch failed to do' };
555+
const result = extractSSEErrorMessage(error);
556+
557+
expect(result.message).toBe('Something fetch failed to do');
558+
expect(result.isTransient).toBe(false);
559+
});
560+
});
531561
});

0 commit comments

Comments
 (0)