pytest-mcp-contract
Pytest helpers for domain MCP tool contracts: registered names, annotations, input schemas, and in-memory handler calls.
This is not protocol conformance. It does not wrap npx @modelcontextprotocol/conformance, does not ship security payloads, and does not run an LLM in CI.
Install
pip install pytest-mcp-contract
# or pinned tag:
pip install git+https://github.com/gmhoward9289-ops/pytest-mcp-contract@v0.1.4
For in-memory registry access against the MCP Python SDK:
pip install "pytest-mcp-contract[mcp]"
The mcp extra pins the MCP Python SDK the same way swamp-ops does today: mcp[cli]>=2.0.0 (SDK 2.x MCPServer).
Develop from a git checkout:
pip install -e ".[mcp]"
Usage
Override the mcp_server fixture with your server object. The plugin lists names from the in-memory registry (no stdio subprocess).
import pytest
from mcp_contract.assert_mcp import assert_tools_named
from mcp_contract.session import list_tool_names
@pytest.fixture
def mcp_server():
from swamp_ops.server import server
return server
def test_tool_names(mcp_server):
assert_tools_named(list_tool_names(mcp_server), {"swamp_estate_status", ...})
list_tool_names reads MCPServer._tool_manager._tools (or tool_manager._tools). That is the same private registry swamp-ops already inspects. If the shape is unknown, the helper fails instead of skipping — registry drift is what this plugin is for.
Pin write tools and their safety annotations:
from mcp_contract.assert_mcp import (
assert_non_readonly_tools_non_destructive,
assert_tools_prefixed,
)
from mcp_contract.session import list_tool_names, tool_registry
def test_write_tools(mcp_server):
names = list_tool_names(mcp_server)
assert_tools_prefixed(names, "swamp_")
assert_non_readonly_tools_non_destructive(
tool_registry(mcp_server),
{"swamp_enqueue_job", "swamp_post_discussion", ...},
)
Calls go through the registered Tool.fn handler, not mcp.Client. v1 is in-memory only. Async handlers are supported: call_registered_tool uses asyncio.run; use acall_registered_tool inside async tests.
Input schema snapshots
Pin tool input JSON schemas and fail CI on accidental drift:
import json
from pathlib import Path
from mcp_contract.schemas import assert_tool_input_schemas_match
@pytest.fixture
def mcp_server():
from swamp_ops.server import server
return server
def test_schemas(mcp_server):
expected = json.loads(Path("tests/fixtures/mcp_schemas.json").read_text())
assert_tool_input_schemas_match(mcp_server, expected, tools=set(expected))
Refresh a snapshot from a live server module:
python -m mcp_contract snapshot tests/fixtures/mcp_schemas.json --module swamp_ops.server
Publish health
After each tag release, CI runs packaging/publish-doctor.sh (also daily) to verify PyPI serves the same version as src/mcp_contract/__init__.py. Local check:
bash packaging/publish-doctor.sh
Proof stack (pair with pytest-session-trace)
| Plugin | Asserts |
|---|---|
| pytest-mcp-contract (this repo) | MCP server registers the right tool names, annotations, input schemas, and handlers |
| pytest-session-trace | A saved agent session (JSONL) actually called those tools in order |
Use both in the same repo: registry correctness and agent behavior — still no LLM in CI. swamp-ops dogfoods the pair in test_mcp_contract.py, test_session_trace.py, and docs/SESSION_TRACE.md.
See also
- pytest-session-trace — assert what an agent called in a saved JSONL session (pairs with this plugin: registry vs behavior)
- MCP Python SDK testing
- FastMCP client/session docs in that SDK
Those cover protocol and transport. This plugin asserts your tool set.
License
Apache-2.0
Release files for pytest-mcp-contract 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_mcp_contract-0.1.4.tar.gz | 15.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_mcp_contract-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.7 kB
Release files / pytest_mcp_contract-0.1.4.tar.gz
| Download URL | pytest_mcp_contract-0.1.4.tar.gz |
|---|---|
| Size | 15.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b1053f5d19711ceff0ac47fa2e369c18c8e15a63181899ef1cd43c75da67f7d4
|
|
BLAKE2b-256 checksum How to use checksums |
89d80f1e817d5d8c8605d03027483c248d01fc5e51269d092e4b7c850d01d118
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.
Transparency logRelease files / pytest_mcp_contract-0.1.4-py3-none-any.whl
| Download URL | pytest_mcp_contract-0.1.4-py3-none-any.whl |
|---|---|
| Size | 15.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
04f05aa4176878ba5a6a69db6ff11d5db2523783b34ae0a808e4e3b0282af5f5
|
|
BLAKE2b-256 checksum How to use checksums |
a41970b92fb8df966cd8d1b4352a2ed2f95c38653faea223ab786349d87e9b6f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.
Transparency log