fix: synchronize MCP clients on launch
This commit is contained in:
1 parent
8522fa3ca3
commit
0e13bb5c29
11 files changed
+475
-124
No files matched your search
@@ -120,7 +120,7 @@ command = "leave-alone"
|
||||
assert path.with_name(path.name + ".artemis-update.bak").read_text(encoding="utf-8") == original
|
||||
|
||||
|
||||
def test_json_only_updates_existing_matching_project(tmp_path):
|
||||
def test_json_migrates_existing_standard_mcp_to_current_checkout(tmp_path):
|
||||
path = tmp_path / "mcp.json"
|
||||
root = tmp_path / "project"
|
||||
config = {
|
||||
@@ -132,19 +132,170 @@ def test_json_only_updates_existing_matching_project(tmp_path):
|
||||
"autoApprove": [],
|
||||
}
|
||||
path.write_text(json.dumps({"mcpServers": {"artemis": config, "other": {"command": "keep"}}}))
|
||||
assert not update.refresh_json(
|
||||
path, ("mcpServers",), tmp_path / "different", "new", ("mobile_batch",)
|
||||
)
|
||||
assert update.refresh_json(path, ("mcpServers",), root, "new", ("mobile_batch",))
|
||||
moved_root = tmp_path / "new release"
|
||||
assert update.refresh_json(path, ("mcpServers",), moved_root, "new", ("mobile_batch",))
|
||||
result = json.loads(path.read_text())["mcpServers"]
|
||||
assert result["artemis"]["env"] == {"CUSTOM": "preserved"}
|
||||
assert result["artemis"]["env"]["CUSTOM"] == "preserved"
|
||||
assert result["artemis"]["env"]["PYTHONPATH"] == str(moved_root)
|
||||
assert result["artemis"]["cwd"] == str(moved_root)
|
||||
assert result["artemis"]["disabled"] is True
|
||||
assert result["other"] == {"command": "keep"}
|
||||
assert not update.refresh_json(path, ("mcpServers",), moved_root, "new", ("mobile_batch",))
|
||||
missing = tmp_path / "not-installed.json"
|
||||
assert not update.refresh_json(missing, ("mcpServers",), root, "new", ())
|
||||
assert not missing.exists()
|
||||
|
||||
|
||||
def test_mcp_sync_installs_detected_clients_and_migrates_codex(tmp_path, monkeypatch):
|
||||
from artemis.interfaces.cli.commands import mcp as mcp_command
|
||||
|
||||
home = tmp_path / "home"
|
||||
home.mkdir()
|
||||
codex_home = home / ".codex"
|
||||
codex_home.mkdir()
|
||||
codex_config = codex_home / "config.toml"
|
||||
codex_config.write_text(
|
||||
'model = "my-model"\n'
|
||||
"[mcp_servers.artemis]\n"
|
||||
'command = "old-python"\n'
|
||||
'args = ["-m", "mcp_server.server"]\n'
|
||||
'cwd = "C:/old-release"\n'
|
||||
'enabled_tools = ["mobile_run_task", "retired_tool"]\n'
|
||||
"enabled = false\n"
|
||||
"[mcp_servers.artemis.env]\n"
|
||||
'PYTHONPATH = "C:/old-release"\n'
|
||||
'OPENAI_API_KEY = "keep-secret"\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
cursor_home = home / ".cursor"
|
||||
cursor_home.mkdir()
|
||||
(cursor_home / "mcp.json").write_text('{"mcpServers":{"other":{"command":"keep"}}}')
|
||||
monkeypatch.setattr("pathlib.Path.home", lambda: home)
|
||||
monkeypatch.setattr(mcp_command, "_get_vscode_user_dir", lambda: home / "vscode-user")
|
||||
monkeypatch.setenv("CODEX_HOME", str(codex_home))
|
||||
monkeypatch.setenv("APPDATA", str(home / "AppData"))
|
||||
root = tmp_path / "new release"
|
||||
assert update.refresh_mcp(root) == 0
|
||||
codex = tomllib.loads(codex_config.read_text(encoding="utf-8"))
|
||||
entry = codex["mcp_servers"]["artemis"]
|
||||
assert codex["model"] == "my-model"
|
||||
assert entry["command"] == update.sys.executable
|
||||
assert entry["cwd"] == str(root)
|
||||
assert entry["args"] == ["-m", "mcp_server"]
|
||||
assert entry["enabled"] is False
|
||||
assert entry["env"]["OPENAI_API_KEY"] == "keep-secret"
|
||||
assert entry["env"]["PYTHONPATH"] == str(root)
|
||||
assert entry["enabled_tools"] == list(mcp_command.ARTEMIS_MCP_TOOLS)
|
||||
cursor = json.loads((cursor_home / "mcp.json").read_text(encoding="utf-8"))
|
||||
assert cursor["mcpServers"]["artemis"]["env"]["PYTHONPATH"] == str(root)
|
||||
assert cursor["mcpServers"]["other"] == {"command": "keep"}
|
||||
first = codex_config.read_text(encoding="utf-8")
|
||||
assert update.refresh_mcp(root) == 0
|
||||
assert codex_config.read_text(encoding="utf-8") == first
|
||||
|
||||
|
||||
def test_mcp_sync_fails_on_invalid_detected_config_without_overwriting(tmp_path, monkeypatch):
|
||||
from artemis.interfaces.cli.commands import mcp as mcp_command
|
||||
|
||||
home = tmp_path / "home"
|
||||
cursor_home = home / ".cursor"
|
||||
cursor_home.mkdir(parents=True)
|
||||
config = cursor_home / "mcp.json"
|
||||
config.write_text("{invalid json", encoding="utf-8")
|
||||
monkeypatch.setattr("pathlib.Path.home", lambda: home)
|
||||
monkeypatch.setattr(mcp_command, "_get_vscode_user_dir", lambda: home / "vscode-user")
|
||||
monkeypatch.delenv("CODEX_HOME", raising=False)
|
||||
assert update.refresh_mcp(tmp_path / "project") == 1
|
||||
assert config.read_text(encoding="utf-8") == "{invalid json"
|
||||
|
||||
|
||||
def test_mcp_sync_installs_codex_when_client_directory_exists(tmp_path, monkeypatch):
|
||||
from artemis.interfaces.cli.commands import mcp as mcp_command
|
||||
|
||||
home = tmp_path / "home"
|
||||
codex_home = home / ".codex"
|
||||
codex_home.mkdir(parents=True)
|
||||
monkeypatch.setattr("pathlib.Path.home", lambda: home)
|
||||
monkeypatch.setattr(mcp_command, "_get_vscode_user_dir", lambda: home / "vscode-user")
|
||||
monkeypatch.setenv("CODEX_HOME", str(codex_home))
|
||||
root = tmp_path / "project"
|
||||
assert update.refresh_mcp(root) == 0
|
||||
config = tomllib.loads((codex_home / "config.toml").read_text(encoding="utf-8"))
|
||||
assert config["mcp_servers"]["artemis"]["cwd"] == str(root)
|
||||
assert config["mcp_servers"]["artemis"]["enabled_tools"] == list(mcp_command.ARTEMIS_MCP_TOOLS)
|
||||
|
||||
|
||||
def test_codex_install_preserves_existing_user_tables(tmp_path):
|
||||
path = tmp_path / "config.toml"
|
||||
path.write_text(
|
||||
'model = "keep"\n[mcp_servers.other]\ncommand = "other-tool"\n', encoding="utf-8"
|
||||
)
|
||||
template = {
|
||||
"command": "new-python",
|
||||
"args": ["-m", "mcp_server"],
|
||||
"cwd": str(tmp_path),
|
||||
"env": {"PYTHONPATH": str(tmp_path)},
|
||||
"enabled_tools": ["mobile_run_task"],
|
||||
}
|
||||
assert update.refresh_codex(
|
||||
path,
|
||||
tmp_path,
|
||||
"new-python",
|
||||
("mobile_run_task",),
|
||||
template=template,
|
||||
install_missing=True,
|
||||
)
|
||||
data = tomllib.loads(path.read_text(encoding="utf-8"))
|
||||
assert data["model"] == "keep"
|
||||
assert data["mcp_servers"]["other"]["command"] == "other-tool"
|
||||
assert data["mcp_servers"]["artemis"]["env"]["PYTHONPATH"] == str(tmp_path)
|
||||
|
||||
|
||||
def test_codex_inline_environment_sync_is_idempotent(tmp_path):
|
||||
path = tmp_path / "config.toml"
|
||||
path.write_text(
|
||||
'[mcp_servers.artemis]\ncommand = "old-python"\n'
|
||||
'args = ["-m", "mcp_server"]\n'
|
||||
'env = { PYTHONPATH = "C:/old", OPENAI_API_KEY = "keep-secret" }\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
template = {
|
||||
"command": "new-python",
|
||||
"args": ["-m", "mcp_server"],
|
||||
"cwd": str(tmp_path),
|
||||
"env": {"PYTHONPATH": str(tmp_path)},
|
||||
"enabled_tools": ["mobile_run_task"],
|
||||
}
|
||||
assert update.refresh_codex(
|
||||
path, tmp_path, "new-python", ("mobile_run_task",), template=template
|
||||
)
|
||||
data = tomllib.loads(path.read_text(encoding="utf-8"))["mcp_servers"]["artemis"]
|
||||
assert data["env"]["OPENAI_API_KEY"] == "keep-secret"
|
||||
assert data["env"]["PYTHONPATH"] == str(tmp_path)
|
||||
assert not update.refresh_codex(
|
||||
path, tmp_path, "new-python", ("mobile_run_task",), template=template
|
||||
)
|
||||
|
||||
|
||||
def test_custom_mcp_wrapper_is_left_unchanged(tmp_path):
|
||||
path = tmp_path / "mcp.json"
|
||||
original = '{"mcpServers":{"artemis":{"command":"uv","args":["run","custom"]}}}'
|
||||
path.write_text(original, encoding="utf-8")
|
||||
assert (
|
||||
update.refresh_json(
|
||||
path,
|
||||
("mcpServers",),
|
||||
tmp_path,
|
||||
"new-python",
|
||||
("mobile_run_task",),
|
||||
install_missing=True,
|
||||
template={"command": "new-python", "args": ["-m", "mcp_server"], "env": {}},
|
||||
)
|
||||
is None
|
||||
)
|
||||
assert path.read_text(encoding="utf-8") == original
|
||||
|
||||
|
||||
def test_frontend_fingerprint_detects_lock_source_and_deleted_assets(tmp_path):
|
||||
ui = tmp_path / "apps/showcase_ui"
|
||||
(ui / "src").mkdir(parents=True)
|
||||
|
||||
Reference in new issue
Block a user