MCP Connection Issues: Why My MCP Server Kept Dropping Connections and How I Fixed It (After 86 Production Outages) A developer discovered that random disconnections in their MCP server were caused by idle timeouts on SSE connections, with proxies dropping connections after 30-100 seconds of inactivity. The fix involved implementing SSE heartbeat comments every 30 seconds to keep the connection alive without interrupting the event stream. The solution was demonstrated using Java Spring Boot's SseEmitter with a scheduled heartbeat task. Honestly, I thought I had everything figured out after 86 outages. Logging was done. Health checks were in place. Tool discovery had fuzzy matching. Authentication handled every possible location. Rate limiting was working. But my MCP server kept dropping connections randomly. Three days of debugging later, I found the problem — and it wasn't what I expected. It was all about SSE Server-Sent Events streaming. If you're building an MCP server, you need to read this. I saved you three days of headache. Let me set the scene. My MCP server has been running in production for a while. Everything works fine most of the time. But randomly, clients would get disconnected mid-request. Sometimes it happened after 30 seconds. Sometimes after 2 minutes. Sometimes it worked perfectly. The worst part? It only happened in production. Locally, everything worked fine. Classic "it works on my machine" situation. Here's what the error looked like from the client side: Error: disconnected before response completed That's it. No additional info. Just ... disconnected. Like any good developer, I started blaming the usual suspects: I spent half a day going through this list. Nothing worked. The problem kept happening. MCP uses SSE for the connection from server to client. The client sends the JSON-RPC request over HTTP POST, and the server streams responses back via SSE. Here's what I missed: SSE connections are idle connections . If nothing is being sent, some proxies/load balancers will drop them. Wait, but how long is idle? Depends on your hosting. On Fly.io, the default idle timeout is 75 seconds. On Cloudflare, it's 100 seconds. On Heroku, it's 55 seconds. And here's the thing about MCP: most of the time, the connection is idle. The client connects, waits for the LLM to process the request, then the server sends the response. If the LLM takes more than your proxy's idle timeout ... boom . Connection dropped. I know, I know. You're thinking "just increase the timeout." But you can't always do that. Some platforms don't let you change it. And even if you can, what if the LLM is processing a really big request that takes 5 minutes? You can't just set the timeout to 10 minutes – that creates other problems. SSE allows any comment line starting with : to be sent as a heartbeat. These comments don't interrupt the actual event stream, they just keep the connection alive. Here's what it looks like on the wire: : heartbeat data: {"jsonrpc":"2.0","method":"tools/list","result":{"tools": ... } : heartbeat : heartbeat data: {"jsonrpc":"2.0","method":"tools/call","result":{"content": ... } The client just ignores the comment lines. But they count as activity, so the proxy doesn't drop the connection. Brilliant, right? Simple, effective, almost no code. I'm building my MCP server with Java Spring Boot, so let me show you the complete implementation. It's about 30 lines of code. First, the SSE emitter configuration with heartbeats: python import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class McpSseConnection { private final SseEmitter emitter; private final ScheduledExecutorService scheduler; private ScheduledFuture