
MCP Internals for Hackers
Day 1 gave us the why. Day 2 gave us the loop. Today we open up the protocol that most modern agents use to reach their tools, because you cannot attack a wire format you have never read.
The Model Context Protocol, MCP, is the plumbing. When an agent lists its tools, calls one, or fetches a document, an MCP message is usually moving underneath. Anthropic published it in late 2024 and adoption was fast. By 2025 the major agent platforms spoke it, and the ecosystem grew to thousands of public servers within a year.
That speed is exactly why it is worth your attention as an attacker. A protocol that got popular in eighteen months did not get eighteen months of hardening. This post walks the transport, the handshake, the message types, and the specific fields that decide whether a deployment is exploitable. No exploit yet. Just the map, drawn well enough that the exploits in week two feel inevitable.
What MCP actually is
Strip the marketing and MCP is three things.
A client. Lives inside the agent host, the IDE, the chat app, the orchestrator. It speaks on the agent’s behalf.
A server. Exposes capabilities: tools the agent can call, resources it can read, and prompts it can use. A server might wrap GitHub, a database, a filesystem, a SaaS API, or a pile of local scripts.
A protocol between them. JSON-RPC 2.0 messages, carried over a transport. That is the whole contract.
The mental model to keep: the client is inside your trust zone, the server frequently is not, and the protocol carries both instructions and data in the same envelopes. That last point is the root of most of what follows, and it is the same structural flaw we named on Day 1 in different words.
The transport layer
MCP messages travel one of two ways, and the difference matters for how you reach a target.
stdio. The server runs as a local subprocess. The client writes JSON-RPC to its standard input and reads from its standard output. This is the default for local tools: filesystem access, a local database, developer utilities. The server executes on the same machine as the client, usually as the same user.
Attacker relevance: if you can influence what stdio server gets installed or how it is configured, you have code execution on the user’s machine, not just influence over an agent. The Cursor configuration incident lived here, where an indirect prompt injection caused the agent to write a malicious .cursor/mcp.json and achieved remote code execution through that config. The config file names which servers launch. Control the config, control the subprocess.
HTTP with Server Sent Events, and the newer streamable HTTP. The server is remote. The client opens an HTTP connection, sends requests, and receives a stream of responses. This is how hosted, multi tenant servers work.
Attacker relevance: now the server is a network service with everything that implies. Authentication, or the lack of it. SSRF if the server fetches URLs you supply. Multi tenancy bugs if one user’s context leaks into another’s. Aggregate scanning found SSRF affecting 36.7 percent of over 7,000 servers, which tells you how often remote servers fetch attacker influenced URLs without a second thought.
One historical note worth carrying: early MCP HTTP transports had no authentication baked into the base spec. Auth was bolted on later, and a large tail of deployed servers never adopted it. When you fingerprint a target in week two, the first question is always which transport, because it decides the entire rest of the test.
The handshake
Every MCP session opens with an initialize exchange. The client announces itself, the server announces what it can do, and they agree on a protocol version. Simplified, the client sends something like this:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": { "tools": {}, "resources": {} },
"clientInfo": { "name": "some-agent", "version": "1.4.0" }
}
}
The server replies with its own capabilities and info. Then the client sends an initialized notification and the session is live.
Why an attacker cares: the handshake is where capability negotiation happens, and capabilities gate which message types are even allowed. It is also an unauthenticated fingerprinting surface on many servers. serverInfo leaks the implementation name and version, which maps straight to known CVEs. You have not sent a single malicious byte and you already know what you are talking to.
The message types that matter
MCP has a handful of methods. Four deserve your full attention because they are where data crosses into the agent.
tools/list
The client asks the server what tools exist. The server returns an array, each entry with a name, a human readable description, and a JSON schema for its parameters.
{
"name": "search_tickets",
"description": "Search support tickets by keyword. Returns matching ticket bodies.",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string" } }
}
}
That description field is the single most important attacker controlled string in the whole protocol. Remember from Day 2 that tool selection is driven almost entirely by descriptions. Whoever writes the description writes part of the agent’s decision logic. Microsoft’s team made the structural point directly: because the protocol blends instructions with data, a change to a tool’s metadata can redirect an agent as effectively as a change to its code.
Read that in the context of a real message and it stops being abstract. The description is not a comment. It is program text that the server controls and the agent obeys.
tools/call
The client invokes a tool with arguments. The server runs it and returns a result. This is the execute stage from Day 2, on the wire.
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "search_tickets",
"arguments": { "query": "refund" }
}
}
Two attacker surfaces meet here. The arguments may be built from earlier attacker controlled context. And the server side implementation of the tool may be vulnerable in entirely classic ways. Aggregate scanning found command injection in 43 percent of tested MCP servers and path traversal prone file operations in 82 percent of 2,614 implementations. A tool that shells out with an unsanitized argument is command injection with extra steps, and the extra step is that a language model fills the argument for you.
The tool result
The server’s response to tools/call returns content that goes straight back into the context window. This is the ingest stage, and it is where a read becomes a compromise.
Nothing in the protocol marks this content as untrusted. It arrives in the same shape as everything else. If the tool read a support ticket, the ticket body is now in context with the same standing as your system prompt. This is precisely the Supabase class of incident, where an attacker embedded a malicious instruction inside a support ticket message and the agent acting through MCP followed it.
The rule from Day 2, restated at the protocol level: every tools/call result is untrusted input. The protocol gives you no field that says otherwise, because no such field exists.
resources/read
The client fetches a resource the server exposes, a file, a record, a page. Same story as a tool result. Content in, no trust label, straight into context. Every resource an attacker can write to is an injection vector.
The trust model, drawn honestly
Put the pieces together and the picture is stark. Here is where trust is assumed versus where it is earned.
| Surface | What the protocol assumes | What is actually true |
| Server identity | Client trusts the configured server | Anyone who edits the config picks the server |
| Tool descriptions | Accurate documentation | Attacker controlled program text |
| Tool results | Data to be summarized | Untrusted input that can carry instructions |
| Resource content | Files to be read | Same, injection vector |
| Transport auth | Present | Often absent on older HTTP servers |
| Server code | Well written | Command injection, path traversal, SSRF common |
Not one of those mismatches is a bug in a specific product. They are properties of a protocol that blends instructions with data and grew faster than it hardened. That is why the response from the standards and security world has been framework level rather than patch level: OWASP shipped a Top 10 for Agentic Applications in December 2025, and the NSA published dedicated MCP security guidance in May 2026. When the NSA writes about your wire format, you are past the point of pretending it is niche.
Fingerprinting an MCP target
Here is the practical takeaway you will use on Day 12 when we enumerate for real. Given any MCP target, you want these facts before touching anything offensive.
- Transport. stdio means local code execution stakes. HTTP means network service stakes.
- Server name and version, from the handshake. Map to known CVEs immediately, including the RCE rated 9.6 disclosed in core MCP infrastructure, CVE-2025-6514.
- The full
tools/list output. Read every description as if it were code, because it is.
- Every parameter schema. Loose typing and free string parameters are where injection rides in.
- Every resource the server exposes and who can write to those resources.
- Whether auth is present on the transport, and whether it actually gates the methods or just the connection.
Six facts. None of them require an exploit. All of them come from talking to the server the way it expects to be talked to.
Homework for Day 3
Pick one MCP server you can inspect. A local filesystem server, a GitHub server, anything you have configured, or a public open source one you can read the code of.
- Identify its transport. stdio or HTTP.
- Find its
tools/list output, from docs, source, or by asking the client to list tools.
- For each tool, copy its description into a notes file and annotate: what would this description make the agent do if it were adversarial?
- For each tool, look at the parameter schema. Circle every free form string, those are your injection candidates.
- If it is an HTTP server, check whether it authenticates and whether the auth gates individual methods.
You now have a target profile. On Day 4 we take the lethal trifecta from Day 1 and turn it into a repeatable checklist you run against a profile like this one, so that reading a server tells you in minutes whether it is worth attacking.