miqa-mcp 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.
- miqa_mcp-0.1.0/.gitignore +9 -0
- miqa_mcp-0.1.0/PKG-INFO +196 -0
- miqa_mcp-0.1.0/README.md +182 -0
- miqa_mcp-0.1.0/pyproject.toml +28 -0
- miqa_mcp-0.1.0/src/miqa_mcp/__init__.py +11 -0
- miqa_mcp-0.1.0/src/miqa_mcp/server.py +92 -0
- miqa_mcp-0.1.0/tests/test_server.py +68 -0
miqa_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: miqa-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MIQA MCP Server
|
|
5
|
+
Author: MIQA / Magna Labs
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: claude,fastmcp,mcp,miqa
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: fastmcp>=2.0
|
|
10
|
+
Requires-Dist: miqatools>=2.0.0rc0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# MIQA MCP
|
|
16
|
+
|
|
17
|
+
A read-only MCP server built with FastMCP. It exposes MIQA test-reporting data to
|
|
18
|
+
Claude Code (and any MCP client) over stdio. It is a thin MCP surface on top of the
|
|
19
|
+
[`miqatools`](../magna-cli) library: the tools call `miqatools`' read-only reporting
|
|
20
|
+
wrappers, which resolve credentials and talk to the MIQA v2 REST API.
|
|
21
|
+
|
|
22
|
+
The packaging shape — a console script that runs a stdio `main()`, configured via
|
|
23
|
+
environment variables — is what makes it registerable with `claude mcp add`.
|
|
24
|
+
|
|
25
|
+
Tools exposed (all READ-ONLY; none mutate MIQA state):
|
|
26
|
+
|
|
27
|
+
- `find_runs_by_version(version_name: str, limit: int = 100) -> list` — find Test
|
|
28
|
+
Chain Runs for a version name (newest-first; empty list if none match).
|
|
29
|
+
- `get_test_run(run_id: int, sample_ds_id: int | None = None) -> dict` — JSON details
|
|
30
|
+
for a single Test Chain Run (optionally narrowed to one sample).
|
|
31
|
+
- `get_test_run_results(run_id: int, sample_ds_id: int | None = None)` — the
|
|
32
|
+
assertion/result summary table (JSON or CSV depending on the server).
|
|
33
|
+
- `get_test_run_report(run_id: int, sample_ds_id: int | None = None, format="json")` —
|
|
34
|
+
the sample-centric structured report (feature-flag gated on the server).
|
|
35
|
+
|
|
36
|
+
Built on the standalone `fastmcp` package (jlowin). Requires Python >= 3.10.
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
The server reads its MIQA credentials from environment variables (resolved by the
|
|
41
|
+
`miqatools` client):
|
|
42
|
+
|
|
43
|
+
- `MIQA_SERVER_URL` — MIQA server hostname (e.g. `yourco.miqa.io`).
|
|
44
|
+
- `MIQA_API_KEY` — MIQA API key.
|
|
45
|
+
|
|
46
|
+
Claude Code injects these into the server subprocess via the `--env` block (see
|
|
47
|
+
"Register with Claude Code" below).
|
|
48
|
+
|
|
49
|
+
## Prerequisite: install miqatools first (not yet on PyPI)
|
|
50
|
+
|
|
51
|
+
This server depends on `miqatools>=2.0.0rc0`, which is **not yet published to PyPI**.
|
|
52
|
+
Until it is, install `miqatools` from its local checkout first (editable), then install
|
|
53
|
+
this server:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
pip install -e ../magna-cli # the miqatools library
|
|
57
|
+
pip install -e . # this server
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Once `miqatools` is published, `pip install .` (or `uvx miqa-mcp`) will resolve it from
|
|
61
|
+
PyPI automatically and this extra step goes away.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 1. Install options
|
|
66
|
+
|
|
67
|
+
Because `miqatools` is not yet on PyPI (see "Prerequisite" above), install it locally
|
|
68
|
+
first, then install this server. A plain virtualenv with editable installs is best while
|
|
69
|
+
`miqatools` is unpublished:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
python -m venv .venv
|
|
73
|
+
.venv/bin/pip install -e ../magna-cli # miqatools (the library dependency)
|
|
74
|
+
.venv/bin/pip install -e . # this server
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The console script then lives at `.venv/bin/miqa-mcp`. It starts and waits silently on
|
|
78
|
+
stdin — that is correct: it is a stdio MCP server waiting for a client to speak the
|
|
79
|
+
protocol. Press Ctrl-C to stop; you normally let Claude Code launch it rather than running
|
|
80
|
+
it by hand.
|
|
81
|
+
|
|
82
|
+
### Eventual published form
|
|
83
|
+
|
|
84
|
+
Once both `miqatools` and this server are published to PyPI, the local-editable dance
|
|
85
|
+
collapses to a normal install (dependencies resolve automatically):
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
uvx miqa-mcp # ephemeral
|
|
89
|
+
pipx install miqa-mcp
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 2. Register with Claude Code
|
|
95
|
+
|
|
96
|
+
`claude mcp add <name> [flags] -- <command> [args...]` registers a stdio server. Claude
|
|
97
|
+
Code launches `<command>` as a subprocess and speaks MCP over its stdin/stdout. The `--`
|
|
98
|
+
separates Claude's own flags from the subprocess command line.
|
|
99
|
+
|
|
100
|
+
Since `miqatools` is unpublished, install into a venv (see above) so `miqa-mcp` is on the
|
|
101
|
+
venv's PATH, then register that console script — passing MIQA credentials via `--env`:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
claude mcp add miqa-mcp \
|
|
105
|
+
--env MIQA_SERVER_URL=yourco.miqa.io \
|
|
106
|
+
--env MIQA_API_KEY=your-key-here \
|
|
107
|
+
-- /absolute/path/to/.venv/bin/miqa-mcp
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
If `miqa-mcp` is already on your PATH (activated venv or pipx install), the command is just
|
|
111
|
+
the script name:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
claude mcp add miqa-mcp --env MIQA_SERVER_URL=yourco.miqa.io --env MIQA_API_KEY=your-key-here -- miqa-mcp
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Passing configuration via env
|
|
118
|
+
|
|
119
|
+
A stdio server cannot receive HTTP headers, so its configuration is passed as environment
|
|
120
|
+
variables, which Claude Code injects into the subprocess. Use `--env KEY=value` (short form
|
|
121
|
+
`-e KEY=value`), and put it before the `--` so it is read as one of Claude's own flags.
|
|
122
|
+
|
|
123
|
+
This server reads `MIQA_SERVER_URL` and `MIQA_API_KEY` from its environment (the
|
|
124
|
+
`miqatools` client resolves them), so both must be passed via `--env`:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
claude mcp add miqa-mcp --env MIQA_SERVER_URL=yourco.miqa.io --env MIQA_API_KEY=your-key-here -- /absolute/path/to/.venv/bin/miqa-mcp
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Repeat `--env` for each variable. This env-block mechanism is the stdio equivalent of the
|
|
131
|
+
`--header` flag that HTTP-transport servers use; stdio servers get their config through env,
|
|
132
|
+
not headers.
|
|
133
|
+
|
|
134
|
+
By default the server is registered at `local` scope (this project only). Add
|
|
135
|
+
`--scope user` to make it available across all your projects.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 3. Verify
|
|
140
|
+
|
|
141
|
+
List registered servers — `miqa-mcp` should appear, and Claude Code will have started it
|
|
142
|
+
and confirmed the connection:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
claude mcp list
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
You can also inspect just this one:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
claude mcp get miqa-mcp
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Inside a Claude Code session, the tools surface as `find_runs_by_version`, `get_test_run`,
|
|
155
|
+
`get_test_run_results`, and `get_test_run_report`. With valid `MIQA_SERVER_URL` /
|
|
156
|
+
`MIQA_API_KEY` in the env, ask the session to find runs for a version name and it will call
|
|
157
|
+
the MIQA API through the server.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 4. Remove
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
claude mcp remove miqa-mcp
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
If you installed with pipx and want to uninstall the package too:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
pipx uninstall miqa-mcp
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 5. Run the tests
|
|
176
|
+
|
|
177
|
+
The test suite asserts the four tools register on the FastMCP instance and that each
|
|
178
|
+
delegates to the corresponding `miqatools` wrapper (the wrappers are mocked, so no network
|
|
179
|
+
is touched). Install `miqatools` first, then this package with its dev extra:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
.venv/bin/pip install -e ../magna-cli
|
|
183
|
+
.venv/bin/pip install -e '.[dev]'
|
|
184
|
+
.venv/bin/pytest
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 6. How this fits together
|
|
190
|
+
|
|
191
|
+
This server is a thin, read-only MCP surface; all MIQA client logic lives in the
|
|
192
|
+
`miqatools` library. The reporting wrappers (`find_tcrs_by_version`, `get_tcr_summary`,
|
|
193
|
+
`get_tcr_report`) and the existing `get_tcr_info_json` helper stay in `miqatools` as client
|
|
194
|
+
code; this repo only adds the FastMCP tools and the stdio entry point. The packaging shape —
|
|
195
|
+
console script -> stdio `main()` -> env-based config (`MIQA_SERVER_URL`, `MIQA_API_KEY`) — is
|
|
196
|
+
what `claude mcp add` launches.
|
miqa_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# MIQA MCP
|
|
2
|
+
|
|
3
|
+
A read-only MCP server built with FastMCP. It exposes MIQA test-reporting data to
|
|
4
|
+
Claude Code (and any MCP client) over stdio. It is a thin MCP surface on top of the
|
|
5
|
+
[`miqatools`](../magna-cli) library: the tools call `miqatools`' read-only reporting
|
|
6
|
+
wrappers, which resolve credentials and talk to the MIQA v2 REST API.
|
|
7
|
+
|
|
8
|
+
The packaging shape — a console script that runs a stdio `main()`, configured via
|
|
9
|
+
environment variables — is what makes it registerable with `claude mcp add`.
|
|
10
|
+
|
|
11
|
+
Tools exposed (all READ-ONLY; none mutate MIQA state):
|
|
12
|
+
|
|
13
|
+
- `find_runs_by_version(version_name: str, limit: int = 100) -> list` — find Test
|
|
14
|
+
Chain Runs for a version name (newest-first; empty list if none match).
|
|
15
|
+
- `get_test_run(run_id: int, sample_ds_id: int | None = None) -> dict` — JSON details
|
|
16
|
+
for a single Test Chain Run (optionally narrowed to one sample).
|
|
17
|
+
- `get_test_run_results(run_id: int, sample_ds_id: int | None = None)` — the
|
|
18
|
+
assertion/result summary table (JSON or CSV depending on the server).
|
|
19
|
+
- `get_test_run_report(run_id: int, sample_ds_id: int | None = None, format="json")` —
|
|
20
|
+
the sample-centric structured report (feature-flag gated on the server).
|
|
21
|
+
|
|
22
|
+
Built on the standalone `fastmcp` package (jlowin). Requires Python >= 3.10.
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
The server reads its MIQA credentials from environment variables (resolved by the
|
|
27
|
+
`miqatools` client):
|
|
28
|
+
|
|
29
|
+
- `MIQA_SERVER_URL` — MIQA server hostname (e.g. `yourco.miqa.io`).
|
|
30
|
+
- `MIQA_API_KEY` — MIQA API key.
|
|
31
|
+
|
|
32
|
+
Claude Code injects these into the server subprocess via the `--env` block (see
|
|
33
|
+
"Register with Claude Code" below).
|
|
34
|
+
|
|
35
|
+
## Prerequisite: install miqatools first (not yet on PyPI)
|
|
36
|
+
|
|
37
|
+
This server depends on `miqatools>=2.0.0rc0`, which is **not yet published to PyPI**.
|
|
38
|
+
Until it is, install `miqatools` from its local checkout first (editable), then install
|
|
39
|
+
this server:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
pip install -e ../magna-cli # the miqatools library
|
|
43
|
+
pip install -e . # this server
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Once `miqatools` is published, `pip install .` (or `uvx miqa-mcp`) will resolve it from
|
|
47
|
+
PyPI automatically and this extra step goes away.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 1. Install options
|
|
52
|
+
|
|
53
|
+
Because `miqatools` is not yet on PyPI (see "Prerequisite" above), install it locally
|
|
54
|
+
first, then install this server. A plain virtualenv with editable installs is best while
|
|
55
|
+
`miqatools` is unpublished:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
python -m venv .venv
|
|
59
|
+
.venv/bin/pip install -e ../magna-cli # miqatools (the library dependency)
|
|
60
|
+
.venv/bin/pip install -e . # this server
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The console script then lives at `.venv/bin/miqa-mcp`. It starts and waits silently on
|
|
64
|
+
stdin — that is correct: it is a stdio MCP server waiting for a client to speak the
|
|
65
|
+
protocol. Press Ctrl-C to stop; you normally let Claude Code launch it rather than running
|
|
66
|
+
it by hand.
|
|
67
|
+
|
|
68
|
+
### Eventual published form
|
|
69
|
+
|
|
70
|
+
Once both `miqatools` and this server are published to PyPI, the local-editable dance
|
|
71
|
+
collapses to a normal install (dependencies resolve automatically):
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
uvx miqa-mcp # ephemeral
|
|
75
|
+
pipx install miqa-mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 2. Register with Claude Code
|
|
81
|
+
|
|
82
|
+
`claude mcp add <name> [flags] -- <command> [args...]` registers a stdio server. Claude
|
|
83
|
+
Code launches `<command>` as a subprocess and speaks MCP over its stdin/stdout. The `--`
|
|
84
|
+
separates Claude's own flags from the subprocess command line.
|
|
85
|
+
|
|
86
|
+
Since `miqatools` is unpublished, install into a venv (see above) so `miqa-mcp` is on the
|
|
87
|
+
venv's PATH, then register that console script — passing MIQA credentials via `--env`:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
claude mcp add miqa-mcp \
|
|
91
|
+
--env MIQA_SERVER_URL=yourco.miqa.io \
|
|
92
|
+
--env MIQA_API_KEY=your-key-here \
|
|
93
|
+
-- /absolute/path/to/.venv/bin/miqa-mcp
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If `miqa-mcp` is already on your PATH (activated venv or pipx install), the command is just
|
|
97
|
+
the script name:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
claude mcp add miqa-mcp --env MIQA_SERVER_URL=yourco.miqa.io --env MIQA_API_KEY=your-key-here -- miqa-mcp
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Passing configuration via env
|
|
104
|
+
|
|
105
|
+
A stdio server cannot receive HTTP headers, so its configuration is passed as environment
|
|
106
|
+
variables, which Claude Code injects into the subprocess. Use `--env KEY=value` (short form
|
|
107
|
+
`-e KEY=value`), and put it before the `--` so it is read as one of Claude's own flags.
|
|
108
|
+
|
|
109
|
+
This server reads `MIQA_SERVER_URL` and `MIQA_API_KEY` from its environment (the
|
|
110
|
+
`miqatools` client resolves them), so both must be passed via `--env`:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
claude mcp add miqa-mcp --env MIQA_SERVER_URL=yourco.miqa.io --env MIQA_API_KEY=your-key-here -- /absolute/path/to/.venv/bin/miqa-mcp
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Repeat `--env` for each variable. This env-block mechanism is the stdio equivalent of the
|
|
117
|
+
`--header` flag that HTTP-transport servers use; stdio servers get their config through env,
|
|
118
|
+
not headers.
|
|
119
|
+
|
|
120
|
+
By default the server is registered at `local` scope (this project only). Add
|
|
121
|
+
`--scope user` to make it available across all your projects.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 3. Verify
|
|
126
|
+
|
|
127
|
+
List registered servers — `miqa-mcp` should appear, and Claude Code will have started it
|
|
128
|
+
and confirmed the connection:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
claude mcp list
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
You can also inspect just this one:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
claude mcp get miqa-mcp
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Inside a Claude Code session, the tools surface as `find_runs_by_version`, `get_test_run`,
|
|
141
|
+
`get_test_run_results`, and `get_test_run_report`. With valid `MIQA_SERVER_URL` /
|
|
142
|
+
`MIQA_API_KEY` in the env, ask the session to find runs for a version name and it will call
|
|
143
|
+
the MIQA API through the server.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 4. Remove
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
claude mcp remove miqa-mcp
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
If you installed with pipx and want to uninstall the package too:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
pipx uninstall miqa-mcp
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 5. Run the tests
|
|
162
|
+
|
|
163
|
+
The test suite asserts the four tools register on the FastMCP instance and that each
|
|
164
|
+
delegates to the corresponding `miqatools` wrapper (the wrappers are mocked, so no network
|
|
165
|
+
is touched). Install `miqatools` first, then this package with its dev extra:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
.venv/bin/pip install -e ../magna-cli
|
|
169
|
+
.venv/bin/pip install -e '.[dev]'
|
|
170
|
+
.venv/bin/pytest
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6. How this fits together
|
|
176
|
+
|
|
177
|
+
This server is a thin, read-only MCP surface; all MIQA client logic lives in the
|
|
178
|
+
`miqatools` library. The reporting wrappers (`find_tcrs_by_version`, `get_tcr_summary`,
|
|
179
|
+
`get_tcr_report`) and the existing `get_tcr_info_json` helper stay in `miqatools` as client
|
|
180
|
+
code; this repo only adds the FastMCP tools and the stdio entry point. The packaging shape —
|
|
181
|
+
console script -> stdio `main()` -> env-based config (`MIQA_SERVER_URL`, `MIQA_API_KEY`) — is
|
|
182
|
+
what `claude mcp add` launches.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "miqa-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MIQA MCP Server"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "MIQA / Magna Labs" }]
|
|
13
|
+
keywords = ["mcp", "fastmcp", "claude", "miqa"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"fastmcp>=2.0",
|
|
16
|
+
"miqatools>=2.0.0rc0",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
dev = [
|
|
21
|
+
"pytest>=8.0",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
miqa-mcp = "miqa_mcp.server:main"
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel]
|
|
28
|
+
packages = ["src/miqa_mcp"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""MIQA MCP — a read-only FastMCP server over the miqatools client."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("miqa-mcp")
|
|
7
|
+
except PackageNotFoundError:
|
|
8
|
+
# running from a source checkout that was never installed.
|
|
9
|
+
__version__ = "0.0.0+source"
|
|
10
|
+
|
|
11
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""MIQA MCP server - a FastMCP surface over the miqatools client"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from fastmcp import FastMCP
|
|
6
|
+
|
|
7
|
+
from miqatools.reporting import (
|
|
8
|
+
find_tcrs_by_version,
|
|
9
|
+
get_tcr_report,
|
|
10
|
+
get_tcr_summary,
|
|
11
|
+
)
|
|
12
|
+
from miqatools.remoteexecution.triggertest_helpers import get_tcr_info_json
|
|
13
|
+
|
|
14
|
+
mcp = FastMCP("miqa-mcp")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@mcp.tool
|
|
18
|
+
def find_runs_by_version(version_name: str, limit: int = 100) -> list:
|
|
19
|
+
"""Find Miqa Test Chain Runs for a given version name.
|
|
20
|
+
|
|
21
|
+
Results are newest-first. Returns an empty list when nothing matches.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
version_name: The version identifier to search for (e.g. "bcftools:1.4.1").
|
|
25
|
+
limit: Maximum number of runs to return (default 100).
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
A list of run summary dicts, each including id, testchain_id,
|
|
29
|
+
testchain_name, status, outcome, version_name, start and end.
|
|
30
|
+
"""
|
|
31
|
+
return find_tcrs_by_version(version_name=version_name, limit=limit)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@mcp.tool
|
|
35
|
+
def get_test_run(run_id: int, sample_ds_id: Optional[int] = None) -> dict:
|
|
36
|
+
"""Get JSON details describing a single Miqa Test Chain Run.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
run_id: The Miqa Test Chain Run ID.
|
|
40
|
+
sample_ds_id: Optional Datasource (sample) ID to narrow the details to
|
|
41
|
+
a single sample within the run.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A dict describing the Test Chain Run.
|
|
45
|
+
"""
|
|
46
|
+
return get_tcr_info_json(run_id, ds_id=sample_ds_id)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@mcp.tool
|
|
50
|
+
def get_test_run_results(run_id: int, sample_ds_id: Optional[int] = None):
|
|
51
|
+
"""Get the assertion/result summary table for a Miqa Test Chain Run.
|
|
52
|
+
|
|
53
|
+
The response may be JSON (dict/list) or CSV/plain text depending on the
|
|
54
|
+
server configuration.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
run_id: The Miqa Test Chain Run ID.
|
|
58
|
+
sample_ds_id: Optional Datasource (sample) ID to narrow the summary to
|
|
59
|
+
a single sample.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Parsed JSON when the server returns JSON, otherwise the raw text body
|
|
63
|
+
(e.g. CSV).
|
|
64
|
+
"""
|
|
65
|
+
return get_tcr_summary(run_id, ds_id=sample_ds_id)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@mcp.tool
|
|
69
|
+
def get_test_run_report(run_id: int, sample_ds_id: Optional[int] = None, format: str = "json"):
|
|
70
|
+
"""Get the sample-centric structured report for a Miqa Test Chain Run.
|
|
71
|
+
|
|
72
|
+
This report is gated by a server feature flag; if it is disabled the call
|
|
73
|
+
raises an error and the caller should fall back to get_test_run_results.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
run_id: The Miqa Test Chain Run ID.
|
|
77
|
+
sample_ds_id: Optional Datasource (sample) ID to narrow the report to a
|
|
78
|
+
single sample.
|
|
79
|
+
format: Report mode; "json" (default) returns structured JSON.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The structured report (JSON) as returned by the server.
|
|
83
|
+
"""
|
|
84
|
+
return get_tcr_report(run_id, mode=format, ds_id=sample_ds_id)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def main() -> None:
|
|
88
|
+
mcp.run()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
main()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
# The server depends on fastmcp (and miqatools); skip if fastmcp is absent.
|
|
4
|
+
pytest.importorskip("fastmcp")
|
|
5
|
+
|
|
6
|
+
from miqa_mcp import server # noqa: E402
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
EXPECTED_TOOLS = {
|
|
10
|
+
"find_runs_by_version",
|
|
11
|
+
"get_test_run",
|
|
12
|
+
"get_test_run_results",
|
|
13
|
+
"get_test_run_report",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _registered_tool_names():
|
|
18
|
+
"""Return the set of registered tool names across fastmcp versions.
|
|
19
|
+
|
|
20
|
+
FastMCP exposes registered tools via an (async) ``list_tools()`` /
|
|
21
|
+
``get_tools()`` API; handle both coroutine and sync return values.
|
|
22
|
+
"""
|
|
23
|
+
import asyncio
|
|
24
|
+
import inspect
|
|
25
|
+
|
|
26
|
+
mcp = server.mcp
|
|
27
|
+
lister = getattr(mcp, "list_tools", None) or getattr(mcp, "get_tools", None)
|
|
28
|
+
assert lister is not None, "Could not introspect registered FastMCP tools"
|
|
29
|
+
|
|
30
|
+
tools = lister()
|
|
31
|
+
if inspect.iscoroutine(tools):
|
|
32
|
+
tools = asyncio.run(tools)
|
|
33
|
+
|
|
34
|
+
if isinstance(tools, dict):
|
|
35
|
+
return set(tools.keys())
|
|
36
|
+
return {t.name for t in tools}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_exactly_four_read_tools_registered():
|
|
40
|
+
names = _registered_tool_names()
|
|
41
|
+
# Exactly the four read tools — no mutating tools, no leftover demo tools.
|
|
42
|
+
assert names == EXPECTED_TOOLS
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_main_calls_run(monkeypatch):
|
|
46
|
+
called = {}
|
|
47
|
+
monkeypatch.setattr(server.mcp, "run", lambda *a, **k: called.setdefault("ran", True))
|
|
48
|
+
server.main()
|
|
49
|
+
assert called.get("ran") is True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_tools_delegate_to_miqatools_wrappers(monkeypatch):
|
|
53
|
+
"""Each tool delegates to the corresponding miqatools wrapper (no network)."""
|
|
54
|
+
monkeypatch.setattr(server, "find_tcrs_by_version", lambda version_name, limit=100: ["run"])
|
|
55
|
+
monkeypatch.setattr(server, "get_tcr_info_json", lambda run_id, ds_id=None: {"id": run_id})
|
|
56
|
+
monkeypatch.setattr(server, "get_tcr_summary", lambda run_id, ds_id=None: "summary")
|
|
57
|
+
monkeypatch.setattr(
|
|
58
|
+
server, "get_tcr_report", lambda run_id, mode="json", ds_id=None: {"mode": mode}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# FastMCP may wrap the function; unwrap via .fn when present.
|
|
62
|
+
def _fn(tool_obj):
|
|
63
|
+
return getattr(tool_obj, "fn", tool_obj)
|
|
64
|
+
|
|
65
|
+
assert _fn(server.find_runs_by_version)("bcftools:1.4.1") == ["run"]
|
|
66
|
+
assert _fn(server.get_test_run)(1386) == {"id": 1386}
|
|
67
|
+
assert _fn(server.get_test_run_results)(1386) == "summary"
|
|
68
|
+
assert _fn(server.get_test_run_report)(1386) == {"mode": "json"}
|