ctop as an Agent Surface
Exposing session data via CLI + MCP so AI agents can introspect themselves and coordinate across terminals.
Ship two surfaces on top of ctop's existing session data — a thin ctop CLI in JSON-output mode (lives in core, zero-dep) and a separate ctop-mcp package (depends on the MCP SDK).
Expose ~10 tools split across READ SELF ALERT WRITE. The whoami and get_alerts tools are the killer features — they let a session ask "should I compact?" or "which of my sister sessions is in trouble?"
Phase 1 (CLI) is ~1 day. Phase 2 (MCP wrapper) is ~1 day. Phase 3 (streaming resources) is a follow-up.
01Why
From the tweet thread: "this is sick! can I have my master agent control my ctop??" — Jared Zoneraich
Today, every Claude/Codex/OpenCode session runs in its own terminal, blind to its siblings. The user is the only entity holding the cross-session picture, and they're holding it via eyeballs on the ctop TUI. That doesn't compose with agents:
- An agent burning context can't see it's about to compact until the editor warns it — by then the next message will trigger a costly compaction.
- A "master" agent dispatching to sub-agents has no way to read what they're working on, kill ghost sessions, or notice one is stalled.
- Cross-cutting checks (am I duplicating work happening in branch
feat/xover in another window?) require human cross-referencing.
ctop already aggregates this data — it's just trapped inside a TUI render loop. Exposing it programmatically costs us ~300 lines of glue.
The mental model
02What ctop already knows
For each session, ctop has these fields populated by getAllAgentProcesses() and friends. Everything below is free — no new collection needed.
Full per-session field list →
03Two surfaces, one core
CLI PHASE 1
Subcommands of the existing ctop binary. JSON output, pipeable, zero-dep. Works for any agent that can shell out, plus humans + cron.
MCP server PHASE 2
Separate ctop-mcp npm package speaking stdio JSON-RPC. Schema-discoverable by Claude Code, Codex, Cursor, etc. Same tool surface, agent-native.
ctop-mcp imports core functions from ctop-claude as a peer dep and wraps each in an MCP tool definition. No duplicated logic, no extra polling loop.
04Tool catalog
Ten tools, four categories. Every tool is also a CLI subcommand of the same name.
| Tool | Category | Purpose | Args |
|---|---|---|---|
| list_sessions | READ | Summary array of every running agent session | agent? · cwd? · status? |
| get_session | READ | Full detail object for one PID | pid |
| read_session_log | READ | Conversation transcript (user + assistant messages) | pid · tail? · since? |
| search_sessions | READ | Full-text search across all session JSONL content | query · agent? · cwd? |
| get_git_diff | READ | Uncommitted changes in a session's working dir | pid or cwd |
| get_aggregate_stats | READ | Totals across all sessions (cost, tokens, active count, …) | — |
| whoami | SELF | Session object for the calling agent (auto-detected) | — |
| get_alerts | ALERT | Computed warnings: low-context, idle, compacting, ghost, rate-limited | severity? |
| kill_session | WRITE | SIGTERM or SIGKILL a PID. Self-only by default; opt-in to kill others | pid · force? |
| send_notification | WRITE | Desktop notification (macOS notification center / Linux libnotify) | title · message |
05The killer tool: whoami
Every other read tool requires the agent to know which session it's looking at. whoami closes that loop — it returns "you are this session." That changes what the agent can do:
- "Am I running out of context?" →
whoami().contextPct < 15→ suggest/compactbefore next turn - "What have I cost so far?" →
whoami().cost - "Am I duplicating work in another session on this same branch?" →
list_sessions({cwd: whoami().cwd})
Detection strategy (in priority order)
CTOP_PIDenv var (most explicit; CLI/MCP server may set it for child agents)- Walk
process.ppidchain looking for a known agent binary ingetAllAgentProcesses() - Fall back to matching
$PWDagainst sessioncwd— pick the most-recent ACTIVE one (single-match heuristic) - Return
nullif none of the above resolve — never guess across users
matchConfidence: "exact" | "ppid" | "cwd-guess" in the response so agents know whether to trust it.
06Per-tool detail
get_session for full detail.info | warn | critical.Alert kinds
low_context— contextPct < 15 (warn) or < 8 (critical)compacting— compaction event detected this tickidle— ACTIVE with tokenRate=0 for > 10 minutesghost— STOPPED/ZOMBIE consuming > 100MBrate_limited— rateLimits non-null and near quotacost_spike— > $5 cost on a single session
force: true) to a session. The only destructive tool. Subject to auth model in §07.Remaining 7 tool specs →
readSessionLog.getGitDiffSummary.calculateAggregateStats.sendNotification.07Authorization model
Read tools surface data the agent could read off disk anyway — no new privilege. Write tools are different: a "master agent" killing a sister session is the kind of footgun where we want defense-in-depth.
| Capability | Default | Opt-in via |
|---|---|---|
| All read tools | allowed | — |
kill_session on own PID |
allowed | — |
kill_session on other PIDs |
denied | CTOP_MCP_ALLOW_KILL=1 |
send_notification |
allowed | — |
| Cross-user access | denied | not supported — process owner check |
process.geteuid() against the target PID's owner.
08Zero-dep constraint
The package README badge says "Zero Dependencies." This is a real promise worth preserving — it's part of why ctop installs cleanly under npx. Two options for the MCP server:
Option A · Separate package RECOMMENDED
Ship ctop-mcp as its own npm package. Peer-depends on ctop-claude. Users who want MCP install both; users who want just the TUI keep zero deps.
- Clean separation — TUI stays pristine
- Familiar pattern (eslint vs eslint-config-x)
- MCP SDK version can move independently
Option B · Hand-roll JSON-RPC
MCP is stdio JSON-RPC 2.0 with a known message schema. ~200 lines of Node, no SDK. Could live in core.
- Preserves zero-dep across the board
- More code to maintain as MCP spec evolves
- Loses SDK conveniences (validation, logging helpers)
Recommendation: Option A. The trade — adding one optional package — is worth the SDK's protocol coverage and version-tracking. If the SDK proves heavy, fall back to B as a v2 refactor.
09Rollout phases
- Add subcommand router to
claude-managerentrypoint - Implement
ls · get · log · search · diff · stats · whoami · alerts · killas one-shot commands - All output JSON with
--json, human-readable by default - Add tests under
test/cli/ - Update README with the new commands
- New
packages/ctop-mcp/directory (or sibling repo) - Depends on
@modelcontextprotocol/sdk+ctop-claude - Wraps each CLI subcommand as an MCP tool with JSON schema
- Stdio transport; auto-detected
CTOP_PIDfor whoami - Example claude.json / codex.toml configs in README
- Expose
ctop://sessionsas an MCP resource list - Expose
ctop://sessions/{pid}/logfor live tailing - fs.watch the JSONL files; emit MCP
notifications/resources/updated - Lets master agent subscribe to sister sessions instead of polling
10Open questions
list_sessions include the current calling session, or filter it out by default?isCaller: true, and let the agent filter.whoami behave when called from a non-agent shell (e.g. via cron or a wrapper script)?{session: null, matchConfidence: "none"} — never guess. The cwd-match fallback only fires if there's exactly one ACTIVE session matching $PWD.ctop-mcp (CTOP_MCP_READ_OWN_ONLY=1) for users who want stricter isolation.get_alerts / list_sessions when called by an agent?ps + lsof + fs.read path is already cached at ~5s granularity. Add a simple 1s minimum interval between identical calls in ctop-mcp to be safe.