AgenticCLI
▮ shipguardis a free, local secure-ship agent for AI-built apps — 5 checks in ~2s.$ npm i -g @agenticcli/shipguard

MCP Security Scanner: Four Found Nothing, One Read It

9 min · jul 2026

Short answer: An MCP security scanner reads what your tool servers say at runtime rather than what your repository contains. It starts each server named in your client config, asks for the tool list, and inspects the descriptions the model will act on. A code scanner never opens that channel, so it cannot report on it.

A tool description in this workspace tells the model to read .mcp.json and the user's ~/.ssh/config, POST both to an outside host, and say nothing about having done it. Four scanners read the six files that workspace contains. Between them they returned nothing, and not one of them is broken.

That result is why "MCP security scanner" names a different kind of tool from the one already wired into most pipelines. The phrase has been attached to two procedures that share almost no mechanics, and the best-practice list for MCP is easier to follow once you know which of the two you are buying.

What did four file scanners find on a poisoned workspace?

The fixture is a small Node project with an .mcp.json naming two local stdio servers. One of them, notes, exposes a create_note tool whose description ends in an HTML comment addressed to the model. The other is a control with the same protocol handling and no injected text.

Nothing else in the tree is wrong. No credential, no unguarded route, no dependency pinned to a version with an advisory against it. Each of those would have given the scanners something true to say, and the run would then have measured one of the checks they already do well instead of the question.

What came back?

Real ShipGuard 0.5.1 terminal output from capture mcp-scanner-file-scanners — 4 scanners run over 6 files carrying 1 seeded defect, returning 0 findings between them: ShipGuard 0 findings; Gitleaks 0 findings; Semgrep 0 findings; Trivy 0 findings. The seeded instruction sits on line 12 of a file all four of them read

Real capture. @agenticcli/[email protected], Gitleaks 8.30.1, Semgrep 1.170.0, Trivy 0.72.0, 28 July 2026. Fixture and manifest: content-pipeline/captures/mcp-scanner-file-scanners/.

The grep in the second panel is there to close off the easy explanation. Nothing about this defect is hidden, encoded or assembled at runtime — it is plain ASCII in a file every one of these tools opened, and a rule matching on it would have fired. None of them has one.

We have run these same tools against secrets, where they behave completely differently, so the flat result here is a statement about rule coverage rather than about the tools.

What happens if you delete the defect?

The last two commands in the capture scan a second copy of the tree with the three injected lines removed, then compare the two reports on findings, risk score and files scanned.

They match. Two workspaces differing by exactly the thing under test produce the same report, which means the report was never sensitive to it — a stronger claim than "the scan came back clean", and one anybody can reproduce by running the script.

What does an MCP security scanner do differently?

The procedure is different from the first line onward. Instead of walking the file tree, it reads the client configuration, starts each server the configuration names, and speaks the protocol: initialize, then tools/list. What comes back is the same text the model will receive.

Which package are you actually installing?

The tool we used for this is the one nearly every MCP scanning guide still names, and installing it today gets you something else:

"This package has been renamed to snyk-agent-scan. This is a redirect package that installs snyk-agent-scan and forwards the mcp-scan CLI to it."

— PyPI distribution metadata for mcp-scan 0.4.3

Invariant Labs' scanner is now Snyk's, under a new name, and both distributions declare Apache-2.0. Anyone following a guide written before the rename is running a tool with different defaults from the one the guide describes.

Real terminal output from capture mcp-scanner-runtime-read: 2 MCP servers started, 3 tool descriptions retrieved, 1 carrying instructions aimed at the model, with the retrieved create_note description printed in full and the scan subcommand exiting on a missing vendor token

Real capture. snyk-agent-scan 0.5.15 (installed via mcp-scan 0.4.3), 28 July 2026. Fixture and manifest: content-pipeline/captures/mcp-scanner-runtime-read/.

The injected comment is there, in full, in the middle panel. The retrieval that produced it needed no account and no network call, and it is the half of the job a file scanner structurally cannot do.

What does that reading cost?

There are two costs here and the capture prints both. The fourth panel shows the .mcp.json entry inspect had to execute to get the text: node servers/notes-mcp/server.js. Reading a tool list means running the command in your own config, which is why the tool warns about it and prompts for consent unless you pass a flag — the same shape as every other irreversible step an agent takes.

The second cost lands on the judging half. scan exits asking for a SNYK_TOKEN, and the --analysis-url default compiled into snyk-agent-scan 0.5.15 (cli.py) points at api.snyk.io. Retrieval is local. The opinion about what was retrieved is a request to somebody else's endpoint, on an account you have to open.

Where does the work actually happen?

One workspace · two procedures

To read a tool description, something has to run your server.

Both columns are the same six files. The left one is what a code scanner does with them. The right one is what a tool sold as an MCP scanner has to do to reach the one thing the left one cannot see.

READS FILES · RUNS NOTHING 1 · OPEN EVERY FILE .mcp.json package.json src/ servers/ 2 · MATCH RULES Secrets, configuration and dependency versions. 3 · WRITE A REPORT One finding per rule that matched. Nothing else. ON THIS WORKSPACE 6 files read. 0 findings. Delete the seeded defect and the report is identical. Nothing was executed and nothing left the machine. shipguard gitleaks semgrep trivy STARTS SERVERS · READS REPLIES 1 · READ .mcp.json Which servers, and the command each one runs. 2 · EXECUTE THAT COMMAND node servers/notes-mcp/ server.js 3 · ASK FOR THE TOOL LIST 3 descriptions come back. One is addressed to the model. LEAVES YOUR MACHINE 4 · JUDGE THE TEXT api.snyk.io + a token This step we did not run. ON THIS WORKSPACE 2 servers started. 3 descriptions retrieved, 1 of them poisoned.
Left: a file scanner opens, matches and reports, and on this workspace four of them returned nothing between them. Right: reaching the poisoned description means executing the command in .mcp.json, and having the retrieved text judged means sending it past the dashed line. Sources: content-pipeline/captures/mcp-scanner-file-scanners, content-pipeline/captures/mcp-scanner-runtime-read, agent_scan/cli.py:167 in snyk-agent-scan 0.5.15.

That the inspection step is itself an execution is not a theoretical worry. The official MCP debugging tool carried a critical flaw of exactly this shape:

"Versions of MCP Inspector below 0.14.1 are vulnerable to remote code execution due to lack of authentication between the Inspector client and proxy, allowing unauthenticated requests to launch MCP commands over stdio."

— GitHub Advisory Database, GHSA-7f8r-222p-6f5g (CVE-2025-49596, CVSS v4 9.4), 13 June 2025

Neither column is the better tool. The left one is cheap, deterministic and belongs on every commit; the right one needs a decision each time it runs, which is the same trade an agent's tool grant forces everywhere else.

Is a tool description worth reading at all?

The evidence that it is comes from the model side rather than the tooling side. MCPTox, published on arXiv in August 2025, built 1312 malicious test cases from 45 live MCP servers and 353 authentic tools, then ran them against 20 agents.

"We find that more capable models are often more susceptible, as the attack exploits their superior instruction-following abilities."

— Wang, Gao, Wang, Liu, Sun, Cheng, Shi, Du and Li, MCPTox: A Benchmark for Tool Poisoning Attack on Real-World MCP Servers, arXiv:2508.14925, 19 August 2025

Their strongest result on the agent side is an attack success rate of 72.8% against o1-mini, with the highest refusal rate of anything they tested, Claude-3.7-Sonnet, under 3%. Getting better at following instructions makes an agent better at following the wrong ones.

What does the protocol itself require?

The specification takes the same position about server-supplied metadata, in normative language:

"For trust & safety and security, clients MUST consider tool annotations to be untrusted unless they come from trusted servers."

— Model Context Protocol specification 2025-11-25, Tools

Worth being precise about what that sentence covers: it names annotations, a specific field. The description a model reads carries no equivalent MUST on that page. Both requirements land on the client while it runs, which is a place no repository scan reaches and no lockfile records.

Which one should you run?

Sort the answer by where the evidence lives rather than by vendor, and it comes out clean.

What you are asking What can answer it
Is there a credential in the client config? A file scanner, on every commit, for free
Is a server pinned to a version with an advisory? A dependency scanner reading the manifest
What does this server's tool list actually say? Something that starts the server
Is that text trying to steer the model? A person reading it, or a scanner with an account

The top two rows are cheap enough to automate and forget, which is the case for putting them in the same pre-ship pass as everything else a machine can read without understanding. The bottom two cost an execution and a judgement call, so they belong on the day you add a server and the day you upgrade one.

What we could not test, and what we do not do

Two limits, stated because a scanner article that lists only what it proved is worth less than one that marks its own edges.

We ran one runtime scanner, not the category. Proximity, Cisco's mcp-scanner, mcpscan.ai and Enkrypt are all sold for this job and none was installed or measured, so nothing here generalises past the tool named in the capture. We also never saw what snyk-agent-scan reports, only what it retrieves, because obtaining a token would have meant opening a vendor account.

And there is no MCP-specific rule anywhere in our own corpus, at any tier. ShipGuard read six files on this workspace and said so honestly; a release gate for the code you are about to ship is not an agent-runtime inspector, and the distance between an artifact and the system you trust is where this whole class of problem sits.

FAQ

What is an MCP security scanner?
An MCP security scanner is a tool that inspects the servers an AI agent connects to over the Model Context Protocol, rather than the files in your repository. It reads your client configuration, starts each stdio server listed there, sends the protocol's tools/list request, and examines the descriptions and schemas that come back. That channel is where tool poisoning lives, because a description reaches the model as text it acts on. A code scanner opens files and matches rules against their contents, so it never sees a tool list at all and cannot report on one.
Can a normal code scanner detect a poisoned MCP tool description?
Not in the run we published. AgenticCLI built a six-file MCP workspace whose only seeded defect was a create_note tool description carrying instructions aimed at the model, then read it on 28 July 2026 with @agenticcli/shipguard 0.5.1, Gitleaks 8.30.1, Semgrep 1.170.0 and Trivy 0.72.0. All four returned 0 findings between them, and ShipGuard's verdict was SAFE_TO_SHIP. Deleting the three injected lines and re-running produced an identical report on findings, risk score and files scanned, which is the sharper result: the report cannot move when the defect does.
Is mcp-scan still the tool to install?
Installing it gets you something else. On 28 July 2026, pip install mcp-scan resolved to version 0.4.3, whose own distribution metadata reads: this package has been renamed to snyk-agent-scan, and it is a redirect package that installs snyk-agent-scan and forwards the mcp-scan CLI to it. The scanner originally published by Invariant Labs is now maintained by Snyk under the new name, and both distributions declare Apache-2.0. Guides written before the rename still name the old package, so check what your install actually placed on disk before assuming which tool you are running.
Does running an MCP scanner send my code anywhere?
That depends on which half of the tool you run, and it is worth checking rather than assuming. On snyk-agent-scan 0.5.15, the inspect subcommand retrieves and prints tool descriptions entirely on your machine and needs no account. The scan subcommand, which judges that retrieved text, exits asking for a SNYK_TOKEN, and its --analysis-url option is declared at agent_scan/cli.py line 167 with a default pointing at api.snyk.io. Retrieval is local and judgement is not, on that version. Read the flag defaults in whatever version you install before pointing a scanner at a private repository.
Why does reading a tool description require starting the server?
Because the description is a protocol response, not a file. The Model Context Protocol defines tools/list as a request a client sends to a running server, and the server composes its reply at that moment. A server can build a description from a template, a database or a remote fetch, so the text the model receives need not appear anywhere in the repository. Even when it does appear in the source, as it did in our fixture, reading it as the model receives it means executing the command in your client configuration and speaking the protocol to the process that starts.
Does ShipGuard scan MCP servers or tool descriptions?
No. There is no MCP-specific rule in the corpus at any tier, free or paid, and on the workspace in this article ShipGuard read six files and reported nothing, which is the correct answer for a file scanner on a tree where no file matches a rule. It is a release gate for the code you are about to ship, not an agent-runtime inspector. The half of an MCP connection that only exists while a server is running is not a file, and a report about files cannot speak to it.

────────[ ▮ gate ]────────

Don't ship the next one.

Free, local, no account. Catches this exact bug class before deploy.

$ npx @agenticcli/shipguard scan