We run two Model Context Protocol servers in production. One of them can create, move, and delete things. The other cannot change a single byte, and never returns a person's name.
Both are correct. That is the point of this post.
Most MCP security writing worries about the wrong thing. It focuses on the malicious server: somebody publishes a poisoned tool, an assistant calls it, secrets leak. That is a real risk and it deserves the attention it gets. But if you operate a server rather than install one, it is not your daily problem. Your daily problem is that the assistant on the other end is not malicious, it is confidently wrong, and it is about to call a tool you gave it.
The useful question is therefore not "is this server secure." It is "when this call is a mistake, who absorbs the damage." Answer that and the guardrails design themselves.
Three doors, three postures
Between two products we ended up with three distinct surfaces, and each one landed somewhere different.
Door one: read and write, on the user's own machine
Hiberden is desktop software for LTO tape archiving, and its MCP server ships inside the application. It exposes around thirty tools and plenty of them write: create a project, assign a policy, move an archive, retire a destination, delete a copy.
Letting an assistant delete something sounds reckless until you work out who is exposed. The person driving the assistant owns the machine, owns the catalog, and could delete the same copy through the application's own interface in two clicks. Routing that through an assistant does not widen the blast radius. It is the same person, the same computer, the same file.
So the guardrails here are not about restricting capability. They are about making consent explicit and keeping receipts:
- The server runs locally, over stdio, as a subprocess on the same machine as the data. There is no listening port and no inbound network surface.
- Permissions are a setting inside the application, not something the assistant can grant itself.
- Read-only is the default. Destructive operations sit behind their own separate switch that a human has to turn on deliberately.
- Every action is written to an audit trail, so "what did it actually do" is answerable after the fact.
- Credentials for the customer's own cloud storage stay in the operating system keyring. They are never returned by a tool and never pass through the assistant.
- The catalog is a local file. There is no service of ours in the path, no account, and no telemetry.
The shape of that list is worth noticing. When the user absorbs the damage, you guard the decision: default to safe, make the dangerous thing an explicit choice, and write down what happened.
Door two: read only, on a shared service
Easy2257 is a hosted service holding 18 U.S.C. 2257 records for producers in a federally regulated vertical. Its MCP server exposes sixteen tools, and not one of them can create, change, sign, delete, or pay for anything.
Run the same test and you get a different answer. A mistaken write here does not ruin the operator's afternoon. It puts a false state change into a legal record about a third party who is not in the room and never consented to an assistant touching their file. The person absorbing the damage is not the person driving.
When you cannot afford a mistaken write, do not try to make writes safe. Remove them. The write path stays on the website, performed by a human, on purpose. Paid actions and legal attestations are somebody's to make deliberately, not an assistant's to infer.
Having removed what it can do, the remaining question is what it can see. Read-only is not automatically safe: a read-only tool that returns a date of birth is still a data breach waiting for an audience. So the second half of the posture is to shrink the payload until there is nothing worth stealing in it:
- No names, no dates of birth, no ID numbers, no images, no addresses, no document contents, no file paths. Not redacted on the way out. Never in the response shape to begin with.
- What comes back is compliance state: record codes, statuses, counts, dates, and content hashes. Records are referred to by public codes rather than by the people in them.
- Every credential is scoped to exactly one account, and lookups of records outside it are throttled rather than merely denied, so the surface cannot be walked.
- Keys are hashed at rest and shown exactly once, capped per account, and revoked automatically on a password reset. The interactive route uses short-lived tokens that disconnect in one click.
That last cluster matters more than it looks. The realistic failure is not somebody breaking the cryptography. It is a key that got pasted into a chat window eight months ago and nobody remembers. Caps, one-time display, and automatic revocation on password reset are all designed for that failure, not for a determined attacker.
Door three: write, for somebody else's software
The same service also has a partner API, and it is the genuinely dangerous door: it can provision an account, open a scene, and deliver a certificate, called by platforms we do not control.
Here the damage lands on third parties and the caller is a machine, so the guardrails go back up and change character again:
- Live keys only, and no sandbox. Every call touches real records, real people, and real email, so there is no pretend mode that lulls anyone into treating it as low stakes.
- The human who creates a live key verifies their identity first. Authentication attaches to a person, not just to a string.
- Keys are shown once and stored only as a hash. Lose it and you rotate it, because we cannot show it to you again.
- Every webhook is signed with a per-endpoint secret that rotates on its own. Verify the signature before you trust the body. A shared signing secret means one compromised integration compromises all of them.
- Certificates are signed tokens verifiable offline against public keys. A platform never has to take our word for anything, and verification does not require calling us.
- Records are consent-gated. A platform never receives a person's records without that person's consent, regardless of what the API key is entitled to.
- Idempotency keys and rate limits, so a retry can never double-write. Assume every call happens more than once, because eventually it will.
The threat model people skip
Everything above assumes the assistant means well. That is the case worth designing for, because it is the common one.
A well-meaning assistant misreads intent constantly. It sees "clean up the old stuff," decides that means the archive from 2019, and reaches for a delete tool because you gave it one. It sees a job that failed and helpfully retries it four times. It reads a tool result containing text somebody else wrote and treats that text as an instruction, which is the part most teams have not internalised yet: a tool result is untrusted input, exactly like a form field, even when your own server produced it, because your server may be relaying content a customer typed.
Three habits follow from that:
- Name tools for what they do, not for what they enable. A tool called
delete_copygets called deliberately. A tool calledcleanupgets called speculatively. - Make destructive operations narrow and specific. Deleting one identified copy is recoverable reasoning. A tool that takes a filter and deletes everything matching it is a loaded weapon with a natural-language trigger.
- Make retries free. Idempotency is a security control, not just a reliability one, because the assistant is going to retry and will not tell you it did.
The principle, in one line
Match the security to the blast radius. The door that can act authenticates people and signs what it sends. The door that can only read carries nothing worth stealing. And the door that runs on somebody's own machine, against their own files, can afford to do more, provided the dangerous parts are a deliberate choice and everything is written down.
What you should not do is pick one posture and apply it everywhere. A read-only server with an over-generous payload is more dangerous than a write-capable server with an explicit permission switch. Capability and exposure are separate axes, and it is worth deciding each one on purpose.
The desktop connector is documented at hiberden.app/mcp and the partner API at docs.easy2257.com, both including exactly what each surface can and cannot do. If you are wiring an assistant into something that matters, local AI integration and API design are both work we take on.
