Update the v2 status banner and pin spawned environments to the running SDK version (#2834)
This commit is contained in:
parent
e196857bc9
commit
1e21814ed5
2 changed files with 107 additions and 16 deletions
|
|
@ -1,24 +1,68 @@
|
|||
"""Tests for mcp.cli.claude — Claude Desktop config file generation."""
|
||||
|
||||
import importlib.metadata
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from mcp.cli.claude import get_uv_path, update_claude_config
|
||||
from mcp.cli.claude import get_uv_path, mcp_requirement, update_claude_config
|
||||
|
||||
|
||||
def _set_mcp_version(monkeypatch: pytest.MonkeyPatch, version: str) -> None:
|
||||
real_version = importlib.metadata.version
|
||||
|
||||
def fake_version(distribution_name: str) -> str:
|
||||
return version if distribution_name == "mcp" else real_version(distribution_name)
|
||||
|
||||
monkeypatch.setattr(importlib.metadata, "version", fake_version)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Temp Claude config dir with get_claude_config_path and get_uv_path mocked."""
|
||||
"""Temp Claude config dir with the config path, uv path, and SDK version mocked."""
|
||||
claude_dir = tmp_path / "Claude"
|
||||
claude_dir.mkdir()
|
||||
monkeypatch.setattr("mcp.cli.claude.get_claude_config_path", lambda: claude_dir)
|
||||
monkeypatch.setattr("mcp.cli.claude.get_uv_path", lambda: "/fake/bin/uv")
|
||||
# The ambient version is a dev build in the repo venv but varies by
|
||||
# environment; pin it so the generated --with requirement is stable.
|
||||
_set_mcp_version(monkeypatch, "1.2.3")
|
||||
return claude_dir
|
||||
|
||||
|
||||
def test_mcp_requirement_pins_release_versions(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Release versions produce an exact pin so spawned environments run the installed SDK version."""
|
||||
_set_mcp_version(monkeypatch, "2.0.0a1")
|
||||
assert mcp_requirement() == "mcp==2.0.0a1"
|
||||
assert mcp_requirement("mcp[cli]") == "mcp[cli]==2.0.0a1"
|
||||
|
||||
|
||||
def test_mcp_requirement_leaves_dev_versions_unpinned(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Dev versions are not published to PyPI, so the requirement falls back to the unpinned package."""
|
||||
_set_mcp_version(monkeypatch, "2.0.0a2.dev3")
|
||||
assert mcp_requirement() == "mcp"
|
||||
assert mcp_requirement("mcp[cli]") == "mcp[cli]"
|
||||
|
||||
|
||||
def test_mcp_requirement_leaves_local_versions_unpinned(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Local version segments (source builds) are not published to PyPI, so no pin is emitted."""
|
||||
_set_mcp_version(monkeypatch, "1.2.3+g0123abc")
|
||||
assert mcp_requirement() == "mcp"
|
||||
|
||||
|
||||
def test_mcp_requirement_falls_back_when_mcp_is_not_installed(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Without distribution metadata there is no version to pin, so the requirement stays unpinned."""
|
||||
|
||||
def raise_not_found(distribution_name: str) -> str:
|
||||
raise importlib.metadata.PackageNotFoundError(distribution_name)
|
||||
|
||||
monkeypatch.setattr(importlib.metadata, "version", raise_not_found)
|
||||
assert mcp_requirement() == "mcp"
|
||||
assert mcp_requirement("mcp[cli]") == "mcp[cli]"
|
||||
|
||||
|
||||
def _read_server(config_dir: Path, name: str) -> dict[str, Any]:
|
||||
config = json.loads((config_dir / "claude_desktop_config.json").read_text())
|
||||
return config["mcpServers"][name]
|
||||
|
|
@ -31,7 +75,7 @@ def test_generates_uv_run_command(config_dir: Path):
|
|||
resolved = Path("server.py").resolve()
|
||||
assert _read_server(config_dir, "my_server") == {
|
||||
"command": "/fake/bin/uv",
|
||||
"args": ["run", "--frozen", "--with", "mcp[cli]", "mcp", "run", f"{resolved}:app"],
|
||||
"args": ["run", "--frozen", "--with", "mcp[cli]==1.2.3", "mcp", "run", f"{resolved}:app"],
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -43,11 +87,19 @@ def test_file_spec_without_object_suffix(config_dir: Path):
|
|||
|
||||
|
||||
def test_with_packages_sorted_and_deduplicated(config_dir: Path):
|
||||
"""Extra packages should appear as --with flags, sorted and deduplicated with mcp[cli]."""
|
||||
"""Extra packages should appear as sorted --with flags with duplicates removed."""
|
||||
assert update_claude_config(file_spec="s.py:app", server_name="s", with_packages=["zebra", "aardvark", "zebra"])
|
||||
|
||||
args = _read_server(config_dir, "s")["args"]
|
||||
assert args[:8] == ["run", "--frozen", "--with", "aardvark", "--with", "mcp[cli]", "--with", "zebra"]
|
||||
assert args[:8] == ["run", "--frozen", "--with", "aardvark", "--with", "mcp[cli]==1.2.3", "--with", "zebra"]
|
||||
|
||||
|
||||
def test_explicit_mcp_cli_kept_alongside_pinned_requirement(config_dir: Path):
|
||||
"""A user-supplied mcp[cli] no longer collapses into the pinned requirement; uv resolves both to the pin."""
|
||||
assert update_claude_config(file_spec="s.py:app", server_name="s", with_packages=["mcp[cli]"])
|
||||
|
||||
args = _read_server(config_dir, "s")["args"]
|
||||
assert args[:6] == ["run", "--frozen", "--with", "mcp[cli]", "--with", "mcp[cli]==1.2.3"]
|
||||
|
||||
|
||||
def test_with_editable_adds_flag(config_dir: Path, tmp_path: Path):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import importlib.metadata
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
@ -8,6 +9,15 @@ import pytest
|
|||
from mcp.cli.cli import _build_uv_command, _get_npx_command, _parse_file_path # type: ignore[reportPrivateUsage]
|
||||
|
||||
|
||||
def _set_mcp_version(monkeypatch: pytest.MonkeyPatch, version: str) -> None:
|
||||
real_version = importlib.metadata.version
|
||||
|
||||
def fake_version(distribution_name: str) -> str:
|
||||
return version if distribution_name == "mcp" else real_version(distribution_name)
|
||||
|
||||
monkeypatch.setattr(importlib.metadata, "version", fake_version)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"spec, expected_obj",
|
||||
[
|
||||
|
|
@ -38,14 +48,23 @@ def test_parse_file_exit_on_dir(tmp_path: Path):
|
|||
_parse_file_path(str(dir_path))
|
||||
|
||||
|
||||
def test_build_uv_command_minimal():
|
||||
"""Should emit core command when no extras specified."""
|
||||
def test_build_uv_command_pins_the_running_mcp_version(monkeypatch: pytest.MonkeyPatch):
|
||||
"""The spawned environment installs the same SDK version that is running, not the latest stable."""
|
||||
_set_mcp_version(monkeypatch, "1.2.3")
|
||||
cmd = _build_uv_command("foo.py")
|
||||
assert cmd == ["uv", "run", "--with", "mcp==1.2.3", "mcp", "run", "foo.py"]
|
||||
|
||||
|
||||
def test_build_uv_command_leaves_source_builds_unpinned(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Source-build versions are not on PyPI, so the requirement stays unpinned."""
|
||||
_set_mcp_version(monkeypatch, "2.0.0a2.dev3+g0123abc")
|
||||
cmd = _build_uv_command("foo.py")
|
||||
assert cmd == ["uv", "run", "--with", "mcp", "mcp", "run", "foo.py"]
|
||||
|
||||
|
||||
def test_build_uv_command_adds_editable_and_packages():
|
||||
def test_build_uv_command_adds_editable_and_packages(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Should include --with-editable and every --with pkg in correct order."""
|
||||
_set_mcp_version(monkeypatch, "1.2.3")
|
||||
test_path = Path("/pkg")
|
||||
cmd = _build_uv_command(
|
||||
"foo.py",
|
||||
|
|
@ -56,7 +75,7 @@ def test_build_uv_command_adds_editable_and_packages():
|
|||
"uv",
|
||||
"run",
|
||||
"--with",
|
||||
"mcp",
|
||||
"mcp==1.2.3",
|
||||
"--with-editable",
|
||||
str(test_path), # Use str() to match what the function does
|
||||
"--with",
|
||||
|
|
|
|||
Loading…
Reference in a new issue