mcp-rig 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.
- mcp_rig-0.1.0/LICENSE +21 -0
- mcp_rig-0.1.0/PKG-INFO +276 -0
- mcp_rig-0.1.0/README.md +249 -0
- mcp_rig-0.1.0/pyproject.toml +66 -0
- mcp_rig-0.1.0/src/mcp_rig/__init__.py +3 -0
- mcp_rig-0.1.0/src/mcp_rig/assertions.py +88 -0
- mcp_rig-0.1.0/src/mcp_rig/batch.py +227 -0
- mcp_rig-0.1.0/src/mcp_rig/checks.py +97 -0
- mcp_rig-0.1.0/src/mcp_rig/cli.py +225 -0
- mcp_rig-0.1.0/src/mcp_rig/client.py +131 -0
- mcp_rig-0.1.0/src/mcp_rig/discovery.py +86 -0
- mcp_rig-0.1.0/src/mcp_rig/junit.py +156 -0
- mcp_rig-0.1.0/src/mcp_rig/lint.py +88 -0
- mcp_rig-0.1.0/src/mcp_rig/report.py +153 -0
- mcp_rig-0.1.0/src/mcp_rig/runner.py +155 -0
- mcp_rig-0.1.0/src/mcp_rig/selection.py +58 -0
- mcp_rig-0.1.0/src/mcp_rig/snapshots.py +307 -0
- mcp_rig-0.1.0/src/mcp_rig/spec.py +260 -0
mcp_rig-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MCP Rig contributors
|
|
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.
|
mcp_rig-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mcp-rig
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deterministic, CI-friendly testing for MCP servers
|
|
5
|
+
Project-URL: Repository, https://github.com/gorkemgul/mcp-rig
|
|
6
|
+
Project-URL: Issues, https://github.com/gorkemgul/mcp-rig/issues
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: cli,developer-tools,mcp,testing
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: jsonschema>=4
|
|
20
|
+
Requires-Dist: mcp<3,>=2.2
|
|
21
|
+
Requires-Dist: pyyaml>=6
|
|
22
|
+
Requires-Dist: referencing>=0.28.4
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# MCP Rig
|
|
29
|
+
|
|
30
|
+
Deterministic, CI-friendly testing for Model Context Protocol servers.
|
|
31
|
+
|
|
32
|
+
MCP Rig currently launches local MCP servers over stdio and runs declarative
|
|
33
|
+
tool suites in YAML.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
Install MCP Rig as an isolated command-line tool with
|
|
38
|
+
[pipx](https://pipx.pypa.io/):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pipx install mcp-rig
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Or install it into the active Python environment with pip:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install mcp-rig
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Development setup
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
python3 -m venv .venv
|
|
54
|
+
.venv/bin/pip install -e ".[dev]"
|
|
55
|
+
source .venv/bin/activate
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Run a suite
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
mcp-rig run examples/fixture.yaml
|
|
62
|
+
mcp-rig run examples/fixture.yaml --junit results.xml
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Run several suites by passing more files or a directory. Directories are
|
|
66
|
+
searched recursively for `.yaml` and `.yml` files:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
mcp-rig run tests/mcp/smoke.yaml tests/mcp/regression.yaml
|
|
70
|
+
mcp-rig run tests/mcp/ --junit results.xml
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
MCP Rig resolves and deduplicates suite paths, then runs them sequentially in
|
|
74
|
+
deterministic path order. A configuration or infrastructure error in one suite
|
|
75
|
+
does not prevent later suites from running. Batch runs print per-suite results
|
|
76
|
+
followed by aggregate suite and case counts. A JUnit file contains one
|
|
77
|
+
`<testsuite>` for every suite or invalid target beneath a shared `<testsuites>`
|
|
78
|
+
root.
|
|
79
|
+
|
|
80
|
+
### Filter cases and tags
|
|
81
|
+
|
|
82
|
+
Suites and individual cases can declare lowercase tags. Suite tags are
|
|
83
|
+
inherited by every case, and case tags are added to that inherited set:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
server: npx @playwright/mcp@latest
|
|
87
|
+
tags: [playwright]
|
|
88
|
+
|
|
89
|
+
tests:
|
|
90
|
+
- name: opens homepage
|
|
91
|
+
tags: [smoke, browser]
|
|
92
|
+
call: browser_navigate
|
|
93
|
+
args:
|
|
94
|
+
url: https://example.com
|
|
95
|
+
|
|
96
|
+
- name: captures screenshot
|
|
97
|
+
tags: [slow]
|
|
98
|
+
call: browser_take_screenshot
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Select cases with case-sensitive shell-style name patterns or effective tags:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
mcp-rig run suites/ --case "opens*"
|
|
105
|
+
mcp-rig run suites/ --tag playwright --tag smoke
|
|
106
|
+
mcp-rig run suites/ --exclude-tag slow
|
|
107
|
+
mcp-rig run suites/ --case "opens*" --tag smoke --exclude-tag flaky
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Repeated `--case` patterns use OR semantics. Repeated `--tag` options use AND
|
|
111
|
+
semantics, so the case must carry every requested tag. A case is filtered out
|
|
112
|
+
when it carries any repeated `--exclude-tag` value. Name, included-tag, and
|
|
113
|
+
excluded-tag filters combine with AND semantics.
|
|
114
|
+
|
|
115
|
+
Filtered runs report selection separately from execution, for example
|
|
116
|
+
`3 selected, 7 filtered out`. Filtered-out cases are not counted as skipped
|
|
117
|
+
and are absent from JUnit; skipped remains reserved for selected cases that
|
|
118
|
+
could not run after an infrastructure error. If no cases match, MCP Rig does
|
|
119
|
+
not start a server, writes an empty report when `--junit` is requested, and
|
|
120
|
+
exits with code `2`.
|
|
121
|
+
|
|
122
|
+
### Snapshot complete tool responses
|
|
123
|
+
|
|
124
|
+
Use `snapshot: true` to compare the complete normalized tool result while still
|
|
125
|
+
combining it with focused expectations:
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
server: npx @playwright/mcp@latest
|
|
129
|
+
|
|
130
|
+
tests:
|
|
131
|
+
- name: opens homepage
|
|
132
|
+
call: browser_navigate
|
|
133
|
+
args:
|
|
134
|
+
url: https://example.com
|
|
135
|
+
expect:
|
|
136
|
+
snapshot: true
|
|
137
|
+
contains: Example Domain
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For a suite named `browser.yaml` or `browser.yml`, MCP Rig stores snapshots in
|
|
141
|
+
`browser.snap.yaml` beside the suite. Structured content is stored as stable,
|
|
142
|
+
readable YAML; otherwise the complete text response is stored. Error state is
|
|
143
|
+
included, while latency is deliberately excluded.
|
|
144
|
+
|
|
145
|
+
An ordinary run never writes files. The first run therefore fails with a
|
|
146
|
+
missing-snapshot assertion. Create or intentionally refresh snapshots with:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
mcp-rig run suites/ --update-snapshots
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Review the generated `.snap.yaml` Git diff, then commit it with the suite. Later
|
|
153
|
+
ordinary runs fail when the response changes and include a unified diff in the
|
|
154
|
+
terminal and JUnit report. Snapshot differences return exit code `1`; malformed
|
|
155
|
+
or unwritable snapshot files return exit code `2`.
|
|
156
|
+
|
|
157
|
+
Snapshot updates compose with `--case`, `--tag`, and `--exclude-tag`. A filtered
|
|
158
|
+
update changes only selected cases and preserves every unselected entry. A
|
|
159
|
+
complete, unfiltered successful update also removes stale entries for cases
|
|
160
|
+
that no longer declare snapshots.
|
|
161
|
+
|
|
162
|
+
See the [real-world server examples](https://github.com/gorkemgul/mcp-rig/tree/main/examples) for
|
|
163
|
+
pinned suites that exercise Playwright MCP, the MCP Everything reference server, and the Time
|
|
164
|
+
MCP server. External examples are kept out of the main CI path and run in a separate manual and
|
|
165
|
+
weekly smoke workflow.
|
|
166
|
+
|
|
167
|
+
The [complete feature tour](https://github.com/gorkemgul/mcp-rig/tree/main/examples/feature-tour)
|
|
168
|
+
provides runnable local examples for every expectation, snapshots, tags and filters, server
|
|
169
|
+
configuration, batch runs, JUnit, diagnostics, and `check`. Start with the
|
|
170
|
+
[custom-server template](https://github.com/gorkemgul/mcp-rig/tree/main/examples/custom-server-template)
|
|
171
|
+
when testing your own stdio server, and use the
|
|
172
|
+
[GitHub Actions example](https://github.com/gorkemgul/mcp-rig/tree/main/examples/ci) for CI.
|
|
173
|
+
|
|
174
|
+
A suite names the stdio server command and the tool calls to verify:
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
server:
|
|
178
|
+
command: python
|
|
179
|
+
args: [path/to/server.py]
|
|
180
|
+
|
|
181
|
+
tests:
|
|
182
|
+
- name: adds two numbers
|
|
183
|
+
call: add
|
|
184
|
+
args: {a: 2, b: 3}
|
|
185
|
+
expect:
|
|
186
|
+
contains: "5"
|
|
187
|
+
|
|
188
|
+
- name: missing user returns an error
|
|
189
|
+
call: get_user
|
|
190
|
+
args: {user_id: 42}
|
|
191
|
+
expect:
|
|
192
|
+
is_error: true
|
|
193
|
+
contains: "not found"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Each case expects a successful tool call unless it declares
|
|
197
|
+
`is_error: true`. Supported expectations are:
|
|
198
|
+
|
|
199
|
+
- `contains` / `not_contains`: require or forbid one string or a list of strings.
|
|
200
|
+
- `matches`: search response text with a Python regular expression.
|
|
201
|
+
- `max_latency_ms`: enforce an inclusive response-time limit.
|
|
202
|
+
- `json_path`: compare exact values through dotted mapping keys and numeric list indexes.
|
|
203
|
+
- `schema`: validate structured results with JSON Schema Draft 2020-12;
|
|
204
|
+
document-local `#...` references are supported, while external references
|
|
205
|
+
are rejected to keep evaluation offline.
|
|
206
|
+
- `snapshot`: compare the complete normalized result with the suite's committed
|
|
207
|
+
`.snap.yaml` sidecar; the value must be `true`.
|
|
208
|
+
|
|
209
|
+
`is_error: true` accepts both an MCP tool result marked as an error and a
|
|
210
|
+
JSON-RPC protocol error returned for that tool call. Timeouts and closed
|
|
211
|
+
connections remain infrastructure errors.
|
|
212
|
+
|
|
213
|
+
JSON checks prefer MCP structured content and otherwise parse response text as
|
|
214
|
+
JSON. Each case may set a positive, finite `timeout_s`; the default is 30
|
|
215
|
+
seconds. A timeout is an infrastructure error, aborts further calls on the
|
|
216
|
+
shared session, and marks later cases as skipped.
|
|
217
|
+
|
|
218
|
+
Terminal and JUnit reports distinguish four states:
|
|
219
|
+
|
|
220
|
+
- passed: the call completed and every expectation matched;
|
|
221
|
+
- failed: the call completed but one or more expectations did not match;
|
|
222
|
+
- error: MCP Rig could not execute the call or suite reliably; and
|
|
223
|
+
- skipped: the case was not started after an infrastructure error.
|
|
224
|
+
|
|
225
|
+
`--junit PATH` writes CI-readable XML without creating missing parent
|
|
226
|
+
directories. Invalid targets and suite configuration are represented as
|
|
227
|
+
synthetic JUnit errors. Exit codes are `0` for success, `1` for assertion
|
|
228
|
+
failures only, and `2` for any configuration, infrastructure, or report-writing
|
|
229
|
+
error; code `2` takes precedence when a batch contains both kinds of failure.
|
|
230
|
+
Add `--server-logs` to expose every suite server's stderr while diagnosing
|
|
231
|
+
startup or tool behavior.
|
|
232
|
+
|
|
233
|
+
## Check a server without a suite
|
|
234
|
+
|
|
235
|
+
Run protocol checks and inspect tool-definition quality without writing YAML:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
mcp-rig check "python path/to/server.py"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The command verifies that the server lists tools, returns an MCP tool error for
|
|
242
|
+
an unknown tool, and remains responsive after the negative call. It also warns
|
|
243
|
+
about missing or short descriptions, invalid input schemas, undocumented
|
|
244
|
+
parameters, and tool descriptions that are likely to be confused with each
|
|
245
|
+
other.
|
|
246
|
+
|
|
247
|
+
Lint warnings are advisory by default. Use `--strict` to make them fail CI:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
mcp-rig check "python path/to/server.py" --strict
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`--probe-invalid-args` calls every tool that declares required parameters with
|
|
254
|
+
an empty argument object and checks that the call is rejected. Use this option
|
|
255
|
+
only with development or test servers: a server that does not enforce its
|
|
256
|
+
declared schema could execute the tool body.
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
mcp-rig check "python path/to/server.py" --probe-invalid-args
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Server stderr is hidden by default. Add `--server-logs` while diagnosing the
|
|
263
|
+
server:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
mcp-rig check "python path/to/server.py" --server-logs
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
For `check`, exit code `0` means all protocol checks passed, `1` means a
|
|
270
|
+
protocol check failed or strict lint found warnings, and `2` means the command,
|
|
271
|
+
server process, connection, or teardown failed.
|
|
272
|
+
|
|
273
|
+
This release supports local stdio servers and tools only.
|
|
274
|
+
|
|
275
|
+
Repository CI tests Python 3.11 through 3.13 and validates both wheel and
|
|
276
|
+
source distributions without publishing them.
|
mcp_rig-0.1.0/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# MCP Rig
|
|
2
|
+
|
|
3
|
+
Deterministic, CI-friendly testing for Model Context Protocol servers.
|
|
4
|
+
|
|
5
|
+
MCP Rig currently launches local MCP servers over stdio and runs declarative
|
|
6
|
+
tool suites in YAML.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Install MCP Rig as an isolated command-line tool with
|
|
11
|
+
[pipx](https://pipx.pypa.io/):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pipx install mcp-rig
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or install it into the active Python environment with pip:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install mcp-rig
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Development setup
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python3 -m venv .venv
|
|
27
|
+
.venv/bin/pip install -e ".[dev]"
|
|
28
|
+
source .venv/bin/activate
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Run a suite
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
mcp-rig run examples/fixture.yaml
|
|
35
|
+
mcp-rig run examples/fixture.yaml --junit results.xml
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run several suites by passing more files or a directory. Directories are
|
|
39
|
+
searched recursively for `.yaml` and `.yml` files:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
mcp-rig run tests/mcp/smoke.yaml tests/mcp/regression.yaml
|
|
43
|
+
mcp-rig run tests/mcp/ --junit results.xml
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
MCP Rig resolves and deduplicates suite paths, then runs them sequentially in
|
|
47
|
+
deterministic path order. A configuration or infrastructure error in one suite
|
|
48
|
+
does not prevent later suites from running. Batch runs print per-suite results
|
|
49
|
+
followed by aggregate suite and case counts. A JUnit file contains one
|
|
50
|
+
`<testsuite>` for every suite or invalid target beneath a shared `<testsuites>`
|
|
51
|
+
root.
|
|
52
|
+
|
|
53
|
+
### Filter cases and tags
|
|
54
|
+
|
|
55
|
+
Suites and individual cases can declare lowercase tags. Suite tags are
|
|
56
|
+
inherited by every case, and case tags are added to that inherited set:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
server: npx @playwright/mcp@latest
|
|
60
|
+
tags: [playwright]
|
|
61
|
+
|
|
62
|
+
tests:
|
|
63
|
+
- name: opens homepage
|
|
64
|
+
tags: [smoke, browser]
|
|
65
|
+
call: browser_navigate
|
|
66
|
+
args:
|
|
67
|
+
url: https://example.com
|
|
68
|
+
|
|
69
|
+
- name: captures screenshot
|
|
70
|
+
tags: [slow]
|
|
71
|
+
call: browser_take_screenshot
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Select cases with case-sensitive shell-style name patterns or effective tags:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
mcp-rig run suites/ --case "opens*"
|
|
78
|
+
mcp-rig run suites/ --tag playwright --tag smoke
|
|
79
|
+
mcp-rig run suites/ --exclude-tag slow
|
|
80
|
+
mcp-rig run suites/ --case "opens*" --tag smoke --exclude-tag flaky
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Repeated `--case` patterns use OR semantics. Repeated `--tag` options use AND
|
|
84
|
+
semantics, so the case must carry every requested tag. A case is filtered out
|
|
85
|
+
when it carries any repeated `--exclude-tag` value. Name, included-tag, and
|
|
86
|
+
excluded-tag filters combine with AND semantics.
|
|
87
|
+
|
|
88
|
+
Filtered runs report selection separately from execution, for example
|
|
89
|
+
`3 selected, 7 filtered out`. Filtered-out cases are not counted as skipped
|
|
90
|
+
and are absent from JUnit; skipped remains reserved for selected cases that
|
|
91
|
+
could not run after an infrastructure error. If no cases match, MCP Rig does
|
|
92
|
+
not start a server, writes an empty report when `--junit` is requested, and
|
|
93
|
+
exits with code `2`.
|
|
94
|
+
|
|
95
|
+
### Snapshot complete tool responses
|
|
96
|
+
|
|
97
|
+
Use `snapshot: true` to compare the complete normalized tool result while still
|
|
98
|
+
combining it with focused expectations:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
server: npx @playwright/mcp@latest
|
|
102
|
+
|
|
103
|
+
tests:
|
|
104
|
+
- name: opens homepage
|
|
105
|
+
call: browser_navigate
|
|
106
|
+
args:
|
|
107
|
+
url: https://example.com
|
|
108
|
+
expect:
|
|
109
|
+
snapshot: true
|
|
110
|
+
contains: Example Domain
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
For a suite named `browser.yaml` or `browser.yml`, MCP Rig stores snapshots in
|
|
114
|
+
`browser.snap.yaml` beside the suite. Structured content is stored as stable,
|
|
115
|
+
readable YAML; otherwise the complete text response is stored. Error state is
|
|
116
|
+
included, while latency is deliberately excluded.
|
|
117
|
+
|
|
118
|
+
An ordinary run never writes files. The first run therefore fails with a
|
|
119
|
+
missing-snapshot assertion. Create or intentionally refresh snapshots with:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
mcp-rig run suites/ --update-snapshots
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Review the generated `.snap.yaml` Git diff, then commit it with the suite. Later
|
|
126
|
+
ordinary runs fail when the response changes and include a unified diff in the
|
|
127
|
+
terminal and JUnit report. Snapshot differences return exit code `1`; malformed
|
|
128
|
+
or unwritable snapshot files return exit code `2`.
|
|
129
|
+
|
|
130
|
+
Snapshot updates compose with `--case`, `--tag`, and `--exclude-tag`. A filtered
|
|
131
|
+
update changes only selected cases and preserves every unselected entry. A
|
|
132
|
+
complete, unfiltered successful update also removes stale entries for cases
|
|
133
|
+
that no longer declare snapshots.
|
|
134
|
+
|
|
135
|
+
See the [real-world server examples](https://github.com/gorkemgul/mcp-rig/tree/main/examples) for
|
|
136
|
+
pinned suites that exercise Playwright MCP, the MCP Everything reference server, and the Time
|
|
137
|
+
MCP server. External examples are kept out of the main CI path and run in a separate manual and
|
|
138
|
+
weekly smoke workflow.
|
|
139
|
+
|
|
140
|
+
The [complete feature tour](https://github.com/gorkemgul/mcp-rig/tree/main/examples/feature-tour)
|
|
141
|
+
provides runnable local examples for every expectation, snapshots, tags and filters, server
|
|
142
|
+
configuration, batch runs, JUnit, diagnostics, and `check`. Start with the
|
|
143
|
+
[custom-server template](https://github.com/gorkemgul/mcp-rig/tree/main/examples/custom-server-template)
|
|
144
|
+
when testing your own stdio server, and use the
|
|
145
|
+
[GitHub Actions example](https://github.com/gorkemgul/mcp-rig/tree/main/examples/ci) for CI.
|
|
146
|
+
|
|
147
|
+
A suite names the stdio server command and the tool calls to verify:
|
|
148
|
+
|
|
149
|
+
```yaml
|
|
150
|
+
server:
|
|
151
|
+
command: python
|
|
152
|
+
args: [path/to/server.py]
|
|
153
|
+
|
|
154
|
+
tests:
|
|
155
|
+
- name: adds two numbers
|
|
156
|
+
call: add
|
|
157
|
+
args: {a: 2, b: 3}
|
|
158
|
+
expect:
|
|
159
|
+
contains: "5"
|
|
160
|
+
|
|
161
|
+
- name: missing user returns an error
|
|
162
|
+
call: get_user
|
|
163
|
+
args: {user_id: 42}
|
|
164
|
+
expect:
|
|
165
|
+
is_error: true
|
|
166
|
+
contains: "not found"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Each case expects a successful tool call unless it declares
|
|
170
|
+
`is_error: true`. Supported expectations are:
|
|
171
|
+
|
|
172
|
+
- `contains` / `not_contains`: require or forbid one string or a list of strings.
|
|
173
|
+
- `matches`: search response text with a Python regular expression.
|
|
174
|
+
- `max_latency_ms`: enforce an inclusive response-time limit.
|
|
175
|
+
- `json_path`: compare exact values through dotted mapping keys and numeric list indexes.
|
|
176
|
+
- `schema`: validate structured results with JSON Schema Draft 2020-12;
|
|
177
|
+
document-local `#...` references are supported, while external references
|
|
178
|
+
are rejected to keep evaluation offline.
|
|
179
|
+
- `snapshot`: compare the complete normalized result with the suite's committed
|
|
180
|
+
`.snap.yaml` sidecar; the value must be `true`.
|
|
181
|
+
|
|
182
|
+
`is_error: true` accepts both an MCP tool result marked as an error and a
|
|
183
|
+
JSON-RPC protocol error returned for that tool call. Timeouts and closed
|
|
184
|
+
connections remain infrastructure errors.
|
|
185
|
+
|
|
186
|
+
JSON checks prefer MCP structured content and otherwise parse response text as
|
|
187
|
+
JSON. Each case may set a positive, finite `timeout_s`; the default is 30
|
|
188
|
+
seconds. A timeout is an infrastructure error, aborts further calls on the
|
|
189
|
+
shared session, and marks later cases as skipped.
|
|
190
|
+
|
|
191
|
+
Terminal and JUnit reports distinguish four states:
|
|
192
|
+
|
|
193
|
+
- passed: the call completed and every expectation matched;
|
|
194
|
+
- failed: the call completed but one or more expectations did not match;
|
|
195
|
+
- error: MCP Rig could not execute the call or suite reliably; and
|
|
196
|
+
- skipped: the case was not started after an infrastructure error.
|
|
197
|
+
|
|
198
|
+
`--junit PATH` writes CI-readable XML without creating missing parent
|
|
199
|
+
directories. Invalid targets and suite configuration are represented as
|
|
200
|
+
synthetic JUnit errors. Exit codes are `0` for success, `1` for assertion
|
|
201
|
+
failures only, and `2` for any configuration, infrastructure, or report-writing
|
|
202
|
+
error; code `2` takes precedence when a batch contains both kinds of failure.
|
|
203
|
+
Add `--server-logs` to expose every suite server's stderr while diagnosing
|
|
204
|
+
startup or tool behavior.
|
|
205
|
+
|
|
206
|
+
## Check a server without a suite
|
|
207
|
+
|
|
208
|
+
Run protocol checks and inspect tool-definition quality without writing YAML:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
mcp-rig check "python path/to/server.py"
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The command verifies that the server lists tools, returns an MCP tool error for
|
|
215
|
+
an unknown tool, and remains responsive after the negative call. It also warns
|
|
216
|
+
about missing or short descriptions, invalid input schemas, undocumented
|
|
217
|
+
parameters, and tool descriptions that are likely to be confused with each
|
|
218
|
+
other.
|
|
219
|
+
|
|
220
|
+
Lint warnings are advisory by default. Use `--strict` to make them fail CI:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
mcp-rig check "python path/to/server.py" --strict
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`--probe-invalid-args` calls every tool that declares required parameters with
|
|
227
|
+
an empty argument object and checks that the call is rejected. Use this option
|
|
228
|
+
only with development or test servers: a server that does not enforce its
|
|
229
|
+
declared schema could execute the tool body.
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
mcp-rig check "python path/to/server.py" --probe-invalid-args
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Server stderr is hidden by default. Add `--server-logs` while diagnosing the
|
|
236
|
+
server:
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
mcp-rig check "python path/to/server.py" --server-logs
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
For `check`, exit code `0` means all protocol checks passed, `1` means a
|
|
243
|
+
protocol check failed or strict lint found warnings, and `2` means the command,
|
|
244
|
+
server process, connection, or teardown failed.
|
|
245
|
+
|
|
246
|
+
This release supports local stdio servers and tools only.
|
|
247
|
+
|
|
248
|
+
Repository CI tests Python 3.11 through 3.13 and validates both wheel and
|
|
249
|
+
source distributions without publishing them.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcp-rig"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Deterministic, CI-friendly testing for MCP servers"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
keywords = ["mcp", "testing", "cli", "developer-tools"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"mcp>=2.2,<3",
|
|
26
|
+
"pyyaml>=6",
|
|
27
|
+
"jsonschema>=4",
|
|
28
|
+
"referencing>=0.28.4",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Repository = "https://github.com/gorkemgul/mcp-rig"
|
|
33
|
+
Issues = "https://github.com/gorkemgul/mcp-rig/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
mcp-rig = "mcp_rig.cli:main"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = [
|
|
40
|
+
"pytest>=8",
|
|
41
|
+
"ruff>=0.6",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/mcp_rig"]
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.sdist]
|
|
48
|
+
only-include = [
|
|
49
|
+
"src/mcp_rig",
|
|
50
|
+
"pyproject.toml",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.sdist.hooks.custom]
|
|
56
|
+
path = "scripts/minimal_sdist.py"
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
line-length = 120
|
|
63
|
+
src = ["src", "tests"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff.lint]
|
|
66
|
+
select = ["E", "F", "I", "B", "UP"]
|