diff --git a/docs/gemini-communication.md b/docs/gemini-communication.md index c707466..9c02e7a 100644 --- a/docs/gemini-communication.md +++ b/docs/gemini-communication.md @@ -62,7 +62,7 @@ The server responds: } ``` -### Notifications (Server-Sent Events) +### Server-Sent Events The server can push notifications to Gemini CLI using SSE: @@ -79,6 +79,10 @@ data: {"jsonrpc":"2.0","method":"notifications/context-update","params":{...}} data: {"jsonrpc":"2.0","method":"notifications/ide/diffAccepted","params":{...}} ``` +**Connection Stability (Heartbeat)**: +To prevent intermediate proxies, firewalls, or the Gemini CLI client from closing the connection during idle periods, the server sends a "keep-alive" comment (`: keep-alive\n\n`) every 15 seconds. These comments are ignored by the MCP protocol parser but keep the TCP connection active. + + ## MCP Protocol MCP (Model Context Protocol) defines a standard way for AI tools to interact with development environments. diff --git a/server/mcp/sse.go b/server/mcp/sse.go index 14d7da5..f6249ff 100644 --- a/server/mcp/sse.go +++ b/server/mcp/sse.go @@ -7,6 +7,7 @@ import ( "gemini-cli/types" "log" "net/http" + "time" ) // HandleSSE handles Server-Sent Events connections @@ -64,12 +65,22 @@ func (s *Server) HandleSSE(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprintf(w, ": connected\n\n") flusher.Flush() + // Create a ticker for heartbeats (every 15 seconds) + // This prevents intermediate proxies/clients from closing idle connections + ticker := time.NewTicker(15 * time.Second) + defer ticker.Stop() + // Send notifications to client for { select { case <-r.Context().Done(): log.Printf("SSE client disconnected") return + case <-ticker.C: + // Send an SSE comment (starting with ':') as a heartbeat + // This is ignored by standard SSE parsers but keeps the connection active + _, _ = fmt.Fprintf(w, ": keep-alive\n\n") + flusher.Flush() case notif := <-notifChan: data, err := json.Marshal(notif) if err != nil {