Test, lint, and vet your MCP servers — pytest + eslint + contract testing for MCP, in one CLI.
Project description
mcpvet
Test, lint, and vet your MCP servers — pytest + eslint + contract testing for MCP, in one CLI.
Why
- ~40% of deployed MCP servers ship a broken tool — an invalid schema, a missing description, or stdout that corrupts the protocol. Agents fail silently and you find out from a user.
- Schema drift breaks agents without a stack trace. Rename an argument, tighten a type, drop a tool — the client just stops calling it. Nothing errors. Nothing tells you.
- Inspector is manual-only. Great for poking at a server by hand, useless in CI. There's no
pytestfor MCP. Now there is.
mcpvet catches all three before they reach production, and it runs in your pipeline.
Quickstart
pip install mcpvet # or: uvx mcpvet ...
# Lint any server — command, URL, or client config
mcpvet lint -- npx -y @modelcontextprotocol/server-filesystem /tmp
mcpvet lint http://localhost:8000/mcp
# Write tests once, run them forever
mcpvet test mcpvet.test.yaml --format junit
# Catch breaking changes between releases
mcpvet snapshot -o v1.json -- python my_server.py
mcpvet diff v1.json "python my_server.py"
Test suites
Declarative YAML — call a tool, assert on the result. No test harness to wire up.
# mcpvet.test.yaml
target:
command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
tests:
- name: list_directory returns entries
tool: list_directory
args: { path: "/tmp" }
expect:
- type: success
- type: contains
value: "tmp"
- type: latency_under
ms: 2000
- name: bad path returns proper MCP error
tool: list_directory
args: { path: "/nonexistent" }
expect:
- type: error
message_contains: "not found"
Expectation types: success, error, contains, equals, regex, latency_under, jsonschema.
Output formats: pretty (default), json, junit.
Lint rules
| Rule | Severity | Checks |
|---|---|---|
| MV101 | error | inputSchema is not a valid JSON Schema |
| MV102 | warning | inputSchema root type is not "object" |
| MV201 | error | tool has no description — models can't choose it reliably |
| MV202 | warning | description is too short (< 10 chars) to be useful |
| MV301 | error | non-protocol output on stdout corrupts the stdio transport |
GitHub Action
- run: pip install mcpvet
- run: mcpvet lint --format json -- python my_server.py
- run: mcpvet test --format junit > results.xml
Exit codes
| Code | Meaning |
|---|---|
| 0 | clean — no findings, all tests passed, no breaking changes |
| 1 | findings, test failures, or breaking changes detected |
| 2 | execution error — couldn't reach or inspect the server |
Roadmap
- record / mock — record real server traffic, replay it as a mock in tests.
- scan — security heuristics: prompt-injection surface, over-broad tool scopes.
- bench — latency and throughput benchmarks with regression tracking.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcpvet-0.1.0.tar.gz.
File metadata
- Download URL: mcpvet-0.1.0.tar.gz
- Upload date:
- Size: 35.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f1b6ca1224a8dd572d5d03d0c90f8a7a4e577a399e97bf79f1a7729b68a18f5
|
|
| MD5 |
df3d85f98ea430aeb92c6c00f884931c
|
|
| BLAKE2b-256 |
4c10a6cf183e1ed7caf7a38aebfbb85ecb66a94af1325e9cd6bdc14a6a78b3b6
|
Provenance
The following attestation bundles were made for mcpvet-0.1.0.tar.gz:
Publisher:
publish.yml on holydement0r/mcpvet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcpvet-0.1.0.tar.gz -
Subject digest:
4f1b6ca1224a8dd572d5d03d0c90f8a7a4e577a399e97bf79f1a7729b68a18f5 - Sigstore transparency entry: 2136211790
- Sigstore integration time:
-
Permalink:
holydement0r/mcpvet@2051e4c3721fe292f3cd6d0b7feaffe013ff54a1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/holydement0r
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2051e4c3721fe292f3cd6d0b7feaffe013ff54a1 -
Trigger Event:
release
-
Statement type:
File details
Details for the file mcpvet-0.1.0-py3-none-any.whl.
File metadata
- Download URL: mcpvet-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a0e4645f47e96ccd06d62821e73fc8c037140c48af6edd84336a7b974c47637
|
|
| MD5 |
c9ed129b3b55c3ebc495b407a50612ff
|
|
| BLAKE2b-256 |
a8c3ab9fc4761d2067e5d83362b64eb2b1d98fa9db63f89c692a4f662f2fced2
|
Provenance
The following attestation bundles were made for mcpvet-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on holydement0r/mcpvet
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mcpvet-0.1.0-py3-none-any.whl -
Subject digest:
1a0e4645f47e96ccd06d62821e73fc8c037140c48af6edd84336a7b974c47637 - Sigstore transparency entry: 2136211820
- Sigstore integration time:
-
Permalink:
holydement0r/mcpvet@2051e4c3721fe292f3cd6d0b7feaffe013ff54a1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/holydement0r
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2051e4c3721fe292f3cd6d0b7feaffe013ff54a1 -
Trigger Event:
release
-
Statement type: