Summary
pytest and pytest-asyncio are not declared anywhere in pyproject.toml. They exist only as inline pip install lines inside CI, so there is no way to set up a working dev environment from the repo alone — you have to read .github/workflows/ci.yml to discover what the test suite needs.
Adopting uv fixes this and brings qrz-mcp in line with adif-mcp, which already uses it.
The concrete gap
Cloning main and following the only documented setup (pip install -e ., from the Development section of the README) gives you a package with no test runner:
$ uv sync # or: pip install -e .
$ uv run pytest -q
error: Failed to spawn: `pytest`
Caused by: No such file or directory (os error 2)
The dependency exists, but only here (.github/workflows/ci.yml:30-33):
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest pytest-asyncio
Adding a dev group fixes it:
[dependency-groups]
dev = ["pytest>=8"]
$ uv sync --group dev
$ uv run pytest -q
43 passed
Note pytest-asyncio is installed by CI but never used — there is not a single async def or await in tests/. The suite passes without it. It can be dropped rather than carried into the new group.
Why uv specifically
adif-mcp already does this, so it is fleet alignment rather than a new pattern:
uv.lock committed
[dependency-groups] dev = [...] in pyproject.toml
- CI on
astral-sh/setup-uv@v1 with uv sync --group dev --frozen and uv run pytest -q
hatchling retained as the build backend
qrz-mcp is currently the odd one out. Of the siblings I checked (adif-mcp, hamqth-mcp, lotw-mcp, pota-mcp, solar-mcp), only adif-mcp has a lockfile — so this is fleet-wide drift, and qrz-mcp is a reasonable second adopter.
Beyond consistency, a committed uv.lock makes CI reproducible: right now pip install pytest resolves to whatever is newest on the day the job runs, so a bad upstream release breaks CI on a commit that changed nothing.
Proposed scope
- Add
[dependency-groups] dev = ["pytest>=8"]; drop the unused pytest-asyncio.
- Commit
uv.lock.
- Switch
ci.yml to astral-sh/setup-uv + uv sync --group dev --frozen + uv run pytest.
- Update the README Development section to
uv sync --group dev / uv run pytest.
- Keep
hatchling as the build backend, and keep publish.yml on python -m build — matching adif-mcp. No packaging or release-path changes.
Deliberately out of scope
Changing the user-facing install instructions. uvx qrz-mcp does work — I verified it resolves 0.3.3 from PyPI, and it is genuinely convenient for MCP clients since it sidesteps "which Python is my client using". But making it the documented install adds a prerequisite for users who have never heard of uv, and adif-mcp still documents plain pip install adif-mcp. If a uv install path is wanted, it should sit alongside pip, as a separate change.
Note that step 4 touches the same README as #7, so this should land after that merges.
Verification
Everything above was run against a clone of main:
uv sync succeeds on the current pyproject.toml unmodified (84 packages locked).
- Adding the dev group makes
uv run pytest work: 43 passed.
- Suite passes without
pytest-asyncio.
- The 3.10–3.13 matrix is preserved under uv via
uv run --python 3.10 ... 3.13 — both ends verified passing, so CI coverage does not narrow. (Worth noting adif-mcp CI pins a single Python 3.11; qrz-mcp should keep its matrix rather than copy that part.)
uv 0.10.1.
Note
Happy to send a PR, but suggesting this waits — #4, #6 and #7 are open and unreviewed, and a CI/packaging change is the riskiest kind to review. The bug fixes are worth more and should clear first.
If uv is not wanted, the underlying defect still stands and can be fixed with plain [project.optional-dependencies] and no new tooling. The undeclared dev dependency is the actual bug; uv is one way to fix it.
AI disclosure
This issue was written by an AI agent (Claude, via OpenCode) working from a human-directed prompt.
All commands, outputs and version numbers quoted above were executed against a clone of main and the sibling repos, not asserted from model knowledge. The adif-mcp configuration was read from that repository rather than assumed.
Maintainer note: the scope boundary in this issue is a judgement call, not a finding — reasonable people could want the uvx install path documented too. The undeclared-dependency part is the objective defect; the rest is a preference worth your input.
Summary
pytestandpytest-asyncioare not declared anywhere inpyproject.toml. They exist only as inlinepip installlines inside CI, so there is no way to set up a working dev environment from the repo alone — you have to read.github/workflows/ci.ymlto discover what the test suite needs.Adopting uv fixes this and brings qrz-mcp in line with
adif-mcp, which already uses it.The concrete gap
Cloning
mainand following the only documented setup (pip install -e ., from the Development section of the README) gives you a package with no test runner:The dependency exists, but only here (
.github/workflows/ci.yml:30-33):Adding a dev group fixes it:
Note
pytest-asynciois installed by CI but never used — there is not a singleasync deforawaitintests/. The suite passes without it. It can be dropped rather than carried into the new group.Why uv specifically
adif-mcpalready does this, so it is fleet alignment rather than a new pattern:uv.lockcommitted[dependency-groups] dev = [...]inpyproject.tomlastral-sh/setup-uv@v1withuv sync --group dev --frozenanduv run pytest -qhatchlingretained as the build backendqrz-mcp is currently the odd one out. Of the siblings I checked (
adif-mcp,hamqth-mcp,lotw-mcp,pota-mcp,solar-mcp), onlyadif-mcphas a lockfile — so this is fleet-wide drift, and qrz-mcp is a reasonable second adopter.Beyond consistency, a committed
uv.lockmakes CI reproducible: right nowpip install pytestresolves to whatever is newest on the day the job runs, so a bad upstream release breaks CI on a commit that changed nothing.Proposed scope
[dependency-groups] dev = ["pytest>=8"]; drop the unusedpytest-asyncio.uv.lock.ci.ymltoastral-sh/setup-uv+uv sync --group dev --frozen+uv run pytest.uv sync --group dev/uv run pytest.hatchlingas the build backend, and keeppublish.ymlonpython -m build— matchingadif-mcp. No packaging or release-path changes.Deliberately out of scope
Changing the user-facing install instructions.
uvx qrz-mcpdoes work — I verified it resolves 0.3.3 from PyPI, and it is genuinely convenient for MCP clients since it sidesteps "which Python is my client using". But making it the documented install adds a prerequisite for users who have never heard of uv, andadif-mcpstill documents plainpip install adif-mcp. If a uv install path is wanted, it should sit alongsidepip, as a separate change.Note that step 4 touches the same README as #7, so this should land after that merges.
Verification
Everything above was run against a clone of
main:uv syncsucceeds on the currentpyproject.tomlunmodified (84 packages locked).uv run pytestwork: 43 passed.pytest-asyncio.uv run --python 3.10 ... 3.13— both ends verified passing, so CI coverage does not narrow. (Worth notingadif-mcpCI pins a single Python 3.11; qrz-mcp should keep its matrix rather than copy that part.)uv 0.10.1.
Note
Happy to send a PR, but suggesting this waits — #4, #6 and #7 are open and unreviewed, and a CI/packaging change is the riskiest kind to review. The bug fixes are worth more and should clear first.
If uv is not wanted, the underlying defect still stands and can be fixed with plain
[project.optional-dependencies]and no new tooling. The undeclared dev dependency is the actual bug; uv is one way to fix it.AI disclosure
This issue was written by an AI agent (Claude, via OpenCode) working from a human-directed prompt.
All commands, outputs and version numbers quoted above were executed against a clone of
mainand the sibling repos, not asserted from model knowledge. Theadif-mcpconfiguration was read from that repository rather than assumed.Maintainer note: the scope boundary in this issue is a judgement call, not a finding — reasonable people could want the
uvxinstall path documented too. The undeclared-dependency part is the objective defect; the rest is a preference worth your input.