How it is built
Architecture overview
An agent that speaks the Model Context Protocol loads servers, and each server offers tools whose names and descriptions the model reads as instructions. That makes two supply chains. One is ordinary software: the server is a package or container with dependencies, a build and a publisher. The other is new: the tool descriptions, schemas and results are text the model trusts, and they can change after a human approved them. A server that was reviewed on Monday can ship a new description on Friday; a name that looks like a trusted server can belong to someone else.
This page describes the defensive architecture: how an organisation decides which servers may run, proves what it is running, notices change, and contains the damage if one turns bad. It contains no attack payloads. The one idea that makes it work is to treat a server as a pinned, signed, reviewed artifact plus a pinned, reviewed description, and re-check both at every load, with the runtime confined so that a bad server can do little even when it gets through. The MCP specification itself says clients must consider tool annotations untrusted unless they come from trusted servers, and that a human should be able to deny tool invocations (source: MCP tools specification). Tools in this area include mcp-scan (now Snyk Agent Scan) and registry verification.
Parts:
- Catalogue and identity — an allowlist of approved servers with a verified namespace, publisher and package —
model-context-protocol(no page yet), Unicode confusables,fuzzy-string-matching(no page yet) - Provenance and signing — proves who built a package, from what source, and that it was not altered —
code-signing(no page yet),signed-attestations(no page yet),reproducible-builds(no page yet), Transparency log, Supply-chain security - Dependency and vulnerability analysis — resolves the server's dependency tree and matches it against advisories —
dependency-version-resolution(no page yet),vulnerability-advisory-matching(no page yet),secret-scanning(no page yet), semver-range-matching (no page yet), software-bill-of-materials (no page yet) - Description and behaviour scanning — inspects tool text and code for injected instructions or data flows — Text classification, Dataflow analysis,
taint-tracking(no page yet), Prompt injection defense, json-rpc (no page yet) - Pinning and change detection — stores hashes of approved packages and descriptions, blocks on difference —
merkle-dag(no page yet),atomic-file-replacement(no page yet), Cryptographic hashes (SHA-2, BLAKE3) - Runtime confinement and policy — limits what a loaded server and its tools can touch — Process sandboxing, Policy engine, Mutual TLS, JWT, JSON Schema validation
- Durability of the registry state — keeps the approvals, pins and logs safe — Primary-backup replication, Point-in-time recovery
- Verification — tests the scanner and gate — Coverage-guided fuzzing, Deterministic simulation testing
- Operations — keeps the gate in the call path, measured and restartable — hdr-histogram (no page yet), Process supervision, Graceful draining
publisher ─► build (CI) ─► package + provenance attestation ─► registry ─► internal catalogue
│ signed, logged in transparency log │ allowlist + pins
▼ ▼
admission gate: verify signature/provenance ─► resolve deps ─► advisory match
│ scan description + code (classifier, dataflow) ─► human review
▼
approved record: {package digest, description hash, tool list hash, permissions}
│
agent host ── at every load: fetch, recompute hashes, compare ─► mismatch = block + alert
│ ok
▼ sandboxed server process (no secrets, egress allowlist, mTLS to host)
tool call ─► policy engine (per tool, per argument) ─► result ─► injection filter ─► model
How the parts fit together
Catalogue and identity: which server is this, really
Start with a closed list. The official MCP Registry gives clients a list of servers like an app store for them, and verifies two things at publication: that the publisher controls the namespace in the server's name (for example a GitHub-based prefix), and that the underlying package matches its metadata; it does not verify that the remote URL is controlled by the publisher or what the server does (source: MCP Registry documentation). That is useful identity, not a safety review. An organisation therefore keeps its own catalogue of approved entries and treats the public registry as a source of candidates.
Look-alike names are the entry attack. Unicode confusables maps each name to a skeleton so that names which differ only by look-alike characters collide, and restricts script mixing; fuzzy-string-matching (no page yet) flags names within a small edit distance of an approved one. Run both at admission and again whenever a name is shown to a human for approval.
Provenance and signing: prove origin, not benevolence
A signature says who published and that bytes are unchanged; it does not say the code is good. The useful layers are: signed artifacts (code-signing (no page yet)); attestations, signed statements about how an artifact was built (signed-attestations (no page yet); the in-toto statement format and the DSSE envelope are the common shape, sources below); and, where achievable, builds that give identical output from identical input (reproducible-builds (no page yet)) so a third party can rebuild and compare. Sigstore ties signing to short-lived certificates issued against a CI identity and records signatures in a public log; npm's provenance feature uses this so users can see where a package was built, signed by Sigstore public-good servers and logged in a transparency ledger (source: npm provenance docs). The log is a Transparency log: an append-only Merkle tree whose signed heads let anyone verify an entry exists and that history was not rewritten; Rekor is the Sigstore instance (source: Sigstore logging overview). SLSA names the levels of build assurance so a policy can say "accept only builds at level N from these builders" (source: SLSA levels). The general control set (lockfiles, pinned versions, verified builds) is in Supply-chain security.
Dependencies and advisories
A server is mostly dependencies. dependency-version-resolution (no page yet) produces the exact set the lockfile picks, so analysis runs on what will actually load. vulnerability-advisory-matching (no page yet) matches package versions against advisory data; OSV is an open database and schema for this (sources: OSV documentation, OSV schema). secret-scanning (no page yet) checks the package contents for embedded credentials. The result is a risk record per version, recomputed when new advisories land, not just at admission. Compare Vulnerability scanner for the general system.
semver-range-matching (no page yet) is what makes the resolved set predictable: a package declares ranges such as "compatible with 2.x", and the matcher decides which published versions satisfy them, which is also how an advisory's affected range ("below 2.4.1") is tested against a pinned version. software-bill-of-materials (no page yet) records the result: the exact packages, versions and hashes inside a server release, produced at build time and stored with the approval. When a new advisory appears, the gate searches the stored bills instead of re-resolving every server, and lists which approved servers contain the affected component.
Description and behaviour scanning
Tool descriptions are model-visible instructions, so a description is code in effect. Scanners treat it that way. Text classification flags descriptions containing instruction-like content aimed at the model (directions to ignore earlier rules, to read unrelated files, to send data elsewhere) rather than documenting what the tool does; the classifier needs examples drawn from your own reviewed catalogue, and it should output a score for a person to review, not a final verdict. For server code, Dataflow analysis and taint-tracking (no page yet) trace untrusted input (arguments, fetched content) to sensitive sinks (file writes, network calls, shell). Agent Scan, the successor to mcp-scan, reads tool descriptions from configured servers and checks them for threats such as prompt injections; its documentation warns that scanning can launch servers or contact URLs, so scans must themselves run in a sandbox (source: Agent Scan README). At runtime the model-facing side is covered by Prompt injection defense: results are data, not instructions.
The scanner gets descriptions the same way a client does: it starts the server in the sandbox and sends a tools/list request, a json-rpc (no page yet) call with a method, parameters and an id. Each response carries tool names, descriptions and input schemas, which become the text that is classified and hashed. The scanner also notes whether a server sends list-changed notifications, since a server that can change its tools after approval needs the re-check described below.
Pinning and change detection: the "rug pull" control
Approval must attach to content, not to a name. The approved record stores the package digest, a hash of each tool's name, description and input schema, and a hash over the whole tool list. Hashing a set of objects that each have their own hash is a content-addressed structure (merkle-dag (no page yet)), which lets the gate report which tool changed. At each load the host fetches the live list, recomputes, compares. MCP servers can announce list changes with a listChanged notification (source: MCP tools specification); a host should treat that as a signal to re-verify, not as permission. Store the approved records with atomic-file-replacement (no page yet) so a crash never leaves a half-written pin. A change does not always mean attack, so the gate's outcome is "block until re-reviewed" with a diff shown to the reviewer.
The digests in the record come from SHA-256 or BLAKE3: a package digest, a hash of each tool's name, description and input schema (serialised in one canonical form so equal content gives equal bytes), and a hash over the sorted list of those. Collision resistance makes a matching digest a proof of identical content, and BLAKE3's speed lets the gate re-hash the live tool list on every session start, not only at install.
Runtime confinement and policy
Assume a server will eventually turn bad and limit the blast radius. Process sandboxing runs the server in a powerless process with only the files, network destinations and environment variables it needs; secrets are not placed in its environment (see Agent credentials and secret brokering). Between host and server, Mutual TLS or short-lived signed tokens (JWT) authenticate remote servers both ways. A Policy engine sits at the host and decides per tool call: this agent, this tool, these argument values, this session context; default deny; high-risk tools require human confirmation. The MCP security best practices page lists the protocol-level traps this must respect: confused deputy through OAuth proxies, token passthrough, session hijacking, local server compromise and over-broad scopes (source: MCP security best practices). See also MCP server, MCP host and Agent sandbox.
The gate also validates each tool call's arguments against the tool's pinned input schema with JSON Schema validation before it reaches the server. Because the schema is part of the approved record, a call with extra fields, wrong types or out-of-range values is rejected at the gate with a typed error, and a server cannot widen what it accepts without changing the hash that triggers re-approval.
State and testing
Approvals, pins, attestation results and logs are state: Primary-backup replication and Point-in-time recovery keep the gate available and recoverable; after a restore, re-verify pins against the transparency log rather than trusting the restored file. Coverage-guided fuzzing hardens the parsers that read untrusted manifests and tool lists, and Deterministic simulation testing exercises the gate under reordering and partial failure.
Operations
Gate latency. hdr-histogram (no page yet) records the time the gate adds to a tool call: allowlist lookup, pin comparison, argument validation and policy decision. A log-linear histogram keeps bounded relative error from microseconds to seconds, so p99 per server shows whether the pin check or a scan result lookup slows every call. The same histogram type tracks scan duration per package, which sets how long a new version waits in quarantine before it can be approved.
Staying in the path. The gate and the scanners are long-running processes. Process supervision restarts a crashed gate under a delay and burst limit. A gate that is down must fail closed for servers that need approval, so supervision keeps the restart fast and bounded and reports a crash loop instead of masking it. Sandboxed MCP server processes are also children of a supervisor, which reaps them and applies the same restart policy.
Rolling the gate. On a deploy or a policy reload graceful draining lets in-flight tool calls and open sessions finish until a deadline while new sessions go to the new instance, and it tells clients to reconnect and resume. A session is never split across two policy versions, which keeps the audit record of what was allowed consistent.
Data and control flow
Admitting a new server:
- A team requests a server by registry name. The gate checks the name against the catalogue for look-alikes.
- It fetches the package, verifies the signature and the build attestation against the transparency log, and checks the builder against policy.
- It resolves the dependency tree from the lockfile and matches versions against advisories; high-severity results stop the flow.
- In a sandbox it starts the server, lists its tools, and records descriptions and schemas. The classifier and static analysis score them.
- A reviewer sees the score, the diff against nothing, the permissions asked, and the data flows found; approves or rejects.
- The approved record (digests, hashes, permissions, reviewer) is written to the catalogue and replicated.
Loading an approved server in an agent session:
- The host reads the pin, fetches the package digest and compares it. Mismatch: refuse and alert.
- It starts the server in the sandbox with the permissions in the record and connects over an authenticated channel.
- It lists tools and compares hashes to the pin. A new or changed tool is held back and reported; unchanged tools proceed.
- During the session each call goes through the policy engine; results pass the injection filter before reaching the model.
- A
listChangednotification triggers step 3 again.
Scaling
In the order you hit them:
- Review capacity. People are the bottleneck for new servers. Levers: a small curated catalogue, risk tiers (read-only local tools versus tools with network and write access), automation that rejects clear failures before a person looks.
- Re-verification cost at load. Hashing and comparing is cheap; fetching live lists adds latency. Levers: cache verified results for the session, verify lists lazily on first use, batch checks.
- Advisory churn. New advisories arrive daily. Levers: reindex by package-version with an inverted index, re-evaluate only affected records, route by severity.
- Fleet size. Many hosts load the same servers. Levers: central gate with signed approval bundles distributed to hosts, caching of verified digests.
- Scanner quality. False positives erode trust. Levers: calibrate the classifier on your catalogue, require two independent signals before blocking.
- Transparency-log dependence. Verification needs log access. Levers: bundle inclusion proofs with artifacts, mirror the log, define offline behaviour.
Failure modes
| Failure | Symptom | Cause | Fix |
|---|---|---|---|
| Description changed after approval | agent starts calling tools in new ways | approval tied to a name, not to a hash | pin description and schema hashes; block on diff; re-review |
| Look-alike server name | wrong server installed | name approved by eye | skeleton comparison and edit-distance check at admission and approval |
| Trusted publisher, bad update | malicious code in a new version | compromised maintainer or CI | pin digests, require provenance from the expected builder, delay adoption of new versions |
| Signature without review | signed but harmful server runs | signature mistaken for safety | policy requires review and behaviour scan beyond signature |
| Scanner executes a bad server | scan host is compromised | scanner starts untrusted commands | run scans in a sandbox with no secrets and limited network |
| Token passthrough | downstream API cannot tell callers apart | server forwards client tokens | accept only tokens issued for the server; exchange for downstream tokens |
| Over-broad permissions | tool reads or writes beyond its job | permissions granted per server, not per tool | per-tool policy; least-privilege sandbox profile |
| Stale advisory data | known-bad version runs | scan only at admission | continuous re-matching; alert and quarantine on new findings |
| Gate unavailable | agents blocked, or bypass is used | single gate with no replication | replicated gate, last-known-good bundles, break-glass procedure that logs |
| Restore loses revocations | revoked server loads again | pins restored from old backup | re-verify against the log after restore; replay the revocation list |
Real implementations
| name | what it is | repo | licence | notes |
|---|---|---|---|---|
| Snyk Agent Scan (formerly mcp-scan) | scanner for agent components: MCP servers, skills, tool descriptions | invariantlabs-ai/mcp-scan | Apache-2.0 | needs a Snyk token to run; output format marked experimental; run in a sandbox |
| MCP Registry | public catalogue of MCP servers with namespace and package verification | modelcontextprotocol/registry | MIT, moving to Apache-2.0 (per LICENSE) | does not review server behaviour |
| Sigstore cosign | signing and verification of containers and other artifacts | sigstore/cosign | Apache-2.0 | short-lived certificates and a public log |
| Sigstore Rekor | transparency log for signed software metadata | sigstore/rekor | Apache-2.0 | public instance and self-hosted option |
| in-toto | framework for verifying each step of a software supply chain | in-toto/in-toto | Apache-2.0 | layouts, steps and signed link metadata |
| SLSA | specification of build-integrity levels | slsa-framework/slsa | Community Specification License 1.0 | a spec, not software |
| OSV-Scanner | vulnerability scanner using OSV data | google/osv-scanner | Apache-2.0 | open advisory database and schema |
The alternative: run only servers you wrote or vendor, and pin them like any dependency. For a handful of internal tools this is cheaper than a gate. A gate pays off once teams adopt third-party servers faster than a person can review them.
Sources
- Model Context Protocol, Tools, Authorization, Security best practices. tools, authorization, security
- MCP Registry, Quickstart and README. quickstart, repo
- Snyk Agent Scan (mcp-scan) README. repo
- Sigstore, Rekor overview. docs
- npm, Generating provenance statements. docs
- SLSA, Specification v1.0 and levels. slsa.dev
- in-toto, Attestation statement and DSSE Envelope. statement, DSSE
- OSV, documentation and schema. osv.dev, schema
- OWASP, LLM03:2025 Supply Chain. genai.owasp.org