MCP Test Harness

PyPI version PyPI downloads Python CI License Tests Coverage GHCR image Website

Latest Release - install from PyPI (pip install mcp-test-harness) or use the OCI image on GitHub Container Registry: ghcr.io/vaquarkhan/mcp-test-harness. Tags: latest and 3.0.8 (runtime, mcp-test entrypoint), dev and 3.0.8-dev (pytest + dev extras). Browse tags on GHCR · docker run --rm ghcr.io/vaquarkhan/mcp-test-harness:latest --version · docs/DOCKER.md · docs/RELEASING.md

Author: Vaquar Khan · License: MIT (NOTICE) · Cite: CITATION.cff · Sponsors: SPONSORS.md


For C-suite & directors

What it is: a CI gate for MCP servers - the connectors that let AI agents call tools, data, and APIs. Teams write deterministic tests once; every pull request proves the server still works before it ships.

Why it matters

Business need What the harness delivers
Ship AI features without silent breakage Automated pass/fail on every PR - no manual Inspector click-through
Audit & governance evidence JUnit / SARIF / HTML reports, conformance badges, security payload packs
Lower cost of quality One tool for functional, regression, performance, and resiliency - not three vendors
Trust the gate 840+ self-tests, 100% library coverage, and e2e dogfood in CI

Not an LLM eval product. It does not score model answers. It proves your MCP server is correct, fast enough, and hardened enough for production. Pair with runtime security (MCP-Bastion) and IDE config scanning (mcp-shark). Positioning: docs/POSITIONING.md · comparison: docs/COMPARISON.md.


For architects & tech leads

Role in the stack: deterministic, code-first test automation between local MCP Inspector exploration and production. Fits stdio / SSE / HTTP transports; plugs into GitHub Actions (Marketplace Action), JUnit consumers, and Code Scanning (SARIF).

Architecture (one run)

mcp-test CLI → config → discover test_*.py → schedule (optional parallel)
  → real MCP session (lifecycle + transport) → assertions → reports
Concern Approach
Correctness Protocol-aware fixtures (mcp_server), schema checks, snapshots
Performance assert_latency / assert_throughput / assert_stateless_throughput, baselines, SLO params
Security / resiliency Payload packs, chaos faults, experiment catalog + scorecard (RFC-005)
Conformance signal Levels + README badge via mcp-test try / conformance (RFC-002); stateless via mcp-test conformance stateless (RFC-006)
Adoption speed init, generate, record (RFC-001) - live session → suite
Extensibility Plugins (assertions, fixtures, reporters, transports)

Deep dives: docs/ARCHITECTURE.md · docs/DECISIONS.md · docs/ENTERPRISE_GOVERNANCE.md · RFCs under docs/design/.


For developers

Fastest path

pip install mcp-test-harness
mcp-test init --server-command "python your_server.py"
# edit tests/test_*.py - then:
mcp-test --config mcp-test.yaml

Zero-config probe: mcp-test try --server-command "…". CI: Marketplace Action mcp-test-harness. Docs hub: docs/QUICK_START.mddocs/DEVELOPER_GUIDE.mddocs/CI_AND_REPORTS.md.

MCP Test Harness is a pytest-style framework for MCP servers: the mcp-test CLI discovers, runs, and reports tests automatically - replacing much of the manual validation you might do in the MCP Inspector. Repository: github.com/vaquarkhan/mcp-test-harness.

Documentation: QUICK_START · DEVELOPER_GUIDE · CI & reports · performance · comparison · discovery checklist. Community: Issues for bugs; PRs for docs and examples.


What ships end-to-end

Everything below is implemented, tested, and documented in this repo (840+ tests, 100% lib coverage gate, e2e dogfood in CI). Version pins live only in PyPI, CHANGELOG.md, and install/Docker tags - not in this map.

Area Feature Where to start
CLI mcp-test - discover, run, filter (-k / -m), parallel, watch, reports; subcommands init, try, record, generate, experiment, conformance CLI Reference · Quick Start
Automation Scaffold (init), zero-config probe (try), live record / generate, --watch re-runs, config-driven CI gates Quick Start · Core Features
Docker OCI image on GHCR (ghcr.io/vaquarkhan/mcp-test-harness) - runtime + :dev; local Dockerfile Docker · docs/DOCKER.md
Hooks pre-commit hook (mcp-test-try) + example config; plugin / discovery hooks for custom automation examples/pre-commit-config.yaml · Plugins
Diagnostics MCP trace - per-test JSON-RPC timeline in HTML/JSON reports; stdio pollution hints example_mcp_trace.md
Resiliency Chaos testing - @marker(tags=["chaos"], chaos_faults=[...]) (delay, 503, truncate, schema drift) example_chaos_testing.md
Resiliency Experiment catalog - mcp-test experiment run --suite core with guardrails and scorecard (RFC-005) docs/design/RFC-005-resiliency-experiments.md
Conformance Levels + badge - mcp-test try / conformance badge (RFC-002); stateless SEP-2575 via mcp-test conformance stateless (RFC-006); README shields.io seal docs/design/RFC-002-conformance-levels.md · RFC-006
Productivity mcp-test record (RFC-001) + generate - live calls → suite + snapshots; schema drafts RFC-001 · example_generate_scaffold.md
Marketplace GitHub Action with conformance PR outputs Marketplace · example_github_actions.md
Platform QA Tool coverage map, unified portal in HTML/JSON, security payload packs, resiliency assertions, performance baselines, assert_throughput / assert_stateless_throughput SLO params docs/POSITIONING.md · docs/SECURITY_TESTING.md · RFC-006
CI / security SARIF export, OWASP MCP rule metadata, PR summary markdown + GitHub Action pr-comment docs/CI_AND_REPORTS.md
Self-test E2E dogfood - pytest spawns real mcp-test CLI against bundled FastMCP fixtures; 100% coverage enforced on every PR We eat our own dogfood
Examples Runnable platform-qa demo pack (trace + chaos + generate) feature-demo/platform-qa
Website Marketing site at vaquarkhan.github.io/mcp-test-harness (deploys html/ via GitHub Actions) html/index.html

Full history: CHANGELOG.md · roadmap: docs/ROADMAP.md.


Visual guide

Developer journey

pip installmcp-test init → write test_*.pymcp-test → merge with confidence.

Three testing modes

Functional correctness, snapshot regression, and SLO-style performance - not three separate tools.

All visual assets

Every diagram lives in docs/images/. Quick reference:

Image What it shows
hero-banner.png Project hero - pytest-style MCP testing
end-to-end-flow.png Full pipeline: CLI → server → assertions → reports
developer-journey.png Install → init → write tests → run → CI
three-testing-modes.png Functional, regression, and performance pillars
architecture-flow.svg Internal modules and data path (vector)
stateless-dual-mode.svg Dual mode: stateful session vs stateless HTTP (SEP-2575 / RFC-006)
mcp-testobarness-feature.png Core feature map
assertions-grid.png Assertion library at a glance
report-formats.png Console, JUnit, JSON, and HTML format overview (infographic)
html-dashboard.png Live HTML dashboard - format previews (click to expand), stat cards, charts, filters, PDF/CSV export
transport-options.png stdio, SSE, and HTTP transports
parallel-execution.png Multi-worker scheduling and module grouping
ci-pipeline.png GitHub Actions PR gate workflow
docker-distribution.png PyPI, GHCR container, and standalone binary
ecosystem-map.png Position vs Inspector, conformance, evals, Bastion
harness-bastion-pairing.png Test in CI, secure in production
testherness.png Quick-start overview
dogfood-e2e.svg Dogfood: pytest runs mcp-test against bundled MCP fixtures (840+ tests, 100% lib coverage)

We eat our own dogfood

A test harness should prove itself. This repo runs 840+ pytest cases with a 100% line coverage gate on src/mcp_test_harness, plus end-to-end dogfood tests that spawn the real mcp-test CLI against a bundled FastMCP server:

# Same gate CI enforces on every PR
coverage run -m pytest tests/ --ignore=tests/test_workspace.py -q
coverage report --fail-under=100

# E2E dogfood only
python -m pytest tests/test_harness_dogfood_e2e.py -m e2e -v

Fixtures live under tests/fixtures/ (minimal_mcp_server.py, harness_self_test/). See docs/DEVELOPER.md and CONTRIBUTING.md.


Why teams adopt this

MCP Test Harness is the deterministic, CI-native gate for MCP servers - one run proves correctness, speed, and security baselines without an LLM in the loop. Full positioning: docs/POSITIONING.md.

Four differentiators

# Differentiator What you get
1 Tri-modal CI architecture Functional + regression + performance in one mcp-test run - assert_latency (p95/p99 + warmup), assert_throughput (min_rps, max_p99_ms, max_error_rate), assert_tool_idempotent, JSON performance baselines
2 Protocol-aware fixtures mcp_server / mcp_server_session - spawn, handshake, inject session, teardown; parallel module grouping
3 Code-first workflows Multi-step agent flows in async Python (anti-DSL); @marker(tags=[...]) for smoke/security/perf
4 Unified portal One HTML/JSON artifact: category scores, tool coverage map (advertised vs tested vs missing auth), security/resiliency packs

Three testing modes in one tool

  • Functional: assert_tool_call, assert_resource_read, schema validation
  • Regression: assert_snapshot, assert_tool_idempotent
  • Performance: assert_latency, assert_throughput, assert_latency_within_baseline

Pair with mcp-shark for IDE config security and MCP-Bastion for runtime protection - see docs/COMPARISON.md.

MCP Test Harness supports Responsible AI and governance programs: schema contract checks, security payload packs (@marker(tags=["security"])), coverage gaps in reports, and EU AI Act demo packs.

Documentation

Hub (table of all guides + suggested reading order): docs/README.md

Document Contents
docs/QUICK_START.md Fastest path - install, mcp-test init, run
docs/DEVELOPER_GUIDE.md Canonical reference - setup, config, stdio/parallel/validation, assertions, reporting
docs/CI_AND_REPORTS.md CI, JUnit, JSON, HTML - do you need to publish test reports? (usually: no)
docs/POSITIONING.md Why we're different - four differentiators, enterprise governance, mcp-shark pairing
docs/PERFORMANCE_TESTING_STRATEGY.md Product pitch - why MCP performance testing belongs in the harness; roadmap and scope
docs/ROADMAP.md Roadmap - now/next/later plan plus prioritized value bets (unified reports, security packs, resiliency, coverage map, SLO load testing)
docs/SECURITY_TESTING.md Security testing - MCP-aware security assertions and CI guidance
docs/CONTRACT_AND_COMPAT.md Contracts & compatibility - drift protection and protocol/client matrix strategy
docs/ENTERPRISE_GOVERNANCE.md Enterprise - audit/policy/tenant governance guidance (including EU AI Act evidence mapping notes)
docs/PLUGIN_REGISTRY.md Plugin registry - extension catalog and integration categories
docs/TUTORIAL.md Step-by-step tutorial
docs/DECISIONS.md Architecture and product decisions
docs/IMPLEMENTATION_CHECKLIST.md Maintainer: features vs. code locations
docs/COMPARISON.md Ecosystem - where this harness fits alongside conformance/eval/benchmark categories
docs/LLM_TEST_GENERATION.md LLM + tests - draft-with-review: good; auto trusted in CI: bad fit for this harness
docs/COLLECTIONS.md Postman / Newman-style multi-step flows, “environments”, and roadmap (declarative collections not in core yet)
docs/DISCOVERY.md Registries and promotion - internal checklist (PyPI, server.json, awesome lists)
docs/DOCKER.md Docker & OCI - PyPI, GHCR / GitHub Packages links, build targets, docker run
docs/RELEASING.md Ship v* - PyPI trusted publishing + GHCR images in one tag
docs/ARCHITECTURE.md Mermaid diagrams: CLI → scheduler → lifecycle → session → tests
docs/EDITORS.md Visual Studio Code & Cursor - snippets, Mermaid preview, recommended extensions
docs/MARKDOWN_CONVENTIONS.md Markdown - [!TIP] / **Feature** callouts and fenced code for readable docs
CHANGELOG.md Release history (Keep a Changelog)
CONTRIBUTING.md How to contribute (tests, coverage, release bumps)
CITATION.cff Optional citation - machine-readable metadata (not required by the license)
Dockerfile (and .dockerignore) Container image for mcp-test - see Docker

For production security (prompt injection defense, PII redaction, rate limiting, RBAC), see MCP-Bastion - the companion security middleware; this repo is for test automation.

Core Features

Feature Description
Test discovery Finds test_*.py files and test_ functions automatically (pytest conventions); broken files log a warning with path and exception
MCP assertions assert_tool_call, assert_resource_read, assert_prompt, assert_capabilities, assert_snapshot, plus assert_tool_schema, assert_protocol_version, assert_tool_idempotent, assert_latency (p95/p99/mean + warmup), assert_throughput (concurrent load + min_rps, max_p99_ms, max_error_rate), assert_latency_within_baseline, security payload packs - see docs/PERFORMANCE.md, docs/SECURITY_TESTING.md
Fixture system Built-in mcp_server / mcp_server_session, custom fixtures; cycle detection for dependency errors
Schema validation JSON-RPC envelope checks; with schema_validation: true (default), post-connect checks on initialize, tools/list (+ tool inputSchema), resources / prompts list shapes, and a best-effort call_tool probe to validate content item shapes
Snapshot testing Compare responses; ignore_fields and mask_patterns for unstable data
Parallel execution Multiple workers; tests from the same file stay on one worker so per-module fixtures remain correct
Watch mode mcp-test --watch re-runs when test *.py files change (configurable poll interval; debounce coalesces rapid saves)
Markers @marker(timeout=60, retry=3, tags=["smoke"]) and @skip(reason="...")
Reports Console summary, JUnit XML (GitHub Actions/Jenkins/GitLab), JSON/HTML with MCP trace timelines, unified portal / coverage map, SARIF for Code Scanning, HTML dashboard with PDF/CSV export and live filters
Plugin system Extend with custom assertions, fixtures, reporters, and transport adapters
Transport support stdio, SSE, streamable HTTP -- test local and remote servers
GitHub Action One-line CI integration with artifact upload
Docker Pre-built on ghcr.io/vaquarkhan/mcp-test-harness (:latest / version tags + :dev); local Dockerfile with mcp-test (runtime) or pytest + dev extras via --target dev (see Docker)
Standalone binary Single binary via PyInstaller, no Python required on target
MCP trace Per-test JSON-RPC event log + interactive HTML timeline - example_mcp_trace.md
Chaos testing @marker(tags=["chaos"], chaos_faults=[...]) - delay, 503, truncate, schema drift - example_chaos_testing.md
mcp-test generate Draft tests from live tools/list + optional drift report - example_generate_scaffold.md

Beginner demo packs by testing type: functional-testing · regression-testing · performance-testing (each includes runnable tests plus JSON/JUnit/HTML report config).
Platform QA (trace + chaos + generate): platform-qa
Full scenario index: examples/feature-demo/README.md

Ecosystem (Conformance, evals, benchmarks)

MCP Test Harness is deterministic (your tests call the protocol directly; no LLM required). The wider MCP space includes protocol conformance suites, agent/LLM evaluation frameworks, and model benchmarks. A concise map of tools, when to use each, and how they complement (not replace) the harness is in docs/COMPARISON.md.

Installation

Current stable version: 3.0.8 (see CHANGELOG.md). Core harness (lightweight: mcp + YAML + anyio; no MCP-Bastion / Presidio stack):

pip install mcp-test-harness
# pin, if you need a fixed version:
# pip install mcp-test-harness==3.0.8

Same release as a container (GHCR, no local Python): docker pull ghcr.io/vaquarkhan/mcp-test-harness:3.0.8 or :latest - see the image section for docker run and dev tags.

Optional mcplint / MCP-Bastion pin helpers (transitive set can be large; same as a full Bastion install):

pip install mcp-test-harness[mcplint]
# or the historical PyPI name for the monorepo shim:
pip install mcplint

Or from source:

git clone https://github.com/vaquarkhan/mcp-test-harness.git
cd mcp-test-harness
python -m venv .venv && source .venv/bin/activate  # or .venv\Scripts\activate on Windows
pip install -e ".[dev]"
mcp-test --version

Docker

One-page guide (PyPI, container registries, Mermaid build diagram, docker run copy-paste): docs/DOCKER.md · System diagram (flow + sequence): docs/ARCHITECTURE.md · Visual Studio Code & Cursor (snippets, Mermaid, extensions): docs/EDITORS.md

Pre-built runtime and dev (test tooling) images are defined in the repo Dockerfile (and .dockerignore keeps the build context small). Each v* git tag triggers CI that pushes ghcr.io/vaquarkhan/mcp-test-harness: e.g. :3.0.8, :latest, :3.0.8-dev, :dev. Quick pull: docker pull ghcr.io/vaquarkhan/mcp-test-harness:latest. All versions (GHCR) · docs/RELEASING.md · docs/DOCKER.md.

Build Description
Default (runtime) mcp-test and core dependencies only - smallest image.
--target dev Adds the same optional packages as pip install -e ".[dev]" (e.g. pytest, jsonschema, PyInstaller). Use this when you want to run the project’s tests/ inside a container.

Build the default image (from the repository root; requires Docker):

docker build -t mcp-test-harness:local .

Smoke the CLI (the image entrypoint is mcp-test):

docker run --rm mcp-test-harness:local --version

Run mcp-test against a project mounted into the working directory (paths below use POSIX shells; on Windows, use PowerShell and replace $PWD with your project path, e.g. ${PWD} in Git Bash, or the full C:\... form):

docker run --rm -v "$PWD":/work -w /work mcp-test-harness:local

The default command shows mcp-test --help. Pass the same arguments you use locally, for example docker run --rm -v "$PWD":/work -w /work mcp-test-harness:local . to discover and run tests in the current config.

Build the dev image and run the test suite (requires your test tree mounted into /work):

docker build -t mcp-test-harness:dev --target dev .
docker run --rm -v "$PWD":/work -w /work --entrypoint pytest mcp-test-harness:dev tests/ -q

For coverage as in pyproject.toml, you can add --cov=src/mcp_test_harness if your tests/ and config are on the mount.

Windows (PowerShell), using Docker Desktop, mount the current directory, for example:

docker run --rm -v "${PWD}:/work" -w /work mcp-test-harness:local

Size and first-build time depend on what you install: the default runtime image matches mcp-bastion-python-free core dependencies. If you add [dev], [mcplint], or pip install mcp-bastion-python in the same environment, the resolver may pull a large transitive set (e.g. Presidio / NLP and, on many Linux x86_64 wheels, very large ML/CUDA-related packages). The first docker build with those extras can take a long time and produce a multi-gigabyte image. That is expected for a full install of the Bastion tree, not a bug in the Dockerfile when you opt into that stack.

Note: The Docker image is optional; many teams use pip install in CI. Use the image when you need a reproducible, Python-isolated environment without a local venv, or a portable mcp-test in pipelines that standardize on containers.

Quick Start

0. Scaffold a starter (optional)

With mcp-test on your PATH (after pip install):

mcp-test init

This writes tests/test_mcp_server_example.py and a minimal mcp-test.yaml. Set your real launch command, for example:

mcp-test init --server-command "python -m your_package.mcp"

Options: mcp-test init --help (custom --dir, --filename, --no-config, --force).

Check the server first (no tests): mcp-test doctor uses the same mcp-test.yaml (or --server-command) to start the server, run the MCP handshake, print the protocol version, list tools / resources / prompts, and optionally run the same post-connect schema checks as a normal run. Exits 0 when healthy, 1 on startup or schema errors. See mcp-test doctor --help.

Editor snippets: the repo includes .vscode/mcp-test-harness.code-snippets - in VS Code or Cursor, type prefixes like mcp-assert-tool or mcp-test-async in a *.py file to insert common patterns.

1. Write a test

Create tests/test_my_server.py:

from mcp_test_harness import assert_tool_call, assert_capabilities

async def test_server_has_tools(mcp_server):
    """Verify the server advertises tool capabilities."""
    await assert_capabilities(mcp_server, {"tools": {}})

async def test_echo_tool(mcp_server):
    """Call the echo tool and check it works."""
    result = await assert_tool_call(mcp_server, "echo", {"message": "hello"})
    assert result is not None

The mcp_server parameter is a built-in fixture. The harness automatically starts your server, connects via MCP, performs the initialize handshake, and injects a ready-to-use session.

2. Run it

mcp-test --server-command "python my_server.py" tests/

Output:

  [PASS] test_server_has_tools (45.2ms)
  [PASS] test_echo_tool (120.8ms)

2 passed, 0 failed, 0 errored, 0 skipped
Total time: 312.5ms

3. Add a config file

Create mcp-test.yaml:

server:
  command: python my_server.py
  transport: stdio

test:
  dirs: [tests/]
  timeout: 30

report:
  format: junit
  output: reports/results.xml

Then just: mcp-test

Assertion Reference

assert_tool_call -- invoke a tool and validate the response

# Basic: fail if the tool returns an error
await assert_tool_call(mcp_server, "echo", {"message": "hello"})

# With expected output
await assert_tool_call(mcp_server, "add", {"a": 1, "b": 2},
    expected=[{"text": "3", "isError": False}])

# Validate arguments against the tool’s inputSchema (requires `jsonschema`)
await assert_tool_call(
    mcp_server, "add", {"a": 1, "b": 2},
    validate_against_input_schema=True,
)

# Use the return value
result = await assert_tool_call(mcp_server, "get_data", {})
assert len(result.content) > 0

Other helpers: assert_tool_schema, assert_protocol_version, assert_tool_idempotent, assert_latency, assert_tool_call_validates_input, assert_tool_denied, assert_authorization_boundary - see Part 3b in the Developer Guide.

assert_resource_read -- read a resource and check content/MIME type

await assert_resource_read(mcp_server, "file:///config.json",
    expected_content='{"debug": true}',
    expected_mime_type="application/json")

assert_prompt -- get a prompt and validate messages

await assert_prompt(mcp_server, "summarize",
    arguments={"text": "The quick brown fox."},
    expected_messages=[{"role": "assistant", "content": "Summary: A fox."}])

assert_capabilities -- verify server capabilities

await assert_capabilities(mcp_server, {"tools": {}, "resources": {}})

assert_snapshot -- regression detection via stored snapshots

from pathlib import Path
from mcp_test_harness import assert_snapshot

async def test_stable_output(mcp_server):
    result = await mcp_server.call_tool("generate_report", {})
    await