Skip to content

Backend tool-refresh creates a new "background idle session" every cycle, never cleaned up — unbounded session/resource growth on Streamable HTTP backends #324

Description

@pos-ei-don

Summary

When MetaMCP refreshes a backend server's tool list (tool-list cache/hash check), it opens a new connection to the backend and logs Created background idle session for server [<name>] <uuid>. This happens repeatedly over the gateway's uptime, but the previously created idle session is never closed. For backends that hold per-session state (e.g. a browser context, as with the official Playwright MCP server), this leaks one browser session per refresh cycle until the backend runs out of resources and dies.

Environment

  • MetaMCP image: ghcr.io/metatool-ai/metamcp:latest, image created 2025-12-11 (sha256:6d5e0cba...)
  • Backend affected: mcr.microsoft.com/playwright/mcp (official Playwright MCP server, Streamable HTTP transport), image created 2026-05-07
  • Backend registered as an aggregated tool source behind a MetaMCP vServer (namespace), reached via /metamcp/<vserver>/mcp
  • Deployment: both as separate Docker containers on the same Docker network

What we observed

docker logs metamcp repeatedly shows, for the same backend UUID, over many hours:

[DEBUG-TOOLS] 🔧 Server: playwright
Creating new connection for server playwright (<backend-uuid>) with namespace: <namespace-uuid>
Connecting to server playwright (<backend-uuid>) with max attempts: 1
[DEBUG-TOOLS] ⏱️  Fetched 23 tools from playwright in ~50ms
[DEBUG-TOOLS] 🔍 Hash check for playwright: UNCHANGED
Created background idle session for server [playwright] <backend-uuid>

Counting Created background idle session for server [playwright] lines since the container's last start (~7.7 days uptime at time of writing) gave 149 occurrences — with no corresponding "session closed"/"session removed" log line anywhere in the same window. The events aren't on a fixed timer (gaps range from ~30s to 100+ minutes), so they appear to be triggered by client reconnects / tool-list refresh checks rather than a single cron.

A separate log line dumping the gateway's tracked session-id array (printed while handling an unrelated request) showed several dozen UUIDs still tracked as live sessions.

Impact

The backend container (Playwright MCP) eventually accumulated enough concurrent idle sessions/browser contexts that it stopped responding and exited (clean exit code 0, not OOM-killed at the container level — but the accumulation is the clear precursor). We had to manually restart the backend to restore service. Since the "hash check: UNCHANGED" path means the tool list didn't even change, no new persistent session should have been necessary at all — this looks like it should have been a stateless check.

Suspected root cause

The periodic/lazy tool-list refresh path for aggregated backends appears to always create a brand-new connection + "background idle session" rather than:

  • reusing an existing idle/cached session for the tool-list check, or
  • explicitly tearing down the session after the hash-check completes (since nothing further is done with it), or
  • deduplicating by backend so only one idle session per backend/namespace is ever kept alive.

Suggested fix

For the tool-list refresh/hash-check path specifically, either avoid opening a full backend session at all (if the transport allows a lighter-weight tools/list without a persistent session), or ensure any session opened purely for this check is closed immediately afterward instead of being kept as a long-lived "background idle session".

How to reproduce

  1. Register any Streamable-HTTP MCP backend that holds per-session server-side state (Playwright MCP is a good example since state cost is visible/expensive).
  2. Leave MetaMCP running for a while with periodic client activity that triggers tool-list refreshes against that backend (or trigger it manually/repeatedly).
  3. Watch docker logs <metamcp> for repeated Created background idle session for server [...] lines with different backend-connection UUIDs (or the same UUID reused as the "logical" identity but a new underlying session/socket each time) accumulating without any matching close.
  4. Watch the backend's own resource usage (process count / memory) grow correspondingly.

Happy to share more log excerpts if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions