feat(gateway): Add configurable MCP heartbeat - #2
Conversation
|
All contributors have signed the Searchbase CLA. |
Idle MCP connections were torn down by clients with a shorter read timeout, since nothing was sent over the wire to keep them alive. The reference MCP SSE client, for example, defaults sse_read_timeout to 300s. Add an optional periodic heartbeat on both the SSE and Streamable HTTP transports, off by default so existing deployments are unchanged. Enable it with SEARCHBASE_MCP_HEARTBEAT_ENABLED and tune it with SEARCHBASE_MCP_HEARTBEAT_INTERVAL (seconds, default 60). Intervals below 15s fail startup, guarding the gateway and the host running it against an accidentally aggressive configuration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1331ebb to
763bd8a
Compare
|
recheck |
There was a problem hiding this comment.
🟡 Changes recommended
The root docker-compose.yml currently enables heartbeat by default, which conflicts with the stated “off by default unless explicitly enabled” behavior and changes default behavior for compose-based runs.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds an optional, configurable MCP transport heartbeat in the Go search-gateway to keep idle SSE and Streamable HTTP connections from being dropped by clients with shorter read timeouts, while keeping the feature disabled by default via new settings and validation.
Changes:
- Introduces MCP heartbeat settings (
SEARCHBASE_MCP_HEARTBEAT_ENABLED,SEARCHBASE_MCP_HEARTBEAT_INTERVAL) with startup validation (min 15s when enabled). - Wires heartbeat configuration into MCP SSE + Streamable HTTP route registration and logs the effective settings at startup.
- Adds unit tests and updates docs/architecture notes to document the new configuration and behavior.
File summaries
| File | Description |
|---|---|
| search-gateway/internal/settings/settings.go | Adds MCP heartbeat defaults, loads them into settings, and validates them at startup. |
| search-gateway/internal/settings/settings_test.go | Adds coverage for heartbeat defaults, env overrides, and min-interval validation behavior. |
| search-gateway/internal/settings/mcp.go | New settings group for MCP heartbeat with minimum interval enforcement. |
| search-gateway/internal/mcp/server.go | Extends MCP route registration to optionally enable keepalive/heartbeat on both transports. |
| search-gateway/cmd/server/main.go | Passes MCP heartbeat settings into route registration and logs the configured values. |
| docs/content/docs/self-hosting.md | Documents the new environment variables in the configuration table. |
| docs/content/docs/mcp-integration.md | Adds guidance on idle timeouts and enabling heartbeat. |
| docker-compose.yml | Adds MCP heartbeat env vars to the dev compose configuration. |
| AGENTS.md | Updates architecture/spec notes to include the new MCP heartbeat settings and validation pattern. |
Review details
- Files reviewed: 9/9 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
I have read the CLA Document and I hereby sign the CLA |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Idle MCP connections were torn down by clients with a shorter read timeout, since nothing was sent over the wire to keep them alive. The reference MCP SSE client, for example, defaults sse_read_timeout to 300s.
Add an optional periodic heartbeat on both the SSE and Streamable HTTP transports, off by default so existing deployments are unchanged. Enable it with SEARCHBASE_MCP_HEARTBEAT_ENABLED and tune it with SEARCHBASE_MCP_HEARTBEAT_INTERVAL (seconds, default 60). Intervals below 15s fail startup, guarding the gateway and the host running it against an accidentally aggressive configuration.
Summary
Idle MCP connections were being torn down by clients with a shorter read timeout,
because nothing was ever sent over the wire to keep them alive. The reference MCP
SSE client, for example, defaults
sse_read_timeoutto 300s, so an idle sessiondies after roughly five minutes.
This adds an optional periodic heartbeat on both MCP transports:
enabled, so current behavior is preserved.
SEARCHBASE_MCP_HEARTBEAT_ENABLED(bool, defaultfalse) turns it on.SEARCHBASE_MCP_HEARTBEAT_INTERVAL(seconds, default60) tunes it, and isonly used when the heartbeat is enabled.
clear error instead of being silently accepted. This mirrors how
Tracing.validate()already rejects a missing tracing endpoint, and guards thegateway and the host running it against an accidentally aggressive
configuration (e.g. setting
1assuming milliseconds).Implementation notes:
internal/settings/mcp.gowith anMcpstruct, getters, and avalidate()method, mirroring the existing
Otel/Tracingpattern inotel.go.MCPServer.RegisterRoutesnow takes the enabled flag and the interval.server.WithKeepAlive(enabled)is always passed;WithKeepAliveIntervalandWithHeartbeatIntervalare only appended when enabled — worth noting thatWithKeepAliveIntervalimplicitly setskeepAlive = truein mcp-go, so addingit unconditionally would re-enable keepalive regardless of the flag.
Testing
go build ./...andgo test ./...pass.false/60s), env var override, rejectionbelow the floor (
1sand14s), acceptance at exactly15s, fallback to thedefault when enabled with no interval set, and that a too-low interval is
ignored while the heartbeat is disabled.
event: endpoint, zero pings.ENABLED=true,INTERVAL=15: SSE emits 2{"method":"ping"}in 40s.ENABLED=true, no interval: 1 ping in 70s, confirming the 60s default.ENABLED=true,INTERVAL=15: Streamable HTTP GET stream (after aninitializehandshake) also emits 2 pings in 40s.ENABLED=truewithINTERVAL=1or14: server refuses to start withmcp heartbeat interval too low: SEARCHBASE_MCP_HEARTBEAT_INTERVAL must be >= 15s, got 1s.ENABLED=falsewithINTERVAL=1: starts normally, heartbeat off.hugo --destination /tmp/searchbase-docs-build.Not covered: an end-to-end soak with a real MCP client left idle past its read
timeout, and the Docker Compose path.
Checklist
AGENTS.mdfor architecture and documentation requirements.Docs updated: both variables added to the environment variable table in
docs/content/docs/self-hosting.md, a new "Idle Connection Timeouts" section indocs/content/docs/mcp-integration.md, and the gateway responsibilities andsettings notes in
AGENTS.md.Contributor License Agreement
If this is your first contribution, the CLA Assistant bot will comment with signing instructions. Pull requests cannot be merged until all contributors have signed the CLA.