Skip to main content

pytest-mcp-plugin

pytest for MCP servers — the testing framework for the Model Context Protocol.

PyPI Python CI Conformance License: MIT

pip install pytest-mcp-plugin
mcp-test demo       # runs a bundled MCP server + tests in 5 seconds

About the name. This package is pytest-mcp-plugin on PyPI. The mcp-test name is reserved by Anthropic for their official MCP SDK, and PyPI's name-similarity policy blocks close variants. The CLI binary is still mcp-test, the Python module is still mcp_test.


Why this exists

MCP servers turn LLM output into real-world side-effects: file reads, SQL queries, HTTP calls, shell commands. Most of them ship with zero automated tests. pytest-mcp-plugin is the missing test harness:

  • Real protocol coverage — runs against Anthropic's own @modelcontextprotocol/conformance suite in CI on every PR. Our bundled demo server passes the suite modulo features it intentionally doesn't implement (locked in conformance-baseline.yml).
  • Batteries-included security packs — opt-in mixin classes that test path traversal, SQL injection, credential leakage, and shell-metacharacter injection against your server with one subclass declaration.
  • stdio + Streamable-HTTP — same fixtures, same assertions, both transports. Includes an in-process FastMCP harness for sub-second tests.
  • Built for CI — pytest-native, JUnit XML output, automatic wire-trace dumps on CI failure, mcp-test GitHub Action for one-line integration.

60-second tour

pip install pytest-mcp-plugin
mcp-test demo

That spins up a bundled stdio MCP server and runs a real test suite against it — no setup, no API keys, no servers to write first.

test_demo.py::test_lists_tools         PASSED
test_demo.py::test_echo                PASSED
test_demo.py::test_add                 PASSED
test_demo.py::test_uppercase           PASSED
test_demo.py::test_fail_returns_error  PASSED

✅ Demo passed. Now write tests for your own MCP server:
   mcp-test init

For HTTP / FastMCP servers:

pip install 'pytest-mcp-plugin[fastmcp]'
python -m mcp_test._demo_server_http &     # boots on :8765
mcp-test conformance --url http://127.0.0.1:8765/mcp

Test your own server

cd my-mcp-server/
mcp-test init
pytest --mcp-command "python my_server.py" -v

Or pin the command in your pyproject.toml:

[tool.mcp-test]
command = "python my_server.py"
timeout = 10

…then just run:

mcp-test run

For per-method timeouts, prefer the built-in smart defaults:

mcp-test run --smart-timeouts
# or list explicit overrides:
pytest --mcp-timeout-method "tools/call=30" --mcp-timeout-method "sampling/createMessage=120"

Write tests

# tests/test_my_server.py
from mcp_test import assert_tool_ok, assert_tool_error, assert_tool_text_contains


def test_search_returns_results(mcp_client):
    result = mcp_client.call_tool("search", query="machine learning")
    assert_tool_ok(result)
    assert len(result.content) > 0


def test_search_handles_empty_query(mcp_client):
    result = mcp_client.call_tool("search", query="")
    assert_tool_error(result)


def test_search_schema(mcp_client):
    tools = mcp_client.list_tools()
    search = tools.find("search")
    assert search.required == ["query"]
    assert search.properties["query"]["type"] == "string"

Batteries-included security test packs

If your server fits one of these shapes, you get 4–8 production-grade security assertions for free by subclassing one mixin:

Pack Catches
FilesystemServerTests path traversal, absolute-path acceptance, sandbox escape via symlinks, resource scope creep
DatabaseServerTests read-only tools that quietly mutate, SQL injection via parameter concatenation
APIWrapperTests tools that call upstream anonymously without configured creds, API-key leakage in tool output
ShellExecTests shell=True injection (canary-probed), allowlist bypass, hidden non-zero exits

Each pack ships with a worked-example demo server that passes the pack — look in examples/<server>/ for the reference implementation and the test file that opts in. Copy-paste-modify is the intended workflow.

from mcp_test.test_packs import FilesystemServerTests

class TestMyFsServer(FilesystemServerTests):
    expected_tools = ("read_file", "list_directory")
    read_tool = "read_file"
    list_tool = "list_directory"
    safe_path = "data/known-good.txt"
    # That's it — you now have 5 security tests.

MCP spec conformance

pytest-mcp-plugin runs Anthropic's official @modelcontextprotocol/conformance suite against the bundled demo server on every PR. The current expected-failures baseline (features the minimal demo doesn't implement) is locked in conformance-baseline.yml; any unexpected failure in CI fails the build.

To run the suite against your server:

mcp-test conformance --url http://127.0.0.1:8765/mcp
# or for a stdio server through the bridge:
mcp-test conformance --command "python my_server.py"

Add --offline to skip the npx round-trip and run our local smoke scenarios only.


Use as a library

from mcp_test import MCPTestClient

with MCPTestClient.from_command("python my_server.py") as client:
    tools = client.list_tools()
    print(tools.names())

    result = client.call_tool("echo", message="hello")
    print(result.text())

For HTTP / Streamable-HTTP:

from mcp_test.http_client import HTTPMCPTestClient

with HTTPMCPTestClient.from_url("http://127.0.0.1:8765/mcp") as client:
    print(client.list_tools().names())

For FastMCP apps, skip the subprocess entirely:

from fastmcp import FastMCP
from mcp_test.fastmcp import FastMCPHarness

app = FastMCP("my-server")

@app.tool()
def echo(msg: str) -> str: return msg

with FastMCPHarness(app) as client:
    assert client.call_tool("echo", msg="hi").text() == "hi"

Run on every PR (GitHub Action)

Drop this into any repo:

# .github/workflows/mcp-tests.yml
name: MCP Tests

on:
  pull_request:
  push:
    branches: [main]

jobs:
  mcp-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: yagna-1/mcp-test@v0.3.0
        with:
          command: "python my_server.py"
          test-dir: "tests"

The action installs pytest-mcp-plugin, runs your suite against your MCP server, and uploads a JUnit XML report. On failures, it also uploads any wire-trace dumps from mcp-traces/.


CLI reference

Command Description
mcp-test demo Run the bundled demo server + tests (zero setup)
mcp-test init Scaffold tests/ with example MCP tests
mcp-test run -c "python server.py" Run pytest against your server
mcp-test snapshot -c "..." Run snapshot tests (--update to refresh)
mcp-test coverage -c "..." Print coverage report (tools/prompts/resources)
mcp-test validate -c "..." Validate tool input schemas
mcp-test conformance --url "..." Run Anthropic's conformance suite via npx (or --offline)
mcp-test bench -c "..." Run lightweight p50/p95/p99 regression probes

All commands accept --help for full options.


Fixtures

The pytest plugin auto-registers four fixtures:

Fixture Scope Use for
mcp_client session Fast — one server process for the whole test run
mcp_client_fresh function Clean state per test
sandboxed_client function Fresh server with cwd=tmp_path and DATA_DIR=tmp_path
snapshot function Snapshot testing helper
pytest --mcp-command "python my_server.py" --mcp-timeout 15 \
  --mcp-timeout-method tools/call=30 \
  --mcp-trace .mcp-test/trace.jsonl

--mcp-timeout-method METHOD=SECONDS may be passed multiple times. On CI, failing tests automatically dump recent JSONL wire frames to mcp-traces/.


Spec-version markers

Mark tests by required MCP spec version; the plugin auto-skips them against older servers.

import pytest

@pytest.mark.mcp_v3  # requires spec >= 2025-06-18
def test_uses_recent_feature(mcp_client):
    ...

Available markers: mcp_v2 (≥ 2025-03-26), mcp_v3 (≥ 2025-06-18), mcp_v4 (≥ 2025-11-25).


Assertion helpers

from mcp_test import (
    assert_tool_ok,
    assert_tool_error,
    assert_tool_error_code,
    assert_tool_text_contains,
    assert_tool_text_equals,
    assert_tool_content_count,
    assert_policy_allows,
    assert_policy_blocks,
    assert_task_completes_within,
    assert_task_cancelled,
    assert_task_failed,
)

Architecture

pytest-mcp-plugin runs your MCP server as a subprocess (stdio) or speaks Streamable-HTTP to a running endpoint, all over JSON-RPC 2.0. A background message pump handles response routing, notification dispatching, and concurrent request support.

mcp_test/
  client.py              # stdio JSON-RPC client
  http_client.py         # Streamable-HTTP + legacy-SSE client
  plugin.py              # pytest plugin (fixtures, options, markers)
  cli.py                 # mcp-test CLI
  assertions.py          # assert_tool_*, assert_policy_*, assert_task_*
  schema_validator.py    # JSON Schema validation for tool inputs
  coverage.py            # tools/prompts/resources coverage tracker
  snapshot.py            # snapshot testing
  auth.py                # OAuth / PKCE / RFC 9728 helpers
  bench.py               # lightweight regression benchmark probes
  conformance.py         # bridge to @modelcontextprotocol/conformance
  compliance.py          # conformance score helpers
  fastmcp.py             # in-process FastMCP harness adapter
  otel.py                # optional OpenTelemetry tracing facade
  replay.py              # deterministic wire-trace replay
  pagination.py          # cursor pagination helpers
  test_packs.py          # batteries-included security test packs
  timeouts.py            # per-method timeout policy
  wire_trace.py          # JSONL wire trace recorder
  types.py               # ToolResult, ToolSchema, MCPError, ...
  _demo_server.py        # bundled stdio demo server
  _demo_server_http.py   # bundled FastMCP HTTP demo server

Status

pytest-mcp-plugin is beta. The CLI surface, plugin API, and test-pack class attributes are stable; minor internals (snapshot format, schema validator details) may still change before 1.0.

Roadmap

See ROADMAP.md for what's next and how we plan to complement — not compete with — Anthropic's official conformance + inspector tools.

Contributing

Bug reports, feature requests, and PRs welcome at github.com/yagna-1/mcp-test.

License

MIT

Release files for pytest-mcp-plugin 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-mcp-plugin 0.3.0
File Size Uploaded
pytest_mcp_plugin-0.3.0.tar.gz 84.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-mcp-plugin 0.3.0
File Interpreter ABI Platform
pytest_mcp_plugin-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 146.9 kB

Release files / pytest_mcp_plugin-0.3.0.tar.gz

Download URL pytest_mcp_plugin-0.3.0.tar.gz
Size 84.6 kB
Tags Source
SHA-256 checksum
How to use checksums
a52dc1096f332beb1e3677deec19cecfcd6a5d6fe35cd472234638f08e707b98
BLAKE2b-256 checksum
How to use checksums
bf305f5d21eb85ea5ea622a85e839151c80a4d53902f0b856e609ba0fdc3abe7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.14

Release files / pytest_mcp_plugin-0.3.0-py3-none-any.whl

Download URL pytest_mcp_plugin-0.3.0-py3-none-any.whl
Size 62.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d1e1f7f9e3f4c6153659e8093c05a4716ebc3551463365907e12b239c7024dff
BLAKE2b-256 checksum
How to use checksums
c7a48eb7ce5228d218e6314ee7d78efcea5e2e1204e0dd83ecec2870533b79bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.14

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page