Summary
When running minibridge in AIO mode wrapping a stdio MCP server (minibridge aio -- postgres-mcp), a backend process is spawned per MCP session and is never terminated when that session ends. Backend processes accumulate over time until the container exhausts memory.
Environment
- Image:
acuvity/mcp-server-postgres:latest (minibridge AIO + crystaldba postgres-mcp 1.25.0)
- Transport: HTTP (
mode=http), client connecting to /mcp (Streamable HTTP) and /sse (legacy) — both affected
server-tls=false (TLS terminated by an upstream reverse proxy)
- Client: OpenAI Responses API, hosted remote MCP tool,
require_approval: "never"
- Deployed via Docker (Swarm), single replica
Steps to reproduce
- Start the container (AIO, stdio backend).
- Issue several requests from the MCP client over time.
- Watch the backend process count inside the container:
watch -n 2 'docker top <container_id> | grep -c postgres-mcp'
Observed behavior
The backend process is spawned per MCP session, not per tool call: within a single session, multiple tools/call correctly reuse one backend. However, the backend is never reaped when the session ends — so one orphaned process leaks per session.
The leak rate depends on the transport:
- With Streamable HTTP (
/mcp): ~1 leaked process per user request (the session is reused across the tool calls within that request).
- With legacy SSE (
/sse): ~1 leaked process per tool call, since each call appears to open a fresh session.
In both cases the orphaned backend (/app/.venv/bin/python3 /app/.venv/bin/postgres-mcp) stays alive as a child of the minibridge aio parent and is never terminated. Each process holds ~95 MB RSS.
Process list after a sequence of requests (parent PID 2005; note the steadily increasing count and the decreasing ELAPSED, one new process per session):
PID PPID ELAPSED RSS COMMAND
2005 1862 39:08 32136 minibridge aio -- postgres-mcp
11391 1862 19:18 1168 sh
12392 2005 15:48 94876 /app/.venv/bin/python3 /app/.venv/bin/postgres-mcp
12402 2005 15:42 95072 /app/.venv/bin/python3 /app/.venv/bin/postgres-mcp
12415 2005 15:37 95152 /app/.venv/bin/python3 /app/.venv/bin/postgres-mcp
12510 2005 15:32 95124 /app/.venv/bin/python3 /app/.venv/bin/postgres-mcp
12547 2005 15:22 95308 /app/.venv/bin/python3 /app/.venv/bin/postgres-mcp
14909 2005 06:26 94712 /app/.venv/bin/python3 /app/.venv/bin/postgres-mcp
16374 2005 00:36 94984 /app/.venv/bin/python3 /app/.venv/bin/postgres-mcp
16382 2005 00:33 95180 /app/.venv/bin/python3 /app/.venv/bin/postgres-mcp
Over a longer-running session this reached ~182 processes / ~4 GB RSS and eventually exhausted host memory, affecting other containers on the same host.
Expected behavior
When an MCP session ends — or after a period of inactivity — the backend process spawned for it should be terminated (SIGTERM, escalating to SIGKILL after a grace period). Ideally a single long-lived backend would be reused across sessions rather than spawning one per session.
Crucially, reaping should not depend on the client cleanly closing the session, because some clients don't (see below). An inactivity timeout on idle backends would make the fix robust regardless of client behavior.
Client session behavior (context, not the root cause)
The OpenAI Responses API hosted MCP client has documented, inconsistent session-handling behavior that interacts badly with one-backend-per-session: it has been widely reported to open a fresh MCP session per tool call without reusing the Mcp-Session-Id header, and in other cases to send premature DELETE/session-termination requests. This behavior has reportedly appeared, been fixed, and regressed over time.
This explains the transport-dependent leak rate observed above (fresh session per call on SSE vs. per-request reuse on Streamable HTTP). However, the root issue here is on the minibridge side: backends spawned for a session are not reaped when that session goes away, regardless of why it goes away. The bug manifests under any client that does not keep a single long-lived session.
Question
Does minibridge (or the AIO wrapper) support a stateless mode, where requests are handled without binding a long-lived backend process to a session? Servers facing the same OpenAI client behavior commonly work around it by going stateless (e.g. FastMCP's stateless_http=True). If minibridge has an equivalent option, that may both avoid the leak and improve compatibility with the OpenAI client — please point to the relevant config.
Notes
tools/list alone does not spawn a backend; sessions that perform tools/call do.
- Happy to provide additional logs (including the JSON-RPC
initialize/DELETE traffic observed for incoming requests) or test a fix.
Summary
When running minibridge in AIO mode wrapping a stdio MCP server (
minibridge aio -- postgres-mcp), a backend process is spawned per MCP session and is never terminated when that session ends. Backend processes accumulate over time until the container exhausts memory.Environment
acuvity/mcp-server-postgres:latest(minibridge AIO + crystaldbapostgres-mcp1.25.0)mode=http), client connecting to/mcp(Streamable HTTP) and/sse(legacy) — both affectedserver-tls=false(TLS terminated by an upstream reverse proxy)require_approval: "never"Steps to reproduce
Observed behavior
The backend process is spawned per MCP session, not per tool call: within a single session, multiple
tools/callcorrectly reuse one backend. However, the backend is never reaped when the session ends — so one orphaned process leaks per session.The leak rate depends on the transport:
/mcp): ~1 leaked process per user request (the session is reused across the tool calls within that request)./sse): ~1 leaked process per tool call, since each call appears to open a fresh session.In both cases the orphaned backend (
/app/.venv/bin/python3 /app/.venv/bin/postgres-mcp) stays alive as a child of theminibridge aioparent and is never terminated. Each process holds ~95 MB RSS.Process list after a sequence of requests (parent PID 2005; note the steadily increasing count and the decreasing
ELAPSED, one new process per session):Over a longer-running session this reached ~182 processes / ~4 GB RSS and eventually exhausted host memory, affecting other containers on the same host.
Expected behavior
When an MCP session ends — or after a period of inactivity — the backend process spawned for it should be terminated (SIGTERM, escalating to SIGKILL after a grace period). Ideally a single long-lived backend would be reused across sessions rather than spawning one per session.
Crucially, reaping should not depend on the client cleanly closing the session, because some clients don't (see below). An inactivity timeout on idle backends would make the fix robust regardless of client behavior.
Client session behavior (context, not the root cause)
The OpenAI Responses API hosted MCP client has documented, inconsistent session-handling behavior that interacts badly with one-backend-per-session: it has been widely reported to open a fresh MCP session per tool call without reusing the
Mcp-Session-Idheader, and in other cases to send prematureDELETE/session-termination requests. This behavior has reportedly appeared, been fixed, and regressed over time.This explains the transport-dependent leak rate observed above (fresh session per call on SSE vs. per-request reuse on Streamable HTTP). However, the root issue here is on the minibridge side: backends spawned for a session are not reaped when that session goes away, regardless of why it goes away. The bug manifests under any client that does not keep a single long-lived session.
Question
Does minibridge (or the AIO wrapper) support a stateless mode, where requests are handled without binding a long-lived backend process to a session? Servers facing the same OpenAI client behavior commonly work around it by going stateless (e.g. FastMCP's
stateless_http=True). If minibridge has an equivalent option, that may both avoid the leak and improve compatibility with the OpenAI client — please point to the relevant config.Notes
tools/listalone does not spawn a backend; sessions that performtools/calldo.initialize/DELETEtraffic observed for incoming requests) or test a fix.