Skip to content

feat(mcp): report descendant processes in inspect_port - #69

Merged
Mapika merged 1 commit into
mainfrom
feat/mcp-process-tree
Aug 1, 2026
Merged

Mapika merged 1 commit into
mainfrom
feat/mcp-process-tree

Conversation

@Mapika

@Mapika Mapika commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Last of Tier 3. inspect_port reported a children count but not what they were, so an agent could not answer the question that actually matters before stopping something: what else goes down with it? A dev server's workers are its children, not separate port owners, so they were invisible.

Flat, not nested

Each entry carries pid, ppid, process, and depth:

"children":2,
"child_processes":[
  {"pid":15,"ppid":6,"process":"node","depth":1},
  {"pid":16,"ppid":6,"process":"node","depth":1},
  {"pid":30,"ppid":15,"process":"node","depth":2},
  {"pid":29,"ppid":16,"process":"node","depth":2}],
"child_processes_truncated":false

That holds exactly what a nested structure would — ppid gives the edges — and is far simpler to emit and to consume. A client that wants a tree can nest by ppid.

children (count of direct children) and child_processes (all descendants) are deliberately separate keys: 2 and 4 above. Reusing the name would have silently changed the type of an existing field.

Bounded, and honest about it

Depth ≤ 3, ≤ 64 nodes, breadth-first — so when the cap bites, what survives is the shallowest and most relevant part of the tree, and child_processes_truncated says so rather than returning a partial tree that looks complete. The node cap matters most on Windows, which pays a process snapshot per node walked.

There is also a cycle guard. A PID cycle should be impossible, but a reparenting race could in principle produce one, and the alternative is an unbounded walk for no benefit.

Per-platform

New get_child_processes(pid) -> Vec<(u32, String)> on each platform. PID and name come back together on purpose: Windows already has both in PROCESSENTRY32W.szExeFile, and resolving names separately would mean opening a handle per child — which fails for anything privileged.

  • Linux — /proc/<pid>/task/<pid>/children
  • macOS — proc_listchildpids with a real buffer. The existing count_children already called it and threw the PIDs away. Sized with headroom over what the sizing call reports, since children can be forked between the two calls and a full buffer truncates silently.
  • Windows — one CreateToolhelp32Snapshot filtered by parent

Verification

Against ps as ground truth, with a listener that forks workers which fork grandchildren:

==> ground truth from ps (pid tree under 6)
  child  15 (ppid 6)      child  16 (ppid 6)
  gchild 30 (ppid 15)     gchild 29 (ppid 16)

==> inspect_port 4000 via MCP
  {"pid":15,"ppid":6,...,"depth":1}   {"pid":16,"ppid":6,...,"depth":1}
  {"pid":30,"ppid":15,...,"depth":2}  {"pid":29,"ppid":16,...,"depth":2}

Same PIDs, same parent links, correct depths.

The walker takes its child lookup as a parameter, so the bounds and the cycle guard are unit-tested directly rather than needing a real process tree: depth cap, node cap with the truncation flag, a cycle, and the empty case.

196 tests, fmt/clippy clean, macOS + Windows type-check — the last matters more than usual here, since two thirds of this is FFI I cannot run locally.

Answers what else stops when you stop a port: a dev server's workers are
its children, not separate port owners. Entries are flat and carry ppid and
depth, which holds what a nested structure would and is simpler to consume.

The walk is breadth-first and bounded, so a truncated result keeps the
shallowest part of the tree, and says that it truncated.
@Mapika
Mapika merged commit 04b8ec0 into main Aug 1, 2026
6 checks passed
@Mapika
Mapika deleted the feat/mcp-process-tree branch August 1, 2026 07:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant