build(deps): pin mcp to 2.1.0 so an SDK minor cannot land unreviewed - #503
Merged
Conversation
`mcp>=2,<3` let 2.1.0 arrive on its own. It redacts a tool exception's message unless the exception derives from the SDK's `ToolError`, so the refusal raised by `recall_mcp.limits.RateLimited` reached clients as a bare "Error executing tool recall_search" rather than its retry guidance. The three assertions in tests/test_rate_limit_http.py caught it, but only after it had broken master three times (#497, #498, #499). `uv.lock` did not prevent any of that: CI installs with `pip install -e`, which ignores the lock, so the lock said 2.0.0 throughout while CI resolved 2.1.0. Pin the three declaration sites that must move together (`mcp`, `dev`, `desktop`) and relock, so a version change is a reviewed diff with a CI run behind it instead of a silent resolution. RateLimited keeps its ToolError base regardless; the pin controls when a new SDK arrives, not whether the code survives it. Verified against mcp 2.1.0 locally: tests/test_rate_limit_http.py 4 passed, and the MCP surface (auth, limits, test_mcp_*, server env) 378 passed, 1 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `floor` job installs with `--resolution lowest-direct`, so pinning `mcp` at an exact version makes its floor degenerate: lowest and newest are now the same single version and that job no longer proves anything about the MCP API surface. Its comment still claimed a `mcp>=2` floor was being exercised. This repository has already been bitten by exactly this: the same comment records `pgvector>=0.3` naming versions where `import recall.store` raises, and concludes that a floor nothing resolves at "is documentation, not a constraint, and it is documentation that was false". Leaving the MCP half stale would have reproduced that. docs/PRODUCTION.md and CHANGELOG.md also name `mcp>=2,<3`, and both are left alone on purpose: they are release notes for 0.5.1 and describe what that release did, which is still true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does and why
mcpwas declared>=2,<3, so 2.1.0 arrived on its own. It redacts a tool exception's messageunless the exception derives from the SDK's
ToolError, so the refusal raised byrecall_mcp.limits.RateLimitedreached clients as a bareError executing tool recall_searchinstead of its retry guidance.
The three assertions in
tests/test_rate_limit_http.pycaught it, but only after it had brokenmaster three times (#497, #498, #499). #501 fixed the code by giving
RateLimitedtheToolErrorbase. This fixes the reason it landed unreviewed at all.
uv.lockprevented none of it: CI installs withpip install -e, which ignores the lock, so thelock read 2.0.0 throughout while CI resolved 2.1.0. The lock constrains
uv syncand nothing else.Pinned at 2.1.0 rather than reverting to 2.0.0 because 2.1.0 is what master is currently green on,
so the pin freezes a state with a full CI run behind it. All three sites that the file's own comment
says must move together are pinned: the
mcp,devanddesktopextras.A second commit corrects a comment in
.github/workflows/ci.yml. Thefloorjob resolveslowest-direct, so an exact pin makes mcp's floor degenerate: lowest and newest become one versionand that job no longer proves anything about the MCP API surface. Its comment still claimed a
mcp>=2floor was exercised. That same comment already records being burned by a false floor claimonce (
pgvector>=0.3), and concludes a floor nothing resolves at "is documentation, not aconstraint, and it is documentation that was false".
docs/PRODUCTION.mdandCHANGELOG.mdalso namemcp>=2,<3and are deliberately left alone:both are release notes for 0.5.1 describing what that release did, which is still true.
Two calls worth reviewing
That visible PR is the reviewed bump this pin exists to force, and
tests/test_dependabot_ignores_match_pins.pyargues for exactly that tradeoff, preferring aself-announcing PR to silence.
==in published metadata constrains consumers. Concretely,semgreppinsmcp==1.23.3, sorecall[mcp]and semgrep can no longer share an environment. semgrep is not adependency of this repo, so CI is unaffected, but downstream users combining recall with another
MCP-consuming package will hit this class of conflict. The alternative shape is a range in package
metadata plus an exact pin in a constraints file, which would require CI to stop using
pip install -ebefore it constrains anything.Area touched
MCP server (dependency metadata and CI configuration only; no runtime code changed).
Checklist
ruff check .passespytestpasses against a real pgvector database. Targeted rather than the full suite, sinceno runtime code changed:
tests/test_rate_limit_http.py4 passed; the MCP surface(
test_auth,test_limits,test_mcp_*,test_server_env_validation) 378 passed,1 skipped;
test_dependabot_ignores_match_pinsplus both citation gates 73 passed.All run with
mcp==2.1.0actually installed. The full suite is left to CI.uv lockwas run,uv.lockis included, anduv lock --checkpasseslocally. The only movement is
mcpandmcp-types, 2.0.0 to 2.1.0.tests/test_rate_limit_http.pyasserts on the client-visible refusal text, not on an internalflag, so it fails exactly when the SDK stops surfacing the message.
make evalwas not re-run.Evaluation impact
No retrieval or trust-layer impact. This changes dependency metadata and one CI comment only.
🤖 Generated with Claude Code