mcp-toolproof 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. mcp_toolproof-0.1.0/.gitattributes +2 -0
  2. mcp_toolproof-0.1.0/.github/workflows/ci.yml +44 -0
  3. mcp_toolproof-0.1.0/.github/workflows/release.yml +20 -0
  4. mcp_toolproof-0.1.0/.gitignore +16 -0
  5. mcp_toolproof-0.1.0/CHANGELOG.md +14 -0
  6. mcp_toolproof-0.1.0/LICENSE +21 -0
  7. mcp_toolproof-0.1.0/PKG-INFO +304 -0
  8. mcp_toolproof-0.1.0/README.md +268 -0
  9. mcp_toolproof-0.1.0/examples/buggy.yaml +32 -0
  10. mcp_toolproof-0.1.0/examples/buggy_server.py +123 -0
  11. mcp_toolproof-0.1.0/examples/toolproof.yaml +53 -0
  12. mcp_toolproof-0.1.0/examples/weather_server.py +100 -0
  13. mcp_toolproof-0.1.0/pyproject.toml +70 -0
  14. mcp_toolproof-0.1.0/src/toolproof/__init__.py +16 -0
  15. mcp_toolproof-0.1.0/src/toolproof/__main__.py +3 -0
  16. mcp_toolproof-0.1.0/src/toolproof/assertions.py +138 -0
  17. mcp_toolproof-0.1.0/src/toolproof/checks.py +108 -0
  18. mcp_toolproof-0.1.0/src/toolproof/cli.py +226 -0
  19. mcp_toolproof-0.1.0/src/toolproof/client.py +304 -0
  20. mcp_toolproof-0.1.0/src/toolproof/config.py +129 -0
  21. mcp_toolproof-0.1.0/src/toolproof/pytest_plugin.py +123 -0
  22. mcp_toolproof-0.1.0/src/toolproof/reporters.py +189 -0
  23. mcp_toolproof-0.1.0/src/toolproof/runner.py +153 -0
  24. mcp_toolproof-0.1.0/tests/__init__.py +0 -0
  25. mcp_toolproof-0.1.0/tests/conftest.py +26 -0
  26. mcp_toolproof-0.1.0/tests/test_assertions.py +112 -0
  27. mcp_toolproof-0.1.0/tests/test_checks.py +73 -0
  28. mcp_toolproof-0.1.0/tests/test_cli.py +115 -0
  29. mcp_toolproof-0.1.0/tests/test_config.py +46 -0
  30. mcp_toolproof-0.1.0/tests/test_http.py +56 -0
  31. mcp_toolproof-0.1.0/tests/test_pytest_plugin.py +63 -0
  32. mcp_toolproof-0.1.0/tests/test_runner.py +120 -0
@@ -0,0 +1,2 @@
1
+ * text=auto eol=lf
2
+ *.bat text eol=crlf
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ${{ matrix.os }}
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest]
15
+ python: ["3.11", "3.12", "3.13"]
16
+ include:
17
+ - os: windows-latest
18
+ python: "3.12"
19
+ steps:
20
+ - uses: actions/checkout@v5
21
+ - uses: actions/setup-python@v6
22
+ with:
23
+ python-version: ${{ matrix.python }}
24
+ - run: pip install -e ".[dev]"
25
+ - run: ruff check .
26
+ - run: ruff format --check .
27
+ - run: mypy
28
+ - run: pytest -v
29
+
30
+ example:
31
+ # Run toolproof against the example server, the same way a user would in CI.
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v5
35
+ - uses: actions/setup-python@v6
36
+ with:
37
+ python-version: "3.12"
38
+ - run: pip install -e .
39
+ - run: toolproof run examples/toolproof.yaml --junit report.xml
40
+ - uses: actions/upload-artifact@v5
41
+ if: always()
42
+ with:
43
+ name: toolproof-report
44
+ path: report.xml
@@ -0,0 +1,20 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write # PyPI trusted publishing
13
+ steps:
14
+ - uses: actions/checkout@v5
15
+ - uses: actions/setup-python@v6
16
+ with:
17
+ python-version: "3.12"
18
+ - run: pip install build
19
+ - run: python -m build
20
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
13
+ report.xml
14
+ report.json
15
+ out.xml
16
+ out.json
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-10-06
4
+
5
+ First release.
6
+
7
+ - `toolproof inspect`: list a server's tools, resources and prompts (table or `--json`)
8
+ - Static checks for tool definitions: names, descriptions, input/output JSON Schemas, required fields
9
+ - YAML test cases with `is_error`, `equals`, `contains`, `regex`, `jsonpath`, `max_latency_ms` and automatic `outputSchema` validation
10
+ - `toolproof run` with per-test timeouts, retries, crash detection and automatic server restart
11
+ - Console, JUnit XML and JSON reports; exit code 0/1 for CI
12
+ - pytest plugin with an `mcp_server` fixture
13
+ - stdio and streamable HTTP transports
14
+ - Example weather server and a buggy server with planted bugs
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nilkanth Suthar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,304 @@
1
+ Metadata-Version: 2.5
2
+ Name: mcp-toolproof
3
+ Version: 0.1.0
4
+ Summary: Automated tests for MCP servers: schema checks, YAML test cases, JUnit output and a pytest plugin.
5
+ Project-URL: Homepage, https://github.com/NilkanthSuthar/toolproof
6
+ Project-URL: Issues, https://github.com/NilkanthSuthar/toolproof/issues
7
+ Author: Nilkanth Suthar
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ci,mcp,model-context-protocol,pytest,testing
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Framework :: Pytest
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: anyio>=4
22
+ Requires-Dist: jsonpath-ng>=1.6
23
+ Requires-Dist: jsonschema>=4.21
24
+ Requires-Dist: mcp<3,>=2.3
25
+ Requires-Dist: pydantic>=2.7
26
+ Requires-Dist: pyyaml>=6
27
+ Requires-Dist: rich>=13
28
+ Requires-Dist: typer>=0.12
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.10; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Requires-Dist: ruff>=0.6; extra == 'dev'
33
+ Requires-Dist: types-jsonschema; extra == 'dev'
34
+ Requires-Dist: types-pyyaml; extra == 'dev'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # toolproof
38
+
39
+ [![CI](https://github.com/NilkanthSuthar/toolproof/actions/workflows/ci.yml/badge.svg)](https://github.com/NilkanthSuthar/toolproof/actions/workflows/ci.yml)
40
+ [![PyPI](https://img.shields.io/pypi/v/mcp-toolproof)](https://pypi.org/project/mcp-toolproof/)
41
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
42
+
43
+ **pytest for MCP servers.** Write repeatable tests for any [Model Context Protocol](https://modelcontextprotocol.io) server's tools and run them in CI.
44
+
45
+ - **Static checks** on every tool definition: missing descriptions, invalid JSON Schemas, duplicate names, required fields that don't exist
46
+ - **YAML test cases**: call a tool and assert on errors, text, regex, JSONPath values, latency and the declared `outputSchema`
47
+ - **Crash detection**: if the server dies mid-test, toolproof reports it with the server's stderr, restarts the server and keeps going
48
+ - **CI-friendly output**: exit code 0/1, JUnit XML and JSON reports
49
+ - **pytest plugin**: an `mcp_server` fixture for tests written in Python
50
+ - Works with any server over **stdio** or **streamable HTTP**, in any language
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pip install mcp-toolproof
56
+ ```
57
+
58
+ Python 3.11+. The package is called `mcp-toolproof` on PyPI. The command and the import are both `toolproof`.
59
+
60
+ ## 30-second quickstart
61
+
62
+ Look at a server:
63
+
64
+ ```bash
65
+ toolproof inspect -- python my_server.py
66
+ toolproof inspect --url http://localhost:8000/mcp
67
+ ```
68
+
69
+ Write `toolproof.yaml`:
70
+
71
+ ```yaml
72
+ server:
73
+ command: ["python", "my_server.py"]
74
+
75
+ tests:
76
+ - name: toronto weather
77
+ tool: get_weather
78
+ args: { city: Toronto }
79
+ expect:
80
+ is_error: false
81
+ contains: "Toronto"
82
+ jsonpath: { "$.temp_c": { type: number } }
83
+ max_latency_ms: 2000
84
+
85
+ - name: rejects empty city
86
+ tool: get_weather
87
+ args: { city: "" }
88
+ expect: { is_error: true }
89
+ ```
90
+
91
+ Run it:
92
+
93
+ ```bash
94
+ toolproof run toolproof.yaml --junit report.xml
95
+ ```
96
+
97
+ ## What a failing run looks like
98
+
99
+ `examples/buggy_server.py` has five planted bugs. Here is `toolproof run examples/buggy.yaml`:
100
+
101
+ ```
102
+ toolproof - buggy-weather 0.0.1
103
+
104
+ Static checks
105
+ ┌──────┬───────────────┬─────────────────┬──────────────────────────────────────────────────────────────────┐
106
+ │ │ Tool │ Check │ Problem │
107
+ ├──────┼───────────────┼─────────────────┼──────────────────────────────────────────────────────────────────┤
108
+ │ FAIL │ search_cities │ description │ tool has no description │
109
+ │ FAIL │ search_cities │ input-schema │ input schema is not valid JSON Schema: 'strng' is not valid │
110
+ │ │ │ │ under any of the given schemas │
111
+ │ FAIL │ lookup │ required-fields │ required fields not listed in properties: city_id │
112
+ └──────┴───────────────┴─────────────────┴──────────────────────────────────────────────────────────────────┘
113
+
114
+ Tests
115
+ ┌──────┬────────────────────────────────────┬─────────────────┬────────┬────────────────────────────────────┐
116
+ │ │ Test │ Tool │ Time │ Details │
117
+ ├──────┼────────────────────────────────────┼─────────────────┼────────┼────────────────────────────────────┤
118
+ │ PASS │ weather works for a normal city │ get_weather │ 5ms │ │
119
+ │ FAIL │ empty city should be an error, not │ get_weather │ 23ms │ no result from server: connection │
120
+ │ │ a crash │ │ │ lost, server probably crashed │
121
+ │ │ │ │ │ (Connection closed) │
122
+ │ PASS │ server still works after the crash │ get_weather │ 3ms │ │
123
+ │ FAIL │ report comes back in time │ slow_report │ 1001ms │ no result from server: timed out │
124
+ │ │ │ │ │ after 1.0s │
125
+ │ FAIL │ temperature matches its output │ get_temperature │ 2ms │ output schema: $.temp_c: '12.5' is │
126
+ │ │ schema │ │ │ not of type 'number' │
127
+ └──────┴────────────────────────────────────┴─────────────────┴────────┴────────────────────────────────────┘
128
+
129
+ FAILED tests: 2 passed, 3 failed | checks: 3 errors, 0 warnings | 7.21s
130
+ ```
131
+
132
+ The same run as JUnit XML (`--junit report.xml`), which GitHub, GitLab and Jenkins show as a normal test report:
133
+
134
+ ```xml
135
+ <testsuite name="tests" tests="5" failures="3" errors="0" time="1.034">
136
+ <testcase classname="toolproof.get_weather" name="weather works for a normal city" time="0.005" />
137
+ <testcase classname="toolproof.get_weather" name="empty city should be an error, not a crash" time="0.023">
138
+ <failure message="no result from server: connection lost, server probably crashed (Connection closed)">...</failure>
139
+ </testcase>
140
+ ...
141
+ </testsuite>
142
+ ```
143
+
144
+ ## YAML reference
145
+
146
+ ```yaml
147
+ server:
148
+ command: ["python", "server.py"] # start the server over stdio...
149
+ # url: http://localhost:8000/mcp # ...or connect over streamable HTTP (exactly one)
150
+ env: { API_KEY: test } # extra environment variables (stdio only)
151
+ cwd: . # working directory, relative to this file (default: this file's folder)
152
+ startup_timeout_s: 30
153
+
154
+ timeout_ms: 10000 # default per-test timeout
155
+ retries: 0 # retry failing tests this many times
156
+
157
+ checks:
158
+ enabled: true
159
+ max_description_length: 1024
160
+ strict: false # treat warnings as failures
161
+
162
+ tests:
163
+ - name: a readable name
164
+ tool: tool_name
165
+ args: { any: json }
166
+ timeout_ms: 2000 # optional override
167
+ retries: 1 # optional override
168
+ expect:
169
+ is_error: false
170
+ equals: { result: 42 } # whole result: structured content, JSON text, or plain text
171
+ contains: "Toronto" # or a list: ["Toronto", "cloudy"]
172
+ regex: "\\d+ C"
173
+ jsonpath:
174
+ "$.temp_c": { type: number, min: -50, max: 50 }
175
+ "$.city": { equals: Toronto }
176
+ "$.wind": { exists: false }
177
+ max_latency_ms: 2000
178
+ output_schema: true # validate against the tool's outputSchema (default on)
179
+ ```
180
+
181
+ Notes:
182
+
183
+ - `jsonpath` looks at the tool's structured content if it returned any, otherwise at its text parsed as JSON.
184
+ - `type` is a JSON Schema type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `null`.
185
+ - If a tool declares an `outputSchema`, every successful call is validated against it automatically.
186
+ - Paths in `command` and `cwd` are relative to the YAML file, so `toolproof run path/to/toolproof.yaml` works from anywhere.
187
+
188
+ ### Static checks
189
+
190
+ | Check | Severity | What it catches |
191
+ |---|---|---|
192
+ | `name` | error | tool with an empty name |
193
+ | `duplicate-name` | error | two tools with the same name |
194
+ | `description` | error | tool with no description |
195
+ | `description-length` | warning | description longer than `max_description_length` |
196
+ | `input-schema` | error | input schema that isn't valid JSON Schema, or whose root isn't `type: object` |
197
+ | `required-fields` | error | `required` lists a field that isn't in `properties` |
198
+ | `required-fields` | warning | tool has properties but none are marked required |
199
+ | `output-schema` | error | `outputSchema` that isn't valid JSON Schema |
200
+
201
+ Errors fail the run. Warnings only fail it with `--strict`.
202
+
203
+ ## CLI reference
204
+
205
+ ```
206
+ toolproof inspect [--url URL | --config FILE | -- COMMAND...] [--json]
207
+ toolproof run [CONFIG] [--junit PATH] [--json PATH] [--timeout-ms N] [--retries N] [--strict] [--no-checks]
208
+ ```
209
+
210
+ Exit codes for `run`: `0` all passed, `1` a test or check failed, `2` bad config or usage.
211
+
212
+ ## pytest plugin
213
+
214
+ Installing toolproof registers a pytest plugin with an `mcp_server` fixture. Point it at your server in `pyproject.toml`:
215
+
216
+ ```toml
217
+ [tool.pytest.ini_options]
218
+ toolproof_command = "python my_server.py"
219
+ # toolproof_url = "http://localhost:8000/mcp"
220
+ # toolproof_config = "toolproof.yaml"
221
+ # toolproof_timeout = "30"
222
+ ```
223
+
224
+ Then write normal tests:
225
+
226
+ ```python
227
+ def test_weather(mcp_server):
228
+ r = mcp_server.call("get_weather", city="Toronto")
229
+ assert not r.is_error
230
+ assert r.data["temp_c"] > -50
231
+
232
+
233
+ def test_forecast_length(mcp_server):
234
+ r = mcp_server.call("get_forecast", {"city": "Calgary", "days": 5})
235
+ assert len(r.data["days"]) == 5
236
+
237
+
238
+ def test_schema(mcp_server):
239
+ assert mcp_server.tool("get_weather").input_schema["required"] == ["city"]
240
+ ```
241
+
242
+ `call()` returns a `CallResult` with `is_error`, `text`, `data` (structured content or parsed JSON), `latency_ms` and `transport_error`. The server starts once per test session.
243
+
244
+ To set the server up in code, override the `mcp_server_config` fixture in `conftest.py`:
245
+
246
+ ```python
247
+ import pytest
248
+ from pathlib import Path
249
+ from toolproof import ServerConfig
250
+
251
+
252
+ @pytest.fixture(scope="session")
253
+ def mcp_server_config():
254
+ return ServerConfig(command=["python", "my_server.py"], env={"MODE": "test"}), Path.cwd()
255
+ ```
256
+
257
+ ## Using it in GitHub Actions
258
+
259
+ ```yaml
260
+ - run: pip install mcp-toolproof
261
+ - run: toolproof run toolproof.yaml --junit report.xml
262
+ - uses: actions/upload-artifact@v5
263
+ if: always()
264
+ with:
265
+ name: toolproof-report
266
+ path: report.xml
267
+ ```
268
+
269
+ ## How is this different from MCP Inspector?
270
+
271
+ [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an interactive debugger: you open a UI, click a tool, type arguments and look at the result. It's great while building a server.
272
+
273
+ toolproof is for what comes after: tests you write once, keep in the repo and run on every pull request. It has no UI. It gives you assertions, an exit code and a JUnit report, so a broken tool fails the build instead of being found by a user.
274
+
275
+ ## Try it on the examples
276
+
277
+ ```bash
278
+ git clone https://github.com/NilkanthSuthar/toolproof
279
+ cd toolproof
280
+ pip install -e .
281
+ toolproof run examples/toolproof.yaml # passes
282
+ toolproof run examples/buggy.yaml # fails, on purpose
283
+ ```
284
+
285
+ ## Roadmap
286
+
287
+ - **v0.1**: inspect, static checks, YAML tests, console/JUnit/JSON reports, pytest plugin
288
+ - **v0.2**: `toolproof fuzz` (schema-based fuzzing with Hypothesis) and `toolproof bench` (p50/p95/p99 latency, throughput)
289
+ - **v0.3**: LLM tool-selection evals, a reusable GitHub Action, an HTML report, docs site
290
+
291
+ ## Development
292
+
293
+ ```bash
294
+ pip install -e ".[dev]"
295
+ pytest
296
+ ruff check . && ruff format --check .
297
+ mypy
298
+ ```
299
+
300
+ The test suite starts the example servers for real, over stdio and HTTP.
301
+
302
+ ## License
303
+
304
+ MIT