CustomLabs
MCP

MCP standardizes the wire format. Running it safely across an org is still yours to build.

Six surfaces and 24 controls. Each control names the layer that owns it and what the spec requires of it. Five attack paths are walked end to end. A ten-question gate checks the next server someone asks you to connect.

Updated First published

42 min read

Markdown

One MCPMCP is an open standard for connecting LLM applications to tools and data sources. server connected to one agent is easy to reason about. The failures in this page start when a second server joins the session. A gateway then brokers identity for both servers. Three teams write the tool descriptions, and none of them talk to each other. The model reads all of those descriptions as one catalog. The protocol has no defect here. It assumes someone builds an integration layer around it. This page covers that layer.

The Agent Tool Interface covers how to design one tool well. That means a tight schema, a clear name, and sane permissions. This page starts where that one stops. It covers identity, the catalog, the trust boundary, supply chain, and operations. These matter once more than the team that wrote a tool can reach it across a process boundary.

Current as of the 2026-07-28 specification revision. The Model Context Protocol's own security guidance is narrower than most teams assume. It tells a host to treat tool descriptions and annotations as untrusted unless they come from a server you trust. It requires explicit user consent before a tool runs. It requires an HTTP server to validate the Origin header against DNS rebinding. Authorization is optional. A server that adopts it must publish protected resource metadata. A token issued for one server must never be accepted or forwarded by another. Past that, the spec is quiet. Catalog size, supply-chain provenance, logging, and incident response are policy questions the protocol leaves entirely to whoever operates the server. MCP Specification: Authorization (2026-07-28) → MCP Security Cheat Sheet →

The six surfaces

Where an MCP rollout actually breaks.#

Each surface fails a different way. The characteristic failure is one layer assuming another layer handled it.

01

Connection & transport

Covers: Where a server runs, as stdio or as a remote HTTP endpoint. The network hygiene each choice needs. This covers Origin-header validation, loopback binding, TLS on anything remote, and isolation of session state.

Breaks when: A server built for local development binds to every interface on the machine, or a remote endpoint skips the Origin check. A rebound DNS name then reaches it from a page the user visited.

Watch: Share of servers reachable only over stdio or a loopback bind, versus reachable on a routable network interface

02

Identity & authorization

Covers: A protected MCP server that acts as an OAuth 2.1 resource server. It uses protected resource metadata discovery, PKCE, and audience-bound tokens scoped to one server. Credentials are short-lived. No personal access token sits pasted in a config file.

Breaks when: A gateway holds one broad, delegated token to keep integration simple, and forwards it unchanged to every server it proxies. Compromising the least-trusted server in the chain then hands over credentials scoped for every other one.

Watch: Share of connected servers that reject a token whose audience doesn't name them specifically

03

The catalog the model sees

Covers: How many servers and tools share one context window. How namespacing keeps two servers' identically named tools from colliding. Whether the model reads a static list or one that changes mid-session.

Breaks when: Two connected servers both register a tool called `search`. The model cannot tell which server a given call reached.

Watch: Count of distinct tool names sharing a session, and how many collide before namespacing is applied

04

The trust boundary

Covers: Tool descriptions, annotations, schemas, and return values, all of them untrusted input by default. Structured returns over raw HTML, no auto-approval, full call parameters shown before execution, and sensitive servers kept apart from general-purpose ones.

Breaks when: Either a tool's return value renders as trusted HTML, or a repeated approval becomes blanket consent. In the second case the host does not ask again about the call about to run.

Watch: Share of destructive or write-capable tool calls that show full parameters before execution, not a summarised description

05

Supply chain & change control

Covers: A registry or allowlist, plus provenance and publisher identity. Pinning a tool definition by hash, and re-consent when a definition changes. Typosquat checks, and sandboxing for a server that runs locally.

Breaks when: A tool a user approved last week gets redefined by its own server, with a new parameter or a new destination. Every session that cached the original approval keeps calling it with no fresh consent prompt.

Watch: Share of connected servers whose current tool definitions are pinned against a recorded hash from approval time

06

Operations

Covers: Per-call logging with parameters and user context, and redaction. Quotas and rate limits per session or tenant. Version pinning with a deprecation window. The contents of an incident runbook when the compromised component is someone else's server.

Breaks when: An MCP server misbehaves in production. The only record of what happened is whatever the model's own transcript kept, because nothing logged the call.

Watch: Share of tool calls with a logged parameter set and user identity retrievable after the fact

The protocol/you split

What the spec guarantees, and what's still yours.#

One row per surface. The right column is where almost every real incident on this page actually starts.

Connection & transport

The spec guarantees

The spec defines stdio and Streamable HTTP as the two transports. On HTTP it requires Origin-header validation on every incoming connection. A server must answer an invalid Origin with a 403, which defeats DNS rebinding. It also says a local server should bind only to 127.0.0.1, which keeps it off every other interface.

Yours to build

Network topology drives several decisions. These are whether a server should be remote at all, TLS termination, the Host-header check OWASP asks for, and per-session state isolation. The spec describes the wire format for them. It does not make them for you.

Identity & authorization

The spec guarantees

Authorization itself is optional, and a server on stdio is told to use its launch environment instead. Once an HTTP server is protected, the requirements are hard. The server acts as an OAuth 2.1 resource server and must implement RFC 9728 protected resource metadata. Clients must send RFC 8707 resource indicators on both the authorization request and the token request. A server must reject any token whose audience does not name it. This holds even for a token that arrived from a trusted-looking gateway.

Yours to build

The spec leaves several policy calls to you. These are which identity provider you trust, how short-lived a credential is, and whether one agent gets one distinct identity per server.

The catalog the model sees

The spec guarantees

A server can declare its tool list as static or dynamic, and notify a connected client when that list changes.

Yours to build

Namespacing, how many servers share one session, and how much of the context windowThe context window is the maximum text, measured in tokens, a model can consider at once. the catalog may spend are your decisions. The spec has no opinion on catalog size or collision handling.

The trust boundary

The spec guarantees

A host should treat tool descriptions and annotations as untrusted unless they come from an already-trusted server. A host must get explicit user consent before a tool call runs.

Yours to build

The spec leaves several implementation decisions open. One is what the consent prompt shows. Another is whether a return value renders as trusted markup or as sanitised structured data. A third is which servers are sensitive enough to isolate.

Supply chain & change control

The spec guarantees

Close to nothing: no required registry, no provenance format, no mandated signing scheme for a tool definition.

Yours to build

You build the allowlist, the way to detect a changed definition, and the sandbox for a locally run server. The protocol gives you none of them. This is the surface where the protocol and a real supply chain are furthest apart.

Operations

The spec guarantees

Version legibility, and that's about it. There is no initialization handshake in this revision. Every request carries its own protocol version. A server must reject a version it does not implement, with an error that lists the versions it does support. Every server must implement a discovery endpoint that a client can query up front.

Yours to build

Logging, redaction, quotas, and a deprecation policy for your own tool contractsA tool contract is the schema a tool exposes to a model. are your design. Knowing that a version mismatch exists tells you nothing about what to do when a third-party server changes underneath you.

The control bank

24 controls, six surfaces.#

Filter by which layer actually owns a control, then copy the visible list as a Markdown checklist.

01 Connection & transport

MCP gives you two transports. Stdio is a subprocess that talks over its own stdin and stdout. Streamable HTTP is a server reachable over a network. The choice isn't cosmetic. A stdio server inherits the process boundary of whatever launched it. Nothing else on the network can reach it. An HTTP server is reachable from anything that can route to it. Every piece of hygiene a web service needs now applies to a tool integration too. That means TLS, the spec's Origin check against DNS rebinding, and a bind address that is never 0.0.0.0 by accident. Most teams get this right for a service they would call "real infrastructure". They skip it for an MCP server. It started as a weekend script, and nobody revisited the bind address after it moved to production.

What it covers
A deliberate choice between a stdio subprocess and a remote HTTP server, made by naming the process or team boundary the tool must cross.
How to build it
Default to stdio for anything launched and used inside one process or one team's infrastructure. Use remote HTTP only once a separate team, product, or organisation needs to call the same server.
Produces
Fewer network-facing servers standing up TLS, auth, and hardening work for a boundary that never actually existed.
Tradeoff
Stdio is simpler to secure but harder to share; moving to remote HTTP later is a real migration, not a config flag.
Prove it
For any remote server, someone can name the other team or product that calls it. If nobody calls it yet, it should still be stdio.
What it covers
A local or development-only MCP server bound to 127.0.0.1, which only processes on the same machine can reach.
How to build it
Set the bind address in the server's own configuration. Do not rely on a framework default. Check the address on every new server before it runs anywhere near a shared machine.
Produces
A server that's unreachable from anything else on the same network, by construction rather than by firewall rule someone has to remember to add.
Tradeoff
A loopback-only bind means a genuinely shared use case has to go through a deliberate remote-transport decision instead of just widening the bind address quietly.
Prove it
A port scan from another host on the same network reaches nothing for any server meant to stay local.
What it covers
The one hard requirement the spec places on this surface. A Streamable HTTP server must check the Origin header on every incoming connection and reject an invalid one with 403. This stops a page the user visited from driving a local server through a rebound DNS name. The spec does not include TLS or a Host-header allowlist. Both are ordinary web-service hardening. The OWASP MCP cheat sheet asks for the Host check by name.
How to build it
Check Origin before a request reaches any handler and answer an unrecognised one with 403, rather than treating the header as advisory. Then terminate TLS in front of the server. Reject any request whose Host header names something this server does not serve. The three defend different things and none substitutes for another.
Produces
A local or internal server that a hostile web page cannot drive, even if the page resolves a name it controls to your loopback address. A remote server no longer leaks credentials in plaintext on the wire.
Tradeoff
A strict allowlist on either header breaks a legitimate client connecting from an origin, or under a hostname, nobody wrote down. List the origins and hostnames you serve. Do not fall back to accepting anything.
Prove it
A request that carries an unrecognised Origin is answered with 403 before any tool handler runs. A request that carries an unexpected Host is rejected too. Tests check both cases directly.
What it covers
A server design where one client's session, its context, its in-flight calls, its cached results, never leaks into another session's view.
How to build it
Key every piece of server-side state by session identifier. Test with two concurrent sessions, and do not assume the framework isolates them.
Produces
A server where one user's conversation can't surface another user's data by accident under concurrent load.
Tradeoff
Per-session isolation costs engineering time on a server that started as a single-user script. It is cheaper to build before the second user arrives than to retrofit afterwards.
Prove it
Two concurrent sessions against the same server, driven with different inputs, produce results that never cross.

02 Identity & authorization

Authorization is optional in MCP, and whether to protect a server at all is your call. Once you do, the 2026-07-28 spec takes a clear position. A protected server acts as an OAuth 2.1 resource server. It must implement RFC 9728 protected resource metadata, so a client can discover how to authenticate. It must validate that any token it receives names that server as the audience under RFC 8707. The line worth reading twice says a server must not accept or transit any other token. That rules out passthrough. It rules out the excuse "the gateway already checked this" as well. Stdio servers sit outside that regime by design. The spec tells them to take credentials from the environment they were launched in. This pushes the identity question back to whatever process owns that environment.

What it covers
A client that always uses PKCE on the OAuth 2.1 authorization code flow. That client also always includes the RFC 8707 `resource` parameter, which names the server the token is for.
How to build it
Wire both into the client's authorization request unconditionally. Treat both as required from the first release. Confirm that the authorization server honours the resource parameter.
Produces
A token minted for exactly one server, with no authorization-code interception path left open.
Tradeoff
Not every authorization server implements resource indicators yet. A client that requires them cannot talk to one that does not. That is the correct failure mode, because the client never falls back silently.
Prove it
A captured authorization request for any MCP connection shows both a PKCE challenge and a resource parameter naming the target server.
What it covers
A hard check on every request that the audience of the token names the receiving server. A standing rule that no component in the chain forwards a received token to a different downstream server.
How to build it
Reject a token with a non-matching audience claim at the server itself, even when a gateway upstream already runs that check. Any code path that reuses an inbound token as an outbound credential is a design defect. Fix it immediately.
Produces
A chain where compromising the least-trusted server in a proxy setup doesn't hand over credentials valid anywhere else.
Tradeoff
Audience-bound tokens per server mean a gateway cannot mint one credential and relay it everywhere. It must broker a distinct token for each downstream server. That adds more moving parts.
Prove it
A token minted for server A, replayed against server B, is rejected, tested directly rather than assumed from the library's defaults.
What it covers
A token scope that maps one-to-one to the tools an integration calls. A separate step-up prompt for anything more sensitive than the original grant covered.
How to build it
Request the minimum scope at connection time. Require a fresh, visible authorization step before any call that needs more than the original grant. A cached token never expands silently.
Produces
A blast radius on a compromised or misbehaving server that matches what the integration needed. Nobody picked the scope because it was convenient to request once.
Tradeoff
Narrow scopes mean more authorization prompts as a workflow's needs grow, which is real friction against a smooth first-run experience.
Prove it
The scope list on a live grant, diffed against the tools that integration calls, shows no unused scope.
What it covers
A distinct credential for each agent-to-server pairing, issued with a short lifetime. No agent shares one long-lived personal access token with other agents that reach the same server.
How to build it
Provision credentials per pairing through the identity system that already issues short-lived tokens elsewhere in the org. Stop pasting a personal access token into a shared config file.
Produces
An audit trail that names which agent made which call. A compromised credential that expires on its own.
Tradeoff
Per-pairing short-lived credentials need real provisioning infrastructure; a shared long-lived token is faster to stand up and worse the moment anything goes wrong.
Prove it
A credential in active use traces to one agent-server pairing. Its remaining lifetime is hours or days. It never lasts until someone rotates it.

03 The catalog the model sees

Connect three servers in one session and the model reads all of their tools and descriptions in the same context window. Nothing partitions one server's catalog from another's. That is what makes cross-server attacks possible. A tool from a server added an hour ago can describe itself as the "preferred" version of a tool the model has called all session. Nothing in the structure tells the model that the two claims come from different trust levels. Namespacing every tool by the server that owns it is the cheapest fix available. The fix is yours to apply. See /tool-design/ for the per-tool naming rules this surface assumes. This page covers what happens once tools from more than one server share the same room.

What it covers
A naming convention that prefixes every tool with the name of its server. Two servers can then never register the same bare name, and the model can always tell them apart.
How to build it
If a server does not add the prefix, apply it at the client or gateway layer. Refuse to connect a second server whose tools would collide with an already-connected one.
Produces
A catalog where a newly connected server's tools can never shadow one the model has already been using all session.
Tradeoff
Namespacing lengthens every tool name the model reads. That is a small cost against the context budget. In return, the catalog stays legible as it grows.
Prove it
A search across the live catalog for duplicate bare tool names, ignoring the namespace prefix, returns none.
What it covers
A ceiling on how many servers and tools one session can connect. Without it, the tool catalog starts to crowd out the task.
How to build it
Measure the token cost of the current catalog like any other context spend. Cap the number of servers connected at the same time. Do not let every available integration join by default.
Produces
A session where the model spends its context budget on the task. It does not spend that budget on a hundred tool descriptions that it will use once or never.
Tradeoff
A hard cap on connected servers means a task that needs a dozen integrations has to request an exception, which is friction worth having.
Prove it
The token cost of the tool catalog in a live session is measured and tracked, not assumed to be negligible.
What it covers
A standing check before a new server joins a session with existing servers. The check asks whether its tool descriptions could reference or impersonate the tools of a connected server.
How to build it
Before approving the connection, read the tool descriptions of a new server for claims about precedence, freshness, or preference ("use this instead of"). Give them the scrutiny a code reviewer gives an unfamiliar dependency.
Produces
A caught instance of a would-be shadowing tool before it ever reaches a live session, rather than after a user reports something odd.
Tradeoff
Manual review of every new server description does not scale past a small, curated set of integrations. Build the registry controls in the supply-chain surface below as well.
Prove it
The last new server connected has a recorded review of its tool descriptions against the existing catalog, beyond an install log entry.
What it covers
A rule that a server which updates its tool list mid-session (`tools/list_changed`) triggers the same visible review as a brand-new tool connection. The new tools never become callable silently.
How to build it
Show a mid-session catalog change to the user or operator. Treat an unreviewed expansion of the tool list as equal to connecting an unvetted new server.
Produces
A session where the set of things a model can call never grows without someone actually seeing that it grew.
Tradeoff
Gating every dynamic update interrupts the normal operation of a legitimate server that changes often. Apply the gate to new tools. Do not apply it to routine parameter tweaks on existing ones.
Prove it
A test server that adds a tool mid-session triggers a visible review before that tool becomes callable, checked directly against a live session.

04 The trust boundary

The spec's security guidance says a host should treat a tool's description and annotations as untrusted unless they come from a server you trust. It also says a host must get explicit consent before a tool runs. Both of those address the same fact. A model reads one undifferentiated stream of tokens. The wire format marks no string as "instruction from us" or "text a server sent". A return value is as untrusted as a description. A ticket-lookup tool that is safe on its own can still hand back a record someone else could write to. The model has no privileged channel to tell that record apart from a real instruction. /security-review/ is where this surface gets checked before a server ships; the prompt-injection-threat-model insight covers the general case this page narrows to one specific channel.

What it covers
A standing rule that the description, annotations, and schema of a tool are data from an untrusted source. This holds until someone individually vets and trusts that server.
How to build it
Render tool descriptions to the user as plain, inert text. Never execute or interpolate them anywhere. Do not grant a server trusted status by default because it is already connected.
Produces
A model and a host that can't be steered by an instruction hidden inside a field nobody expected to carry one.
Tradeoff
Treating even long-standing integrations as untrusted by default means re-earning trust explicitly for each one, rather than assuming it from tenure.
Prove it
A tool description containing an embedded instruction, tested deliberately against the live host, produces no behaviour change beyond displaying the text.
What it covers
A tool response format limited to structured data (JSON or a typed object). The calling application controls how that data is displayed. The model or client never renders raw markup directly.
How to build it
Constrain every tool's return schema to structured fields, and reject a tool definition that hands back a raw HTML blob for direct rendering.
Produces
A return value that can carry a hidden script or instruction with nowhere for it to actually execute.
Tradeoff
Structured returns need an explicit rendering layer on the client side instead of just displaying whatever the server sent, which is more work upfront.
Prove it
Every live tool's return schema is inspected and confirmed structured, not a free-text or HTML field.
What it covers
A consent step, shown fresh for every destructive or sensitive call. It displays the actual parameters about to be sent. A friendly one-line summary of the intended tool behaviour is not enough.
How to build it
Render the literal argument values in the consent prompt. An earlier approval of the same tool never counts as approval of the parameters of this call.
Produces
A user who can actually catch a hallucinated or maliciously altered parameter before it's sent, because they saw it, not a paraphrase of it.
Tradeoff
Full-parameter prompts add more friction than a one-tap approval. A user under time pressure may stop reading them closely. Solve that as a training and UX problem. It is no reason to summarise.
Prove it
A destructive tool call's consent prompt, captured from a live session, shows the actual argument values rather than a generic description.
What it covers
A rule that a server with access to sensitive data or actions runs in a session by itself, or only with other equally trusted servers. It never mixes with a general-purpose or exploratory server.
How to build it
Classify each server by what it can reach before connecting it. Route sensitive-server sessions through a separate, more restricted configuration than the one for everyday browsing or search tools.
Produces
A blast radius, if a general-purpose server turns out to be malicious or compromised, that never reaches the sensitive server sitting in the same session.
Tradeoff
Isolating sensitive servers has a cost. A workflow that needs both a sensitive tool and a general one in the same task must be redesigned around that separation. That is real friction.
Prove it
No live session mixes a classified-sensitive server with an unvetted general-purpose one, checked against the actual session configuration.

05 Supply chain & change control

This is the surface where the spec has the least to say and your own policy carries the most weight. There's no standard registry, no required signing, no provenance format — a server is trustworthy because someone checked, not because the protocol enforces it. The failure worth naming is the rug pull. A definition was safe when a user first approved it. It changes shape later. The transport does not force a new consent prompt. Only a check that you build forces one. Pinning a definition by hash at approval time is the cheapest fix. Require re-consent as soon as the definition changes. Build this on top of the protocol, because the protocol does not provide it.

What it covers
A registry or allowlist that a server must clear, naming its publisher and confirming that identity, before it can be connected in any production session.
How to build it
Stand up a lightweight internal registry (a reviewed spreadsheet is enough to start). Record publisher, purpose, and approval date for every sanctioned server. Block anything not on it at the gateway.
Produces
A concrete answer to "who published this, and did we check" for every server in production, instead of an install history nobody can reconstruct.
Tradeoff
An allowlist is one more thing to maintain, and a useful new server waits on review before anyone can use it.
Prove it
Every server connected in production maps to an entry in the registry with a recorded publisher and approval date.
What it covers
A recorded hash of a tool's schema and description, taken the moment it's approved, checked against the live definition on every subsequent connection.
How to build it
Hash the tool definition at approval and store the hash with the registry entry. Compare on connect. Treat any mismatch as a new, unapproved tool and never as an update to the old one.
Produces
A structural way to detect a rug pull the moment it happens, rather than relying on someone noticing the tool behaves differently.
Tradeoff
Hash pinning breaks on a legitimate, benign update as readily as on a malicious one. Pair it with the re-consent flow below. Never use it as a silent block.
Prove it
A deliberately modified tool definition, reconnected against a stored hash, is flagged as changed before it's callable.
What it covers
A rule that any change to the schema, description, or destination of a tool invalidates a prior approval. The change triggers the full consent flow again. The cached approval does not carry over.
How to build it
Wire the hash check above directly into the consent system, so a mismatch forces the same explicit approval step a brand-new tool would need.
Produces
Protection against the specific rug-pull attack path below: a tool that was safe when approved and gets redefined afterward.
Tradeoff
Frequent, legitimate tool updates from an actively maintained server mean more re-consent prompts, which a user may start clicking through without reading.
Prove it
A tool whose definition changed since last approval requires a new consent prompt before its next call, tested directly.
What it covers
Process-level isolation (containers, restricted filesystem access, no unscoped network egress) for any server that runs as a local process. A routine dependency scan on its package manifest.
How to build it
Run local servers in the sandboxing regime that a team already requires for untrusted third-party code. Add the dependency manifest of the server to the scanning that already runs on the rest of the codebase.
Produces
A compromised or malicious local server contained to its sandbox instead of holding the same filesystem and network access as the process that launched it.
Tradeoff
Sandboxing adds setup overhead for each server. An overly restrictive sandbox breaks a server that needs broader access. Grant that access deliberately and never by default.
Prove it
A locally run server's process is confirmed sandboxed (restricted filesystem, no unscoped egress), and its last dependency scan is on record.

06 Operations

What the spec gives you here is version legibility and nothing else. As of 2026-07-28 there is no initialization handshake: every request declares its own protocol version. A server that does not implement the requested version must say so and list what it does support. Servers must also implement a discovery endpoint a client can ask up front. That is useful for an operator. It stops well short of logging, redaction, quotas, or a deprecation policy. All of those are your build. The gap matters more here than on most integration surfaces. You are often operating someone else's software. A third-party server runs against your production data, and you do not control its release cadence. An incident runbook that assumes you own every component in the chain fails. Plan for the day a support ticket tells you that the vendor pushed a new tool definition overnight. Write the runbook for that case, and for the case where the broken component is yours.

What it covers
A logging record for every tool call. It captures the tool name, its parameters, the identity of the agent and user behind it, and the result. It applies sensible redaction to sensitive data.
How to build it
Instrument logging at the gateway or host layer so it is uniform across every connected server. Do not depend on each server to log its own calls consistently.
Produces
A record anyone can inspect that shows which agent made which call, and with what parameters. The team does not rebuild this from the model's own transcript after the fact.
Tradeoff
Full-parameter logging sometimes puts sensitive data in logs. Redaction rules need design from the start. Waiting until the first sensitive value appears in a log line is too late.
Prove it
A tool call picked at random from the last day has a log entry recoverable by tool name, parameters, and calling identity.
What it covers
A ceiling on call volume per session and per tenant. One misbehaving agent or runaway loop then cannot exhaust a shared server's capacity for everyone else.
How to build it
Enforce limits at the gateway, keyed by session and tenant identity. Define the response when a ceiling is hit, such as backoff or a hard stop. Do not use an unbounded queue.
Produces
A shared server that degrades for the one tenant that's misbehaving, not for every tenant connected to it.
Tradeoff
A rate limit set too low throttles a legitimate high-volume workload; calibrate it against real traffic rather than a round number picked in advance.
Prove it
A session artificially driven past its quota is throttled or stopped, and every other session on the same server is unaffected, tested directly.
What it covers
An explicit version pin for the MCP protocol revision and for every tool contract a client depends on. A published window runs before an old version stops being supported.
How to build it
Record the pinned version in each server's registry entry. Require any breaking change to ship with a deprecation notice that carries a real date.
Produces
A client that fails predictably at a known date instead of breaking silently the moment a server updates.
Tradeoff
Supporting an old version through its deprecation window means running more than one shape of the same integration at once. This costs maintenance and buys a safety margin.
Prove it
Every connected server's pinned version and its stated deprecation date, if any, are on record and checked against what's actually running.
What it covers
A specific incident procedure for a compromised or misbehaving third-party MCP server. The procedure says how to disconnect the server fast. It says what to check for damage done while the server was connected. It also names who has the authority to make that call.
How to build it
Write the runbook against the worst case: a vendor's server starts returning altered data, or is compromised outright. Cover incidents outside your own code as well as inside it. Name who can pull the plug without waiting for the vendor.
Produces
A response measured in minutes for a third-party server incident. This runbook does not assume every component is one your own team can patch, so nobody has to scramble.
Tradeoff
A runbook for a case where you don't control the fix sometimes ends in one action: disconnect and wait. That can feel unsatisfying compared to a real fix.
Prove it
A tabletop exercise that simulates a third-party server compromise reaches disconnection inside the runbook's stated target time.
Show, don't just state

A control bank entry, in a real config.#

One server entry, wrong then right, in the shape it actually ships in: a client config file.

The same server entry, two ways.

One client config trusts the server with everything it can reach. The other hands it exactly what the job needs, and no more.

What breaks claude_desktop_config.json
{
  "mcpServers": {
    "internal-tools": {
      "command": "npx",
      "args": ["-y", "internal-tools-mcp"],
      "env": {
        "API_TOKEN": "sk-live-9f8a7b6c5d4e3f2a"
      }
    }
  }
}

A broad live token in plain text, every tool exposed, and no pin on what gets pulled and run.

What works claude_desktop_config.json
{
  "mcpServers": {
    "internal-tools": {
      "command": "docker",
      "args": [
        "run", "--rm",
        "ghcr.io/acme/internal-tools-mcp@sha256:3af9c1",
        "--allow-tools", "search_kb,read_ticket"
      ],
      "env": {
        "API_TOKEN": "${INTERNAL_TOOLS_RO_TOKEN}"
      }
    }
  }
}

A scoped read-only credential from the environment, an explicit tool allow-list, and the image pinned by digest.

Five attack paths

Walked end to end, not left as a category.#

Each one is a specific sequence, the control that stops it, and the layer that owns that control.

Diagram in three lanes, by layer: Host, Identity, Server. Host holds three attack paths: the poisoned tool description; cross-server tool shadowing; and the post-approval rug pull. Identity holds the OAuth-proxy confused deputy. Server holds exfiltration through an approved tool's return value. No edges connect the paths; the diagram shows only which layer each one runs through. HOST IDENTITY SERVER The poisoned tool description Cross-server tool shadowing The post-approval rug pull The OAuth-proxy confused deputy Exfiltration through an approved tool's return value
Three of the five attack paths run through the host; the other two run one each through identity and through the server.

The poisoned tool description

How it runs: A server, compromised or malicious from the start, publishes a tool whose name and parameters look ordinary. Its description field carries an embedded instruction. The model reads it as context it should act on. Nothing in the transport marks the field as data, so the model cannot tell it from a directive. The model follows the hidden instruction on its next turn, reading a file or calling a tool nobody asked it to.

Stopped by: Treat every description as untrusted by default. Show the full parameters of any resulting call before it executes. Together these catch the instruction before it turns into an action.

Host See the control: Treat every tool description and annotation as untrusted by default →

Cross-server tool shadowing

How it runs: A session already has a legitimate `file_search` tool in active use. A newly connected server registers its own tool under the same bare name. Its description claims to be the "updated" or "preferred" version. Later calls the model meant for the original tool go to a server that should never have seen those queries.

Stopped by: Namespacing every tool by the server that owns it removes the name collision the shadowing attack depends on entirely.

Host See the control: Namespace every tool by the server that owns it →

The OAuth-proxy confused deputy

How it runs: A gateway sits in front of several MCP servers and holds one broad, delegated token to keep the integration simple. A request meant only for the least-privileged server in the chain carries that same token. The receiving server has no audience check to fail. It treats the token as valid proof of authorization for actions the original grant never covered.

Stopped by: Validating token audience at every server and refusing to forward a received token to another downstream server closes this path. It is the one attack the spec names as disallowed.

Identity See the control: Validate audience on every token, and never forward one to a different server →

The post-approval rug pull

How it runs: A user approves a tool the first time it appears, reasonably, because its description and schema look narrow and safe. Days later, the server that publishes the tool redefines the same tool name without notice. The tool now accepts a new parameter or reaches a new destination. Every session that cached the original approval keeps calling the new, unreviewed version with no fresh consent prompt.

Stopped by: Pin the tool's definition by hash at approval time. Require a new consent prompt as soon as the hash changes. This catches the redefinition before anyone calls the unreviewed version again.

Host See the control: Require fresh consent the moment a tool's definition changes →

Exfiltration through an approved tool's return value

How it runs: A legitimate, already-approved tool, such as a ticket lookup or a document fetch, returns content that contains an embedded instruction. Whoever populated that record could write arbitrary text into it. The model reads the return value as ordinary data on one call and as an instruction on the next. It then forwards something it was never asked to forward. The recipient is a second, also-approved tool that can act on it.

Stopped by: Returning structured data instead of raw, renderable content limits what a return value can smuggle. Isolating sensitive servers bounds what a successful exfiltration can reach.

Server See the control: Return structured data, never rendered HTML, from a tool call →

The server intake gate

Ten questions before the next server gets connected.#

What a platform team actually needs the day someone asks to add a third-party MCP server.

  1. Who operates this server, and is there a named contact for it?

    Why it's on the gate
    An unowned server is a support and security dead end the day something breaks — there's nobody to ask what changed.
    Passing answer
    A named publisher or internal team, reachable, recorded before the server is added to any production session.
    Artifact
    An owner field in the server registry, filled in before approval, not after an incident.
  2. Does the requested scope map one-to-one to the tools this workflow actually calls?

    Why it's on the gate
    An over-scoped grant is exactly what turns a compromised or misbehaving server into a bigger incident than it needed to be.
    Passing answer
    A scope list with no entry unused by the tools actually invoked.
    Artifact
    A diff of the granted scope against the tool list, run at approval and rechecked periodically.
  3. Is the token this server receives bound to it specifically, and rejected if it isn't?

    Why it's on the gate
    This is the exact gap the confused-deputy attack path above exploits.
    Passing answer
    A test call with a token minted for a different server is confirmed rejected, rather than assumed to be.
    Artifact
    A recorded test result from a token-audience check, not a design document describing one.
  4. What does the tool catalog look like once this server joins an existing session, and does anything collide?

    Why it's on the gate
    A new server can shadow an existing tool the moment its name or description overlaps with one already in use.
    Passing answer
    A reviewed diff of the catalog before and after connecting, with no unresolved name collision.
    Artifact
    The before/after catalog diff, attached to the approval record.
  5. Can this server's tool definitions change without anyone finding out?

    Why it's on the gate
    An unpinned definition is exactly what the post-approval rug pull depends on.
    Passing answer
    Definitions pinned by a recorded hash at approval time, with re-consent required the moment that hash changes.
    Artifact
    The recorded hash from the approval, checked against the currently live definition.
  6. What does a destructive or irreversible call from this server show the user before it runs?

    Why it's on the gate
    A summarised description hides exactly the parameter a user would have caught if they'd seen it in full.
    Passing answer
    The full, literal parameters of the call, shown fresh every time, never a cached blanket approval.
    Artifact
    A captured screenshot or spec of the consent prompt for this server's most destructive tool.
  7. Where does this server actually run, and is it reachable from anything it shouldn't be?

    Why it's on the gate
    A server bound wider than intended is reachable from more of the network than anyone signed off on.
    Passing answer
    Bound to loopback or a private network segment, with TLS on anything remote.
    Artifact
    A port scan or network diagram confirming the actual reachable surface, not the intended one.
  8. Are this server's calls logged with full parameters and the calling identity, and where does that log live?

    Why it's on the gate
    Without this, an incident review has nothing to reconstruct what actually happened.
    Passing answer
    Full parameters and calling identity logged centrally, with redaction rules for anything genuinely sensitive.
    Artifact
    A sample log line for a real call to this server.
  9. What's the version and deprecation story if this server's tools change shape?

    Why it's on the gate
    A breaking change with no warning is a production incident; one with a stated window is a scheduled migration.
    Passing answer
    A version marker on the current definition and a stated notice window before an old shape stops working.
    Artifact
    The server's own published versioning or deprecation policy, or the internal equivalent if it doesn't have one.
  10. If this server is compromised, what's the actual blast radius, and who responds?

    Why it's on the gate
    Every other question above is worth nothing if nobody's named to act when the answer to this one turns out badly.
    Passing answer
    A blast radius bounded by the scopes already granted, and a named on-call path in the incident runbook.
    Artifact
    The runbook entry naming this server specifically, not a generic incident-response document.
What this is built from

Grounded in the spec, not a vendor's incident count.#

No third-party prevalence statistics or CVE identifiers. Attack classes are stable; those numbers rot in weeks.

Sources

  1. Model Context Protocol - MCP Specification: Authorization (2026-07-28)

    The specification text our identity and authorization surface is checked against. Retrieved 2026-08-24.

  2. Model Context Protocol - MCP Specification: Tools (2026-07-28)

    The specification text our tool-design and catalogue rules are checked against. Retrieved 2026-08-24.

  3. OWASP - MCP Security Cheat Sheet

    The MCP-specific checklist our intake gate is checked against. Retrieved 2026-08-24.

Adding a third-party MCP server soon

A Ship Audit checks the intake gate above against the server someone asks you to connect. It runs before that server joins a production session.

Questions

Before you connect the next server.#

What platform teams ask us before they let MCP past a single pilot integration.

01 Isn't MCP itself the security boundary?

No. The spec standardizes the wire format for a tool call that crosses a process boundary. It also mandates a few things. One is Origin validation on an HTTP transport. Another applies once you choose to protect a server: audience-bound tokens with no passthrough. Past that, the catalog you expose, the servers you trust, and the blast radius of a bad call are yours to design. The protocol guarantees none of it.

Link to this answer: Isn't MCP itself the security boundary?
02 We only connect first-party servers we wrote ourselves. Does the trust boundary still apply?

Yes, though the risk shifts shape. A first-party server can't typosquat you. Its tool descriptions and return values still sit in the same context window as every other connected server's. A bug in your own server's output can read like an instruction just as easily as a malicious one. Namespacing and structured returns aren't only for third parties.

Link to this answer: We only connect first-party servers we wrote ourselves. Does the trust boundary still apply?
03 What's the single fastest way to get burned on this surface?

A gateway that holds one broad, delegated token and forwards it unchanged to every server behind it. That is the confused-deputy path above. The spec calls out this one mistake by name: a server must not accept or transit a token issued for someone else.

Link to this answer: What's the single fastest way to get burned on this surface?
04 Do we need all 24 controls before connecting a single server?

No. Scope the controls to what the server can do. A read-only, first-party lookup tool needs far less than a remote, third-party server with write access. The ten-question intake gate above is the fast version of that call.

Link to this answer: Do we need all 24 controls before connecting a single server?
05 How is this different from the trust-boundary surface on /tool-design/?

/tool-design/ is about designing one tool well — a tight schema, a clear name, sane permissions. This page covers what happens once that tool is reachable across a process boundary by more people than the team that wrote it. Identity, the catalog, and supply chain are organisation-level problems. A single tool's design cannot solve them alone.

Link to this answer: How is this different from the trust-boundary surface on /tool-design/?
06 Does Dynamic Client Registration still work?

It's retained for backwards compatibility with authorization servers that predate Client ID Metadata Documents, but the 2026-07-28 revision deprecates it. Don't design a new integration around it.

Link to this answer: Does Dynamic Client Registration still work?
Take the review seriously

A poisoned tool description can steer a model without ever touching its prompt. A security review walks that same attack surface for the system you are connecting a server to, not just the protocol.

Source: https://customlabs.io/mcp/

↑↓ navigate ↵ select esc close