MCP Binary Pinning
When you install an MCP server onto an agent, MUR records the SHA-256 of the binary that will actually be spawned. On every startup it re-hashes that binary and compares. If it no longer matches, the agent refuses to start.
mur agent mcp inspect coach # pinned vs current, every server
mur agent mcp inspect coach --server media # just one
mur agent mcp pin coach media # re-approve the binary now on disk
mur agent mcp vendor coach fetch-mcp # install a runner-launched server where MUR can verify it
mur doctor # drift across every agent, at a glance
The pin answers a narrow question: is the program my agent is about to run the same one I approved? An MCP server is code that runs on your machine with the entitlements you granted the agent — swapping that binary is the cheapest way to turn a governed agent into someone else's.
What refusing to start looks like
B0 rule 6: MCP `media` changed since install — run `mur agent mcp inspect media`
to review the drift and `mur agent mcp pin media` to re-approve, or
`mur agent mcp remove media` to uninstall.
The agent does not come up until you decide. That is the point: a binary that changed without your say-so is exactly the case where "start anyway and warn" is the wrong default.
An unsigned MCP binary is refused the same way on macOS and Windows (code signature check). On Linux the signature check is a documented no-op.
Recovering from drift
mur agent mcp inspect <agent>— see what changed. It reports the pinned hash, the current hash, and a status.- Decide whether you expected it. A vendor upgrade you performed yourself is expected; a binary that changed on its own is not.
- Either
mur agent mcp pin <agent> <server>to re-approve, ormur agent mcp remove <agent> <server>to uninstall.
inspect exits with a status code so you can wire it into a check:
| Exit | Meaning |
|---|---|
0 | clean — pinned matches current |
1 | binary drift |
4 | unpinned (installed before pinning existed) |
5 | binary missing |
6 | interpreter-launched — reported, not enforced (below) |
With --server omitted it reports the worst status across all of the agent's servers. Add --probe to also spawn the server and verify its tools/list against the pinned description hash — off by default so inspect stays fast.
Servers launched through an interpreter
An entry like command: npx, args: @yawlabs/fetch-mcp is not pinned in any useful sense. The hash covers npx — so it changes every time you upgrade Node, and it says nothing about @yawlabs/fetch-mcp, which npx resolves at run time and may fetch fresh tomorrow.
Enforcing that would be the worst of both: your agents stop starting after an unrelated brew upgrade node, and the code you actually wanted pinned was never covered. So these entries are reported, not enforced:
⚠drs-clinician/fetch-mcp: launched via `npx` — the pin covers the interpreter,
not the server code, so it is not enforced.
The same applies to node, python, uvx, bunx, and friends. Treat it as a statement about what MUR can and cannot vouch for: an interpreter-launched MCP server runs code MUR has not verified.
Two things narrow that gap, in order of how much they buy you.
Pin which release runs
mur agent mcp pin records the version the runner resolves to right now, rewriting @yawlabs/fetch-mcp to @yawlabs/fetch-mcp@0.3.6 in the entry's args. A spec with no version is resolved fresh at every start, so the code your agent runs can change with no action from you and no signal; mur doctor calls that out specifically:
⚠drs-clinician/fetch-mcp: `npx @yawlabs/fetch-mcp` has no pinned version —
resolved fresh on every start.
This pins identity, not contents. Better than nothing, and it is one command.
Vendor the package
mur agent mcp vendor coach fetch-mcp
Installs the exact version under ~/.mur/mcp-packages/<agent>/<server>/, repoints the entry to launch the installed script directly (node <path>), and records a fingerprint. The agent then starts from a directory nothing else writes to — no resolution step, no network.
The fingerprint is the SHA-256 of the install's package-lock.json. npm records an integrity hash there for every package in the dependency tree, so one small file covers all of it: for @yawlabs/fetch-mcp that is a 47 KB lockfile standing in for 37 MB of node_modules, and the cost of checking it does not grow with the tree. Once vendored, the entry is enforced like any other pin — a changed tree refuses the startup.
Installs run with --ignore-scripts. A package's postinstall is arbitrary code execution at install time, which is the thing being guarded against; a server that can't start without its install scripts isn't one to vendor without thinking about it.
What the lockfile does not cover: it pins what was installed. Editing a file inside node_modules afterwards does not change it, so this detects a re-install or a swapped dependency, not post-install tampering with the checked-out files. Catching that needs a full tree hash at every startup. Vendoring is a large step up from npx, not a guarantee that every byte is what the publisher shipped.
Entries installed before pinning existed
An entry with no pin is skipped rather than refused, so upgrading MUR never strands an older agent. mur agent mcp inspect reports it as unpinned (exit 4); mur agent mcp pin adopts it.
MUR's own MCP server
MUR ships its own MCP server (mur-mcp-server, the one behind the media capability) and refreshes the copy under ~/.mur/mcp-servers/ from the binary beside the running mur whenever the two differ — so upgrading MUR changes that binary by design.
Those entries are re-pinned automatically after the refresh. Without that, every routine mur upgrade would stop every agent using it.
This is not a hole in the check. That binary was just written by your own runtime from your own installation: its trust anchor is "same install as the agent runtime", not a hash recorded weeks ago, and anyone able to replace it has already replaced mur itself. Third-party MCP servers — the reason pinning exists — are never re-pinned for you.
Related
- Capabilities — installing bundled MCP servers, skills, and program requirements onto an agent
mur agent mcp list— what's wired onto an agentmur agent addon reimport— re-verify an imported Claude plugin against its content hash