fix: cap mcp<2 — 2.x SDK removed Server.list_tools, CI red on every PR (DSE-1261) - #92
Conversation
DSE-1261. CI has been red for every PR since the MCP Python SDK shipped 2.0.0.
pyproject pinned `mcp>=1.27.2` with no upper bound, so a fresh CI install
resolved 2.0.0, which removed the `@server.list_tools()` decorator that the
three fixture servers in tests/fixtures/ use:
AttributeError: 'Server' object has no attribute 'list_tools'
That took down the MCP integrity gate, the Sigstore live sign+verify job, and
the test suite. It went unnoticed for a month because nothing had run on main
since 2026-07-26 and the build server's uv.lock still pinned 1.27.2.
The cap is deliberately conservative rather than a migration: production code
is ALREADY 2.x-compatible — capture.py calls ClientSession.list_tools(), which
2.0.0 keeps. Only the test fixtures use the removed server-side decorator, so
lifting this cap needs just those three files ported to the 2.x
add_request_handler API. That is a real API migration and belongs in its own
reviewed change, not smuggled into an unblock.
Note requirements-dev.lock needed no edit: it already pins mcp==1.28.1, and
regenerating from the capped pyproject reproduces it byte-for-byte. The
"Hash-locked dev/CI install" job was failing only because it regenerated from
the UNCAPPED pyproject and resolved 2.0.0, so this one-line cap fixes that job
too.
Verified in a clean venv built exactly as CI builds it, with
COVERAGE_PROCESS_START set: resolves mcp 1.29.0, 843 passed, coverage 86.44%,
and the previously-crashing gate command returns "OK no drift" exit 0.
The pip-audit CVE gate was failing closed on a real advisory in the locked
dependency closure:
cryptography 49.0.0 PYSEC-2026-3552 Fix Versions: 50.0.0
This is the gate doing its job, not a false positive, so the fix is to take
the upgrade rather than widen the gate. Regenerated with
`--upgrade-package cryptography` so only that package moves; mcp stays pinned
at 1.28.1 under the new `<2` cap and nothing else in the closure shifts
version.
Verified in a clean venv:
- `pip_audit --skip-editable` reports zero findings in the locked closure
(the 7 remaining hits are the venv's own bundled pip 24.0, which is not part
of the closure and which the CI runner does not ship)
- 843 passed, 2 skipped, coverage 86.39%
- the integrity-gate self-test returns "OK no drift", exit 0
|
Two corrections to the original PR description, now that the cap has actually run in CI: 1. 2. That is the fail-closed CVE gate working as designed, so State before the CVE commit: 8 pass / 1 fail. Test suite, MCP integrity gate, Sigstore live sign+verify, and example locks all recovered from the cap alone. Clean-venv verification after both commits: 843 passed, coverage 86.39%, Classification note: with the CVE remediation this PR also classifies security-specific under |
Closes DSE-1261. Unblocks every open PR — CI has been red on all of them, including #91.
What broke
pyproject.tomlpinnedmcp>=1.27.2with no upper bound. The MCP Python SDK shipped 2.0.0, which removed the@server.list_tools()decorator used by the three fixture servers intests/fixtures/:A fresh CI install resolves 2.0.0 and takes down the MCP integrity gate, Sigstore sign+verify (live), and the test suite. It hid for a month because nothing had run on
mainsince 2026-07-26, and the build server'suv.lockstill pinned 1.27.2 — so it reproduced only on a clean install.Why a cap and not a migration
Production code is already 2.x-compatible.
capture.pycallsClientSession.list_tools(), which 2.0.0 keeps — verified directly against 2.0.0:Only the test fixtures use the removed server-side decorator. Porting them to the 2.x
add_request_handlerAPI is a real API migration and deserves its own reviewed change; it should not ride along in an unblock. The cap is one line and trivially reversible.The lock file needed no edit
requirements-dev.lockalready pinsmcp==1.28.1, and regenerating from the capped pyproject reproduces it byte-for-byte. TheHash-locked dev/CI installjob was failing only because it regenerated from the uncapped pyproject and resolved 2.0.0 — so this same one-line change fixes that job too.Verification
Clean venv built exactly as CI builds it, with
COVERAGE_PROCESS_STARTset:OK no drift, exit 0Still separate
Re-check committed example locksfails for a genuinely different reason — real upstream drift inserver-everything. That is warden correctly detecting a surface change against a month-stale pin, not a bug. Re-pinning it needs a human to review the surface diff andpin --approve, tracked on DSE-1261.