Skip to content

Run a defensible MCP audit ​

This workflow is for an authorized security assessment where another analyst must be able to understand what was tested, what happened, and what remains uncertain.

1. Create the engagement record ​

Before the first request, record:

FieldExample
AuthorizationTicket, assessment letter, or owner approval
Target identityServer name, commit/image, environment, endpoint
Transporthttps, streamablehttp, sse, or stdio
AuthenticationDedicated identity, scope, expiry, and auth source
Test windowStart/end time and timezone
Allowed effectsRead-only, test writes, registration, redirect probes, runtime observation
Data handlingReport location, retention, redaction, disclosure owner
ReproductionCLI version, schema version, seed, config hash, command line

Do not use production credentials or unapproved intrusive probes. The official MCP Security Best Practices and authorization specification are useful review context, but the engagement's authorization and target behavior determine what you may run.

2. Establish a baseline ​

Start with low volume and realistic values:

bash
mcp-fuzzer \
  --mode tools \
  --protocol streamablehttp \
  --endpoint https://target.example/mcp \
  --phase realistic \
  --runs 10 \
  --seed 42 \
  --output-dir reports/01-baseline

For stdio, add --enable-safety-system, --fs-root, and usually --no-network. For CI or target inventories, add --fail-if-no-tools so an unreachable or protected endpoint cannot look like an empty clean result.

The baseline should answer:

  • Was the endpoint reachable and correctly negotiated?
  • What tools, resources, prompts, and protocol version were exposed?
  • Which credentials and tool mappings were actually used?
  • What normal success, rejection, timeout, and error responses look like?

3. Build a test matrix ​

Do not run every flag by default. Select the smallest matrix that answers the research question:

SurfaceFirst passExpansion
Tool arguments/results--mode tools --phase realistic--phase aggressive, --tool NAME, larger bounded --runs
Protocol messages--mode protocol --protocol-phase realisticAggressive phase, --protocol-type, --stateful
Resources/prompts--mode resources or --mode promptsSupply the target-specific URI/name/arguments
Tool and schema metadata--security-auditReview each check's evidence and reproduce manually
OAuth boundary--auth-audit--auth-audit-intrusive only with explicit authorization
Local process behaviorstdio safety controls--runtime-probe with explicit exec/host allowlists
Version behaviorDefault negotiated version--spec-schema-version VERSION for a target-specific comparison

Use separate output directories for baseline, aggressive, auth, and runtime runs. This preserves the comparison between normal behavior and probe-induced behavior.

4. Exercise input and protocol boundaries ​

Aggressive inputs are useful when the target is isolated and the authorization allows them:

bash
mcp-fuzzer \
  --mode tools \
  --protocol https \
  --endpoint https://target.example/mcp \
  --phase aggressive \
  --security-audit \
  --runs 20 \
  --seed 42 \
  --output-dir reports/02-tools-aggressive

Interpret outcomes carefully:

  • A well-formed protocol error or schema rejection is normally validation evidence, not a vulnerability.
  • Accepted malformed input, unexpected side effects, sensitive output, transport anomalies, crashes, and reproducible state changes deserve review.
  • A timeout or server error is a symptom to reproduce, not automatically a security finding.

5. Run security checks with clear boundaries ​

--security-audit combines read-only inspection of advertised tool/schema content with selected evidence-backed output oracles. It can report signals for poisoning, hidden instructions, ANSI/control content, duplicate or drifting definitions, typosquatted names, dangerous capability combinations, insecure remote transport, and command/path/SQL/output-injection evidence from the same fuzz run.

Add --security-audit-intrusive when an authorized HTTP/SSE probe is in scope. It sends a foreign Origin and reports only a successful response as missing_origin_validation; same-host redirects are followed first so that endpoint canonicalization is not mistaken for acceptance, and a probe refused with HTTP 401 is recorded as origin_validation_inconclusive. Use a separate assessment phase if the target owner treats this extra request as intrusive.

--auth-audit reviews published OAuth metadata, authorization behavior, and unauthenticated tool exposure when authentication is advertised. The default path is read-only. Intrusive registration and redirect probes can change target state and must be separately authorized.

These checks map to security research vocabulary and source material, including the OWASP MCP Top 10. A mapping is not a proof of exploitability or a final severity rating.

6. Reproduce before reporting ​

For each candidate finding, preserve:

  1. The target revision and authorization scope.
  2. The exact command, configuration path, schema version, seed, and tool or protocol type.
  3. The finding ID/category, severity emitted by the tool, and evidence fields.
  4. The request/input and the smallest relevant server response or runtime observation.
  5. The expected secure behavior and the observed behavior.
  6. A second run showing whether the behavior is deterministic, stateful, or intermittent.
  7. Manual validation of exploitability, privilege, data sensitivity, and business impact.

Use Interpret evidence and findings for artifact handling. Keep reports restricted until credentials, tokens, private paths, server responses, and generated payloads have been reviewed and redacted.

7. Hand off or automate ​

An assessment handoff should include the scope record, test matrix, blocked or completed status, findings, reproductions, limitations, and recommended next tests. For repeatable checks, use evidence collection in CI with private artifacts and an explicit policy for which reviewed categories should fail a build.

Released under the MIT License.