MCP Server Fast-Path Entry — cos-mcp-start
P: Explanation of the dedicated
cos-mcp-startentry point that skips thecli.mainClick overhead, and when the choice between entry points matters. R: Editing.mcp.json, packaging changes topyproject.toml, or diagnosing slow MCP boot. S: General CLI work that doesn't touch MCP server start-up. N: mcp-error-envelope.md, mcp-schema-traps.md
Nav: Engineering Index | Docs Index
Why two entry points
.mcp.json historically pointed at cos server-start, which loads cli.main — a 380 ms tax for the click app to import every subcommand (add-stack, doctor, eject, init, setup, update, plus the graph_os and board_os command groups). Once the entry point ran the assignment-prefixed os.execvpe to src/core/thinking_os/server.py, all that work was thrown away.
That overhead doesn't matter when only one subprocess starts at session boot. It matters a lot when an MCP client opens a SECOND subprocess for auxiliary work (Anthropic VSCode extension's session-title generator, config-cache loader). Under contention — multiple agents, recovering DB locks — the duplicated startup cost was enough to push aux init past the 60 s budget the extension applies.
src/cli/mcp_start.py (registered as the cos-mcp-start console_script in pyproject.toml) is the same logic with the cli.main import skipped. Cold start drops from ~590 ms (avg over 5 runs) to ~430 ms — a 26 % cut. Under five concurrent boots the worst-case time is ~800 ms.
What it does
Identical contract to the original server_start() body:
- Resolve
cos_root = parent.parentofsrc/cli/mcp_start.py. - Set
COS_DB_PATHandCOS_STATE_DIRenv defaults fromPath.cwd() / .coding-os/. - Sweep stale
src/core/thinking_os/server.pyinstances bound to the same DB (parent dead OR etime >COS_STALE_SERVER_AGE_S, default 12 h). os.execvpethe same Python interpreter ontoserver.py.
The orphan sweep helper _sweep_stale_servers is duplicated between src/cli/main.py:_sweep_stale_servers and src/cli/mcp_start.py:_sweep_stale_servers so neither file imports the other. Keep them in sync — both are simple enough that this is cheaper than a third module.
Adapter contract
src/adapters/claude/update_mcp_json.py writes a .mcp.json block of:
{
"mcpServers": {
"coding-os": {
"command": "cos-mcp-start",
"args": []
}
}
}
when shutil.which("cos-mcp-start") is truthy, else falls back to cos server-start, else to the editable uv run form. The Codex adapter (src/adapters/codex/ensure_codex_mcp.py) follows the same precedence.
make sync re-runs install for every adapter and rewrites .mcp.json to the best entry available on the host PATH at install time.
Don't
- Inline this logic into
src/core/thinking_os/server.py. Server.py runs as the MCP child, after exec. By the time it's executing it can't kill its own siblings or set its own env defaults — the parent already chose them. - Drop the orphan sweep. Codex pools MCP children (PPID =
codex app-server, alive for hours); without the sweep they pile up and contend on the SQLite WAL. The 12 h threshold is conservative — overnight orphans get reaped at the next morning's first agent spawn. - Add MCP traffic to the sweep decision. Heartbeat-based liveness ("did this server answer in the last N seconds?") would require either probing every candidate (slow) or a shared lock file (race-prone). Etime + PPID liveness is the cheapest correct heuristic.