Skip to main content

MCP-Strike

Active, runtime adversarial testing for MCP servers: point it at your own server and find out what breaks.

Most existing MCP scanners read tool descriptions and configs statically. MCP-Strike connects to a live Model Context Protocol server, runs a battery of real attacks against the actual tool surface, observes what comes back, and (optionally) uses an LLM-as-judge plus an LLM-driven adaptive agent to score each finding.

The wedge: a usable, active, single-developer tool for the dev who builds their own MCP server and just wants a "point it at my server and tell me what breaks" answer. Enterprise vendors aim at security teams; academic benchmarks produce papers; this is for you.

Quickstart

pip install mcp-strike      # or: uvx mcp-strike demo
mcp-strike demo

That's it. demo spins up a bundled deliberately-vulnerable MCP server, scans it, and prints a colorized report. No keys required (the LLM features auto-enable only if you set OPENAI_API_KEY).

What you get

A typical mcp-strike demo --no-judge --no-agent run:

╭────────────────────── Responsible use ──────────────────────╮
│ MCP-Strike is a defensive tool for testing MCP servers you  │
│ own. Running it against systems you don't control may be    │
│ illegal in your jurisdiction. See the "Responsible use"     │
│ section of this README for the full note.                   │
╰─────────────────────────────────────────────────────────────╯

Scan summary: 29 check(s) ran. 5 SUCCESS, 2 UNCERTAIN, 22 FAILURE

┏━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳──────────────────────────────────────┓
┃ Verdict   ┃ Stage      ┃ Attack                       ┃ Tool            ┃ Rationale                            ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇──────────────────────────────────────┩
│ SUCCESS   │ metadata   │ description_prompt_injection │ get_weather     │ Description contains 2 known         │
│           │            │                              │                 │ prompt-injection markers.            │
│ SUCCESS   │ metadata   │ overreaching_parameters      │ submit_feedback │ Tool requests 'user_password',       │
│           │            │                              │                 │ 'api_key'.                           │
│ SUCCESS   │ parameters │ path_traversal_probe         │ read_file       │ Tool returned /etc/passwd content    │
│           │            │                              │                 │ for traversal payload.               │
│ SUCCESS   │ response   │ data_exfiltration_probe      │ fetch_status    │ Response matched 3 exfiltration      │
│           │            │                              │                 │ patterns.                            │
│ SUCCESS   │ response   │ response_injection_probe     │ get_news        │ Response contains 2 injection        │
│           │            │                              │                 │ markers.                             │
│ UNCERTAIN │ response   │ data_exfiltration_probe      │ read_file       │ Tool errored on benign input;        │
│           │            │                              │                 │ cannot probe normal behavior.        │
│ UNCERTAIN │ response   │ response_injection_probe     │ read_file       │ Tool errored on benign input;        │
│           │            │                              │                 │ cannot probe normal behavior.        │
└───────────┴────────────┴──────────────────────────────┴─────────────────┴──────────────────────────────────────┘

FAILURE rows are hidden by default for readability; pass --show-all to see the clean checks too.

Exact SUCCESS/UNCERTAIN counts can vary slightly by host OS: the path-traversal probe targets /etc/passwd on POSIX and win.ini on Windows, so the read_file finding fires on either platform.

How it works

A scan runs in three layers, cheapest first:

  1. Deterministic static attacks scan the tool surface for known bad patterns. Zero cost, fast.
  2. LLM judge (optional) reviews each finding and confirms or overrides. Cheap (gpt-4o-mini by default), capped per run.
  3. Adaptive LLM agent (optional) probes each tool with multi-round reasoning, finding things the static patterns can't.

Five static attacks ship in v0.1, organized by the MCP pipeline stage they target:

Attack Stage Active? Catches
description_prompt_injection metadata passive Tool descriptions carrying injection markers aimed at the calling LLM
overreaching_parameters metadata passive Tools whose schema asks for password, api_key, ssn, etc.
path_traversal_probe parameters active Tools that handle string parameters as filesystem paths (probes every string param with /etc/passwd and Windows win.ini payloads)
response_injection_probe response active Tools whose response contains injection content aimed at the calling LLM
data_exfiltration_probe response active Tools whose response coaxes the LLM to POST/send data to a sink

The adaptive agent is the differentiator: given the tool surface, an LLM proposes payloads, observes responses, and iterates up to 3 rounds per tool. It catches social-engineering and unusual injection patterns the static heuristics can't enumerate ahead of time.

Configure the LLM features (optional)

The judge and agent are off by default; they auto-enable as soon as OPENAI_API_KEY is available, either as a shell export or in a .env file at the repo root.

# Option A: shell export
export OPENAI_API_KEY=sk-...

# Option B: .env file (gitignored; auto-loaded by the CLI)
echo 'OPENAI_API_KEY=sk-...' > .env

mcp-strike demo            # judge + agent both run

A precedence note: a shell-exported variable always wins over .env. If you want .env to take precedence, unset the shell var first.

All CLI flags

Run mcp-strike --help, mcp-strike scan --help, or mcp-strike demo --help for the full per-command listing. The most commonly used flags:

Global

Flag Effect
--version / -V Print the installed version and exit.

Scan shape (apply to scan and demo)

Flag Effect
--only NAME Run only the named attack (repeatable). Recognizes the special name adaptive_agent.
--call-timeout SECONDS Per-MCP-call timeout (default 30). A buggy or malicious server can't block the scan beyond this.
--show-all Include FAILURE rows in the terminal report (hidden by default).
--notice / --no-notice Print the responsible-use notice at startup (default on, terminal mode only).
--json Emit JSON to stdout instead of a terminal table. Suppresses the notice.
--output-file PATH Write the JSON report to a file. Implies --json.

LLM judge

Flag Effect
--judge / --no-judge Force the judge on/off. Default: auto, enabled iff OPENAI_API_KEY is set.
--judge-model NAME Override the default judge model (gpt-4o-mini).
--max-llm-calls N Cap on real judge LLM calls per run (default 20).

Adaptive agent

Flag Effect
--agent / --no-agent Force the agent on/off. Default: auto, enabled iff OPENAI_API_KEY is set.
--agent-model NAME Override the default agent model (gpt-4o-mini).
--agent-max-rounds N Per-tool round cap for the agent (default 3).
--max-agent-calls N Cap on real agent LLM calls per run (default 50).

Scan your own MCP server

# Stdio transport (the only one v0.1 supports; HTTP coming in a later release)
mcp-strike scan --command python --arg -m --arg your_mcp_server
mcp-strike scan --command /path/to/your-server --arg --port --arg 8080

# Servers that need environment variables to start:
mcp-strike scan --command python --arg -m --arg your_mcp_server \
  --env API_TOKEN=... --env DB_URL=postgres://...

--arg is repeatable; pass each argument separately so quoting works. --env KEY=VALUE is likewise repeatable. Environment variables are merged onto a minimal default environment, so your own shell secrets (including OPENAI_API_KEY) are not forwarded to the target server unless you pass them explicitly.

JSON output for CI

mcp-strike demo --json --no-notice --output-file scan.json

Stable schema, suitable for CI pipelines. Top-level shape:

{
  "summary": {
    "total": 29,
    "success": 5,
    "uncertain": 2,
    "failure": 22,
    "llm_calls_used": 7,        // null when judge didn't run
    "llm_calls_cap": 20,
    "agent_calls_used": null,   // null when agent didn't run
    "agent_calls_cap": null
  },
  "results": [
    {
      "attack_name": "description_prompt_injection",
      "stage": "metadata",
      "target_tool": "get_weather",
      "verdict": "success",
      "rationale": "...",
      "evidence": { "...": "..." },
      "judge": {
        "verdict": "success",
        "rationale": "...",
        "model": "gpt-4o-mini",
        "ran": true
      }
    }
  ]
}

By default the exit code is 0 even when findings exist, so you can parse the JSON and decide for yourself. To let the scan itself gate a build, add --fail-on:

mcp-strike scan --command ... --fail-on success     # exit 1 if any confirmed vuln
mcp-strike scan --command ... --fail-on uncertain   # exit 1 on vulns OR unresolved rows

Exit codes: 0 = clean (or --fail-on not set), 1 = findings met the --fail-on threshold, 2 = an operational error (e.g. the target couldn't be launched). A --fail-on gate never masks a 2.

Architecture at a glance

Module Role
mcp_strike.client Stdio transport wrapper over the official mcp SDK
mcp_strike.target Normalized Target adapter (what attacks consume)
mcp_strike.attacks BaseAttack ABC + decorator registry + the 5 concrete attacks
mcp_strike.judge LLM-as-judge layer (NullJudge no-op + OpenAIJudge)
mcp_strike.agent Adaptive LLM-driven attacker
mcp_strike.report Renderers: terminal (rich) + JSON
mcp_strike.config pydantic config models + .env loader
mcp_strike.cli typer entry point with scan / list-attacks / demo
mcp_strike.demo_server Bundled vulnerable target with 6 planted vulnerabilities

See ROADMAP.md for what's shipped and what's next.

Development

Requires Python 3.10+ and uv.

git clone https://github.com/LeoMartinezTAMUK/mcp-strike.git
cd mcp-strike
uv sync --extra dev

uv run ruff check .                # lint
uv run mypy src/mcp_strike         # type-check
uv run pytest -v --cov             # tests + coverage report
uv run mcp-strike demo             # eyeball the scan output

See CONTRIBUTING.md for project conventions, how to add a new attack, and how to plug in a different LLM provider.

Status & roadmap

Currently 0.1.0 / alpha. CI gates on ruff + mypy + pytest across Python 3.10 through 3.13; production source has ~96% line coverage. See ROADMAP.md for what's next.

Responsible use

MCP-Strike is a defensive tool for testing MCP servers you own or are explicitly authorized to test. It is not a weapon for attacking third-party infrastructure. Running it against systems you don't control may be illegal in your jurisdiction and is not a supported use case. The CLI prints a responsible-use notice at startup; you can suppress it for scripting with --no-notice once you've internalized it.

If you find a vulnerability in someone else's server while using this tool, follow responsible disclosure: contact the operator of that server privately before publishing details.

To report a security issue in MCP-Strike itself (not in a third-party server), see SECURITY.md.

Contact

Built and maintained by Leo Martinez III.

For security issues in MCP-Strike itself, see SECURITY.md. For bug reports and feature requests, the issue tracker is the fastest path. For anything else, the email and LinkedIn above both work.

License

MIT. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_strike-0.1.2.tar.gz (168.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_strike-0.1.2-py3-none-any.whl (61.2 kB view details)

Uploaded Python 3

File details

Details for the file mcp_strike-0.1.2.tar.gz.

File metadata

  • Download URL: mcp_strike-0.1.2.tar.gz
  • Upload date:
  • Size: 168.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mcp_strike-0.1.2.tar.gz
Algorithm Hash digest
SHA256 ce0f05b158cdf6a35a08eff55fd9eb55c44a1673b3b6147f83461fa21d0ca513
MD5 5f1d723856dc5053f3eac25549d72550
BLAKE2b-256 5c73a464e9da0a0dfe73a5f8428ee76806ce27b952aac5587b7396f5dfc5d28b

See more details on using hashes here.

File details

Details for the file mcp_strike-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: mcp_strike-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 61.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for mcp_strike-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 a833a9f4a5ea332bc8acc7c0513bd65783acb0f33f5cc31c96c243a6956fab1f
MD5 45b1acbbd01bf4e62162f1fcadf4c827
BLAKE2b-256 10439638a398e17d4bc41819e7b6bd98bb4673921728eef7a9724d5f828b9cec

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page