# MCP security

> MCP security covers the attacks on MCP servers and clients, from tool poisoning to token passthrough, and the controls the spec and OWASP list for each.

Source: https://www.honeyhive.ai/learn/mcp-security (updated 2026-11-02)

MCP security is the set of controls that keep a Model Context Protocol connection from being abused. Two kinds of risk meet in it. Classic web risks (OAuth mistakes, request forgery, over-broad tokens) are documented in the spec's [security best practices](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices). Model-side risks, where text in a tool description or result steers the model, are documented by researchers such as [Invariant Labs](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks). For what the protocol is, see [Model Context Protocol](/learn/model-context-protocol).

All spec statements below come from revision July 2026.

## Where the risk sits

The spec's own list is aimed at "developers implementing MCP authorization flows, MCP server operators, and security professionals" ([security best practices](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)). Who has to act depends on the attack:

| Attack | Who must defend | Core control, per the source |
|---|---|---|
| Confused deputy | MCP proxy server operators | Per-client consent before forwarding to the third-party authorization server, and exact `redirect_uri` matching ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)) |
| Token passthrough | Server authors | Servers MUST NOT accept tokens not explicitly issued for the MCP server ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)) |
| SSRF | Client builders, and authorization servers that fetch client metadata | Require HTTPS, block private and link-local ranges, validate redirect targets, consider an egress proxy ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)) |
| State handle hijacking | Server authors | MUST NOT treat possession of a handle as authentication; bind handles to the verified user ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)) |
| Local server compromise | Client builders, then users | Show the exact startup command, require approval, sandbox ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)) |
| Malicious authorization URL | Client builders | Allow only `http` and `https`; reject `javascript:`, `data:`, `file:`, `vbscript:`; never open URLs through a shell ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)) |
| Mix-up attack | Client builders | Validate `iss` on the authorization response. The spec says PKCE alone does not prevent it ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)) |
| Over-broad scopes | Server authors and client builders | Minimal initial scopes, step up through `WWW-Authenticate` challenges ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)) |

## The attacks in more detail

### Confused deputy

The spec says this needs all four conditions at once: a proxy (the role an [MCP gateway](/learn/mcp-gateway) plays) that uses a static client ID with a third-party authorization server, dynamic client registration, a consent cookie set after the first authorization, and no per-client consent in the proxy. With all four, an attacker can register a client with their own `redirect_uri`, send the user a link, and receive an authorization code because the consent screen is skipped ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)). Removing any one condition breaks the chain, but the spec's requirement is the consent step.

### Token passthrough

This is the case where a server accepts a token from a client and forwards it downstream without checking it was issued to that server. The spec lists lost control (rate limits and monitoring tied to the token audience get bypassed), weak audit trails and broken trust boundaries as the harms. The authorization spec adds that servers MUST validate the token's audience and "MUST NOT accept or transit any other tokens" ([authorization spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)).

### SSRF

A malicious MCP server controls several URLs the client later fetches during OAuth discovery, such as `resource_metadata` and the token endpoint. The spec's examples include the `169.254.169.254` cloud metadata address, localhost services, DNS rebinding and redirect chains ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)). It warns against hand-written IP validation, because attackers use encodings (octal, hex, IPv4-mapped IPv6) that custom parsers miss.

### State handle hijacking

This revision of MCP has no protocol-level sessions. A server that needs state across calls mints a handle (the spec's examples are a shopping cart ID or a workflow ID) and takes it back as a tool argument. If the server does not check that the handle belongs to the caller, anyone who guesses it acts on that user's state ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)). Servers on protocol version November 2025 or earlier use server-assigned session IDs, and the spec points to the older version of its page for those.

### Local server compromise

A local server runs on the user's machine with the client's privileges. The spec names three routes: a malicious startup command in a client configuration, a payload inside the server, and an insecure server left running on localhost and reached by DNS rebinding. Its example commands are an `npx` install followed by a `curl` upload of `~/.ssh/id_rsa`, and a `sudo rm -rf` ([spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)).

The `stdio` transport is not the problem. The spec states it "is not inherently vulnerable", and flags only proxy designs that spawn servers as child processes, where a client-side XSS bug can become code execution.

### Tool poisoning, shadowing and rug pulls

Invariant Labs published the tool poisoning write-up on 1 April 2025, so it predates the current spec. Its definition is malicious instructions in a tool description, invisible to users but visible to the model. In its demonstration, an `add` tool's description told the agent to read `~/.cursor/mcp.json` and SSH keys and send them in a `sidenote` parameter, and Cursor leaked the files while the user saw a simplified tool name. In a second demonstration a malicious server's description shadowed a trusted email tool, and the Cursor agent sent all emails to the attacker even when the user named a different recipient ([Invariant Labs](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks)).

The same post describes the rug pull: "a malicious server can change the tool description after the client has already approved it." Its suggested mitigations are showing full tool descriptions to users, pinning tools by hash or checksum so changes are detected, and stricter data-flow boundaries between servers.

Since then, the OWASP MCP Top 10 [lists](https://owasp.org/www-project-mcp-top-10/) Tool Poisoning as MCP03:2025. The project page marks the list as beta, with no version or release date shown. Tool poisoning is a case of indirect [prompt injection](/learn/prompt-injection), which the protocol cannot fix by itself. The [spec overview](https://modelcontextprotocol.io/specification/2026-07-28) says MCP "cannot enforce these security principles at the protocol level", and the model reads descriptions and results as text.

## OWASP MCP Top 10 at a glance

All items are tagged 2025 on the [OWASP project page](https://owasp.org/www-project-mcp-top-10/), where the list is in beta:

| ID | Name |
|---|---|
| MCP01 | Token Mismanagement & Secret Exposure |
| MCP02 | Privilege Escalation via Scope Creep |
| MCP03 | Tool Poisoning |
| MCP04 | Software Supply Chain Attacks & Dependency Tampering |
| MCP05 | Command Injection & Execution |
| MCP06 | Prompt Injection via Contextual Payloads |
| MCP07 | Insufficient Authentication & Authorization |
| MCP08 | Lack of Audit and Telemetry |
| MCP09 | Shadow MCP Servers |
| MCP10 | Context Injection & Over-Sharing |

## Controls the MCP spec asks for on every tool call

Beyond the attack list, the [tools spec](https://modelcontextprotocol.io/specification/2026-07-28/server/tools) says there SHOULD always be a human in the loop who can deny tool invocations, and that clients MUST treat tool annotations as untrusted unless they come from a trusted server. Servers MUST validate inputs, apply access controls, rate limit and sanitize outputs. Clients SHOULD show tool inputs before the call, validate results before the model sees them, set timeouts and log usage for audit.

Authorization is optional in MCP. HTTP-based servers should follow the [authorization spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization), which requires OAuth 2.1 on the authorization server, Resource Indicators (RFC 8707) with a `resource` parameter on both authorization and token requests, and no access tokens in the URI query string. A stdio server should instead take credentials from the environment.

## A checklist by role

This is a summary of the requirements above, not an extra standard.

| Role | Do |
|---|---|
| Server author | Validate token audience. Never forward tokens you did not issue. Check handle ownership on every call. Publish minimal scopes. Validate inputs and sanitize outputs. |
| Client or host builder | Show full tool descriptions and inputs. Block private IP ranges and non-HTTP schemes on OAuth URLs. Show the exact command for local server setup and sandbox it. Validate `iss`. Log tool usage. |
| Team installing servers | Treat a local server as code running with your privileges. Review the startup command. Pin tool definitions where the client allows it, so a rug pull shows up as a change. Keep an inventory, since OWASP lists unmanaged servers as MCP09 Shadow MCP Servers. |

Runtime filtering of tool inputs and outputs is covered in [AI guardrails](/learn/ai-guardrails) and [agent guardrails](/learn/ai-agent-guardrails), and attacking a deployed agent on purpose to find these gaps in [AI red teaming](/learn/ai-red-teaming).

## Watching MCP calls in production

The OWASP list includes MCP08 Lack of Audit and Telemetry, and the spec asks clients to log tool usage. One way to get that record is tracing. HoneyHive's [MCP tracing guide](https://docs.honeyhive.ai/v2/tracing/mcp-tracing) covers Python servers and clients: it marks OpenInference auto-instrumentation as experimental and recommends the `@trace` decorator for production, with `enrich_span` to add tool name, arguments and results. Any tracing or logging stack that records tool name, arguments and result per call serves the same purpose. For how tool calls sit inside a whole agent run, see [agent observability](/learn/agent-observability).

## FAQ

### What are the main MCP security risks?

The MCP spec [lists](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices) confused deputy, token passthrough, SSRF, state handle hijacking, local server compromise, OAuth URL injection, mix-up attacks and over-broad scopes. Tool poisoning, named by [Invariant Labs](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks), is the best-known attack on the model side, and the OWASP MCP Top 10 [lists](https://owasp.org/www-project-mcp-top-10/) it as MCP03:2025.

### What is MCP tool poisoning?

Invariant Labs defines it as malicious instructions embedded in MCP tool descriptions that are "invisible to users but visible to AI models." It is a form of indirect prompt injection ([Invariant Labs](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks)).

### Is a local stdio MCP server safer than a remote one?

The spec advises servers meant to run locally to use stdio to limit access to the client, and says stdio is not inherently vulnerable ([security best practices](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices)). A local server still runs with the client's privileges, so the spec also asks clients to show the exact startup command and sandbox it.

### Does MCP require OAuth?

No. Authorization is optional. HTTP-based transports should follow the authorization spec, and stdio implementations should instead take credentials from the environment ([authorization spec](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)).
