mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description PR #2963 widened the MCP dependency to 2.x while Headroom's live MCP server still uses the v1 low-level `Server.list_tools()` and `Server.call_tool()` decorators. Fresh installs therefore crash before serving tools. Restore the v1 cap until the explicit SDK 2.x port in #2658 lands, and pin that compatibility contract with a regression test. Closes #2977 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Restore `mcp>=1.28.1,<2.0.0` in the `proxy` and `mcp` extras. - Regenerate `uv.lock`, resolving MCP 1.28.1 and removing the incompatible 2.x transitive set. - Add a dependency-contract test covering both shipping extras. ## Testing - [x] Unit tests pass (`pytest`) - [x] Linting passes (`ruff check .`) - [ ] Type checking passes (`mypy headroom`) - [x] New tests added for new functionality - [x] Manual testing performed ### Test Output ```text $ uv run pytest -q tests/test_mcp_dependency_contract.py tests/test_ccr_mcp_server.py tests/test_cli/test_mcp.py 41 passed in 0.56s $ uv run ruff check tests/test_mcp_dependency_contract.py All checks passed! $ uv run ruff format --check tests/test_mcp_dependency_contract.py 1 file already formatted ``` ## Real Behavior Proof - Environment: macOS arm64, Python 3.13.14, uv-managed project environment. - Exact command / steps: resolve the `mcp` extra, inspect the installed SDK version and v1 decorators, then instantiate `HeadroomMCPServer(check_proxy=False)`. - Observed result: `1.28.1 True True`; server construction returns a v1 `Server` successfully. - Not tested: full stdio exchange against every external MCP client; existing MCP unit and CLI suites cover server setup and handlers. ## Runtime Rollout Safety - Rollout-managed feature(s): None; dependency resolution guard. - Minimum rollout channel: Stable/default. - Stable/default behavior changed: Fresh installs stop resolving the incompatible MCP SDK 2.x release. - Kill switch / disable path: Revert the dependency cap after #2658 lands. - Unsafe override required: No. - Qualification impact: MCP extras and proxy installs remain on the maintained MCP 1.x line. - Rollback path: Revert this PR; not recommended until the v2 server port is merged and tested. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [x] I did **not** edit `CHANGELOG.md` — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this) ## Screenshots (if applicable) Not applicable. ## Additional Notes The documentation change is the inline dependency rationale next to the cap. The long-term migration remains #2658; this PR deliberately does not mix that breaking SDK port into the release-blocker rollback.
29 lines
1.1 KiB
Python
29 lines
1.1 KiB
Python
"""Keep the installed MCP SDK compatible with Headroom's live server API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import tomllib
|
|
from packaging.requirements import Requirement
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _mcp_requirement(dependencies: list[str]) -> Requirement:
|
|
matches = [Requirement(value) for value in dependencies if Requirement(value).name == "mcp"]
|
|
assert len(matches) == 1, "expected exactly one mcp requirement"
|
|
return matches[0]
|
|
|
|
|
|
def test_all_shipping_mcp_extras_exclude_incompatible_sdk_v2() -> None:
|
|
"""SDK 2 removed Server.list_tools()/call_tool(); #2658 owns that port."""
|
|
project = tomllib.loads((ROOT / "pyproject.toml").read_text())["project"]
|
|
optional = project["optional-dependencies"]
|
|
|
|
for extra in ("proxy", "mcp"):
|
|
requirement = _mcp_requirement(optional[extra])
|
|
assert requirement.specifier.contains("1.28.1")
|
|
assert not requirement.specifier.contains("2.0.0"), (
|
|
f"the {extra!r} extra admits MCP SDK 2.x before the server port in #2658"
|
|
)
|