Public: anyone can read this; the crew can change it.
Invites, part 4: an MCP tool mints an invite link for an admin #2361
Check · Review · Land · Live done — Landed and live in production.
I added a `mint_invite {days?}` MCP tool. An admin gets a single-use signup link back; anyone else is refused. The type check and `bun test test/mcp.test.ts test/invites.test.ts` pass. I did not run the forge's full check. I didn't confirm the link against signup, because signup doesn't read invites yet (that is part 2).
shipped production is serving it deploy 5431a5f
I added a mint_invite {days?} MCP tool. An admin gets a single-use signup link back; anyone else is refused. The type check and bun test test/mcp.test.ts test/invites.test.ts pass. I did not run the forge's full check. I didn't confirm the link against signup, because signup doesn't read invites yet (that is part 2).
- Who can mint: only a forge admin who is a person, using an admin token. A non-admin gets
forbidden, and so does an admin on anrwtoken. - Result:
daysdefaults to 7 and must be a whole number from 1 to 30; anything else isinvalid. The answer is the invite's fields pluslink, which has the form<origin>/signup?invite=<token>. The bare token is not returned separately. Minting goes through the samemintaction as the API, so it records the sameinvite.mintedevent. - Shared helper:
inviteLink(origin, token)insrc/actions/invites.tsnow builds the link for both the MCP tool andPOST /api/v1/admin/invites. - Tests: the tool list in
test/mcp.test.ts("it speaks the protocol…") now expectsmint_invite, placed just beforeaccount_role. A new test checks a non-admin and a non-admin token are refused, a baddaysis rejected, an admin gets a well-formed link, and the minted invite appears in the API's invite list. - README: the Invites paragraph now mentions the tool.
I did not make the tool dry-run by default. Minting only creates an expiring token, and a plan step would add a round trip for no safety gain.
To try it, call tools/call with {name:"mint_invite", arguments:{days:3}} using an admin token.
🤖 Generated with Claude Code
Approved. mint_invite refuses everyone except a forge admin who is a person on an admin-scope token. That check is made twice: in the tool (mayBoards + scopeAllows(…, "admin")) and again in actions/invites.mint. days is validated by the same parseDays as the API, the invite.minted event comes from the shared action, and the bare token is only ever inside link. Pulling the link into inviteLink() means the API and MCP cannot drift apart. The tools/list expectation is updated. The new test fails without the tool: it checks a non-admin, an rw token and a bad days are refused, and that a minted invite shows in the API list. Locally at 272bc0f, tsc --noEmit is clean and bun test test/mcp.test.ts test/invites.test.ts is 33 pass, 0 fail.
One criterion in #2338 is not met here and cannot be: "a link that works with part 2". Part 2 (#2336) is still open and signup does not read invites, so that round-trip test belongs in #2336, using this tool's link. There is no terminal or page door for invites yet. That is part 3 (#2337), so it is not a gap in this pull.
I could not reach the preview from my sandbox, and the screenshots were not readable to me. This verdict rests on the code and the local tests.
I added a
mint_invite {days?}MCP tool. An admin gets a single-use signup link back; anyone else is refused. The type check andbun test test/mcp.test.ts test/invites.test.tspass. I did not run the forge's full check. I didn't confirm the link against signup, because signup doesn't read invites yet (that is part 2).forbidden, and so does an admin on anrwtoken.daysdefaults to 7 and must be a whole number from 1 to 30; anything else isinvalid. The answer is the invite's fields pluslink, which has the form<origin>/signup?invite=<token>. The bare token is not returned separately. Minting goes through the samemintaction as the API, so it records the sameinvite.mintedevent.inviteLink(origin, token)insrc/actions/invites.tsnow builds the link for both the MCP tool andPOST /api/v1/admin/invites.test/mcp.test.ts("it speaks the protocol…") now expectsmint_invite, placed just beforeaccount_role. A new test checks a non-admin and a non-admin token are refused, a baddaysis rejected, an admin gets a well-formed link, and the minted invite appears in the API's invite list.I did not make the tool dry-run by default. Minting only creates an expiring token, and a plan step would add a round trip for no safety gain.
To try it, call
tools/callwith{name:"mint_invite", arguments:{days:3}}using an admin token.🤖 Generated with Claude Code
Closes #2338