mcp-pact
Consumer-driven contract testing for MCP servers.
Agents declare the tools and arguments they depend on. CI verifies that a server surface still satisfies those contracts before you deploy.
Why
Most MCP tooling snapshots the provider and diffs schemas. That answers “did the server change?”
mcp-pact answers a different question:
If I ship this MCP server change, which agents break?
Install
pip install mcp-pact
Requires Python 3.10+.
Quickstart
1. Write a consumer contract
# contracts/support_agent.yaml
consumer: support-agent
provider: docs-mcp
interactions:
- tool: search_docs
arguments: { query: "refund policy", limit: 5 }
required_arguments: [query]
- tool: get_doc
arguments: { doc_id: "POL-12" }
required_arguments: [doc_id]
2. Snapshot the provider surface
Export your MCP tools/list result to JSON (see examples/provider_v1.json).
3. Verify in CI
# one consumer
mcp-pact verify \
--contract examples/consumer_support_agent.yaml \
--provider examples/provider_v1.json
# all consumers before release
mcp-pact can-deploy \
--provider examples/provider_v1.json \
--contracts contracts/*.yaml
# schema diff + consumer impact
mcp-pact diff \
--old examples/provider_v1.json \
--new examples/provider_v2_breaking.json \
--contracts examples/consumer_support_agent.yaml
# generate adversarial argument cases from schemas
mcp-pact fuzz --provider examples/provider_v1.json --out fuzz_cases.json
Exit code 1 means at least one consumer would break.
What is checked?
| Check | Breaking when |
|---|---|
| Tool presence | Consumer tool is missing on the provider |
| Required args | Consumer needs an argument the provider removed |
| Provider-required args | Provider newly requires an argument the consumer never sends |
| Types | Provider input type no longer accepts what the consumer sends |
| Description-only | Classified as routing (agent selection risk); not a hard fail by default |
Python API
from mcp_pact import (
load_consumer_contract,
load_provider_surface,
can_i_deploy,
generate_fuzz_cases,
)
provider = load_provider_surface("provider.json")
contract = load_consumer_contract("agent.yaml")
decision = can_i_deploy(provider, [contract])
assert decision.ok, decision.to_dict()
Pytest plugin
pytest --mcp-provider-surface=provider.json \
--mcp-consumer-contracts=contracts/a.yaml,contracts/b.yaml
def test_agents_still_compatible(mcp_can_i_deploy):
assert mcp_can_i_deploy.ok, mcp_can_i_deploy.to_dict()
Comparison
| Library | Model |
|---|---|
mcp-contract, mcp-diff, … |
Provider snapshot + schema lockfile |
| mcp-pact | Consumer-driven contracts, can-i-deploy, fuzz case generation |
Development
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest
Status
Alpha (0.1.0). Offline-first: works on committed JSON/YAML provider surfaces. Live MCP probing is planned.
License
Metadata
Release files for mcp-pact 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mcp_pact-0.1.0.tar.gz | 13.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_pact-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.2 kB
Release files / mcp_pact-0.1.0.tar.gz
| Download URL | mcp_pact-0.1.0.tar.gz |
|---|---|
| Size | 13.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3b58e19a24b7afa22db929cc383601982b1b37ea5e86f02461c41e0d8d8b496e
|
|
BLAKE2b-256 checksum How to use checksums |
b074d75ba20103262c5e35d7ea8be75af810638bdf832077c2b4e0d447318119
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / mcp_pact-0.1.0-py3-none-any.whl
| Download URL | mcp_pact-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
70221312ee1670a8fba10ff089ba86f179953a1db05f9a7aec8953d9fcd05ba8
|
|
BLAKE2b-256 checksum How to use checksums |
07d18f3917aa997c2e67da61bdf69254deb5a69f8daa7215518cf52afbdc27ef
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|