A public endpoint whose tools change things
Give an agent a key
that only opens one door.
Read-only tools are easy to hand out. The question that decides whether anybody trusts an agent with a write is where the check lives, and a rule written into a prompt is a request: the model can be talked out of it, and a model that has been talked out of it still reports success.
So press the button. It opens a real session against the live server, writes something inside the grant, then tries two things one step outside it and shows you what came back.
This drives the same endpoint your Claude would connect to. Nothing here is a recording.
Or point your own Claude at it
claude mcp add --transport http aoc https://www.ayushopchauhan.com/api/mcp
Ask it to open a session, then ask it to write into a collection that is not yours. Watch it be told which grant it would need. The transport hands the session id back in the Mcp-Session-Id header, so a compliant client starts sending it on its own and the tool list changes without anybody doing anything.
Four things it does that a permission check does not
The check runs at the write, not at connect
A session is looked up on every call, so an expired or narrowed grant takes effect on the next request rather than the next reconnect. Everything in lib/mcp/store.ts calls the same decision function before it touches the database, and none of those functions takes an argument that skips it.
The tool list is filtered by the grant
A session that was not granted delete is not offered delete_note. Most servers advertise every tool and check permission when it is called, which turns the list into a menu of things to try. Filtering the list means the model never forms the intention, and the call is checked anyway.
A refusal names the grant that was missing
Not "forbidden". An agent told only that something failed retries the same call. An agent told it needs the "delete" operation, and that it holds "read" and "write", can either ask for a wider session or stop.
Refusals are logged as loudly as successes
Every attempt is written to the audit table with the collection it named, the grant that was missing, and how long it took. A log that only records what worked tells you nothing about what was attempted, which is the half an auditor cares about.
What everyone else has been doing to it
The last 24 calls from every visitor, 0 of them refused. Content is left out; this is who did what, not what they wrote.
| 16:20:01 | allowed | open_session | guest-c9da5ec10428 |
| 16:10:00 | allowed | open_session | guest-50777ee15063 |
| 16:07:01 | allowed | open_session | guest-b0058f0f5e5d |
| 16:00:02 | allowed | open_session | guest-3bdc3cc18f7b |
| 15:50:01 | allowed | open_session | guest-9ff9ddc242d0 |
| 15:40:00 | allowed | open_session | guest-693c03d02426 |
| 15:30:01 | allowed | open_session | guest-c6cc1155d5b0 |
| 15:20:02 | allowed | open_session | guest-4490dd34bf9c |
| 15:10:00 | allowed | open_session | guest-d2b40db341ee |
| 15:07:01 | allowed | open_session | guest-56491704ef54 |
| 15:00:01 | allowed | open_session | guest-ae8fe1ab2bbb |
| 14:50:00 | allowed | open_session | guest-dddce1754e8a |
| 14:40:01 | allowed | open_session | guest-0ea5c14708b0 |
| 14:30:02 | allowed | open_session | guest-7446488c7d3d |
| 14:20:03 | allowed | open_session | guest-1fc10843349a |
| 14:10:02 | allowed | open_session | guest-70f3466c612b |
| 14:07:02 | allowed | open_session | guest-38f11aa836e8 |
| 14:00:04 | allowed | open_session | guest-a0099c3a008b |
| 13:50:02 | allowed | open_session | guest-0ac90575f590 |
| 13:40:01 | allowed | open_session | guest-de2394199f2f |
| 13:30:02 | allowed | open_session | guest-0a58a3fcecb3 |
| 13:20:02 | allowed | open_session | guest-5520dbc66e11 |
| 13:10:02 | allowed | open_session | guest-5669d4f5994c |
| 13:07:01 | allowed | open_session | guest-b1fa9e10fd6a |
What this is not
There is no login. A session id is the only credential, anyone holding one can use it, and it lasts an hour. That is the right shape for a public demonstration and the wrong shape for real data, where the session would be issued against an identity rather than to whoever asked.
The documents are notes in a table. Pointing the same boundary at something that matters is a change of storage.
53 unit tests cover the decision function and 54 more drive this endpoint over HTTP. Both suites exist because a boundary that is correct and not wired in is the same as no boundary, and only the second kind of test can tell you which one you have.
The same server also carries the read-only tools behind the site console. The address guard is a box you can attack, the eval scores the engine on precision and recall, the token measurement prices the same question four ways, and every defect any of it has had is written down.