Skip to content

WIP: feat: add opt-in SSE keep-alive to streaming HTTP transports - #233

Draft
sideeffffect wants to merge 1 commit into
softwaremill:masterfrom
sideeffffect:feat/sse-keepalive
Draft

WIP: feat: add opt-in SSE keep-alive to streaming HTTP transports#233
sideeffffect wants to merge 1 commit into
softwaremill:masterfrom
sideeffffect:feat/sse-keepalive

Conversation

@sideeffffect

Copy link
Copy Markdown

Closes #9.

What

Long-running tool calls hold the Server-Sent Event response stream open; idle connections can be dropped by proxies/load balancers in between. Each streaming HTTP server transport gains an opt-in keepAlive: Option[FiniteDuration] (default None):

OxServerHttpTransport(List("mcp"), keepAlive = Some(15.seconds))

When set, a data-less ping Server-Sent Event (event: ping, no data: line) is emitted on the response stream at that interval while the stream is open. The events carry no data, so per the SSE spec they are not dispatched as messages — all three streaming clients already skip events via event.data.filter(_.nonEmpty) — while the bytes keep the connection warm.

How

Injected with each backend's idiomatic operator, only when configured (zero overhead otherwise):

  • oxmessages.merge(Flow.tick(interval, ping), propagateDoneLeft = true)
  • ziomessages.mergeHaltLeft(ZStream.fromSchedule(Schedule.spaced(interval)).as(ping))
  • pekko — built-in Flow[ServerSentEvent].keepAlive(interval, () => ping)

In every case the keep-alive stream is tied to the message stream's completion, so the response ends normally when the tool call finishes.

Notes on the design

This addresses the ask in #9 (a convenient keep-alive abstraction) without reviving the deprecated standalone SSE transport, and without a JSON-RPC ping request (which chimp clients would answer with MethodNotFound). sttp's ServerSentEvent has no comment field, so a data-less typed event is the cleanest keep-alive that stays invisible to MCP clients. Kept off by default so it can't change existing behaviour.

Tests

One spec per backend ({Ox,Zio,Pekko}ServerKeepAliveSpec) drives a transport with a short interval and a handler that keeps the stream open, asserting a data-less ping event is produced. sbt scalafmtCheckAll compile Test/compile compileDocs passes locally. Marked as draft/WIP.

🤖 Generated with Claude Code

Add a `keepAlive: Option[FiniteDuration]` parameter to the ox, zio and pekko
streaming HTTP server transports. When set, a data-less `ping` Server-Sent
Event is emitted on the response stream at that interval while the stream is
open, keeping idle connections alive through proxies during long-running tool
calls. The events carry no data and are ignored by MCP clients (all three
streaming clients skip events without data). Off by default.

Closes softwaremill#9

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sideeffffect sideeffffect changed the title feat: add opt-in SSE keep-alive to streaming HTTP transports WIP: feat: add opt-in SSE keep-alive to streaming HTTP transports Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSE auto ping

1 participant