Skip to content

HTTP transport orphans Browserbase sessions on client disconnect — cleanup path is unwired #187

Description

@yawbtng

Summary

In SHTTP (--port) mode, when an MCP client disconnects without calling end, the underlying Browserbase session, its SessionManager, and the Server instance are never cleaned up. SessionManager.closeAllSessions() exists but has no callers anywhere, so the only path that actually closes a Browserbase session today is the end tool.

How I found it

Reading the session lifecycle (server.tstransport.tscontext.tssessionManager.ts) to understand who owns and tears down a Browserbase session — the teardown chain doesn't connect.

Details

  1. server.close() doesn't reach the SessionManager. The MCP SDK's Server.close() tears down the protocol, but nothing wires it to Context's SessionManager. So the exit watchdog's serverList.closeAll() (program.ts:85) closes the protocol but not the browsers. (stdio masks this: the process exits, the CDP socket drops, and Browserbase reaps the session.)
  2. HTTP onclose only removes the routing entry. transport.onclose (transport.ts:56) does sessions.delete(...) — no server.close(), no serverList.close(server). So for a long-running --port server, each disconnect leaks:
    • a Server in ServerList._servers (grows unbounded),
    • a SessionManager holding a live Stagehand instance,
    • the Browserbase cloud session itself (its CDP link is independent of the HTTP transport, so it stays open until Browserbase's inactivity timeout — or indefinitely with keepAlive: true).

SessionManager.closeAllSessions() (sessionManager.ts:447) looks purpose-built for this but has no callers, which suggests the cleanup was intended and just not connected.

Impact

  • Memory growth in long-running self-hosted HTTP servers (_servers and SessionManagers accumulate).
  • Browserbase session-minutes consumed by orphaned sessions until server-side timeout; unbounded with keepAlive.
  • Mostly affects self-hosted SHTTP mode; stdio is largely masked by process exit.

Proposed fix (small)

  • In index.ts (where Context is in scope), wire the server's onclosecontext.getSessionManager().closeAllSessions(), so any server.close() frees the browser sessions (also fixes the stdio exit path).
  • In transport.ts onclose, call await serverList.close(server) (triggers the above + removes the entry from _servers).

Happy to open a PR for this. One coordination note: the in-flight stg-1927 branch rewrites index.ts/transport.ts, so I wanted to check first — fold this into that work, or a standalone PR against main? (Not a dup of #149, which is a connection failure rather than a teardown leak.)

cc @Kylejeong2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions