vercel/ai · error · MCPClientError
MCP SSE Transport Error: Connection closed unexpectedly
Error message
MCP SSE Transport Error: Connection closed unexpectedly
What it means
The SSE stream ended (reader returned done) while the transport still considered itself connected, i.e. the server closed the connection without a proper shutdown. processEvents throws MCPClientError so callers of establishConnection/send learn the session died instead of hanging.
Source
Thrown at packages/mcp/src/tool/mcp-sse-transport.ts:161
this.onerror?.(error);
return reject(error);
}
const stream = response.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(new EventSourceParserStream());
const reader = stream.getReader();
const processEvents = async () => {
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
if (this.connected) {
this.connected = false;
throw new MCPClientError({
message:
'MCP SSE Transport Error: Connection closed unexpectedly',
});
}
return;
}
const { event, data } = value;
if (event === 'endpoint') {
if (this.endpoint) {
continue;
}
const endpoint = new URL(data, this.url);
if (endpoint.origin !== this.url.origin) {
this.connected = false;View on GitHub (pinned to 69428b1f8b)
Solutions
- Wrap client operations in retry logic that recreates the client/transport and re-calls tools() after this error
- Increase/proxy keep-alive and idle timeouts (e.g. nginx proxy_read_timeout) or send periodic traffic to keep the SSE stream alive
- Verify the MCP server is not crashing or being redeployed mid-session; check server logs at the disconnect time
- Reconnect and re-list tools before continuing, since session state is lost
Example fix
// before
const client = await createMCPClient({ transport });
const tools = await client.tools(); // throws if server dropped
// after
async function withRetry(fn) {
try { return await fn(); }
catch (e) {
if (MCPClientError.isInstance(e)) { client = await createMCPClient({ transport: newTransport() }); return fn(); }
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
// check connection state before issuing requests
if (!clientIsHealthy()) { client = await createMCPClient({ transport: newTransport() }); } Type guard
function isConnectionClosedError(e: unknown): boolean {
return MCPClientError.isInstance(e) && e.message.includes('Connection closed unexpectedly');
} Try / catch
try {
result = await client.tools();
} catch (error) {
if (isConnectionClosedError(error)) {
await delay(backoff++ * 1000);
client = await createMCPClient({ transport: newTransport() });
result = await client.tools();
} else throw error;
} Prevention
- Configure generous keep-alive/idle timeouts on proxies fronting the SSE server
- Implement reconnect-with-backoff around all client calls
- Monitor server deploys/crashes; expect session loss on restart and re-fetch tools after reconnect
When it happens
Trigger: SSE server closes the response stream (crash, deploy, idle timeout, proxy terminating long-lived connections) while this.connected is still true; network interruption causing the body stream to end early.
Common situations: Long-running sessions behind reverse proxies (nginx/ALB) with aggressive idle timeouts; serverless or autoscaling platforms recycling connections; MCP server restart during an active session.
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- OpenCode event stream ended before the turn settled.
- Video generation timed out after ${timeoutMs}ms.
- The response body is empty.
- Incomplete Amazon Bedrock event-stream frame: ${buffer.lengt
- Failed to fetch the response.
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/96b1a4778eb3273b.
Report an issue: GitHub.