An offline-first scanner for malicious or tampered MCP servers, built for the individual developer.
mcpmoat finds the MCP servers that Claude Code, Cursor and Codex load on your machine, reads the tools they expose, and checks them for tool poisoning, shadowing, rug pulls, dangerous config and typosquatting.
Status: v1.0.0. scan
runs the config checks and the rug-pull check. When it connects to the servers, it also applies the
tool poisoning and shadowing rules to the description and the input schema of each tool. A clean
scan (exit code 0) does not mean that the tools are safe. See
what this does not do.
- Offline-first. No account and no telemetry. It connects only to the servers you configured,
and to an LLM provider only if you use
--llm. - Checks the config. It flags plaintext secrets, unpinned packages, insecure URLs, auto-install flags and look-alike package names.
- Checks the tools. It flags hidden instructions, invisible characters, exfiltration and shadowing in the description and the input schema of each tool.
- Catches rug pulls. It pins every tool and reports a tool that changes, appears or goes away.
- Built for CI. Exit code 0, 1 or 2, a
--fail-onthreshold, and one JSON document with--json. - Optional LLM rating. Off by default. With
--llm, tool names and descriptions leave your machine.
brew install --cask Ducarv/tap/mcpmoat # macOS and Linux
go install github.com/Ducarv/mcpmoat/cmd/mcpmoat@v1.0.0 # needs Go 1.27 or laterYou can also download an archive from the releases page.
docs/install.md has all methods. The macOS binaries are not notarized yet, and the
Homebrew tap gets stable releases only. Run mcpmoat version to check the install. Here is how to
verify a download.
mcpmoat scanscan starts each stdio server in your configs, with the command your client already runs. To read the
configs only, run mcpmoat scan --no-connect first. scan prints a server table, a summary and the
findings, worst first. A shortened sample:
SUMMARY
github (claude-code, global): ok, 12 tools, 0 findings
files (cursor, global): ok, 4 tools, 1 finding
FINDINGS
HIGH (1)
MOAT-T4-002 Plaintext secret in config
Server: files (cursor, global)
Where: env GITHUB_TOKEN
Fix: Move the secret out of the config: use an environment variable reference ...
More about the output: docs/scan-output.md.
Scan the config only. This starts no server and reads no pins.
mcpmoat scan --no-connectFail a build on findings. Exit code 1 means a finding that is not ignored is at or above
--fail-on (default high). Exit code 2 means an error, and it wins over 1. See
docs/exit-codes.md and docs/ci.md.
mcpmoat scan --fail-on mediumRead the report as JSON.
mcpmoat scan --json | jq '.findings[] | {rule_id, server}'Check for a rug pull. A scan pins the tools of each new server. Later runs show a diff when they
change. Review the diff, then accept it with the digest that scan prints. An accept does not make
the new text safe: a tool poisoning finding on the new text stays after the accept. See
docs/pins.md.
mcpmoat pin --accept <server> --expect <digest>Ignore a finding. Use --ignore. scan reads a .mcpmoat-ignore file in the working
directory only with --use-ignore-file, because a cloned repository can hide findings with its own
file. Use the flag only with a file that you trust. The file cannot drop pin findings, LLM findings
or the findings that say the analysis stopped early or that findings were left out (MOAT-T1-023,
MOAT-T1-024 and MOAT-T4-005). Only --ignore can. See
docs/ignore.md.
mcpmoat scan --ignore MOAT-T4-003Rate tools with an LLM. This sends tool names and descriptions to the provider. Set
MCPMOAT_LLM_API_KEY first. See docs/llm.md.
mcpmoat scan --llm- It does not guarantee safety. It has false positives and false negatives.
- It does not analyse server source code, so it does not find malware inside a server.
- It does not protect against prompt injection in content the agent reads.
scanstarts your configured stdio servers. It passes them only a few env variables, but this is not a sandbox: a server runs as you and can read your files and use the network. It never starts a server from an untrusted project file.- A server can show mcpmoat a different tool list than it shows your AI client (cloaking).
mcpmoat does not name itself. It sends the name of the client that owns the config, but a server
can still tell it apart. A server that never answers an unknown list request shows as
timeout. The pins cover only what mcpmoat sees. - A clean scan (exit code 0) does not mean that the tools are safe. The tool rules find only known
patterns, and they do not run with
--no-connect. The check that compares the tools of two servers for shadowing does not run inscanyet (#200).
The full list, with the detail on cloaking, is in docs/limits.md.
docs/README.md is the index. Start with Install, Scan output, Pins and rug pulls, Exit codes and Use mcpmoat in CI. For the design, read Architecture and the requirements.
Clone the repo, branch from dev and open your pull request against dev. main holds released
code. CONTRIBUTING.md explains the checks, and
docs/writing-rules.md shows how to add a rule. Build with
go build -o mcpmoat ./cmd/mcpmoat. To report a vulnerability, read
SECURITY.md.
The threat model builds on the tool poisoning research of Invariant Labs and on the OWASP MCP Top 10.
Built by Ducarv. See the contributors.
