Skip to content

feat(gateway): Add configurable MCP heartbeat - #2

Merged
coolapso merged 2 commits into
coolapso:mainfrom
c0lap5o:feat/mcp-configurable-heartbeat
Sep 9, 2026
Merged

coolapso merged 2 commits into
coolapso:mainfrom
c0lap5o:feat/mcp-configurable-heartbeat

Conversation

@c0lap5o

@c0lap5o c0lap5o commented Sep 8, 2026

Copy link
Copy Markdown
Shrek-Memes-15-1024x1024-4081206483

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_timeout to 300s, so an idle session
dies after roughly five minutes.

This adds an optional periodic heartbeat on both MCP transports:

  • Off by default. Nothing changes for existing deployments unless explicitly
    enabled, so current behavior is preserved.
  • SEARCHBASE_MCP_HEARTBEAT_ENABLED (bool, default false) turns it on.
  • SEARCHBASE_MCP_HEARTBEAT_INTERVAL (seconds, default 60) tunes it, and is
    only used when the heartbeat is enabled.
  • A minimum of 15s is enforced at startup: a lower value fails fast with a
    clear error instead of being silently accepted. This mirrors how
    Tracing.validate() already rejects a missing tracing endpoint, and guards the
    gateway and the host running it against an accidentally aggressive
    configuration (e.g. setting 1 assuming milliseconds).

Implementation notes:

  • New internal/settings/mcp.go with an Mcp struct, getters, and a validate()
    method, mirroring the existing Otel / Tracing pattern in otel.go.
  • MCPServer.RegisterRoutes now takes the enabled flag and the interval.
    server.WithKeepAlive(enabled) is always passed; WithKeepAliveInterval and
    WithHeartbeatInterval are only appended when enabled — worth noting that
    WithKeepAliveInterval implicitly sets keepAlive = true in mcp-go, so adding
    it unconditionally would re-enable keepalive regardless of the flag.
  • Both values are included in the startup log line.

Testing

  • go build ./... and go test ./... pass.
  • New settings tests cover: defaults (false / 60s), env var override, rejection
    below the floor (1s and 14s), acceptance at exactly 15s, fallback to the
    default when enabled with no interval set, and that a too-low interval is
    ignored while the heartbeat is disabled.
  • Verified at runtime against a locally built gateway:
    • No env vars set: SSE idle for 40s emits only 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 an
      initialize handshake) also emits 2 pings in 40s.
    • ENABLED=true with INTERVAL=1 or 14: server refuses to start with
      mcp heartbeat interval too low: SEARCHBASE_MCP_HEARTBEAT_INTERVAL must be >= 15s, got 1s.
    • ENABLED=false with INTERVAL=1: starts normally, heartbeat off.
  • Docs site builds: 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

  • I updated documentation where relevant.
  • I checked AGENTS.md for 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 in
docs/content/docs/mcp-integration.md, and the gateway responsibilities and
settings 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.

@github-actions

github-actions Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

All contributors have signed the Searchbase CLA.
Posted by the CLA Assistant Lite bot.

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>
@c0lap5o
c0lap5o force-pushed the feat/mcp-configurable-heartbeat branch from 1331ebb to 763bd8a Compare September 8, 2026 20:30
@c0lap5o

c0lap5o commented Sep 8, 2026

Copy link
Copy Markdown
Author

recheck

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment thread docker-compose.yml Outdated
@c0lap5o

c0lap5o commented Sep 8, 2026

Copy link
Copy Markdown
Author

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>
@coolapso
coolapso merged commit 903689c into coolapso:main Sep 9, 2026
0 of 3 checks passed
github-actions Bot added a commit that referenced this pull request Sep 9, 2026
@c0lap5o
c0lap5o deleted the feat/mcp-configurable-heartbeat branch September 9, 2026 17:07
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.

3 participants