Public: anyone can read this; the crew can change it.
An agent works the board too: an MCP server at POST /mcp #13
Correction to this issue's last line: the briefs I named (ledger, torque) are not on this crew — I invented them in the run that opened this. The crew is atlas (planning), scout (builds), mason (maintains and refactors), sentinel (security review) and juniper (experience review). Read the suggestion here as scout to build, with sentinel reviewing the pull where the issue asks for a security review and juniper where the change is visible on the page. A person still assigns.
Closing this as already built, verified on main at e888c44 rather than read: src/mcp.ts (136 lines) serves initialize, ping, tools/list and tools/call at POST /mcp, with list_boards, list_cards, add_card and move_card; GET /mcp answers 405 with a sentence; a notification (no id) gets 202 and no body; list_boards returns [] for an anon caller so the endpoint cannot be used to enumerate boards. It shipped with the first build (#7), which is why it was never picked up — the map had it waiting behind #12 for a bus it turned out not to need.
The half this issue existed for — an agent's move appearing for the people watching — holds too. Booted locally, created a board, opened GET /b/<id>/events, then called tools/call add_card over HTTP: the stream produced id: 1 / data: {"by":null} within the second, because mcp.ts calls touch(board.id, null) itself after createCard and moveCard (src/mcp.ts:70, :97). Two people and one agent on one board is met for the web pages; what is left of the direction's "each seeing the others' moves" is the terminal, and that is #31.
Two gaps this closes over, both already filed, neither worth holding the endpoint open for: an MCP write is not rate-limited (#40, which asks for one bucket in front of every write, MCP included) and an MCP add_card title reaches the terminal unescaped (#34).
Serve MCP over JSON-RPC 2.0 at
POST /mcpwithlist_boards,list_cards,add_cardandmove_card, so a Claude session moves cards under the same forge identity and everyone watching the board sees it happen.Done looks like
POST /mcpspeaks JSON-RPC 2.0:initialize(protocol version,serverInfo,capabilities.tools),tools/list,tools/call. The id is echoed; a notification (no id) gets202and no body; unknown method-32601, unparsable JSON-32700, bad params-32602. Errors are JSON-RPC errors, not HTML, and never leak a stack.tools/listand the MCP result shape{ content: [{ type: "text", text }], isError? }:list_boards(the caller's boards),list_cards(board id → columns with their cards in order),add_card(board, column, title),move_card(board, card, column, index).src/routes.tsuses — id shapes, trimmed titles, length caps, a non-negative integer index — and the database is reached only throughsrc/db.ts.who(request):list_boardsreturns only boards the caller owns, and a call acts under that identity. A caller who names a board id may work it, as the link does on the web; the README says so plainly./mcpis safe without the HTML same-origin rule: it accepts onlycontent-type: application/jsonand never form encoding, so a cross-site form cannot reach it; a body over a sane cap (say 64 KiB) is refused. The reasoning sits in a comment beside the same-origin check insrc/server.ts.initialize,tools/listandtools/callover fetch;add_cardthenlist_cardsshows the card;move_cardreorders it; an unknown tool, a bad argument, a form-encoded post and an oversized body each fail without changing the board; a mutating call emits a bus event.bun testpasses.Files
src/mcp.ts(new),src/mcp.test.ts(new),src/routes.ts,src/server.ts,README.md.Risks
An
/mcpthat accepted form encoding would be a CSRF hole straight past the same-origin check; alist_boardsthat ignores identity leaks every board; unbounded bodies and unboundedlist_cardsresponses are a cheap denial of service; tool errors that echo internals are a disclosure.Suggested brief: torque (tools with exact, validated inputs and results that match the tests); sentinel to review the pull.