From f4172f73b071f9a7d042b46627c62474abb74e91 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Wed, 8 Jul 2026 04:10:13 -0400 Subject: [PATCH] fix(mcp): preserve config overrides for profile reads (#5619) --- api/config.py | 7 ++++ .../test_issue3294_profile_toolset_scoping.py | 25 +++++++++++++ tests/test_issue538_mcp_management.py | 36 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/api/config.py b/api/config.py index 96ff96ef..6c93ce19 100644 --- a/api/config.py +++ b/api/config.py @@ -703,6 +703,13 @@ def get_config_for_profile_home(profile_home: "Path | str | None") -> dict: target = Path(profile_home).expanduser() except Exception: return get_config() + try: + from api.profiles import get_active_hermes_home + + if Path(get_active_hermes_home()).expanduser() == target: + return get_config() + except Exception: + pass # If the ambient resolver already points at this profile home, defer to # get_config() so in-memory overrides (monkeypatched cfg) are honored. This # MUST run before the nonexistent-home guard below: a matching ambient home diff --git a/tests/test_issue3294_profile_toolset_scoping.py b/tests/test_issue3294_profile_toolset_scoping.py index d3e4b40d..6d9d3905 100644 --- a/tests/test_issue3294_profile_toolset_scoping.py +++ b/tests/test_issue3294_profile_toolset_scoping.py @@ -108,6 +108,31 @@ def test_matching_home_defers_to_get_config(_two_profiles): ] +def test_active_home_with_external_config_override_defers_to_get_config(_two_profiles, monkeypatch, tmp_path): + """The active home still honors HERMES_CONFIG_PATH when the override lives + outside that home.""" + cfg, default_home, profile_b_home = _two_profiles + from api import profiles + + active_home = tmp_path / "active-home" + override_path = tmp_path / "override-dir" / "config.yaml" + active_home.mkdir() + _write_cfg(active_home, ["wrong-active-home-file"]) + override_path.parent.mkdir() + import yaml + + override_path.write_text( + yaml.safe_dump({"platform_toolsets": {"cli": ["override-config"]}}, sort_keys=False), + encoding="utf-8", + ) + monkeypatch.setenv("HERMES_CONFIG_PATH", str(override_path)) + monkeypatch.setattr(profiles, "get_active_hermes_home", lambda: active_home) + cfg.reload_config() + + same = cfg.get_config_for_profile_home(active_home) + assert same.get("platform_toolsets", {}).get("cli") == ["override-config"] + + def test_matching_ambient_home_that_does_not_exist_still_defers_to_get_config(_two_profiles, monkeypatch, tmp_path): """Regression (#4516 gate): the nonexistent-home guard must run AFTER the ambient-resolver short-circuit. A home that matches the ambient config path diff --git a/tests/test_issue538_mcp_management.py b/tests/test_issue538_mcp_management.py index 803d3d94..dcbaa446 100644 --- a/tests/test_issue538_mcp_management.py +++ b/tests/test_issue538_mcp_management.py @@ -1,6 +1,7 @@ """Tests for issue #538 — MCP server management API.""" import json, pytest from unittest.mock import patch, MagicMock, call +import yaml from api.routes import ( _handle_mcp_servers_list, _handle_mcp_server_update, @@ -141,6 +142,41 @@ class TestMcpList: """YAML numeric false-y values should not show a disabled server as enabled.""" assert _parse_mcp_enabled(0) is False + def test_active_home_list_reads_external_config_override_used_by_writes(self, monkeypatch, tmp_path): + """HERMES_CONFIG_PATH outside the active home remains the read/write authority.""" + from api import config, profiles, routes + + active_home = tmp_path / 'active-home' + override_path = tmp_path / 'override-dir' / 'config.yaml' + active_home.mkdir() + override_path.parent.mkdir() + active_home.joinpath('config.yaml').write_text( + yaml.safe_dump({'mcp_servers': {'wrong-home': {'command': 'wrong'}}}, sort_keys=False), + encoding='utf-8', + ) + override_path.write_text( + yaml.safe_dump({'mcp_servers': {'override-srv': {'command': 'override'}}}, sort_keys=False), + encoding='utf-8', + ) + monkeypatch.setenv('HERMES_CONFIG_PATH', str(override_path)) + monkeypatch.setattr(profiles, 'get_active_hermes_home', lambda: active_home) + monkeypatch.setattr(routes, 'get_active_hermes_home', lambda: active_home) + monkeypatch.setattr(routes, '_mcp_runtime_status_by_name', lambda: {}) + config.reload_config() + + h = _make_handler() + _handle_mcp_servers_list(h) + payload = _json_payload(h) + assert [srv['name'] for srv in payload['servers']] == ['override-srv'] + + h = _make_handler() + h.command = 'PUT' + _handle_mcp_server_update(h, 'new-srv', {'command': 'new-command'}) + saved = yaml.safe_load(override_path.read_text(encoding='utf-8')) + active_home_saved = yaml.safe_load(active_home.joinpath('config.yaml').read_text(encoding='utf-8')) + assert 'new-srv' in saved['mcp_servers'] + assert 'new-srv' not in active_home_saved['mcp_servers'] + class TestMcpSave: """PUT /api/mcp/servers/ — add or update."""