Public: anyone can read this; the crew can change it.
MCP `origins`: list the caller's connected origins and their repositories, and sync one, with the same rules as the API #4828
Check · Review · Land · Live done — Landed and live in production.
MCP now has an `origins` tool, placed right after `outsiders`, taking `action` (`list`, `repos` or `sync`) and `id`. The new `test/mcp-origins.test.ts` and the other MCP tests pass (101 tests). `tsc` could not check anything here: it fails with "Cannot find type definition file for 'bun'", so the forge's check is the first real type check.
shipped production is serving it deploy 78f82e5
MCP now has an origins tool, placed right after outsiders, taking action (list, repos or sync) and id. The new test/mcp-origins.test.ts and the other MCP tests pass (101 tests). tsc could not check anything here: it fails with "Cannot find type definition file for 'bun'", so the forge's check is the first real type check.
- What it calls: the same functions the API routes call:
listUserOrigins,listRemoteReposandsyncOriginNow, always for the caller. The answers go through the API's ownoriginJsonandremoteRepoJson, so they carry no token. I exported those two fromsrc/http/api.ts; the routes themselves are untouched. - Token scope: like the API's origin routes, it needs an admin token, because an origin spends the person's credential on another forge. A
roorrwtoken getsforbidden.listanswers withoutid;reposandsyncneedidor getinvalid. - Not found: an unknown
id, or another person's, answersnot_found. An unreachable origin answersorigin_unreachable. - Size: the description is one short sentence, with no description on
id, to keeptools/listunder the 37,000-byte headroom. I did not measure the bytes the tool adds;mcp-size.test.tspasses. - Docs and tests:
docs/MCP.mdhas the row besideoutsiders.test/mcp-origins.test.tsuses the fake origin fromtest/fake-origin.ts, with no network. It covers the owner listing the origin and its repositories and starting a sync, with no token in any answer. It also covers another person seeing nothing, unknown and missing ids, bad actions, and the scope refusal.test/mcp.test.tsnow expectsoriginsin the tool list. That test was out of date, not wrong.
I left out connecting and disconnecting origins and mirroring a repository over MCP. Those are separate verbs, and migrate (#4163) is where mirroring belongs.
To try it, call tools/call with { name: "origins", arguments: { action: "list" } } using an admin token.
Clean. The origins tool calls the same listUserOrigins / listRemoteRepos / syncOriginNow the API routes call. It is always scoped to the authenticated caller, with the user coming from the verified token and never from an argument. Answers go through the API's own originJson / remoteRepoJson, so no credential reaches a response or an error. It holds the same admin-token bar as requireAccount({ adminToken: true }), and that check runs inside run. So the readsWithout: "id" read path (list on a ro/rw token) is still refused, and the test covers exactly that. Another person's id answers not_found, the same as an unknown one, so it does not leak whether the id exists. No SQL or shell is built from input, and the API routes are unchanged apart from two exports. At head 3870ba85, bun test passes for mcp-origins, mcp and mcp-size (44 pass) and tsc --noEmit is clean. On the preview, / answers and /mcp refuses a call without a token. There is no terminal door in this repository to mirror. It does what #4811 asked and nothing more.
MCP now has an
originstool, placed right afteroutsiders, takingaction(list,reposorsync) andid. The newtest/mcp-origins.test.tsand the other MCP tests pass (101 tests).tsccould not check anything here: it fails with "Cannot find type definition file for 'bun'", so the forge's check is the first real type check.listUserOrigins,listRemoteReposandsyncOriginNow, always for the caller. The answers go through the API's ownoriginJsonandremoteRepoJson, so they carry no token. I exported those two fromsrc/http/api.ts; the routes themselves are untouched.roorrwtoken getsforbidden.listanswers withoutid;reposandsyncneedidor getinvalid.id, or another person's, answersnot_found. An unreachable origin answersorigin_unreachable.id, to keeptools/listunder the 37,000-byte headroom. I did not measure the bytes the tool adds;mcp-size.test.tspasses.docs/MCP.mdhas the row besideoutsiders.test/mcp-origins.test.tsuses the fake origin fromtest/fake-origin.ts, with no network. It covers the owner listing the origin and its repositories and starting a sync, with no token in any answer. It also covers another person seeing nothing, unknown and missing ids, bad actions, and the scope refusal.test/mcp.test.tsnow expectsoriginsin the tool list. That test was out of date, not wrong.I left out connecting and disconnecting origins and mirroring a repository over MCP. Those are separate verbs, and
migrate(#4163) is where mirroring belongs.To try it, call
tools/callwith{ name: "origins", arguments: { action: "list" } }using an admin token.Closes #4811