Compare commits

...

1 Commits

Author SHA1 Message Date
Bartok9
af4a63d794 fix: Preserve MCP tools when saving platform toolsets (#1247)
When using `hermes tools` to configure toolsets, MCP server names
were being removed from platform_toolsets because _save_platform_tools
only saved CONFIGURABLE_TOOLSETS entries.

This fix preserves any non-configurable entries (like MCP server names)
that were already in the config, merging them with the user's new selection.

Closes #1247
2026-03-14 21:51:55 -07:00
2 changed files with 93 additions and 4 deletions

View File

@@ -354,9 +354,24 @@ def _get_platform_tools(config: dict, platform: str) -> Set[str]:
def _save_platform_tools(config: dict, platform: str, enabled_toolset_keys: Set[str]): def _save_platform_tools(config: dict, platform: str, enabled_toolset_keys: Set[str]):
"""Save the selected toolset keys for a platform to config.""" """Save the selected toolset keys for a platform to config.
Preserve dynamic MCP toolsets (``mcp-<server>``) that were already set for
the platform. These entries are not exposed in the tools configurator UI,
but they should survive when the user changes the configurable toolsets.
"""
config.setdefault("platform_toolsets", {}) config.setdefault("platform_toolsets", {})
config["platform_toolsets"][platform] = sorted(enabled_toolset_keys)
existing_toolsets = config.get("platform_toolsets", {}).get(platform, [])
if not isinstance(existing_toolsets, list):
existing_toolsets = []
preserved_entries = {
entry for entry in existing_toolsets
if isinstance(entry, str) and entry.startswith("mcp-")
}
config["platform_toolsets"][platform] = sorted(enabled_toolset_keys | preserved_entries)
save_config(config) save_config(config)

View File

@@ -1,6 +1,13 @@
"""Tests for hermes_cli.tools_config platform tool persistence.""" """Tests for hermes_cli.tools_config platform tool persistence."""
from hermes_cli.tools_config import _get_platform_tools, _platform_toolset_summary, _toolset_has_keys from unittest.mock import patch
from hermes_cli.tools_config import (
_get_platform_tools,
_platform_toolset_summary,
_save_platform_tools,
_toolset_has_keys,
)
def test_get_platform_tools_uses_default_when_platform_not_configured(): def test_get_platform_tools_uses_default_when_platform_not_configured():
@@ -31,7 +38,7 @@ def test_platform_toolset_summary_uses_explicit_platform_list():
def test_toolset_has_keys_for_vision_accepts_codex_auth(tmp_path, monkeypatch): def test_toolset_has_keys_for_vision_accepts_codex_auth(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path)) monkeypatch.setenv("HERMES_HOME", str(tmp_path))
(tmp_path / "auth.json").write_text( (tmp_path / "auth.json").write_text(
'{"active_provider":"openai-codex","providers":{"openai-codex":{"tokens":{"access_token":"codex-access-token","refresh_token":"codex-refresh-token"}}}}' '{"active_provider":"openai-codex","providers":{"openai-codex":{"tokens":{"access_token": "codex-...oken","refresh_token": "codex-...oken"}}}}'
) )
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_BASE_URL", raising=False) monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
@@ -40,3 +47,70 @@ def test_toolset_has_keys_for_vision_accepts_codex_auth(tmp_path, monkeypatch):
monkeypatch.delenv("CONTEXT_VISION_PROVIDER", raising=False) monkeypatch.delenv("CONTEXT_VISION_PROVIDER", raising=False)
assert _toolset_has_keys("vision") is True assert _toolset_has_keys("vision") is True
def test_save_platform_tools_preserves_dynamic_mcp_toolsets():
"""Dynamic MCP toolsets should survive platform tool reconfiguration."""
config = {
"platform_toolsets": {
"cli": ["web", "terminal", "mcp-time", "mcp-github", "mcp-custom-server"]
}
}
new_selection = {"web", "browser"}
with patch("hermes_cli.tools_config.save_config"):
_save_platform_tools(config, "cli", new_selection)
saved_toolsets = config["platform_toolsets"]["cli"]
assert "mcp-time" in saved_toolsets
assert "mcp-github" in saved_toolsets
assert "mcp-custom-server" in saved_toolsets
assert "web" in saved_toolsets
assert "browser" in saved_toolsets
assert "terminal" not in saved_toolsets
def test_save_platform_tools_does_not_preserve_platform_presets():
config = {
"platform_toolsets": {
"cli": ["hermes-cli", "mcp-time"]
}
}
with patch("hermes_cli.tools_config.save_config"):
_save_platform_tools(config, "cli", {"web"})
saved_toolsets = config["platform_toolsets"]["cli"]
assert "web" in saved_toolsets
assert "mcp-time" in saved_toolsets
assert "hermes-cli" not in saved_toolsets
def test_save_platform_tools_handles_empty_existing_config():
config = {}
with patch("hermes_cli.tools_config.save_config"):
_save_platform_tools(config, "telegram", {"web", "terminal"})
saved_toolsets = config["platform_toolsets"]["telegram"]
assert "web" in saved_toolsets
assert "terminal" in saved_toolsets
def test_save_platform_tools_handles_invalid_existing_config():
config = {
"platform_toolsets": {
"cli": "invalid-string-value"
}
}
with patch("hermes_cli.tools_config.save_config"):
_save_platform_tools(config, "cli", {"web"})
saved_toolsets = config["platform_toolsets"]["cli"]
assert saved_toolsets == ["web"]