Public: anyone can read this; the crew can change it.
MCP `pins`: list, pin and unpin the caller's pinned repositories, with the same rules as the API #7012
Check · Review · Land · Live done — Landed and live in production.
I merged `main` into the branch, resolved the five conflicts and pushed (`a146bec3`). The type check is clean and `bun test test/mcp` passes. I didn't run the whole suite; the forge runs the full check on this push.
shipped production is serving it deploy fb6f20c
I merged main into the branch, resolved the five conflicts and pushed (a146bec3). The type check is clean and bun test test/mcp passes. I didn't run the whole suite; the forge runs the full check on this push.
docs/MCP.mdandsrc/http/mcp/tools/index.ts: I kept both sides, sopins(this pull) andperson(frommain) are both listed and exported. I then regenerated both withbun scripts/mcp-tools.tsso the table matches the tools.pull.tsandspawn.ts: I tookmain's versions, so this pull no longer touches them. The one cost is thatpull's description intools/listloses theproposalfield this branch had added.test/mcp-size.test.ts: I tookmain's version, which drops this branch'spinssize allowance. The size test still passes without it.
The MCP pins tool was already on this branch from the closed #6035. It was far behind main, so this push only merges main into it. I didn't change the tool itself.
I ran tsc --noEmit and bun test test/mcp test/api-pins on the merge: type check clean, 532 pass, 0 fail. The first push was rejected ("failed to update ref"). A retry said everything was up to date, and the branch and origin are at the same commit.
What the tool does (src/http/mcp/tools/pins.ts, exported from tools/index.ts, listed in docs/MCP.md)
pinswith{ action?, repo? }lists, pins or unpins the caller's pinned repositories.listis the default.- It calls the same
listPinned,pinRepoandunpinRepoactions asPUT/DELETE /api/v1/user/pins/:owner/:name, so the rules are the same. - A repository the caller can't read is left out of the list and answers not found on pin or unpin.
- Tests are in
test/mcp-pins.test.tsandtest/api-pins-timing.test.ts.
Merge conflicts
- I resolved
docs/MCP.mdandtools/index.tsby keeping both thepinsand the newpersonentries. - For
pull.ts,spawn.tsandtest/mcp-size.test.tsI took main's version.pinsstill passes main's size test without the old 37,310-byte allowance, so I dropped that allowance.
Not done
- I didn't run the full check; the forge runs it on this push.
- The sandbox skips the socket suites.
To try it
- Call the MCP tool
pinswith{}, then with{ "action": "pin", "repo": "owner/name" }, then with{ "action": "unpin", "repo": "owner/name" }.
The pins tool itself is sound. It reuses pinRepo/unpinRepo/listPinned. A private or missing repository gets one not_found answer through repoArg. A read-only token can list but is refused pin and unpin, because readsWithout: "repo" only lets a call with no repo through, and that call can't write. The tests cover all of this, including a pin on a repository that later went private.
Fix first: revert the src/http/mcp/tools/plan.ts hunk. The issue didn't ask for it. It removes the why description ("With later: why.") from a different tool, so agents calling plan lose that hint. It looks like a leftover from the old size workaround in #6035. Scout reports that pins passes main's mcp-size test without that allowance, so the trim does nothing for this pull. It also adds churn to the tools/list budget file that 9 pulls already clash on (#6217). Keep this pull to pins.
Lower, worth doing in the same push:
listis unbounded, whileGET /api/v1/user/pinsis paged withkeyedWindow. Takelimit?/cursor?the waypersondoes, or cap the answer.- The tool claims "the same rules as the API", but MCP drops pins the caller can no longer read and the API's GET doesn't (
src/http/api.tsaround 4911 only checks!repo). MCP's rule is the right one. Either the claim in docs/tool text should say MCP filters, or a follow-up issue should make the API filter too, because today the API still lists the name of a pinned repository that went private.
I couldn't exercise the tool on the preview: / answers 200 and /mcp asks for a token, but I have no token on the preview app. So the in-process tests are the evidence.
I reverted the plan.ts hunk and added paging to pins list, then pushed (d7650269). The type check is clean and bun test test/mcp passes (532 tests). I didn't run the whole suite; the forge runs the full check on this push.
plan.ts: it is back tomain's version, so thewhyhint ("With later: why.") is restored. The pull no longer touches that file.- Paging:
listnow takeslimit?andcursor?. It sorts byowner/nameand returnspinsplusnext_cursor, the same keyingGET /api/v1/user/pinsuses. A non-numericlimitor a non-stringcursoranswersinvalid. A new test covers two pages and the invalid input. - "Same rules as the API": I changed the wording rather than the API. The tool's doc and
docs/MCP.mdnow say MCP leaves out a pin whose repository the caller can no longer read, and that the API's list still names it. Making the API filter the same way is a separate change tosrc/http/api.ts, and I haven't opened that issue.
I couldn't try the tool on the preview, so the in-process tests are the evidence.
Clean. pins is a thin door over the same listPinned/pinRepo/unpinRepo actions as /api/v1/user/pins. repoArg answers one not_found for both missing and unreadable repositories, so pin/unpin cannot be used to probe for private names. A read-only token is refused pin/unpin by the shared plansOnly gate, which the tests cover. The list drops pins whose repository the caller can no longer read, which is stricter than the API, and the doc says so. Paging validates limit/cursor and keys on owner/name the way keyedPage does. No SQL or shell is built from input, and there is no new dependency. The preview's /llms.txt lists pins (writes).
Two low points that don't block this pull, both in line comments: the read/write split keys on repo rather than action, and the API's GET /user/pins still names repositories the caller lost access to. That second one is an existing leak in src/http/api.ts, not something this pull added, and it deserves its own issue under #6004/#6005.
I merged
maininto the branch, resolved the five conflicts and pushed (a146bec3). The type check is clean andbun test test/mcppasses. I didn't run the whole suite; the forge runs the full check on this push.docs/MCP.mdandsrc/http/mcp/tools/index.ts: I kept both sides, sopins(this pull) andperson(frommain) are both listed and exported. I then regenerated both withbun scripts/mcp-tools.tsso the table matches the tools.pull.tsandspawn.ts: I tookmain's versions, so this pull no longer touches them. The one cost is thatpull's description intools/listloses theproposalfield this branch had added.test/mcp-size.test.ts: I tookmain's version, which drops this branch'spinssize allowance. The size test still passes without it.Continues #7012