Skip to content

feat(mcp): permissions management, per-wallet activity, and a published tool list - #365

Merged
QSchlegel merged 1 commit into
preprodfrom
claude/mcp-permissions-preprod
Aug 13, 2026
Merged

feat(mcp): permissions management, per-wallet activity, and a published tool list#365
QSchlegel merged 1 commit into
preprodfrom
claude/mcp-permissions-preprod

Conversation

@QSchlegel

Copy link
Copy Markdown
Collaborator

Re-targets the work from #363 onto preprod.

#363 was merged into claude/mcp-ipfs-rationale at 11:21, five minutes after that branch had already gone into preprod as #362 (11:16). The commit landed on a branch nothing points at any more, so none of it reached preprod. This is the same cherry-picked commit, byte-identical tree, based directly on preprod.

It also means the heavier CI workflows never ran on it — they only trigger for PRs targeting main/preprod, and #363 targeted a feature branch. This PR is the first real CI run for this change.

Permissions are enforced, not decorative

A permissions UI on top of the previous code would have been a lie. The MCP endpoint trusted the access token's scope claim, and those tokens are self-contained with an hour's life — so unticking a permission, or revoking a connection outright, would have done nothing until the token happened to expire. The card would say "revoked" while the client kept working.

resolveMcpCaller now reads the stored grant on every request: no grant → 401, and the token's scopes are intersected with the grant's. Changes apply on the next call, and a token cannot gain reach it wasn't issued with even if the grant is later widened. Three tests pin those cases. Cost is one extra indexed lookup on a path that already does one for bots.

The card gets per-scope checkboxes; saving moves the grant and its live refresh tokens in one transaction, so a refresh cannot re-widen what was just narrowed. Emptying a grant is refused — that leaves a connection that authenticates but does nothing, which reads as broken. Revoke is the honest action.

Tool-call logging

Every tool call writes an AuditLog row from the single wrapper all tools pass through — tool, client, scope, status, duration — keyed to the wallet it touched, so the drill-down is an indexed lookup. Only walletId is taken from the arguments; tool inputs carry rationale prose that shouldn't live in an audit row. Fire-and-forget, so a logging failure can't break a call.

The wallet overview card lists clients that used that wallet, expandable to individual calls — a different question from the profile card's "what have I connected".

Tool list

All 13 on the landing page, grouped by permission. It lives in a plain data file because importing the registry would pull the API handlers and Mesh WASM into the browser bundle; a test keeps the two in sync name-for-name and scope-for-scope.

Verification

tsc --noEmit clean, npm run build green, 821 + 85 tests passing. Tool list verified rendering in a browser.

Not verified visually: the permissions checkboxes and the wallet activity card both need a wallet session and a database, neither available locally. Their procedures are unit-tested — including that walletClients refuses a wallet you are not a signer of — but the rendered result will first be real on preprod.

🤖 Generated with Claude Code

…ed tool list

Three connected changes: users can now see what the tools are, control what
each client may do, and audit what it actually did.

**Permissions are now enforceable, not just displayed.** The MCP endpoint
previously trusted the access token's `scope` claim. Those tokens are
self-contained and live an hour, so removing a permission — or revoking a
connection outright — did nothing until the token happened to expire, which
makes a permissions UI a lie. `resolveMcpCaller` now reads the stored
OAuthGrant on every request: a missing grant is a 401, and the token's
scopes are *intersected* with the grant's, so a change applies on the very
next call and a token can never gain reach it was not issued with. Costs
one indexed lookup, on the same path that already does one for bots.

The profile card gains per-scope checkboxes and a save action.
`updateConnectionScopes` moves the grant and its live refresh tokens in one
transaction, so a refresh cannot re-widen what was just narrowed. Emptying
a grant is refused: a connection that authenticates but can do nothing
reads as broken, and revoking is the honest action.

**Tool calls are now recorded.** Every invocation writes an AuditLog row
from the single wrapper in `createMcpServer` — tool, client, scope, status,
duration — keyed to the wallet it touched, so the per-wallet view is an
indexed lookup rather than a scan. Only the walletId is read from the
arguments: tool inputs carry user-authored prose (rationales, descriptions)
that has no place in an audit row. Writes are fire-and-forget; an audit miss
must never break a tool call.

**Wallet overview gains an AI client activity card**, listing the clients
that used *this* wallet with call counts and failures, expandable to the
individual calls. The profile answers "what have I connected"; this answers
"what has touched this wallet", which is the question a co-signer asks.

**The landing page lists the tools**, grouped by the permission each needs.
The list lives in `src/data/mcp-tools.ts` rather than importing the registry,
which would drag the API handlers and Mesh WASM into the client bundle; a
test asserts the two match name-for-name and scope-for-scope. A second test
pins the audit action string the tRPC router hard-codes for the same reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
multisig Ready Ready Preview Aug 13, 2026 11:49am

Request Review

@QSchlegel
QSchlegel merged commit 31374d5 into preprod Aug 13, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant