MCP Security in 2026: What Changed, What Attacks Work, and How We Lock Down Our Servers
The protocol got simpler in July and the attacks got better documented. Here is the threat list, what we actually run in production, the gaps we still have, and a checklist for anyone putting their software behind MCP.
TL;DR: Three things moved MCP security this year. The 2026-07-28 specification made the protocol stateless: no more sessions, no more Mcp-Session-Id, no more initialize handshake. In May the NSA published design guidance for MCP deployments. And the attack research filled in: tool poisoning works against most models, and the worst real-world bugs were in tooling around MCP, such as clients and inspectors that ran commands or listened without authentication. We run MCP servers in production with close to two hundred tools across our services, so this post covers the threat list, what our servers actually do about each threat (and what they don't do yet), and a checklist you can apply to your own.
What changed in 2026
The spec went stateless
The 2026-07-28 changelog is the largest revision since MCP launched. The parts that matter for security:
- Sessions are gone. The
Mcp-Session-Idheader and protocol-level sessions were removed from the Streamable HTTP transport. Servers that need state across calls now pass explicit, server-minted handles as ordinary tool arguments. - The handshake is gone. There is no
initializestep. Every request carries its protocol version and client capabilities, and servers must implement a newserver/discovercall. - Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents, which removes an open endpoint that let anyone create a client record on your server.
- Authorization tightened at the edges. Clients must validate the
issparameter on authorization responses, and must not reuse credentials with a different authorization server. - A deprecation policy, with a minimum twelve-month window before a deprecated feature is removed.
For security, losing sessions is good news. A session ID was never meant to be a credential, but servers that treated it as one ("this session was authenticated, so trust the next call") were one leaked header away from a problem. In a stateless protocol, every request has to authenticate on its own.
The NSA published guidance
On 20 May 2026 the NSA's Artificial Intelligence Security Center released a Cybersecurity Information Sheet, Model Context Protocol (MCP): Security Design Considerations for AI-Driven Automation (summary by Reed Smith). The risks it names are the ones we see: unscreened data passing between systems, model outputs treated as instructions, weak checks on who is calling and what they may do, tokens without expiry or revocation, and automated actions with no human review. Its recommendations are least privilege per tool, separation by trust level, validation of every request, and detailed logs of tool requests and results.
The attacks are documented now
- Tool poisoning. Invariant Labs showed it in April 2025: a calculator tool whose description told the agent to read the user's SSH key and send it as a tool argument. The MCPTox benchmark (AAAI 2026) then tested 353 real tools from 45 live servers against 20 models. The highest attack success rate was 72.8%, and the most cautious model refused fewer than 3% of attacks. More capable models were often the more susceptible ones, because they follow instructions better.
- Command execution in local tooling. CVE-2025-6514 in
mcp-remote(CVSS 9.6, fixed in 0.1.16) let a malicious server run commands on the connecting machine. In April 2026, OX Security reported that the SDKs' STDIO transport runs whatever command it is given, with more than 30 disclosures and over 10 critical or high CVEs across products built on it (CSA research note). Anthropic's position is that sanitising that input is the developer's job. - Unauthenticated developer tools. CVE-2025-49596 in MCP Inspector (CVSS 9.4) came from missing authentication on a local service.
Read the list again and notice what is missing: a flaw in the protocol's OAuth. The incidents come from what surrounds it, such as local processes, debugging tools, and servers that authenticate the caller but never ask what that caller may do.
Update, 16 October 2026: the implementation bugs kept coming
A week after this post went up, the pattern held, with the first MCP flaw on CISA's exploited list:
- LiteLLM, CVE-2026-59822, on CISA KEV. The LiteLLM proxy's MCP endpoint turned a failed key check into an empty auth object, so any bearer token, even one character, opened a session. Fixed in 1.84.0 in May, exploited from July according to Wiz, and added to the Known Exploited Vulnerabilities catalog on 2 September. We use LiteLLM ourselves and checked our exposure: the full write-up.
- Grafana MCP accepted sessions it never issued. It validated
Mcp-Session-Idby format only, so a caller could invent one and call tools with the server's Grafana service account. Fixed in v1.1.0 on 10 August, alongside an SSRF in its API tool, CVE-2026-19516 (Pillar Security). This is the "session ID treated as a credential" failure described above, in a widely used server. - The MCP Python SDK's OAuth client could skip issuer checks. A malicious server answering 404 to discovery forced a legacy fallback that skipped issuer validation, sending client secrets, authorization codes and PKCE verifiers to the attacker. Fixed in 1.30.0 and 2.2.0 (Cycode).
- Scanners missed malicious servers. Two working malicious MCP servers passed five scanners (Pluto Security), and a malicious npm package,
gadgethumans-mcp, sent a wallet private key to a remote endpoint on every tool call (Knostic). A clean scan is not a review.
Two of the three code bugs are the same mistake: a path where "invalid" was handled as "fine". Agents built on top of MCP have their own version of the problem, which we cover in AI agent security.
How we secure ours
We build and run MCP servers for our own products and for clients: our CRM, the server behind our 91-tool post, and the catalogue editors of the sites we operate. Everything below is in the code today. Where something is not done, we say so.
Every request authenticates on its own. Our CRM's MCP endpoint runs the official Go SDK in stateless mode, so the session removal changed nothing for it. Our older 91-tool server still issues a session ID, but only as a label: authentication is the bearer credential on every request, never the session.
Two credential types, routed by shape. Machine callers use pre-shared keys with a fixed prefix. People connecting Claude or ChatGPT go through OAuth. Each credential goes to its own verifier based on its shape, so rolling out OAuth never broke an integration that used a key. We had that bug in an early version, which is why we extracted the endpoint into a small library, rflkt/mcp, with a conformance package that tests the behaviour. It is open source.
OAuth tokens that can't be replayed elsewhere and can be revoked. Access tokens are pinned to one signing algorithm and checked for issuer, audience (the token must have been minted for this exact server), and expiry, in that order. They live 15 minutes. Revoking a connection works within that window, and if the revocation check itself is unavailable, the token is rejected, not admitted: disconnect has to mean disconnect. Error messages never say which check failed, so a token holder can't probe how tokens are built.
Read and write scopes per tool, derived rather than listed. A token can carry crm:read, crm:write or both. Each tool's required scope is recorded at the moment the tool is registered, from its own read-only annotation. A tool with no annotation is treated as a write. There is no second, hand-maintained list for someone to forget to update when they add a tool, and a read-only token calling a write tool gets a 403 before the tool runs.
Authorization comes from the database, not the credential. Neither our keys nor our tokens carry a role. On every request, the server looks up the caller's current membership in their organisation and puts the user, organisation and role in the request context. A user removed from an organisation loses access on their next call, even with a token that has not expired yet. Tools read identity only from that context, and searches such as the account list return only records the caller owns.
Writes the user can see and undo. For agents that write to production, we prefer making every change visible with a one-step undo to an "approve" button people click without reading. We explained why in We Deleted the Approve Button.
What we don't do yet:
- Older integration keys are broader than we'd like. New integrations go through OAuth with per-tool scopes. The pre-shared keys our earlier integrations use don't have that granularity yet, and tightening them is next on the list.
- No dedicated audit log of tool calls. Failures are logged with the tool name, but we don't yet keep a tamper-evident record of every call and its arguments, which is what the NSA recommends.
- We don't speak 2026-07-28 yet. Our servers still negotiate earlier protocol versions, which current clients accept. We will migrate within the deprecation window.
A checklist for exposing your software over MCP
If you are putting your ERP, CRM or internal tools behind an MCP server, these are the questions we would ask in a review:
- Does every request authenticate on its own? If anything relies on a session ID, fix that before migrating to the stateless spec.
- Are tokens bound to your server? Check the audience on every token. A token minted for another service must fail.
- Is authorization checked per tool, server-side? "Authenticated" is not "allowed". Derive each tool's permission from the tool itself, and default unknown tools to the most restrictive level.
- Are roles read at call time? Revoking someone in your application should cut their AI access on the next request, not when a token expires.
- Can a revoked connection keep working during an outage? Fail closed.
- What do your tools return? Anything a tool returns, such as an email body, a customer note or a web page, can carry instructions for the model. Treat returned text as data, and keep the tools that write separate from the tools that read untrusted content.
- Which third-party MCP servers do your people install? Review them like any dependency that runs code on their machine, because with STDIO that is what they are. Pin versions.
- Do you log tool calls? At minimum: who, which tool, when, and the result. Ideally the arguments too, kept somewhere the agent can't edit.
- Can a user see and undo what the agent did? If not, the first mistake will be found by a customer.
Related: what MCP is, what MCP OAuth is, and how users can connect Claude to your software themselves.