Cap mcp below 2.0 so fresh installs start - #317
Conversation
mcp 2.0.0 removed the module-level `request_ctx` contextvar from
mcp.server.lowlevel.server. nerve/mcp_server/http.py imports it at module
scope, so a fresh install fails at startup with
ImportError: cannot import name 'request_ctx'
from 'mcp.server.lowlevel.server'
mcp was never declared in pyproject.toml — it arrived transitively via
claude-agent-sdk, whose constraint is `mcp<3.0.0,>=1.23.0`. Nothing else in
the tree caps it, so once 2.0.0 landed on PyPI every fresh resolve picked it
up. Declaring mcp explicitly is also the correct hygiene here: three modules
under nerve/mcp_server/ import it directly, and an upper bound on an
otherwise-transitive dependency can only be expressed by declaring it.
CI stayed green through this because .github/workflows/ci.yml keys its uv
cache on pyproject.toml; the restored cache carried stale index metadata, so
runs kept resolving 1.29.0 while genuinely fresh installs broke. Touching
pyproject.toml changes that key, so this PR's own run does a real resolve.
The bound is a stopgap, not the destination — claude-agent-sdk already
allows mcp<3, so porting nerve/mcp_server/ to the 2.x request-context API
is separate follow-up work.
Fixes #316
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
CI result, confirming the fresh-resolve claim above with the run log rather than inference: So run 32243134290
Worth stating plainly for the record: green CI on its own could not have |
|
Closing in favour of #319, which does the real port. This cap was only ever a Worth noting what closing this actually required, because it wasn't only the PR: The trade-off to be aware of: |
Fixes #316.
What
Declares
mcpexplicitly inpyproject.tomlwith an upper bound:mcp>=1.23,<2.One dependency line plus the comment explaining why the bound exists and when to
lift it. Nothing else changes.
Why
mcp2.0.0 removed the module-levelrequest_ctxcontextvar frommcp.server.lowlevel.server.nerve/mcp_server/http.pyimports it at modulescope (line 32, used in
_resolve_client_infoand_bound_identity_from_request), so a fresh install dies at startup with theImportErrorin the issue.mcpwas never declared inpyproject.toml— it arrived transitively throughclaude-agent-sdk, whose constraint ismcp<3.0.0,>=1.23.0. Nothing else inthe tree caps it, so once 2.0.0 landed on PyPI (2026-07-28, four minutes after
1.29.0) every fresh resolve picked it up.
Declaring it explicitly is also the right hygiene independent of this bug: three
modules under
nerve/mcp_server/importmcpdirectly, and an upper bound on anotherwise-transitive dependency can only be expressed by declaring it. The floor
mirrors
claude-agent-sdk's own>=1.23.0, so nothing is narrowed except thetop end.
Why CI never caught it
.github/workflows/ci.ymlkeys its uv cache onpyproject.toml(
cache-dependency-glob). The restored cache carried stale index metadata, soruns kept resolving the old version — run 32061993270
(main, 2026-08-17, green) installed
mcp==1.29.0three weeks after 2.0.0 shipped.CI was green on stale metadata while every genuinely fresh install broke.
Usefully, touching
pyproject.tomlchanges that cache key — so this PR's own runperforms a real resolve rather than restoring the stale one.
That blind spot is worth closing separately (dependency drift stays invisible
until
pyproject.tomlhappens to change), but it's a CI-config change and notthis PR's job.
Testing
Verified both directions in a throwaway Python 3.13 venv, resolving with
--refreshso no stale index metadata could mask the result.With the bound — resolves
mcp==1.29.0, the newest 1.x:uv pip install -e ".[test]" --refresh→mcp==1.29.0(and no longer pulls the 2.x-onlymcp-types)from mcp.server.lowlevel.server import request_ctx→ OKimport nerve.gateway.server(thenerve startpath) → OKpytest tests/ -q→ 3303 passed in 107sNegative control, forcing
mcp==2.0.0into the same venv to confirm thebound is load-bearing rather than cosmetic:
import nerve.mcp_server.http→ reproduces the issue'sImportErrorexactlyimport nerve.gateway.server→ sameImportError, i.e. startup is what breakspytest tests/ --co→ 8 collection errors (test_mcp_loopback,test_mcp_session_binding,test_satellite_sessions,test_server_periodic_loops, …)Frontend is untouched by a Python dependency change, so
npm run buildwas notrun locally; CI covers it.
Follow-up
This is a stopgap.
claude-agent-sdkalready allowsmcp<3, so the real work isporting
nerve/mcp_server/fromrequest_ctxto the 2.x request-context API(2.0 keeps a
ServerRequestContextclass but no module-level contextvar). Thecomment on the pin says as much so the bound doesn't quietly become permanent.
🤖 Generated with Claude Code