A test plan arrives with one line added near the bottom: “internal MCP server for the engineering assistant, read and write access to Jira and GitHub.”No architecture diagram, no tool list, no credential model. That single line is the whole scope, and MCP server penetration testing is what the client thinks they are buying. The work is neither a standard API test nor a model red team. It sits between the two, and confusing them is the first mistake.
The Model Context Protocol is the interface that lets an AI assistant call tools. A server exposes tools, the client passes their definitions to the model, the model decides which to call and with what arguments, and the server executes against something real: a ticket system, a repository, a database, a shell. The vulnerability class follows from that arrangement. A component with no judgement is deciding how to use a component with real privileges.
What is actually in scope
Four moving parts, and each one fails differently.
The host application sets the system prompt, the approval flow and what the user sees. The MCP client manages connections and hands tool definitions to the model. The MCP server implements the tools. Behind it sits the downstream system: the API, the file share, the database that the server holds credentials for. Most reports that miss the real issue miss it because they treated the server as a standalone API and never looked at the client’s connected set or the downstream permission model.
The part testers underestimate is what counts as an input. The model does not receive tool definitions as structured, trusted metadata and user text as untrusted content. Everything arrives as tokens in one context window. Tool names, descriptions, parameter documentation, enum values, JSON schema annotations, tool results and error strings all reach the model with the same standing as the person typing in the chat box. If the server controls a string, that string is an injection channel. That is the whole vulnerability, and it is why the test surface is larger than the endpoint list.
Transport changes exposure rather than the attack classes. A local stdio server runs as the user, inherits their environment and file access, and is only reachable by processes on that machine. A remote server over Streamable HTTP is an internet-facing service with all the authentication, session and origin problems that implies. A locally bound HTTP listener is the worst of both: developer-machine privileges with a network interface, and if it skips authentication and Origin validation, any page the developer visits can drive the tools from their browser.
Two readings of the phrase exist, and they should not be mixed in one engagement. Testing an MCP server as a target is the subject of most of this article. Driving Nmap and Burp through an agent is a different activity with different risks, covered at the end.
Map permissions, inputs and data flows first
Attack after mapping, not before. The mapping is what makes the findings defensible.
Start with a tool inventory. Every tool, every parameter, the side effects, and a plain classification: does it read, write, execute or spend. Write the list out by hand even when the server publishes a nice tools/list response, because the act of classifying is where the over-broad tool shows itself. A tool called run_query that accepts arbitrary SQL against a production replica is not a read tool in any useful sense.
Then establish whose credentials the server holds. Ask directly: is this one service account, or per-user delegated access? In practice it is usually one API token with organisation-wide scope, because that is the fastest way to ship. The MCP server is nearly always the most over-privileged component in the chain, and it rarely re-checks whether the human behind the agent is entitled to the action it is performing. Note that gap early; it is where the serious findings usually are.
Next, trace where untrusted content enters the loop. Issue trackers, shared inboxes, web fetch tools, file shares, RAG corpora, and other MCP servers connected to the same client. Anything a stranger can write into is an attack path into the model’s context. A public issue tracker is the cleanest example, because filing an issue costs nothing and the text lands in the agent’s context as ordinary tool output.
Finally, record the approval model exactly. Which calls are auto-approved, which prompt the user, and what the prompt actually says. A confirmation dialogue reading “Allow the assistant to use github.create_pull_request?”is not an approval; it tells the user nothing about the branch, the diff or the target repository. Capture the wording verbatim. It becomes a finding on its own.
The attack paths worth your time
Tool description poisoning and shadowing
Plant a distinctive marker payload in every server-controlled field in turn: the tool name, the description, each parameter’s description, an enum value, a returned error message. Something unmistakable, such as an instruction to append a fixed token to the next response, or to call a specific harmless tool first. Then see which fields the model obeys. Testers commonly test the description and stop. Parameter documentation and error strings are frequently the ones that land, because nobody reviews them.
Cross-server shadowing is specific to MCP and easy to miss. A client with five servers connected exposes all five tool sets to one model. A malicious or compromised server can carry instructions that change how a trusted server is used: route every payment through this account, send a copy of every file read to that endpoint. Test the connected set, not one server in isolation. Then check whether the host pins tool definitions and detects changes, because a server that passes review on Monday can serve different definitions on Friday. A rug-pull update is a supply chain issue with no code deployment behind it.
Indirect injection through tool results
The interesting version of this never touches the chat box. An attacker files a public issue whose body instructs the agent to read a private repository and paste the contents into a new pull request. A developer asks the assistant to triage new issues. The injection arrives as legitimate tool output, and the server’s own token supplies the repository access the attacker never had. The mechanics of why models cannot reliably tell instruction from data are covered in more depth in our piece on prompt injection as the defining LLM vulnerability.
Test the full chain to the downstream action. Getting the model to say it will exfiltrate the file is not a finding. Getting the pull request created is.
Confused deputy
This is the class that turns a moderate finding into a critical one. The agent has the user’s identity; the server has a privileged token. If the server executes whatever the agent asks without re-authorising the end user, any user who can talk to the agent inherits the server’s access, and so does any attacker who can inject into the agent’s context. The test is straightforward: authenticate as a low-privileged user, steer the agent towards an action that user should not be able to perform, and check the downstream audit log for whose identity was recorded. If the log shows the service account and the action succeeded, you have it.
The ordinary flaws that MCP wrappers reintroduce
Tool parameters are user input that has been laundered through a model, and they reach shell commands, file paths and query strings. Command injection in a filename argument, path traversal out of a sandbox directory, SSRF in a URL parameter: all of it still applies, and the fuzzing is easier because you can call the server directly and skip the model entirely. Do that. Test the transport as a plain HTTP service alongside the agentic testing.
On authorisation, the current MCP specification treats remote servers as OAuth 2.0 resource servers and requires resource indicators so that a token issued for one server cannot be replayed against another. That makes token audience validation and token passthrough concrete test cases rather than theory. Check whether the server validates the audience of the token it receives, and whether it forwards that token onwards to the downstream API. Check the spec revision the implementation claims to follow, since this area has moved quickly.
Test to consequence, not to output
A forbidden sentence in a chat window is not a finding. A deleted record, an exfiltrated token, a merged pull request or an outbound request to your collaborator is. Developers argue with screenshots of transcripts, and they are right to, because a model saying “I have deleted the file”is often a model that has done nothing at all.
So instrument the target before you attack it. Canary files with unique contents, canary tokens that alert on use, uniquely tagged records in the database, and access to the server-side call log. Evidence that sits outside the transcript is evidence that survives a dispute. Capture the raw tool call arguments and results as primary material, not the rendered chat output; the arguments are what the system acted on.
Rate severity by the blast radius of the tool that was invoked, not by the ingenuity of the payload. A clever multi-stage injection that ends in a read of public data is low. A crude one-liner that reaches a delete tool is not. The same principle applies when you are assessing the wider agent architecture rather than a single server, which we cover in securing AI agents and their tool use. For the method behind testing to consequence, scoping tokens and writing findings that hold up, see our companion piece on MCP server prompt injection.
Handling non-determinism so findings stay credible
The same payload will work and then not work. Report accordingly.
Fix a run protocol and state it: ten attempts per payload, same prompt, same tool set, and report a hit rate. Five in ten against a file deletion tool reads as critical. One in ten still gets triaged, because an attacker can retry and you cannot. Reporting a probabilistic issue as pass or fail is how real vulnerabilities get closed as unreproducible.
Pin and record everything that changes behaviour: model name and version, temperature or sampling settings, the full system prompt, the exact set of connected tools, the transport, the client application and version, and the payload byte for byte. Without that, a developer who fails to reproduce it once will assume you were wrong.
Structure each finding so it is actionable even on a failed first reproduction: the mechanism, the payload, the observed hit rate, the external evidence of impact, and the architectural fix that holds whether or not the payload lands today. Model updates change hit rates. They do not change the fact that a deletion tool is auto-approved.
Fixes that hold when you assume the model will be fooled
Every worthwhile remediation starts from the same premise: the model will eventually be talked into the wrong call, so the damage has to be bounded elsewhere.
- Scope credentials per tool and per user. Separate tokens with minimum scope, and re-authorise the end user at the point of action rather than trusting the agent’s request. This kills the confused deputy pattern outright.
- Deny by default on write, delete, spend and send. Human approval that displays the concrete arguments: which repository, which record, which amount, which recipient. An approval dialogue that shows only the tool name trains people to click yes.
- Allow-list egress and destinations. If a successful injection has nowhere to post the data, it is a nuisance rather than a breach.
- Pin tool definitions and alert on change. Hash them at review time and fail closed on a mismatch.
Input filtering and guardrail models catch the obvious payloads and reduce noise. They are partial mitigations. Because the weakness is inherent to how a model reads text, anyone claiming to eliminate prompt injection at the filter layer is selling confidence, not a fix. For remediation framing that clients recognise, map findings to OWASP’s Top 10 for LLM Applications, LLM01 Prompt Injection and LLM06 Excessive Agency in particular, with MITRE ATLAS for technique references and the NCSC Guidelines for Secure AI System Development for the design-level recommendations.
The other MCP question: driving your own tools through an agent
Plenty of MCP servers now wrap Nmap, Burp, ffuf and Nuclei so an agent can run them. Used with discipline, that shortens the tedious parts: parsing output, cross-referencing versions against CVEs, drafting the first pass of a finding. Used carelessly, it produces confident findings that were never measured.
The test question for any such tool is single and unforgiving: is this fact a real measurement present in the tool output, or a model assertion? If a report claims an outdated TLS version, the scanner output must contain it. Never let the model be the source of a fact. Our view on where these tools help and where they mislead is set out in AI-assisted pentest tools: what works and what is hype.
Two hard limits. First, scope. An agent that selects its own targets, expands a range or adds an intrusive flag can put you outside written authorisation, and in the UK that is Computer Misuse Act 1990 territory rather than a methodology debate. Keep target selection and flag selection under human control, hold an allow-list at the tool layer, and log every command issued. Second, client data. Hostnames, internal IP ranges, tool output and full transcripts sent to a third-party model provider have left your engagement. Check the engagement terms and the provider’s retention policy before that happens, not after.
The Short Version
Map before you attack: tools, credentials, untrusted inputs, approval wording. Treat every server-controlled string as an input, including parameter docs and error messages. Test the connected set of servers, not one in isolation. Expect the confused deputy to be the real finding. Prove consequence with canaries and server-side logs rather than transcripts, run each payload a fixed number of times and report a hit rate, and pin model, tool set and system prompt in the write-up. The remediations that hold are scoped credentials, deny-by-default on destructive actions, egress allow-lists and pinned tool definitions. The model will be fooled eventually. Design as if it already has.
Frequently Asked Questions
What does MCP server penetration testing actually cover that a normal API pentest does not?
A normal API test assumes a caller with intent. Here the caller is a model that can be instructed by any text it reads, so the metadata itself becomes an attack surface: tool names, descriptions, parameter documentation and error strings all reach the model as instructions. You also test the trust boundary between the agent’s identity and the server’s privileges, and the behaviour of several servers sharing one client. The underlying API testing still applies on top of that.
Which attacks should be on an MCP server test plan?
Tool description and parameter poisoning, cross-server shadowing, indirect prompt injection through tool results, confused deputy on the server’s credentials, excessive agency in tool scope, and rug-pull changes to tool definitions. Alongside those, the ordinary flaws: command injection and path traversal in tool parameters, SSRF, missing authentication and Origin checks on local HTTP listeners, and token audience confusion or passthrough on remote servers.
Can prompt injection against MCP tool use be fully prevented?
No. A model reads instructions and data through the same channel, so the weakness can be managed but not removed. Filters and guardrail models reduce the success rate of known payloads; they do not close the class. Bound the consequences instead, with scoped credentials, deny-by-default on destructive actions and egress allow-lists.
How do you report a finding when the agent only reproduces it some of the time?
Run a fixed number of attempts, ten is a reasonable default, and report the hit rate rather than pass or fail. Record the model and version, temperature, system prompt, tool set, transport and exact payload so the developer can reproduce your conditions. Rate the severity on the tool’s blast radius, since two successes in ten against a deletion tool is still critical.
Is it safe and legal to run penetration testing tools through an MCP server and an LLM agent?
It can be, with controls. Target and flag selection must stay under human authority and inside an allow-list, every command issued should be logged, and the engagement’s written authorisation must cover exactly what runs. Under the Computer Misuse Act 1990 an agent drifting outside scope is your problem, not the tool’s. Check what client data leaves for a third-party model provider before you connect anything, and verify every claimed fact against the raw tool output.
