From 3c8d9f851e34b2cc73d1f097a1e11fa5f68d857f Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Wed, 8 Jul 2026 02:59:18 -0400 Subject: [PATCH] fix(mcp): scope list config reads to active profile (#5619) --- api/routes.py | 5 ++-- tests/test_issue538_mcp_management.py | 30 ++++++++++++++++++----- tests/test_issue697_mcp_tool_inventory.py | 7 ++++-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/api/routes.py b/api/routes.py index eff7e3c3..81314e57 100644 --- a/api/routes.py +++ b/api/routes.py @@ -2822,6 +2822,7 @@ from api.config import ( _load_yaml_config_file, _save_yaml_config_file, reload_config, + get_config_for_profile_home, _cfg_lock, PENDING_BG_TASK_COMPLETIONS, ) @@ -24181,7 +24182,7 @@ def _mcp_tools_from_registry(server_summaries): def _handle_mcp_tools_list(handler): """List known MCP tools from already-available runtime inventory only.""" - cfg = get_config() + cfg = get_config_for_profile_home(get_active_hermes_home()) servers = cfg.get("mcp_servers", {}) if not isinstance(servers, dict): servers = {} @@ -24695,7 +24696,7 @@ def _handle_notes_item(handler, parsed): def _handle_mcp_servers_list(handler): """List configured MCP servers with safe, read-only runtime visibility.""" - cfg = get_config() + cfg = get_config_for_profile_home(get_active_hermes_home()) servers = cfg.get("mcp_servers", {}) if not isinstance(servers, dict): servers = {} diff --git a/tests/test_issue538_mcp_management.py b/tests/test_issue538_mcp_management.py index 77c3ea72..803d3d94 100644 --- a/tests/test_issue538_mcp_management.py +++ b/tests/test_issue538_mcp_management.py @@ -42,17 +42,33 @@ SAMPLE_MCP = { class TestMcpList: """GET /api/mcp/servers — list with masked secrets.""" - @patch('api.routes.get_config') - def test_returns_servers_list(self, mock_cfg): + @patch('api.routes.get_config_for_profile_home') + @patch('api.routes.get_active_hermes_home') + def test_returns_servers_list(self, mock_home, mock_cfg): + mock_home.return_value = sentinel_home = object() mock_cfg.return_value = {'mcp_servers': SAMPLE_MCP} h = _make_handler() _handle_mcp_servers_list(h) assert h.send_response.called status = h.send_response.call_args[0][0] assert status == 200 + mock_cfg.assert_called_once_with(sentinel_home) - @patch('api.routes.get_config') - def test_empty_config(self, mock_cfg): + @patch('api.routes.get_config_for_profile_home') + @patch('api.routes.get_active_hermes_home') + def test_reads_active_profile_home_for_servers(self, mock_home, mock_cfg): + mock_home.return_value = sentinel_home = object() + mock_cfg.return_value = {'mcp_servers': {'active': SAMPLE_MCP['searxng']}} + h = _make_handler() + _handle_mcp_servers_list(h) + payload = _json_payload(h) + assert [srv['name'] for srv in payload['servers']] == ['active'] + mock_cfg.assert_called_once_with(sentinel_home) + + @patch('api.routes.get_config_for_profile_home') + @patch('api.routes.get_active_hermes_home') + def test_empty_config(self, mock_home, mock_cfg): + mock_home.return_value = object() mock_cfg.return_value = {} h = _make_handler() _handle_mcp_servers_list(h) @@ -65,8 +81,10 @@ class TestMcpList: assert payload['reload_required'] is True @patch('api.routes._mcp_runtime_status_by_name') - @patch('api.routes.get_config') - def test_list_payload_includes_status_tool_counts_and_safe_invalid_config(self, mock_cfg, mock_runtime): + @patch('api.routes.get_config_for_profile_home') + @patch('api.routes.get_active_hermes_home') + def test_list_payload_includes_status_tool_counts_and_safe_invalid_config(self, mock_home, mock_cfg, mock_runtime): + mock_home.return_value = object() mock_cfg.return_value = { 'mcp_servers': { 'searxng': {'command': 'mcp-searxng', 'args': ['--port', '8888']}, diff --git a/tests/test_issue697_mcp_tool_inventory.py b/tests/test_issue697_mcp_tool_inventory.py index 4dfd4ba1..decfe92c 100644 --- a/tests/test_issue697_mcp_tool_inventory.py +++ b/tests/test_issue697_mcp_tool_inventory.py @@ -29,8 +29,10 @@ def _read(relative_path: str) -> str: class TestMcpToolInventoryApi: @patch("api.routes._mcp_runtime_status_by_name") - @patch("api.routes.get_config") - def test_endpoint_returns_sanitized_registered_mcp_tools(self, mock_cfg, mock_runtime): + @patch("api.routes.get_config_for_profile_home") + @patch("api.routes.get_active_hermes_home") + def test_endpoint_returns_sanitized_registered_mcp_tools(self, mock_home, mock_cfg, mock_runtime): + mock_home.return_value = sentinel_home = object() mock_cfg.return_value = { "mcp_servers": { "web-reader": {"url": "http://localhost:3001/mcp", "headers": {"Authorization": "Bearer secret-token"}}, @@ -72,6 +74,7 @@ class TestMcpToolInventoryApi: {"name": "url", "type": "string", "required": True, "description": "URL to fetch"}, {"name": "limit", "type": "integer", "required": False, "description": "Maximum bytes"}, ] + mock_cfg.assert_called_once_with(sentinel_home) raw = json.dumps(payload) assert "secret-token" not in raw assert "default" not in raw