From ff9b0528a206927b9c3da09144c2af8bade39215 Mon Sep 17 00:00:00 2001 From: alt-glitch Date: Fri, 24 Apr 2026 15:55:51 +0530 Subject: [PATCH] fix(tools): normalize numeric entries and clear stale no_mcp in _save_platform_tools YAML parses bare numeric toolset names (e.g. 12306:) as int, causing TypeError in sorted() since the read path normalizes to str but the save path did not. The no_mcp sentinel was preserved in existing entries even when the user re-enabled MCP servers, causing MCP to stay silently disabled. --- hermes_cli/tools_config.py | 3 ++ tests/hermes_cli/test_tools_config.py | 50 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/hermes_cli/tools_config.py b/hermes_cli/tools_config.py index 45c6ba3dd..1bfb36df0 100644 --- a/hermes_cli/tools_config.py +++ b/hermes_cli/tools_config.py @@ -701,6 +701,7 @@ def _save_platform_tools(config: dict, platform: str, enabled_toolset_keys: Set[ existing_toolsets = config.get("platform_toolsets", {}).get(platform, []) if not isinstance(existing_toolsets, list): existing_toolsets = [] + existing_toolsets = [str(ts) for ts in existing_toolsets] # Preserve any entries that are NOT configurable toolsets and NOT platform # defaults (i.e. only MCP server names should be preserved) @@ -708,6 +709,8 @@ def _save_platform_tools(config: dict, platform: str, enabled_toolset_keys: Set[ entry for entry in existing_toolsets if entry not in configurable_keys and entry not in platform_default_keys } + if "no_mcp" not in enabled_toolset_keys: + preserved_entries.discard("no_mcp") # Merge preserved entries with new enabled toolsets config["platform_toolsets"][platform] = sorted(enabled_toolset_keys | preserved_entries) diff --git a/tests/hermes_cli/test_tools_config.py b/tests/hermes_cli/test_tools_config.py index d7a2f7c7c..91b38e375 100644 --- a/tests/hermes_cli/test_tools_config.py +++ b/tests/hermes_cli/test_tools_config.py @@ -670,3 +670,53 @@ def test_get_platform_tools_feishu_tools_not_on_other_platforms(): enabled = _get_platform_tools({}, plat) assert "feishu_doc" not in enabled, f"feishu_doc leaked onto {plat}" assert "feishu_drive" not in enabled, f"feishu_drive leaked onto {plat}" + + +def test_save_platform_tools_normalizes_numeric_entries(): + """YAML may parse bare numeric toolset names as int. They should be + normalized to str so they survive the save round-trip. + """ + config = { + "platform_toolsets": { + "cli": ["web", "terminal", 12306, "custom-mcp"] + } + } + + with patch("hermes_cli.tools_config.save_config"): + _save_platform_tools(config, "cli", {"web", "browser"}) + + saved = config["platform_toolsets"]["cli"] + assert "12306" in saved + assert 12306 not in saved + + +def test_save_platform_tools_clears_stale_no_mcp(): + """When the new selection doesn't include no_mcp, the sentinel should + be stripped from preserved entries so MCP servers are re-enabled. + """ + config = { + "platform_toolsets": { + "cli": ["web", "terminal", "no_mcp"] + } + } + + with patch("hermes_cli.tools_config.save_config"): + _save_platform_tools(config, "cli", {"web", "browser"}) + + saved = config["platform_toolsets"]["cli"] + assert "no_mcp" not in saved + + +def test_save_platform_tools_preserves_explicit_no_mcp(): + """When the new selection explicitly includes no_mcp, it should be kept.""" + config = { + "platform_toolsets": { + "cli": ["web", "no_mcp"] + } + } + + with patch("hermes_cli.tools_config.save_config"): + _save_platform_tools(config, "cli", {"web", "no_mcp"}) + + saved = config["platform_toolsets"]["cli"] + assert "no_mcp" in saved