DOC GK-011 TYPE ENGINEERING LOG SUBJECT MCP-PIN LOG 4,812 ENTRIES

The tool you
approved is not
the tool you run.

You add an MCP server, your client shows you the tool descriptions, they look fine, you click approve. That decision is never revisited. The server can serve one set of definitions on Monday and a different set on Tuesday, and no major client re-prompts. mcp-pin fingerprints what you approved and blocks the session when it moves. Code: mcp-pin. Public log: mcp-pin.gautamkhosla.com. MIT, zero runtime dependencies.

ONE APPROVAL. EVERY SESSION AFTERWARDS. MON you read them. approve. TUE definitions changed WED changed again NO CLIENT RE-PROMPTS. NO INTEGRITY CHECK IN THE SPEC. A TOOL DESCRIPTION IS NOT DATA THE MODEL READS AND SETS ASIDE. IT IS AN INSTRUCTION. CHANGING IT HAS THE REACH OF A CHANGED SYSTEM PROMPT.
FIG 11.1 · The problem is not that the model will certainly obey. It is that nobody checked, and nobody was told.

SEC. 01

A consent that is never re-asked

Approval in MCP is a single event at connect time. The client renders the tool list, you read it, you accept. Nothing in the protocol requires the server to serve you the same list again, and nothing in the clients I checked notices when it does not.

The reason this matters more than it sounds is the position tool descriptions occupy. They are not passive metadata sitting next to the model. They are text the model reads as guidance about what to do next, which puts a description edit in the same blast radius as a system prompt edit, delivered by a third party, after you stopped looking.

The silence is the vulnerability. A server that changes what its tools say, between the session where you consented and the session where it runs, produces no signal at all.

CLIENT calls queued MCP-PIN pinned vs observed SERVER stdio blocked pinned   40c179188ad9 observed 7b21e004c5af SESSION BLOCKED QUEUED CALLS WERE NOT FORWARDED. NOT WARNED ABOUT. NOT FORWARDED. ACCEPT IT DELIBERATELY:  mcp-pin approve <id>
FIG 11.2 · A warning that arrives after the call is a log entry, not a control. Traffic is held until the check completes.

SEC. 02

Two surfaces, one engine, zero inference

The proxy sits in front of a server you already run. First connect pins the full metadata of every tool. Every connect after that re-derives the same fingerprint and compares. If anything moved, the session stops and you get a diff.

The ordering here is the part that took the longest to get right. Client traffic is queued until the check completes, and on drift nothing queued is forwarded. An earlier version completed the handshake first and reported afterwards, which is a notification system wearing a control system's name.

The tool never asks a model whether a change looks dangerous. It computes a hash and compares it. That is deliberate: a deterministic check keeps working when a model has a bad day, and it keeps working on the subtle changes a model would wave through.

RFC 8785 CANONICALIZATION, THEN SHA-256 key orderignored tool orderignored whitespaceignored one character of a descriptioncounts name description input schema annotations IN SCOPE THE RULE: IF THE MODEL CAN READ IT, IT IS IN SCOPE.
FIG 11.3 · Adding or removing a tool moves the set hash too, so a server cannot quietly drop one either.

SEC. 03

What counts as a change

The whole tool object is fingerprinted: name, description, input schema and annotations, canonicalized per RFC 8785 and hashed with SHA-256. Reformatting is invisible. A single character of a description is not.

Scoping this by hand would have been the obvious mistake. Any rule of the form "watch the description but not the schema" hands an attacker the exact field to use, and a schema change that adds an argument the model is told to populate is a description change wearing different clothes. The rule is mechanical instead: if the model can read it, it is in scope.

4,812 ENTRIES · 248 SERVERS · CHAIN INTACT HEAD · Ed25519 each entry hash linked to the one before it the verifier pins PUBLIC_KEY.txt it will not accept a head signed by whatever key arrives with the file CHANGE ONE BYTE OF HISTORY AND ANYONE HOLDING AN OLDER COPY CAN PROVE IT.
FIG 11.4 · The log records changes, not heartbeats. A server that never moves produces exactly one entry, which is why a quiet log is a good log.

SEC. 04

A history for what you install

The proxy answers "did this change since I approved it". The log answers a question the proxy structurally cannot: what did a server you did not write look like last Tuesday. Nobody was keeping that record, so there was no way to check it retroactively.

The point of a transparency log is that you do not have to trust whoever runs it. Entries are hash linked, the head is signed with Ed25519, and the verifier pins the published key rather than trusting one that arrives alongside the data. Downloading the log and verifying it takes one command and does not involve me.

The badge server authors can display only ever states a fact about time. It says unchanged 91d or changed today. It never says safe, because the project cannot know that and will not imply it.

PRE-REGISTERED 1 SEPTEMBER 2026, BEFORE ANY CODE WAS WRITTEN 10 public MCP server READMEs carry the badge UNEVALUATED OR 100 unique proxy installs, CI excluded UNEVALUATED DEADLINE 15 OCTOBER 2026. NEITHER: ARCHIVE, AND PUBLISH THE NUMBERS AS THEY STAND. IT LIVES IN THE README SO IT CANNOT BE QUIETLY RENEGOTIATED LATER.
FIG 11.5 · Adoption, not correctness. The code working is not evidence that anyone needed it.

SEC. 05

The condition for stopping

One kill test, written on 1 September before any code existed. By 15 October 2026 either ten public MCP server READMEs carry the badge, or there are a hundred unique proxy installs excluding CI. If neither, the repository is archived and the numbers are published as they stand.

It measures adoption rather than correctness on purpose. I can already show the hash comparison works; that proves nothing about whether the problem was worth solving. The thing I do not know is whether anyone else considers unannounced tool drift a risk they will spend sixty seconds on, and only strangers can answer that.

It sits in the README, above the fold, rather than in a private note, because a stop condition nobody can see is a stop condition I can argue my way past.

PRIOR ART, NAMED IN THE README MCP-WARDEN · v1 a lockfile for the server you build classifies each schema mutation uploads SARIF, inspects results MCP-PIN a history for the servers you install blocks the session, not the build public log across every reachable server ON SCHEMA DIFFING, WARDEN IS MORE THOROUGH THAN THIS PROJECT. IT WAS ALSO FIRST. IF YOU MAINTAIN A SERVER AND WANT A CI GATE, USE WARDEN. THE README SAYS SO.
FIG 11.6 · One is a lockfile for what you ship. The other is a history for what you install.

SEC. 06

Someone got there first

mcp-warden exists, it is at v1, it uses the same RFC 8785 plus SHA-256 canonicalization, and on schema diffing it is better than this project: it classifies each mutation individually rather than reporting one opaque change, and it uploads SARIF to code scanning.

That comparison is in my README, recommending warden for the job it does better. Discovering it after I had built most of this was uncomfortable, and burying it would have been the cheap move, but a security tool that hides the competitor has already told you what kind of tool it is.

The overlap turned out to be narrower than it first looked. A lockfile tells you that your own server changed since your last commit. It cannot tell you what a third party's tools said last Tuesday, because nobody kept that record. That gap is the only thing this project justifies, and stating it plainly forced me to work out whether there was one.

LISTED IN THE README, NOT DISCOVERED BY A USER same-user malware can delete the pin store and re-pin itself day-one malice that never changes looks perfectly stable stdio only: HTTP and SSE servers are crawled, not proxied about 38% of discovered npm packages yield a toolset at all not a prompt injection defence: it reports that bytes differ A PIN IS NOT A SAFETY RATING. A SECURITY TOOL THAT OVERSELLS ITSELF IS WORSE THAN NONE.
FIG 11.7 · The claim the evidence supports is that definitions changed between sessions. Everything wider than that is somebody else's sentence.

SEC. 07

What it does not protect against

A local pin store cannot defend against a process running as you. That program can delete the store and re-pin itself. This is an architectural limit rather than a bug, and it will not be fixed by writing the same files harder.

Day-one malice is invisible for the same structural reason. The tool detects change; a server that ships hostile definitions on the first connect and never touches them again is, to a difference engine, perfectly well behaved.

One limitation cuts against the project's own reason to exist and is in the table anyway. Testing on 2 September showed Claude Desktop refusing obvious injected instructions inside tool descriptions and warning the user unprompted. The model catches the loud version already. The argument for a deterministic check is that it does not depend on the payload being obvious, and that is a narrower argument than the one I started with.

4 SEPTEMBER RE-PROBE OF ALL 248 RECORDED SERVERS higher tool count than recorded18 resolved by the re-probe17 that returned nextCursor0 I assumed pagination. The extra tools were on page 1. listings depend on the environment the crawler ran under PRE-0.1.1 RECORDS ARE PUBLISHED AS A FLOOR, NOT A COUNT. THE CRAWL SCHEDULE IS DISABLED UNTIL THE ENVIRONMENT IS RECORDED PER LISTING.
FIG 11.8 · The fix that looks like a fix. Pagination was a real bug. It was not this bug.

SEC. 08

What broke, and what I would change

Versions at or below 0.1.0 silently lost concurrent pin writes while reporting success. Two connects racing produced a confirmation and no record. For a tool whose entire product is a reliable record of what you approved, reporting success without writing is the single worst available failure mode, and it shipped. Use 0.1.2.

The crawler ignored nextCursor, so paginated servers were truncated. I found it, fixed it, and republished every affected record as a floor rather than a count. Then the controlled re-probe on 4 September came back with eighteen servers reporting more tools than I had recorded, and none of those eighteen return nextCursor at all. The extra tools were on page one. Pagination was a real bug that I fixed correctly, and it was not the cause of the numbers I had attributed to it. The actual dependency is the environment the crawl runs under, so the schedule is off until each listing records the conditions it was taken under.

I patched the defect I found, the record looked repaired, and the deeper one was still sitting underneath untouched. That is the second time this month.

The GitHub Action shipped and does not work. Its bootstrap instructions reference a package that is not on npm, and the baseline does not survive the runner. It is now marked experimental with an explicit do-not-adopt line rather than quietly removed, because somebody may already have copied it into a workflow.

A reachable denial of service in the canonicalizer, found during hardening. It is the first thing that touches untrusted input, which is both the worst place for that bug and the most predictable.

What I would change: record the environment before the first crawl, not after the numbers disagree. Every wrong conclusion in the last week came from a measurement whose conditions were not stored alongside it, which is a caveat I have written on the front of other people's numbers and did not apply to my own.

DOC GK-011 · MCP-PIN · MIT · 4,812 log entries, 248 servers Previous log: GK-010 · PLIMSOLL →