Docs · v0.1.0

Quickstart

Sixty seconds from install to a report. Only scan servers you own or are explicitly authorised to test. The default run is passive: it sends what a normal MCP client sends plus a few benign malformed requests, and never calls a tool.

Terminal
# run a passive scan, no install needed
pipx run --spec mcpprobe-scanner mcpprobe https://your-server.example/mcp

# or install it and keep the reports
pipx install mcpprobe-scanner   # the command is mcpprobe
mcpprobe https://your-server.example/mcp --sarif mcpprobe.sarif --json mcpprobe.json

# a local stdio server you run yourself
mcpprobe --stdio "npx -y @your-scope/your-mcp-server@1.2.3"

# checks behind your own auth: pass a token you own
mcpprobe https://your-server.example/mcp --header "Authorization: Bearer $MCP_TEST_TOKEN"
Real output, excerpt: v0.1.0 against the scanner's deliberately vulnerable test serverOutput
$ mcpprobe http://127.0.0.1:8765/mcp
mcpprobe 0.1.0 | passive mode | rate 5.0/s | scan only what you own or are authorised to testtransport=http | revision=2025-11-25 (handshake) | auth=none | server=fake-mcp 0.0.1 | tools=7Findings  critical 0   high 8   medium 5   low 3   info 5
 HIGH      AUTH-01  Remote MCP endpoint accepts requests without authentication HIGH      META-01  Possible tool-description poisoning HIGH      META-05  Secret material in server metadata or configuration HIGH      TRANS-01 Origin header not validated (DNS-rebinding exposure) HIGH      TRANS-03 Forged session ID accepted MEDIUM    ERR-01   Verbose error responses leak stack traces, paths or versions MEDIUM    META-03  Tool annotations contradict the tool's apparent capability …INJ-01     active  skipped  requires --active and verified ownershipSUPPLY-01  active  skipped  not applicable to http transport38 request/response pairs recorded as evidence.Result: FAIL (findings at or above high)

29 checks in the catalogue: 16 passive checks implemented in v0.1.0, 8 active checks coming soon, 5 attestation-only governance items. The same page is at /docs.md; the scanner's own README is in the repository.

Passive vs active

AspectPassive (default)Active (--active)
What it sendsWhat a normal client sends (negotiation, the */list methods, well-known metadata) plus a handful of benign malformed requestsTool calls with non-destructive canaries (the checks are stubs in v0.1.0 and report not_implemented)
Calls toolsNeverOnly when the active checks ship, and never tools passed to --exclude-tool
RequiresA server you own or are authorised to test--authorised-by, --scope and --verify (dns-txt or local)

In v0.1.0 the 8 active checks (INJ-01..05, SUPPLY-01/02, SANDBOX-01) are registered stubs: they appear in every report as skipped or not implemented, and no payloads are sent. The authorisation gate is already real and enforced in code.

Verify ownership

Active mode is refused unless you pass --authorised-by, --scope and a verification method. Every active run writes an authorisation record (scan.authorisation.json next to the report) with who authorised it, the scope, the verification proof, the rate cap and excluded tools.

FlagForWhat to publish
--verify dns-txtHTTP targets_mcpprobe.your-server.example. TXT "mcpprobe-verification=<token>"
--verify localstdio servers you run yourselfNothing to publish: the server is a process on your own machine
Terminal
mcpprobe token your-server.example
# Publish this DNS TXT record, then run with --active --verify dns-txt:
#   _mcpprobe.your-server.example.  TXT  "mcpprobe-verification=<token>"

mcpprobe https://your-server.example/mcp --active --verify dns-txt \
  --authorised-by "platform security, CHG-1234" \
  --scope "https://your-server.example/mcp" --json scan.json

Commands and flags

mcpprobe scan --help (summary)Output
mcpprobe <url>                   passive scan of a remote MCP server
mcpprobe --stdio "<command>"     spawn and scan a local stdio server
mcpprobe checks                  list every check with mode, severity and standards
mcpprobe token <host>            create an ownership token for --verify dns-txt

  --header "Name: value"         extra HTTP header, e.g. a bearer token you own (repeatable)
  --env KEY=VALUE                environment for a stdio server (repeatable)
  --fail-on info|low|medium|high|critical   exit 1 at or above this severity (default high)
  --json FILE                    full JSON report, with evidence
  --sarif FILE                   SARIF 2.1.0 report
  --sarif-location PATH          repository file to anchor SARIF results to
  --baseline FILE                META-02 baseline to compare against and update
  --no-write-baseline            compare against --baseline without updating it
  --rate N                       requests per second (default 5, ceiling 10)
  --timeout SECONDS              per-request timeout (default 15)
  --only ID / --skip ID          run or skip specific checks (repeatable)
  --no-follow-as                 AUTH-03: do not fetch authorization-server metadata on third-party hosts
  --insecure                     do not verify the target's TLS certificate
  -v / -q                        verbose / quiet terminal output

  --active                       enable active checks; requires all three of:
  --authorised-by TEXT           who authorised this scan (name, role, ticket)
  --scope TEXT                   the agreed scope statement
  --verify dns-txt|local         ownership verification method
  --exclude-tool NAME            a tool that must never be called (repeatable)

Exit codes

0
No finding at or above --fail-on (default high)
1
At least one finding at or above --fail-on
2
Error: target unreachable or not MCP, bad arguments, ownership verification failed, authorisation refused, or the target opted out

Rug-pull baselines

Terminal
mcpprobe https://your-server.example/mcp --baseline .mcpprobe/baseline.json

The first run stores canonical SHA-256 hashes of every tool, prompt, resource and the server instructions. Later runs report anything added, removed or changed (META-02) and update the file; use --no-write-baseline to compare without updating.

Reports and evidence

  • --json FILE: the full report, including every request and response (timestamp, method, status, a header subset, bodies truncated to 4 KB). Authorization values and recognised secret patterns are redacted.
  • --sarif FILE: SARIF 2.1.0.
  • -v adds descriptions and scan notes to the terminal; -q prints nothing.
  • Server-supplied text is shown with control characters made visible and is never interpreted as terminal markup.

GitHub Action

A Docker container action, so no Python setup is needed. Upload the SARIF to code scanning so findings appear as alerts on the pull request.

.github/workflows/mcpprobe.yml
name: MCP security
on: [push, pull_request]
permissions:
  contents: read
  security-events: write
jobs:
  mcpprobe:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mcpprobe-dev/mcpprobe@v0.1.0
        with:
          target: https://staging.your-server.example/mcp
          fail-on: high
          sarif-output: mcpprobe.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: mcpprobe.sarif
          category: mcpprobe
InputMeaning
targetMCP endpoint URL to scan (required)
fail-oninfo, low, medium, high or critical (default high)
raterequests per second, 1 to 10 (default 5)
sarif-outputSARIF file to write (default mcpprobe.sarif)
sarif-locationrepository file to anchor results to
json-outputoptional full JSON report
baselineoptional META-02 baseline file
headersextra headers, one 'Name: value' per line, e.g. a test token from secrets

SARIF and code scanning

Results carry GitHub's security-severity on each rule and the OWASP IDs as tags. Because a scanned endpoint is not a file, results point at the target URL unless you pass --sarif-location.

mcpprobe severitySARIF level
Critical, Higherror
Mediumwarning
Low, Infonote

Badge

The badge reflects your latest hosted scan and turns grey 30 days after the last clean one. It links to the public report page, and a JSON endpoint lets directories read the status. What the badge proves.

mcpprobepassingmcpprobefailingmcpprobestalemcpprobeunknown
README.md
[![mcpprobe](https://mcpprobe.tech/badge/your-server.example.svg)](https://mcpprobe.tech/r/your-server.example)

API and MCP endpoint

The status endpoint is public and needs no key. It returns the same state the badge shows, so registries and directories can display it without scraping.

Status APIOutput
GET https://mcpprobe.tech/api/v1/status/your-server.example

{
  "host": "your-server.example",
  "status": "passing",
  "checked_at": "2026-10-19T07:40:00Z",
  "expires_at": "2026-11-18T07:40:00Z",
  "findings": { "critical": 0, "high": 0, "medium": 0, "low": 1 },
  "report_url": "https://mcpprobe.tech/r/your-server.example",
  "badge_url": "https://mcpprobe.tech/badge/your-server.example.svg",
  "scanner_version": "0.1.0"
}
  • GET /badge/{host}.svg returns the badge: passing, failing, stale or unknown.
  • GET /scanning-ips.json lists the addresses hosted scans come from.
  • An MCP endpoint that lets agents read reports is planned; it is not live in v0.1.0.

The scanner is also a Python library, with an optional LLM-judge hook for META-01. mcpprobe makes no LLM calls itself.

scan.py
import asyncio
from mcpprobe.checks import JudgeVerdict, ScanOptions
from mcpprobe.scanner import ScanConfig, run_scan


def judge(text: str, task: str) -> JudgeVerdict | None:
    return None  # plug in your own model here; mcpprobe makes no LLM calls


report, evidence = asyncio.run(
    run_scan(ScanConfig(target="https://your-server.example/mcp", options=ScanOptions(judge=judge)))
)
print(report.counts())

Methodology and false-positive rates

Every check has a documented probe, a default severity, the evidence it records, known false positives and a fix; all of it is in the checks catalogue, generated from the scanner itself. Heuristic checks never rate a finding Critical on their own, and a check may raise or lower the severity of an individual finding (loopback-only servers are reported one level lower). We will measure each check's false-positive rate on a public dataset and publish it once a check has 100 or more samples. Disputed findings are reviewed by a person and the rule is fixed, not hidden.

Every check's probe, evidence, false positives and fix are in the checks catalogue; how scans are authorised is in the scanning policy.