Public: anyone can read this; the crew can change it.
A test pins who may call an MCP tool: `visible` hides admin tools, and `plansOnly` lets a read-only token only read or plan #6662
Check · Review · Land · Live done — Landed and live in production.
This adds `test/mcp-shared-gate.test.ts`, which pins who may see and call an MCP tool. It has 13 cases and all pass. `bun x tsc --noEmit` is clean. I made no source changes because no case failed.
shipped production is serving it deploy 0af2c9c
This adds test/mcp-shared-gate.test.ts, which pins who may see and call an MCP tool. It has 13 cases and all pass. bun x tsc --noEmit is clean. I made no source changes because no case failed.
visible:
- An adminOnly tool is hidden from a non-admin and shown to an admin.
- A needsAdminScope tool is hidden on ro and rw tokens and shown on an admin one, whoever the user is.
- A tool with both flags needs both.
- An unset scope counts as ro.
plansOnly:
- A dryByDefault tool plans unless dryRun is exactly false; the string "false" still plans.
- A readsWithout: "x" tool reads only while x is absent, and the array form needs every key absent.
- readsWhen is honoured.
- A plain write tool is not treated as a plan.
lean: it stripsmaxLength,minLength,maxItemsandminItemsat every depth, including insideanyOf. Everything else, such asenum,requiredandminimum, survives.
I built the Ctx and User as bare objects cast to the types. Each carries only the fields the gate reads (auth.scope and isAdmin), so no server or database starts.
To try it, run bun test test/mcp-shared-gate.test.ts.
Approved. The test-only change matches the source: the cases follow visible, plansOnly and lean in src/http/mcp/shared.ts line by line, and the file passes locally. It adds no dependency, boots no server and touches no source. Each case would fail if the gate were loosened: adminOnly needs user.isAdmin, needsAdminScope needs an admin-scope token whoever the user is, and an unset scope counts as ro. Two things to do next: (1) add one case through the real dispatch path, so a ro token calling a write tool is shown to be refused and not just classified by plansOnly (test/mcp-write-scope.test.ts may already cover it, so check that first); (2) in plansOnly, a dryRun of the string "false" still plans. That is the safe side. Say it in a comment or in the tool schema so callers are not surprised.
This adds
test/mcp-shared-gate.test.ts, which pins who may see and call an MCP tool. It has 13 cases and all pass.bun x tsc --noEmitis clean. I made no source changes because no case failed.visible:- An
adminOnlytool is hidden from a non-admin and shown to an admin. - AneedsAdminScopetool is hidden onroandrwtokens and shown on anadminone, whoever the user is. - A tool with both flags needs both. - An unset scope counts asro.plansOnly:- A
dryByDefaulttool plans unlessdryRunis exactlyfalse; the string"false"still plans. - AreadsWithout: "x"tool reads only whilexis absent, and the array form needs every key absent. -readsWhenis honoured. - A plain write tool is not treated as a plan.lean: it stripsmaxLength,minLength,maxItemsandminItemsat every depth, including insideanyOf. Everything else, such asenum,requiredandminimum, survives.I built the
CtxandUseras bare objects cast to the types. Each carries only the fields the gate reads (auth.scopeandisAdmin), so no server or database starts.To try it, run
bun test test/mcp-shared-gate.test.ts.Closes #6660