MCP servers eagerly start per session, causing headed browser MCP processes to accumulate
Summary
Codex CLI appears to eagerly start configured MCP servers for every session/resume, even when the tools are not used. This creates a noisy lifecycle problem for headed browser MCP servers: each long-lived Codex session keeps its own visible browser/tool server processes alive.
This is most visible on macOS when a Python-based headed MCP server is configured globally, because every Codex session shows another Python app in the Dock.
Example Configuration
A global MCP setup with headed browser tools such as:
[mcp_servers.browser-use]
command = "browser-use"
args = ["--headed", "--profile", "Default", "--mcp"]
[mcp_servers.browser-use-cdp]
command = "browser-use"
args = ["--headed", "--cdp-url", "http://127.0.0.1:9222", "--mcp"]
Starting or resuming multiple Codex sessions results in each Codex process owning its own MCP child processes, for example:
codex resume ...
browser-use --headed --profile Default --mcp
browser-use --headed --cdp-url http://127.0.0.1:9222 --mcp
npm exec @playwright/mcp@latest --browser=chrome
Expected Behavior
One of these would avoid the accumulation:
- Lazy-start MCP servers only when a tool from that server is first used.
- Add a config option to mark MCP servers as lazy/on-demand vs eager.
- Avoid auto-starting headed/browser MCP servers until needed.
- Clearly shut down unused MCP servers after an idle timeout.
Actual Behavior
Each Codex session starts the configured MCP server processes and keeps them alive for the lifetime of that Codex session. If several old sessions remain open, the user sees many headed MCP processes, even if none of those sessions are actively using browser tooling.
The child processes are not necessarily orphaned: their parent is still the live Codex session. So this is not always a process leak in the OS sense. The problem is eager startup and session-lifetime retention of GUI-capable MCP tools.
Collision Notes
This does not appear to be primarily a Chrome profile or CDP port collision with current browser-use behavior:
browser-use --profile Defaultcopies the Chrome profile into a temporary user data directory before launch.- The local browser launcher chooses a free remote debugging port.
- A
browser_close_allstyle tool can close active browser sessions, but it does not necessarily terminate the MCP server process while Codex keeps that server connected.
So the confusing user-visible failure mode is many Dock/process-list entries, not necessarily profile corruption.
Why This Matters
Headed MCP browser tools are useful for auth-heavy/operator flows where isolated Playwright tests are the wrong model. But when configured globally, eager startup makes normal Codex usage feel like it is spawning unexplained GUI apps.
A lazy MCP lifecycle would make global browser-tool configuration much safer and less surprising.
10 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I confirmed the core behavior with a black-box red/green repro on Codex CLI
0.130.0.The test configures a dummy MCP server whose command only creates a probe file, then asks Codex to reply
OKand not use tools.Repro command shape:
Control, without MCP config:
Current behavior with the configured dummy MCP server:
The JSONL output only shows the model replying
OK; no MCP tool is invoked. The probe file is still created, which proves the configured MCP server process was started eagerly before actual MCP tool use.This issue may be a concrete headed-browser/process-lifecycle symptom of the broader lazy-loading request in #2335, with overlap with #20883 for per-session MCP process pooling. It also relates to #21318, but that issue focuses on startup/tool-discovery blocking; this one focuses on unwanted process startup and accumulation for GUI/headed MCP servers.
Possible design direction, if maintainers agree this belongs as more than a duplicate of #2335:
A
required = trueserver probably should not bemanual, becausecodex execalready has useful fail-fast semantics for required MCP startup failures./mcp load <server>command in the TUI;--with-mcp=playwright,browser-use;startup_modereduces unnecessary startup; #20883-style project/session pooling reduces duplication when a server is actually loaded.This would let users keep globally useful MCP definitions without paying the process, UI, and memory cost in every session. It would also give headed/browser MCP servers a safer path: configured globally, but started only when the user actually needs real browser automation.
If this direction is aligned with maintainers' expectations, I can attempt a scoped PR for the config shape and initial manual/lazy loading behavior. No worries if this should instead stay consolidated under #2335.
One more design note after looking at the two browser-use variants:
browser-use-cdpshould probably be treated as a distinct, valuable opt-in mode rather than just another browser server to eagerly start. CDP is useful specifically because it can attach to an already-running Chrome/Chromium instance in a known state: logged in, on the right tab, with the user's existing cookies/profile/session. That is different from launching an isolated browser.The lifecycle fix should preserve that value while avoiding accidental attachment/process buildup. A good shape might be:
So the bug is not that CDP exists; the bug is that eager MCP startup makes a powerful attach-to-my-browser workflow happen too early and too often.
Cross-linking a focused mitigation branch relevant to this eager-start behavior:
https://github.com/openai/codex/compare/main...mySebbe:codex:mysebbe/defer-subagent-mcp-startup?expand=1
Since opening a PR against
openai/codexfrom this account currently fails withmySebbe does not have the correct permissions to execute CreatePullRequest, maintainers can fetch it directly:This branch does not implement a user-facing per-server
startup_modesetting. It is narrower: forked/subagent sessions can inherit the parent MCP tool snapshot and keep MCP clients lazy unless required/explicitly used. That should reduce the duplicate stdio process buildup discussed here without changing root-session eager behavior by default.This would be very helpful to me. There is no feedback within the GUI app that this is happening, however due to some slow MCP servers (I had to have a wrapper script to stagger auth), I ended up having to wait 80 seconds before it starts to actually process something (even a 0s "what is the time?")
The key split here is "tool availability" versus "process admission." A lot of the pain comes from treating them as the same decision.
For headed/browser MCPs, the safer default is:
owning_session,last_tool_call_at,idle_timeout_at)That keeps globally configured browser tooling usable without turning every idle Codex session into a visible GUI process farm. We ran into the same class of issue with long-lived coding-agent runtimes, and the big win was making admission explicit: a process should be alive because something still owns it, not just because a session once had access to it.
Split MCP lifecycle into configured, advertised, process started, first tool invoked, leased, and idle expired. Tool discovery can advertise a manifest without owning a process; the first actual call starts it and writes a lease receipt with owning session, last call, pid, and idle deadline.
Diagnostics can then show whether latency came from manifest load or process admission, and headed/browser servers stop accumulating across idle sessions.
---
_Generated with ax._
Never had this problem with copilot, only 1 instance was started no matter how many agents were used. With codex it's horrendous and all my ram is consumed. I am constantly destroying the MCP containers, very annoying.
I can reproduce the high baseline memory impact on Linux/WSL2 with
chrome-devtools-mcp.Environment:
codex-cli 0.142.46.6.87.2-microsoft-standard-WSL2, x86_64chrome-devtools-mcpwatchdog args show app version1.4.0While investigating high memory usage, I found that multiple live Codex sessions had started separate
chrome-devtools-mcpprocess trees even though I was not intentionally using the Chrome DevTools MCP tool in those sessions.Using
/proc/*/smaps_rollupPSS to avoid over-counting shared memory:The individual process list had many repeated trees like:
Typical RSS values were roughly:
So the practical effect on a small 8 GiB dev machine is that idle or mostly idle Codex sessions can keep around ~2 GiB of browser MCP helper processes.
I also pulled current
mainlocally and saw that there are recent mitigations around MCP manager shutdown/reuse, but this still does not appear to be true lazy startup. The current session startup path still constructs an MCP connection manager and initializes enabled MCP servers, so this issue seems only partially mitigated unless the process admission model changes.Expected behavior for heavy/browser MCPs:
/mcp load;