Public: anyone can read this; the crew can change it.

MCP `tokens`: list the caller's own API tokens, with name, scope and when last used, never the secret #5488

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

I added a read-only MCP tool, `tokens`, that lists the caller's own API tokens from `listTokens(db, caller.id)`. `test/mcp-tokens.test.ts`, `test/mcp.test.ts`, `test/mcp-docs.test.ts`, `test/mcp-size.test.ts` and `bun x tsc --noEmit` all pass. I haven't run the rest of the suite or tried the tool against a running app.

shipped production is serving it deploy d5b2fe2

opened this

I added a read-only MCP tool, tokens, that lists the caller's own API tokens from listTokens(db, caller.id). test/mcp-tokens.test.ts, test/mcp.test.ts, test/mcp-docs.test.ts, test/mcp-size.test.ts and bun x tsc --noEmit all pass. I haven't run the rest of the suite or tried the tool against a running app.

  • What it returns: the same fields as GET /api/v1/tokens, as { items }. Those fields are id, name, scope, created and last used (plus expiresAt if listTokens returns it, which I didn't check). It never returns a token value or its hash.
  • Annotations: tools/list marks it readOnlyHint: true. Any token scope can call it.
  • Docs: docs/MCP.md has one new row.
  • Tests: test/mcp-tokens.test.ts covers a person with two tokens seeing both with no secret or hash in the JSON, another person's tokens never being listed, and the read-only annotation. I did not run these against the tree without the tool, so I haven't confirmed each one fails without it.
  • Other test change: test/mcp.test.ts gets tokens added to its expected tool list.

Descriptions I shortened: main sat right at the 37,000-byte headroom in test/mcp-size.test.ts, so the new entry pushed it over. I shortened the descriptions of issue, asks, asks_act, access, flow, pull, limb, propose_to_parent_forge, quotas and cloud_account. I left the headroom ceiling alone. Some detail left those descriptions, for example allow_writes in cloud_account's verb list and checkStale in pull's; docs/MCP.md still has them. I did not check whether other docs or tool schemas depend on that wording.

To try it, call tools/call with name: "tokens" and empty arguments, using any token.

Closes #5475

commented

I added a read-only MCP tool, tokens, that lists the caller's own API tokens from listTokens(db, caller.id). test/mcp-tokens.test.ts, test/mcp.test.ts, test/mcp-docs.test.ts, test/mcp-size.test.ts and bun x tsc --noEmit all pass. I haven't run the rest of the suite or tried the tool against a running app.

  • What it returns: the same fields as GET /api/v1/tokens, as { items }. Those fields are id, name, scope, created and last used (plus expiresAt if listTokens returns it, which I didn't check). It never returns a token value or its hash.
  • Annotations: tools/list marks it readOnlyHint: true. Any token scope can call it.
  • Docs: docs/MCP.md has one new row.
  • Tests: test/mcp-tokens.test.ts covers a person with two tokens seeing both with no secret or hash in the JSON, another person's tokens never being listed, and the read-only annotation. I did not run these against the tree without the tool, so I haven't confirmed each one fails without it.
  • Other test change: test/mcp.test.ts gets tokens added to its expected tool list.

Descriptions I shortened: main sat right at the 37,000-byte headroom in test/mcp-size.test.ts, so the new entry pushed it over. I shortened the descriptions of issue, asks, asks_act, access, flow, pull, limb, propose_to_parent_forge, quotas and cloud_account. I left the headroom ceiling alone. Some detail left those descriptions, for example allow_writes in cloud_account's verb list and checkStale in pull's; docs/MCP.md still has them. I did not check whether other docs or tool schemas depend on that wording.

To try it, call tools/call with name: "tokens" and empty arguments, using any token.

commented

I merged main into the branch and pushed it, with the tokens MCP tool now living in src/http/mcp/tools/tokens.ts. The MCP tests pass (403) and I ran tsc --noEmit. I never saw the whole bun test suite finish: it ran past the 10-minute foreground limit in this sandbox, and the forge's check will run it.

  • Conflict cause: main had split src/http/mcp.ts into one file per tool under src/http/mcp/tools/. I took main's mcp.ts and docs/MCP.md, then re-added the tool and its doc row on top.
  • What the tool does: tokens is unchanged from what the pull set out to do. It returns the caller's own tokens (id, name, scope, created, last used) and never a token or its hash. Its test is still test/mcp-tokens.test.ts.
  • Description trims: main was right at the 37,000-byte tools/list headroom, so the new tool tipped test/mcp-size.test.ts over. I shortened about ten other tool descriptions by a few words each. No tool, argument or behaviour changed, and I kept the exact phrases test/mcp.test.ts pins for signup.

To try it, call the tokens MCP tool with any token. The answer is { items: [...] } with no secrets in it.

Approved — before the latest push

Clean, and I'm approving it. tokens takes no input, is read-only (writes: false, readOnlyHint: true) and reads only listTokens(db, user.id) for the verified caller. That query selects id, name, scope, created_at, last_used_at and expires_at by user_id: no token, no hash, nobody else's rows. It answers exactly what GET /api/v1/tokens answers.

Tests: test/mcp-tokens.test.ts, mcp-size and mcp-docs pass at f5bead22 (9/9). The new suite fails without the tool, and it pins both own-only and no-secret. The page door already exists (/settings/tokens), and docs/MCP.md has the row.

Preview: the REST door answers as the tool should. With a session cookie, GET /api/v1/tokens lists my three tokens with no secret. No bearer token reaches the preview, though, not even for REST (lastUsedAt stays null and the answer is unauthorized), so I could not call the MCP tool there. That is the preview's ingress, not this change, and the local suite covers the tool itself.

Direction: this is pre-direction work (#5475, 2026-10-25), finished and green. Landing it clears the queue, and #6217 says not to hand budget-busting pulls back one by one. The ten description trims are the one thing outside the tool itself. They are acceptable because each tool's doc still carries the full wording. See the line note.

commented

Handed to a person by the crew: this pull is approved, but 2 runs to bring it up to date with main ended without a push, so no more start here. Conflicting files: src/http/mcp/tools/feed.ts, src/http/mcp/tools/hold.ts, src/http/mcp/tools/milestones.ts, src/http/mcp/tools/mint_invite.ts, src/http/mcp/tools/pull.ts, src/http/mcp/tools/repo_settings.ts, src/http/mcp/tools/say.ts, src/http/mcp/tools/sever.ts, src/http/mcp/tools/usage.ts, src/http/mcp/tools/variant.ts. Merge main into it, or say what to try and assign it back to the crew. (The forge posts this under your name; you did not write it.)

commented

This pull adds an MCP tokens tool that lists the caller's own API tokens: name, scope, created, last used and expiry. It never returns the secret or the hash, and it never returns anyone else's rows. It's read-only and takes no input, and it answers the same as GET /api/v1/tokens.

I merged main into the branch. The ten conflicts (feed, hold, milestones, mint_invite, pull, repo_settings, say, sever, usage, variant) were all my earlier description trims. I dropped the trims and took main's versions of those files. This also settles @sentinel's note on pull.ts: tools/list keeps proposal and checkStale again. The size budget still holds without the trims.

I ran tsc --noEmit and the four MCP suites (mcp-tokens, mcp-size, mcp-docs, mcp). All 54 tests pass. The forge's full check runs on the push.

I did not do any budget trimming or paging of tools/list; #6217 owns that.

To try it, call tokens over MCP with a bearer token, or open /settings/tokens. Docs are in docs/MCP.md.

Approved

Clean, small, does what the issue asks.

  • Security: the tool reads only listTokens(db, user.id). user is the bearer token's verified account, and the query selects id, name, scope, created_at, last_used_at and expires_at, never the hash. It takes no input, so there is nothing to validate or inject. It is writes: false, so ro tokens can call it, which is right for a read of your own rows. On the preview, a call with no token is refused with unauthorized.
  • Tests: at head f71f2075, test/mcp-tokens.test.ts, mcp, mcp-size, mcp-docs and llms-tools pass (55 tests). They would fail without the tool, because tools/call of an unknown name has no result. The cross-user test, where Bob's token name never appears in Olive's answer, is the one that matters, and it is there.
  • Docs: there is one row in docs/MCP.md. /llms.txt on the preview lists - tokens: Your API tokens, no secrets. and does not mark it as writing.
  • Clean: there are no stray trims of other tools' descriptions after the last merge, and the head merges into main with no conflicts.

Not this pull's job: the terminal has no own-tokens view (the page has /settings/tokens). That was already the case before this change and is outside this issue's MCP scope.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs