Skip to content

A future cleanup can't silently re-break the MCP server (WOR-868) - #474

Merged
oblangatas merged 2 commits into
mainfrom
fix/wor-868-mcp-fastmcp-pin
Aug 9, 2026
Merged

A future cleanup can't silently re-break the MCP server (WOR-868)#474
oblangatas merged 2 commits into
mainfrom
fix/wor-868-mcp-fastmcp-pin

Conversation

@oblangatas

@oblangatas oblangatas commented Aug 1, 2026

Copy link
Copy Markdown
Owner

TL;DR

The mcp<2 cap already landed on main via WOR-864. This adds the guard so it can't be dropped again.

Features

Feature Audience Before After
Drift guard on the mcp cap Us cap can be removed silently, breaking every fresh install a test fails and explains why

Before / After

BEFORE — remove the upper bound and nothing complains until users hit it:

$ sed -i 's/"mcp>=1.0,<2"/"mcp>=1.0"/' pyproject.toml
$ pytest tests/          # green. breakage ships.

AFTER:

$ pytest tests/test_mcp_dependency_cap.py
FAILED — `mcp` requirement 'mcp>=1.0' has no upper bound. mcp 2.0.0 removed
`mcp.server.fastmcp`, so an uncapped range breaks `worthless mcp` on every
fresh `uvx worthless[mcp]` resolve.

Summary

I hit this bug independently while diagnosing an unrelated red check, filed it as WOR-868, and fixed it — then found WOR-864 had landed the identical cap on main first. The cap is not in this PR anymore; I dropped it during rebase, and pyproject.toml here is byte-identical to main. What remains is the one piece main does not have: a test that fails if the cap is ever removed.

Setup

This is worth guarding rather than trusting to memory, because the failure mode is invisible in normal development. uv.lock pins the dev and CI environment to mcp 1.x, so every local run and every CI job passes. But uvx worthless[mcp] — what npx worthless-mcp actually runs — resolves fresh from pyproject.toml and ignores the lockfile. The lockfile hides a broken constraint.

The only job that exercises the real wrapper is path-gated on MCP/npm files, so it doesn't run on ordinary pushes to main. That combination is how a broken worthless mcp reached PyPI in 0.3.10 and 0.3.11: nothing in the normal path could see it.

What

  • If someone lifts the mcp cap without porting to the 2.x API first, a test fails immediately and names the consequence.
  • No behaviour change — this PR adds one test file and nothing else.

Why

The cap is the kind of line that looks like stale conservatism to a future reader ("why is this pinned? let's modernise"). Removing it silently re-breaks the MCP server for every fresh install, and no existing test or CI job would notice. The guard makes the reason non-deletable.

How

tests/test_mcp_dependency_cap.py asserts the mcp requirement in pyproject.toml has an upper bound, and the failure message explains why and what porting is required first.

It matches the requirement line with a regex instead of parsing TOML: tomllib is 3.11+ and we still support 3.10, and this answers one question about one line — not worth a third-party parser or a version-conditional import.

Follow-ups

  • Port to the mcp 2.x API (mcp.server.mcpserver) so the cap can be lifted deliberately.
  • WRTLS-199 hid the cause — the original crash reported "an internal error occurred" rather than naming the missing module. Worth its own ticket.
  • Consider running wrapper-mount on main, not only when MCP/npm paths change.
  • 0.3.10 and 0.3.11 on PyPI are still broken for MCP users; that needs a release with the WOR-864 cap.

Tests

tests/test_mcp_dependency_cap.py — passes against main's cap. Verified it actually bites: removed the cap locally, test went red with the explanatory message, restored it.

Root cause was confirmed against the published wheel in a clean python:3.12-slim container before any of this: worthless[mcp]==0.3.11 → mcp 2.0.0 → fastmcp import fails → WRTLS-199; with mcp>=1.0,<2 → mcp 1.29.0 → import OK → server starts.

Relates to WOR-868 (duplicate of WOR-864, which shipped the cap).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Added regression coverage to verify the MCP dependency is capped below version 2.
    • Added validation for accepted and rejected version constraint formats.
    • Improved test diagnostics for missing, ineffective, or overly broad version caps.

@github-actions github-actions Bot added the bug Something isn't working label Aug 1, 2026
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d2f517ff-e0a4-4011-bbeb-c92778cddbfd

📥 Commits

Reviewing files that changed from the base of the PR and between f1320b6 and e6205f3.

📒 Files selected for processing (1)
  • tests/test_mcp_dependency_cap.py

📝 Walkthrough

Walkthrough

Adds a Python 3.10-compatible regression test that parses the raw mcp requirement from pyproject.toml. It accepts caps that exclude mcp 2.x and rejects missing, ineffective, or inclusive caps that admit version 2.

Changes

MCP dependency cap

Layer / File(s) Summary
Constraint parser
tests/test_mcp_dependency_cap.py
Parses the raw mcp requirement and reports missing or ineffective upper bounds. It handles exclusive and inclusive constraints.
Regression coverage
tests/test_mcp_dependency_cap.py
Checks the project requirement and parameterizes accepted pre-2.0 caps and rejected caps that admit mcp 2.x.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the regression test that prevents future cleanup from removing the MCP dependency cap and breaking the MCP server.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/wor-868-mcp-fastmcp-pin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_mcp_dependency_cap.py`:
- Around line 32-38: Update the MCP requirement assertion in the dependency-cap
test to require an upper bound below version 2, rather than merely checking for
any “<” comparator. Add a regression fixture using mcp>=1.0,<3 and verify it
fails, while preserving acceptance of valid pre-2 caps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: adc28029-9e40-4a15-a533-a67ca8fb06e7

📥 Commits

Reviewing files that changed from the base of the PR and between ad40bf2 and 7df39f5.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • pyproject.toml
  • tests/test_mcp_dependency_cap.py

Comment thread tests/test_mcp_dependency_cap.py
@oblangatas

Copy link
Copy Markdown
Owner Author

#473 is now merged (7a70a9d), so this branch is ready to rebase.

What changes for you: the mcp>=1.0,<2 hunk in pyproject.toml is now a no-op — #473 landed the identical ceiling. Git should drop it cleanly on rebase.

What survives, and why it matters: tests/test_mcp_dependency_cap.py is the one thing #473 does not have. Its spec-vs-implementation review flagged exactly that gap — the cap is enforced by measurement there, but nothing stops a future edit removing it. Your regression test closes that. Please keep it.

Two things worth knowing before you rebase:

1. scan should go green on its own. It was failing here because .grype.yaml was missing ignores for CVE-2026-11940 / CVE-2026-11972 — two CPython tarfile CVEs whose only fix is a 3.15 pre-release. That was red on main and on every branch, not just this one. #473 adds them with a verified reachability argument.

2. There is now a gate on ignore expiry. Grype silently drops the unknown expiry key, so every time-boxed suppression in the repo was actually permanent — measured on grype 0.114.0, an expiry backdated to 2020 still suppressed and the scan exited 0. scripts/hooks/check_grype_ignore_expiry.py now enforces it at commit time and in docker-security.yml. If you add a suppression it must carry a CVE id and a non-expired date, or the hook fails.

Also still open from CodeRabbit here: the assertion at tests/test_mcp_dependency_cap.py:38 checks "<" in mcp_spec, which passes for mcp>=1.0,<99. I posted a concrete fix in that thread — parse the bound and compare it as a version rather than looking for a character.

I deliberately did not push to this branch; it is checked out in an active worktree and that is yours to drive.

@oblangatas
oblangatas force-pushed the fix/wor-868-mcp-fastmcp-pin branch from 7df39f5 to f1320b6 Compare August 2, 2026 14:38
@oblangatas oblangatas changed the title The MCP server starts again for anyone who installs Worthless fresh (WOR-868) A future cleanup can't silently re-break the MCP server (WOR-868) Aug 2, 2026
oblangatas and others added 2 commits August 9, 2026 08:28
CodeRabbit caught this on review and it was agreed but never applied: the
guard asserted `"<" in mcp_spec`, which passes for `mcp>=1.0,<99`. A test
whose whole job is to catch the cap being dropped would have sailed through
exactly the regression it exists to prevent.

Now the upper bound is parsed and compared as a version, so only a real cap at
or below 2 passes. Inclusive bounds are handled too — `<=2` admits 2.0.0
itself, which is the broken release.

Proven, not asserted. Against the real pyproject.toml:
  mcp>=1.0,<99  -> FAILS   (the old assertion passed this)
  mcp>=1.0,<3   -> FAILS   (the old assertion passed this)
  mcp>=1.0      -> FAILS
Parametrised cases pin both directions: <2, <2.0.0, < 2 and <=1.9 accepted;
no-cap, <99, <3, <2.1 and <=2 rejected. The bound checker is split out so
those can be exercised without rewriting pyproject.toml.

Uses packaging.version.Version so 2.0.0 compares equal to 2 — matching
existing precedent in tests/test_install_static.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@oblangatas
oblangatas force-pushed the fix/wor-868-mcp-fastmcp-pin branch from f1320b6 to e6205f3 Compare August 9, 2026 07:13
@sonarqubecloud

sonarqubecloud Bot commented Aug 9, 2026

Copy link
Copy Markdown

@oblangatas
oblangatas merged commit fdeac80 into main Aug 9, 2026
41 checks passed
@oblangatas
oblangatas deleted the fix/wor-868-mcp-fastmcp-pin branch August 9, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant