CustomLabs
MCP

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

Six surfaces, 24 controls tagged by who owns each one and what the spec actually requires of it, five attack paths walked end to end, and a ten-question gate for the next server someone asks you to connect.

Updated First published

42 min read

Markdown

A single MCPMCP is an open standard for connecting LLM applications to tools and data sources. server connected to a single agent is easy to reason about. The failures in this page show up once a second server joins the same session, a gateway starts brokering identity for both, and the catalog the model reads gets built from tool descriptions three different teams wrote without ever talking to each other. None of that is a defect in the protocol. It's the integration layer the protocol assumes someone builds around it.

The Agent Tool Interface covers designing one tool well: a tight schema, a clear name, sane permissions. This page starts where that one stops: identity, the catalog, the trust boundary, supply chain, and operations, once a tool is reachable across a process boundary by more than the team that wrote it.

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, requires explicit user consent before a tool runs, and requires an HTTP server to validate the Origin header against DNS rebinding. Authorization is optional, but a server that adopts it inherits real teeth — it must publish protected resource metadata, and 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 actually runs — stdio versus a remote HTTP endpoint — and the network hygiene each choice demands: Origin-header validation, binding to loopback rather than every interface, TLS on anything remote, and isolating one session's state from the next.

Breaks when: A server built for local development ends up bound to every interface on the box, or a remote endpoint skips the Origin check and lets a rebound DNS name reach it from a page the user merely 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 as an OAuth 2.1 resource server: protected resource metadata discovery, PKCE, audience-bound tokens scoped to one server, and short-lived credentials in place of a personal access token pasted into 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 land in one context windowThe context window is the maximum text, measured in tokens, a model can consider at once. at once, namespacing so two servers' identically named tools don't collide, and whether the model reads a static list or one that changes mid-session.

Breaks when: Two connected servers both register a tool called `search`, and the model has no reliable way to tell which one a given call actually 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: A tool's return value renders as trusted HTML, or a repeated approval gets remembered as blanket consent instead of being asked again for the specific 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, provenance and publisher identity, pinning a tool definition by hash, re-consent when a definition changes, typosquat checks, and sandboxing a server that runs locally.

Breaks when: A tool a user approved last week gets silently redefined by its own server (a new parameter, a new destination), and 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, redaction, quotas and rate limits per session or tenant, version pinning with a real deprecation window, and what an incident runbook contains when the compromised component is someone else's server.

Breaks when: An MCP server misbehaves in production and the only record of what happened is whatever the model's own transcript happened to keep, because nothing logged the actual 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, with a 403 on an invalid one, to defeat DNS rebinding, and says a local server should bind to 127.0.0.1 rather than every interface.

Yours to build

Whether a given server should be remote at all, TLS termination, the Host-header check OWASP asks for, and per-session state isolation are network-topology decisions the spec describes the wire format for but doesn't make 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, though, the requirements are hard: it acts as an OAuth 2.1 resource server, must implement RFC 9728 protected resource metadata, clients must send RFC 8707 resource indicators on both the authorization and token request, and a server must reject any token whose audience doesn't name it — no exceptions for one that arrived from a trusted-looking gateway.

Yours to build

Which identity provider you trust, how short-lived a credential is, and whether one agent gets one distinct identity per server are policy calls the spec leaves entirely to you.

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 window the catalog itself is allowed to spend are all your call — the spec has no opinion on catalog size or collision handling.

The trust boundary

The spec guarantees

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

Yours to build

What that consent prompt actually shows, whether a return value renders as trusted markup or sanitised structured data, and which servers are sensitive enough to isolate are all implementation decisions the spec leaves open.

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

An allowlist, a way to detect a changed definition, and a sandbox for a locally run server are entirely your build — 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 one it doesn't implement with an error listing the versions it does support, and every server must implement a discovery endpoint 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 entirely your design — knowing 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 shapes of transport: stdio, a subprocess talking over its own stdin and stdout, and Streamable HTTP, a server reachable over a network. The choice isn't cosmetic. A stdio server inherits the process boundary of whatever launched it and is unreachable from anywhere else on the network by construction; an HTTP server is reachable from anything that can route to it, which means every piece of hygiene a web service needs (TLS, the Origin check the spec requires to block DNS rebinding, a bind address that isn't 0.0.0.0 by accident) now applies to a tool integration too. Most teams get this right for a service they'd call "real infrastructure" and skip it for an MCP server, because it started life as a weekend script and nobody revisited the bind address once it moved to production.

What it covers
A deliberate choice between a stdio subprocess and a remote HTTP server, made by naming the actual process or team boundary the tool needs to cross.
How to build it
Default to stdio for anything launched and used inside one process or one team's infrastructure, and reach for remote HTTP only once a genuinely 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 specific other team or product that calls it — if the honest answer is nobody yet, it should still be stdio.
What it covers
A local or development-only MCP server bound explicitly to 127.0.0.1, not to 0.0.0.0 or an interface reachable from the rest of the network.
How to build it
Set the bind address explicitly in the server's own configuration rather than accepting a framework default, and check it 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, which is what stops a page the user merely visited from driving a local server through a rebound DNS name. TLS and a Host-header allowlist are not in the spec at all; both are ordinary web-service hardening, and 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 rather than inside a bespoke implementation, and reject a 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 a hostile web page cannot drive, even after it resolves a name it controls to your loopback address, and a remote one that 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. Enumerate the ones you actually serve rather than falling back to accepting anything.
Prove it
A request carrying an unrecognised Origin is answered with 403 before any tool handler runs, and one carrying an unexpected Host is rejected too, both tested directly rather than assumed from a framework default.
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 explicitly, and test with two concurrent sessions on purpose rather than assuming the framework already isolates them.
Produces
A server where one user's conversation can't surface another user's data by accident under concurrent load.
Tradeoff
Explicit per-session isolation costs real engineering time on a server that started as a single-user script; it's cheaper to build before the first second user than to retrofit after.
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 is the one place it takes a real position instead of leaving the details to you: a protected server acts as an OAuth 2.1 resource server, must implement RFC 9728 protected resource metadata so a client can discover how to authenticate, and must validate that any token it receives was issued with that server named as the audience under RFC 8707. The line worth reading twice is the one that says a server must not accept or transit any other token — no passthrough, no "the gateway already checked this." Stdio servers sit outside that regime by design; the spec tells them to take credentials from the environment they were launched in instead, which 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, and always includes the RFC 8707 `resource` parameter naming the specific server the token is for.
How to build it
Wire both into the client's authorization request unconditionally, rather than treating them as optional hardening added later, and confirm the authorization server actually honours the resource parameter rather than silently ignoring it.
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 can't talk to one that doesn't, which is the correct failure mode rather than a silent downgrade.
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 a server receives, that the token's audience names that server specifically — and a standing rule that no component in the chain ever forwards a token it received to a different downstream server.
How to build it
Reject a token whose audience claim doesn't match at the server itself, in addition to any check a gateway upstream already runs, and treat any code path that reuses an inbound token as an outbound credential as a design defect to fix immediately.
Produces
A chain where compromising the least-trusted server in a proxy setup doesn't hand over credentials valid anywhere else.
Tradeoff
Per-server audience-bound tokens mean a gateway can't just mint one credential and relay it everywhere; it has to broker a distinct token per downstream server, which is 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 a given integration actually calls, with a separate, explicit step-up prompt for anything more sensitive than the original grant covered.
How to build it
Request the minimum scope at connection time, and require a fresh, visible authorization step before any call that needs more than what was originally granted, rather than silently expanding what a cached token can do.
Produces
A blast radius, on a compromised or misbehaving server, bounded by what that integration actually needed rather than by whatever scope 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 actually calls, shows no unused scope.
What it covers
A distinct credential for each agent-to-server pairing, issued with a short lifetime, rather than one long-lived personal access token shared across every agent that needs to reach that server.
How to build it
Provision credentials per pairing through whatever identity system already issues short-lived tokens elsewhere in the org, and retire the practice of pasting a personal access token into a shared config file.
Produces
An audit trail that can actually say which agent made which call, and a compromised credential that expires on its own instead of staying valid indefinitely.
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 is traceable to exactly one agent-server pairing, and its remaining lifetime is measured in hours or days, not in "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 all of their descriptions, in the same context window at once — nothing partitions one server's catalog from another's. That's what makes cross-server attacks possible in the first place: a tool from a server added an hour ago can describe itself as the "preferred" version of a tool the model has already been calling all session, and there's no structural signal telling the model those two claims come from different trust levels. Namespacing every tool by the server that owns it is the cheapest fix available, and it's on you to apply it — see /tool-design/ for the per-tool naming rules this surface assumes; this page is about what happens once more than one server's tools share the same room.

What it covers
A naming convention that prefixes every tool with the server it came from, so two servers can never register the same bare name and have the model unable to tell them apart.
How to build it
Apply the prefix at the client or gateway layer if a server doesn't do it itself, and refuse to connect a second server whose tools would otherwise collide with an already-connected one.
Produces
A catalog where a newly connected server's tools can never quietly shadow one the model has already been using all session.
Tradeoff
Namespacing lengthens every tool name the model has to read, a small, real cost against context budget for a catalog that 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
An explicit ceiling on how many servers and tools can be connected in one session before the tool catalog itself starts crowding out the actual task.
How to build it
Measure the token cost of the current catalog the same way you'd measure any other context spend, and cap the number of simultaneously connected servers rather than letting every available integration join by default.
Produces
A session where the model spends its context budget on the task, not on reading past a hundred tool descriptions it will use once or never.
Tradeoff
A hard cap on connected servers means a task that genuinely 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, run before a new server joins a session with existing ones, for whether its tool descriptions could plausibly reference or impersonate an already-connected server's tools.
How to build it
Read a new server's tool descriptions specifically for claims about precedence, freshness, or preference ("use this instead of") before approving the connection, the same 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's descriptions doesn't scale past a small, curated set of integrations without also building the registry controls in the supply-chain surface below.
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 updating its tool list mid-session (`tools/list_changed`) triggers the same visible review a brand-new tool connection would, rather than silently expanding what's callable.
How to build it
Surface a mid-session catalog change to the user or operator explicitly, and treat an unreviewed expansion of the tool list as equivalent 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 a legitimate, frequently changing server's normal operation; scope the gate to genuinely new tools, not 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 own security guidance says a tool's description and annotations should be treated as untrusted unless they come from a server you already trust, and that a host must get explicit consent before a tool runs. Both of those are aimed at the same underlying fact: a model reads one undifferentiated stream of tokens, and nothing in the wire format marks a string as "instruction from us" versus "text a server sent." A return value is exactly as untrusted as a description — a ticket-lookup tool that's perfectly safe on its own can still hand back a record someone else was able to write to, and 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 a tool's description, its annotations, and its schema are all data from an untrusted source unless that specific server has been individually vetted and trusted.
How to build it
Render tool descriptions to the user as plain, inert text rather than executing or interpolating them anywhere, and don't grant a server "trusted" status by default just because it's 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 constrained to structured data (JSON, a typed object) that the calling application controls how to display, rather than raw markup the model or a client renders 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, that displays the actual parameters about to be sent, not a friendly one-line summary of what the tool is supposed to do.
How to build it
Render the literal argument values in the consent prompt, and never let a prior approval of the same tool stand in for approval of this specific call's parameters.
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 are more friction than a one-tap approval, and a user under time pressure may stop reading them closely; that's a training and UX problem to solve, not a 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 genuinely sensitive data or actions runs in a session by itself, or alongside only other equally trusted servers, never mixed in with a general-purpose or exploratory one.
How to build it
Classify each server by what it can reach before connecting it, and route sensitive-server sessions through a separate, more restricted configuration than the one used 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 means a workflow that genuinely needs both a sensitive tool and a general one in the same task has to be redesigned around that separation, which 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 specific failure worth naming is the rug pull: a definition that was safe when a user first approved it changes shape later, and nothing in the transport forces a new consent prompt unless you build that check yourself. Pinning a definition by hash at approval time, and requiring re-consent the moment it changes, is the cheapest version of the fix, and it has to be built on top of the protocol rather than assumed from 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 (even a reviewed spreadsheet to start) that records publisher, purpose, and approval date for every sanctioned server, and 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 genuinely 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 it alongside the registry entry, then compare on connect; treat any mismatch as a new, unapproved tool rather than 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 just as readily as a malicious one, so it has to be paired with the re-consent flow below rather than used 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 a tool's schema, description, or destination invalidates a prior approval and triggers the full consent flow again, rather than letting the cached approval 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 running as a local process, plus a routine dependency scan on its package manifest.
How to build it
Run local servers in the same sandboxing regime a team would already require for untrusted third-party code, and add the server's dependency manifest to whatever scanning 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 real setup overhead per server, and an overly restrictive sandbox breaks a server that genuinely needs broader access, which has to be granted deliberately rather than 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 at all: every request declares its own protocol version, and a server that doesn't implement the requested one has to say so explicitly and list what it does support. Servers must also implement a discovery endpoint a client can ask up front. That is genuinely useful for an operator, and it stops well short of logging, redaction, quotas, or a deprecation policy, all of which are your build. The gap matters more here than on most integration surfaces, because the thing you're operating is frequently someone else's software: a third-party server you don't control the release cadence of, running against your production data. An incident runbook that assumes you own every component in the chain doesn't survive contact with "the vendor pushed a new tool definition overnight and we found out from a support ticket." Write the runbook for that specific case, alongside the case where the broken thing is yours.

What it covers
A logging record for every tool call that captures the tool name, its parameters, the identity of the agent and user behind it, and the result, with sane redaction for anything genuinely sensitive.
How to build it
Instrument logging at the gateway or host layer so it's uniform across every connected server, rather than depending on each server to log its own calls consistently.
Produces
A specific, inspectable record to answer "what did this agent actually call, and with what," instead of reconstructing it from a model's own transcript after the fact.
Tradeoff
Full-parameter logging means sensitive data sometimes lands in logs; redaction rules need real design, not an afterthought once the first sensitive value shows up in a log line.
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
An enforced ceiling on call volume per session and per tenant, so a single misbehaving agent or a runaway loop can't exhaust a shared server's capacity for everyone else.
How to build it
Enforce limits at the gateway, keyed by session and tenant identity, with a defined response (backoff, hard stop) when a ceiling is hit rather than 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 every tool contract a client depends on, with a published window before an old version stops being supported.
How to build it
Record the pinned version alongside each server's registry entry, and require any breaking change to ship a deprecation notice with a real date, not an immediate cutover.
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, real maintenance cost for a real 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: how to disconnect it fast, what to check for damage done while it was connected, and who has the authority to make that call.
How to build it
Write the runbook against the actual worst case (a vendor's server starts returning altered data, or gets compromised outright) rather than only against incidents in your own code, and name who can pull the plug without waiting for the vendor.
Produces
A response measured in minutes for a third-party server incident, instead of a scramble because the existing runbook assumed every component was one your own team could patch.
Tradeoff
Writing a runbook for a case you don't control the fix for means the best available action is sometimes just disconnect and wait, which 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.
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, though, carries an embedded instruction the model reads as context it should act on, since nothing in the transport marks that field as data rather than a directive. The model quietly follows the hidden instruction on its next turn, reading a file or calling a tool nobody asked it to.

Stopped by: Treating every description as untrusted by default, and showing the full parameters of any resulting call before it executes, catches 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 claiming to be the "updated" or "preferred" version, and subsequent calls the model intended for the original tool get routed 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, with no audience check to fail, treats it 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 a different downstream server, closes exactly this path — it's the one attack the spec explicitly 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 it silently redefines the same tool name to accept a new parameter or reach a new destination, and every session that cached the original approval keeps calling the new, unreviewed version with no fresh consent prompt.

Stopped by: Pinning the tool's definition by hash at approval time and requiring a new consent prompt the moment that hash changes catches the redefinition before it's ever called again unreviewed.

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 perfectly legitimate, already-approved tool, a ticket lookup or a document fetch, returns content that itself contains an embedded instruction, because whoever populated that record was able to write arbitrary text into it. The model reads the return value as ordinary data on one call and as an instruction on the next, then forwards something it was never asked to forward to 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 actually smuggle, and 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 actually 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 specific server someone's asking you to connect, before it's in 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 crossing a process boundary, and it does mandate a few real things: Origin validation on an HTTP transport, and, once you choose to protect a server at all, audience-bound tokens with no passthrough. Past that, the catalog you expose, which servers you trust, and the blast radius of a bad call are yours to design; the protocol doesn't guarantee any 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, but its tool descriptions and return values still sit in the same context window as every other connected server's, so 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's the confused-deputy path above, and it's the one mistake the spec explicitly calls out 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 actually 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 is 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 can't solve 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