DEV Community

Manh Liem
Manh Liem

Posted on

Your MCP server is an attack surface. Here is the 30-second check most teams skip

MCP (Model Context Protocol) has quietly become the default way LLM apps connect to tools, databases, and internal APIs. That is a good thing. It also means the most common security review of an MCP-enabled app still treats the tool layer like a library call and checks almost nothing about it. I red-team LLM apps for a living, and the MCP layer is where the quiet, unlogged failures happen. Here is the 30-second check I run on every one.

Why MCP is different from the prompt

A prompt is untrusted text. A tool is a capability. When a model calls a tool, it is not producing a string, it is triggering a side effect: a file write, an SQL query, an HTTP request, a payment. The prompt-injection literature focuses heavily on getting the model to say the wrong thing. The harder problem is getting it to do the wrong thing, because the action returns a normal-looking result and the surrounding code records a success.

Three failure modes dominate what I see in the field:

1. The tool description is the new prompt. MCP servers ship with natural-language descriptions of every tool. Those descriptions are read by the model and effectively become part of the instruction surface. If a server description is verbose, ambiguous, or contains an example that is too permissive, it can steer the model toward broader use than the app intended. The check: read every tool description as if it were an attacker wrote it, and ask what the widest reasonable action the model could take is.

2. The argument boundary is the real boundary. The description is only as safe as the argument validation behind it. A tool that accepts a path, query, or url argument and passes it straight through is a path traversal, injection, or SSRF vector that the model can reach from a single crafted user message. The check: for every string argument that reaches the filesystem, a query builder, or an HTTP client, confirm there is an explicit allow-list or a validated scheme. "We sanitize the model output" is not a boundary, because the model output is exactly the channel the attacker controls.

3. The success path is the blind spot. When a tool call returns, the app usually logs tool=send_email status=ok and moves on. The model may have composed a message to the wrong recipient, or included data that should not have been included, and nothing raised. The check: add one assertion on the most sensitive field of the highest-risk tool (recipient, destination, amount) and fail loudly if it falls outside a narrow expected set. One test is enough to start. The point is to make the quiet failure loud.

How to run it without a pentest budget

I package the eight highest-value probes from this checklist as a free endpoint: point it at a tool spec or a prompt, and it returns a short risk report with the specific field it flagged. It is not a substitute for a real review, but it is a 30-second signal that catches the most common MCP mistakes. You can run it here: https://llmrt-companion.manhliemcn4euwlu.workers.dev/agent-scan . The report is deterministic and the hash of a self-scan is published so you can verify the same input gives the same output.

If you are building an MCP server, the single highest-leverage thing you can do before shipping is to write down, for each tool, the one field that, if wrong, would be the incident. Then write the one test that catches it. Everything else is hardening.

Top comments (0)