|
5 | 5 |
|
6 | 6 | import pytest |
7 | 7 |
|
| 8 | +from mcp.cli import cli as cli_module |
8 | 9 | from mcp.cli.cli import _build_uv_command, _get_npx_command, _parse_file_path # type: ignore[reportPrivateUsage] |
9 | 10 |
|
10 | 11 |
|
@@ -78,24 +79,69 @@ def test_get_npx_unix_like(monkeypatch: pytest.MonkeyPatch): |
78 | 79 | def test_get_npx_windows(monkeypatch: pytest.MonkeyPatch): |
79 | 80 | """Should return one of the npx candidates on Windows.""" |
80 | 81 | candidates = ["npx.cmd", "npx.exe", "npx"] |
| 82 | + located = {cmd: f"C:\\Node\\{cmd}" for cmd in candidates} |
81 | 83 |
|
82 | 84 | def fake_run(cmd: list[str], **kw: Any) -> subprocess.CompletedProcess[bytes]: |
83 | | - if cmd[0] in candidates: |
| 85 | + assert kw.get("shell") is not True |
| 86 | + assert cmd[0] in located.values() |
| 87 | + if Path(cmd[0]).name in candidates: |
84 | 88 | return subprocess.CompletedProcess(cmd, 0) |
85 | 89 | else: # pragma: no cover |
86 | 90 | raise subprocess.CalledProcessError(1, cmd[0]) |
87 | 91 |
|
88 | 92 | monkeypatch.setattr(sys, "platform", "win32") |
| 93 | + monkeypatch.setattr("shutil.which", located.get) |
89 | 94 | monkeypatch.setattr(subprocess, "run", fake_run) |
90 | | - assert _get_npx_command() in candidates |
| 95 | + assert _get_npx_command() in located.values() |
| 96 | + |
| 97 | + |
| 98 | +def test_get_npx_windows_skips_failed_candidates(monkeypatch: pytest.MonkeyPatch): |
| 99 | + """Should keep checking candidates if one exists but fails.""" |
| 100 | + located = {"npx.cmd": "C:\\Node\\npx.cmd", "npx.exe": "C:\\Node\\npx.exe"} |
| 101 | + |
| 102 | + def fake_run(cmd: list[str], **kw: Any) -> subprocess.CompletedProcess[bytes]: |
| 103 | + assert kw.get("shell") is not True |
| 104 | + if cmd[0].endswith("npx.cmd"): |
| 105 | + raise subprocess.CalledProcessError(1, cmd) |
| 106 | + return subprocess.CompletedProcess(cmd, 0) |
| 107 | + |
| 108 | + monkeypatch.setattr(sys, "platform", "win32") |
| 109 | + monkeypatch.setattr("shutil.which", located.get) |
| 110 | + monkeypatch.setattr(subprocess, "run", fake_run) |
| 111 | + |
| 112 | + assert _get_npx_command() == located["npx.exe"] |
91 | 113 |
|
92 | 114 |
|
93 | 115 | def test_get_npx_returns_none_when_npx_missing(monkeypatch: pytest.MonkeyPatch): |
94 | 116 | """Should give None if every candidate fails.""" |
95 | 117 | monkeypatch.setattr(sys, "platform", "win32", raising=False) |
| 118 | + monkeypatch.setattr("shutil.which", lambda cmd: None) |
| 119 | + assert _get_npx_command() is None |
96 | 120 |
|
97 | | - def always_fail(*args: Any, **kwargs: Any) -> subprocess.CompletedProcess[bytes]: |
98 | | - raise subprocess.CalledProcessError(1, args[0]) |
99 | 121 |
|
100 | | - monkeypatch.setattr(subprocess, "run", always_fail) |
101 | | - assert _get_npx_command() is None |
| 122 | +def test_dev_runs_inspector_without_shell(monkeypatch: pytest.MonkeyPatch): |
| 123 | + """mcp dev should not route file paths or args through a platform shell.""" |
| 124 | + calls: list[tuple[list[str], dict[str, Any]]] = [] |
| 125 | + |
| 126 | + class Server: |
| 127 | + dependencies = ["server-dep"] |
| 128 | + |
| 129 | + def fake_run(cmd: list[str], **kw: Any) -> subprocess.CompletedProcess[str]: |
| 130 | + calls.append((cmd, kw)) |
| 131 | + return subprocess.CompletedProcess(cmd, 0) |
| 132 | + |
| 133 | + monkeypatch.setattr(cli_module, "_parse_file_path", lambda file_spec: (Path("server&calc.py"), None)) |
| 134 | + monkeypatch.setattr(cli_module, "_import_server", lambda file, server_object: Server()) |
| 135 | + monkeypatch.setattr(cli_module, "_build_uv_command", lambda file_spec, with_editable, with_packages: ["uv", "run"]) |
| 136 | + monkeypatch.setattr(cli_module, "_get_npx_command", lambda: "C:\\Node\\npx.cmd") |
| 137 | + monkeypatch.setattr(subprocess, "run", fake_run) |
| 138 | + |
| 139 | + with pytest.raises(SystemExit) as exc_info: |
| 140 | + cli_module.dev("server&calc.py", with_packages=["cli-dep"]) |
| 141 | + |
| 142 | + assert exc_info.value.code == 0 |
| 143 | + assert len(calls) == 1 |
| 144 | + cmd, kwargs = calls[0] |
| 145 | + assert cmd == ["C:\\Node\\npx.cmd", "@modelcontextprotocol/inspector", "uv", "run"] |
| 146 | + assert kwargs["check"] is True |
| 147 | + assert "shell" not in kwargs |
0 commit comments