Public: anyone can read this; the crew can change it.
MCP `explore` gains a `tree` view: an app's fork chain in the page's own words, no new tool #11405
Check · Review · Land · Live done — Landed and live in production.
MCP `explore` now takes `{ kind: "tree", app: "owner/name" }` and returns that app's fork chain as plain rows. There is no new tool. One part of the spec is not met: "last taken" is not a real take time, because the forge doesn't record one. It is the time the fork's default branch last moved, which the forks page labels "pushed".
shipped production is serving it deploy abf00b0
MCP explore now takes { kind: "tree", app: "owner/name" } and returns that app's fork chain as plain rows. There is no new tool. One part of the spec is not met: "last taken" is not a real take time, because the forge doesn't record one. It is the time the fork's default branch last moved, which the forks page labels "pushed".
The new tests, mcp-size, mcp-table and the other tests scripts/fast.ts picked all pass, and tsc and the gate are clean. I did not run bun scripts/design-lint.ts, since nothing under src/web changed. The forks page and the answer share the "follows" and "standing" wording but not the "pushed" time, which the page prints as a relative time and the answer as an ISO timestamp.
What it returns. The answer is { app, upstream, forks, truncated }. Each row is { repo, follows, standing, updatedAt }.
upstreamis the chain above the app, nearest first, ending at the highest ancestor the caller can read.forksare the app's direct forks, at most 60.truncatedis true when more exist.followsuses the Following card's wording ("Follows x automatically", "Proposes updates from x", "Does not follow x"). The top of a chain says "This repository follows nothing."standinguses the forks page's drift wording ("2 ahead · 0 behind", or "standing unknown").updatedAtis the fork's last-moved time described above.
Privacy. A fork the caller can't read is not a row. An app the caller can't read, or one that doesn't exist, answers not_found.
Where it lives.
- The reader is
src/actions/fork_tree_rows.ts. - The tool change is in
src/http/mcp/tools/explore.ts. docs/MCP.mdnames the view, and I regenerated its tool table withscripts/mcp-table.ts.test/mcp-tree.test.tscovers the chain, the wording, a private fork hidden from a stranger, and an unreadable app. Its parity check fetches/alice/lib-mid/forksand confirms the page carries the same "follows" and "standing" text.
Size. The description change stays inside the existing ceilings; I raised none. Most of the new text is in the tool's doc line, which clients never receive.
Left alone. The existing fork_tree and forks tools, which answer in other shapes (whole tree, and drift pages), are unchanged.
Try it. Call explore with { "kind": "tree", "app": "alice/lib-mid" } and compare the rows with /alice/lib-mid/forks.
Clean: reuses explore (no new tool), reads gated by repoArg/canRead/visibleForks, unreadable ancestors cut the chain, forks capped at 60, input length-capped. Tests cover chain, wording parity with the forks page, private-fork omission, not_found. Note: updatedAt is last push, not a true take time (disclosed, no record exists). The terminal already has fork screens; this is an MCP-only view as the issue asked.
MCP
explorenow takes{ kind: "tree", app: "owner/name" }and returns that app's fork chain as plain rows. There is no new tool. One part of the spec is not met: "last taken" is not a real take time, because the forge doesn't record one. It is the time the fork's default branch last moved, which the forks page labels "pushed".The new tests,
mcp-size,mcp-tableand the other testsscripts/fast.tspicked all pass, andtscand the gate are clean. I did not runbun scripts/design-lint.ts, since nothing undersrc/webchanged. The forks page and the answer share the "follows" and "standing" wording but not the "pushed" time, which the page prints as a relative time and the answer as an ISO timestamp.What it returns. The answer is
{ app, upstream, forks, truncated }. Each row is{ repo, follows, standing, updatedAt }.upstreamis the chain above the app, nearest first, ending at the highest ancestor the caller can read.forksare the app's direct forks, at most 60.truncatedis true when more exist.followsuses the Following card's wording ("Follows x automatically", "Proposes updates from x", "Does not follow x"). The top of a chain says "This repository follows nothing."standinguses the forks page's drift wording ("2 ahead · 0 behind", or "standing unknown").updatedAtis the fork's last-moved time described above.Privacy. A fork the caller can't read is not a row. An app the caller can't read, or one that doesn't exist, answers
not_found.Where it lives.
src/actions/fork_tree_rows.ts.src/http/mcp/tools/explore.ts.docs/MCP.mdnames the view, and I regenerated its tool table withscripts/mcp-table.ts.test/mcp-tree.test.tscovers the chain, the wording, a private fork hidden from a stranger, and an unreadable app. Its parity check fetches/alice/lib-mid/forksand confirms the page carries the same "follows" and "standing" text.Size. The description change stays inside the existing ceilings; I raised none. Most of the new text is in the tool's
docline, which clients never receive.Left alone. The existing
fork_treeandforkstools, which answer in other shapes (whole tree, and drift pages), are unchanged.Try it. Call
explorewith{ "kind": "tree", "app": "alice/lib-mid" }and compare the rows with/alice/lib-mid/forks.Closes #11377