mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
feat(mcp): curated exclude list for cloudflare + glob tool filters + default_excluded manifests
The cloudflare entry's 3,320-endpoint surface is ~43% product families a personal/dev account never touches (Zero Trust org-fleet suite, Magic Transit/WAN, Cloudforce One, Radar analytics, API Shield, legacy migration surfaces). Ship a 34-pattern curated exclude list in the manifest: 3,320 -> 1,905 tools kept, and everything Cloudflare adds later stays enabled by default. Mechanism, two small extensions: - tools/mcp_tool.py: tools.include/exclude entries containing glob metacharacters now match via fnmatch (plain names stay exact-match), so a product family is one pattern instead of hundreds of stale literals. - hermes_cli/mcp_catalog.py: manifests may declare tools.default_excluded (mutually exclusive with default_enabled); install writes it to tools.exclude and skips the probe/checklist — a 3,320-row curses checklist is not a UX. Prior user include selections still win on reinstall. Verified by replaying the real filter functions over the live-probed 3,320-tool list: 1,415 excluded, zero overmatch against a per-product target audit; DNS/Workers/R2/D1/tunnels/Access/AI kept.
This commit is contained in:
parent
ce0defe4d8
commit
58c97b9ddd
6 changed files with 295 additions and 14 deletions
|
|
@ -224,6 +224,31 @@ class TestManifestParsing:
|
|||
|
||||
assert list_catalog() == []
|
||||
|
||||
def test_tools_default_excluded_parsed(self, catalog_dir):
|
||||
body = _basic_manifest(
|
||||
tools={"default_excluded": ["docs", "*_radar_*"]},
|
||||
)
|
||||
_write_manifest(catalog_dir, "demo", body)
|
||||
e = _entry("demo")
|
||||
assert e.tools.default_excluded == ["docs", "*_radar_*"]
|
||||
assert e.tools.default_enabled is None
|
||||
|
||||
def test_tools_default_excluded_bad_shape_rejected(self, catalog_dir):
|
||||
body = _basic_manifest(tools={"default_excluded": "docs"}) # str, not list
|
||||
_write_manifest(catalog_dir, "demo", body)
|
||||
from hermes_cli.mcp_catalog import list_catalog
|
||||
|
||||
assert list_catalog() == []
|
||||
|
||||
def test_tools_enabled_and_excluded_mutually_exclusive(self, catalog_dir):
|
||||
body = _basic_manifest(
|
||||
tools={"default_enabled": ["a"], "default_excluded": ["b"]},
|
||||
)
|
||||
_write_manifest(catalog_dir, "demo", body)
|
||||
from hermes_cli.mcp_catalog import list_catalog
|
||||
|
||||
assert list_catalog() == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Install flow
|
||||
|
|
@ -245,6 +270,60 @@ class TestInstall:
|
|||
assert servers["demo"]["args"] == ["-y", "demo-mcp"]
|
||||
assert servers["demo"]["enabled"] is True
|
||||
|
||||
def test_install_default_excluded_writes_exclude_without_probe(
|
||||
self, catalog_dir, monkeypatch
|
||||
):
|
||||
"""Exclude-mode manifests skip the probe/checklist and write
|
||||
tools.exclude verbatim (names + glob patterns)."""
|
||||
body = _basic_manifest(
|
||||
tools={"default_excluded": ["docs", "*_radar_*"]},
|
||||
)
|
||||
_write_manifest(catalog_dir, "demo", body)
|
||||
import hermes_cli.mcp_catalog as mc
|
||||
from hermes_cli.config import load_config
|
||||
|
||||
def _fail_probe(name):
|
||||
raise AssertionError("probe must not run for exclude-mode manifests")
|
||||
|
||||
monkeypatch.setattr(mc, "_probe_tools", _fail_probe)
|
||||
mc.install_entry(_entry("demo"), enable=True)
|
||||
|
||||
server = load_config()["mcp_servers"]["demo"]
|
||||
assert server["tools"]["exclude"] == ["docs", "*_radar_*"]
|
||||
assert "include" not in server["tools"]
|
||||
|
||||
def test_reinstall_prior_include_wins_over_default_excluded(
|
||||
self, catalog_dir, monkeypatch
|
||||
):
|
||||
"""A user's prior include selection survives reinstall of an
|
||||
exclude-mode manifest (prior selection > manifest default)."""
|
||||
body = _basic_manifest(
|
||||
tools={"default_excluded": ["*_radar_*"]},
|
||||
)
|
||||
_write_manifest(catalog_dir, "demo", body)
|
||||
import hermes_cli.mcp_catalog as mc
|
||||
from hermes_cli.config import load_config, save_config
|
||||
|
||||
cfg = load_config()
|
||||
cfg.setdefault("mcp_servers", {})["demo"] = {
|
||||
"command": "npx",
|
||||
"args": ["-y", "demo-mcp"],
|
||||
"enabled": True,
|
||||
"tools": {"include": ["tool_a"]},
|
||||
}
|
||||
save_config(cfg)
|
||||
|
||||
import sys as _sys
|
||||
probed = [("tool_a", "a"), ("tool_b", "b")]
|
||||
monkeypatch.setattr(mc, "_probe_tools", lambda name: probed)
|
||||
monkeypatch.setattr(_sys.stdin, "isatty", lambda: False)
|
||||
|
||||
mc.install_entry(_entry("demo"), enable=True)
|
||||
|
||||
server = load_config()["mcp_servers"]["demo"]
|
||||
assert server["tools"]["include"] == ["tool_a"]
|
||||
assert "exclude" not in server["tools"]
|
||||
|
||||
def test_install_rejects_exfil_shaped_stdio_manifest(self, catalog_dir):
|
||||
body = _basic_manifest(
|
||||
"evil",
|
||||
|
|
|
|||
|
|
@ -3832,6 +3832,59 @@ class TestMCPSelectiveToolLoading:
|
|||
"mcp__ink_exclude__list_services",
|
||||
]
|
||||
|
||||
def test_exclude_filter_supports_glob_patterns(self):
|
||||
"""Glob entries in tools.exclude match families of tool names."""
|
||||
config = {
|
||||
"url": "https://mcp.example.com",
|
||||
"tools": {"exclude": ["*_radar_*", "docs"]},
|
||||
}
|
||||
registered, _ = self._run_discover(
|
||||
"ink_glob",
|
||||
[
|
||||
"get_radar_http_summary",
|
||||
"post_radar_scans",
|
||||
"docs",
|
||||
"get_zones_dns_records",
|
||||
],
|
||||
config,
|
||||
session=SimpleNamespace(),
|
||||
)
|
||||
assert registered == ["mcp__ink_glob__get_zones_dns_records"]
|
||||
|
||||
def test_include_filter_supports_glob_patterns(self):
|
||||
"""Glob entries in tools.include whitelist families of tool names."""
|
||||
config = {
|
||||
"url": "https://mcp.example.com",
|
||||
"tools": {"include": ["*_dns_*"]},
|
||||
}
|
||||
registered, _ = self._run_discover(
|
||||
"ink_glob_inc",
|
||||
["get_zones_dns_records", "post_zones_dns_records", "docs"],
|
||||
config,
|
||||
session=SimpleNamespace(),
|
||||
)
|
||||
assert registered == [
|
||||
"mcp__ink_glob_inc__get_zones_dns_records",
|
||||
"mcp__ink_glob_inc__post_zones_dns_records",
|
||||
]
|
||||
|
||||
def test_plain_exclude_names_do_not_glob_match(self):
|
||||
"""Entries without metacharacters stay exact-match (no surprise hits)."""
|
||||
config = {
|
||||
"url": "https://mcp.example.com",
|
||||
"tools": {"exclude": ["docs"]},
|
||||
}
|
||||
registered, _ = self._run_discover(
|
||||
"ink_exact",
|
||||
["docs", "docs_search", "get_docs"],
|
||||
config,
|
||||
session=SimpleNamespace(),
|
||||
)
|
||||
assert registered == [
|
||||
"mcp__ink_exact__docs_search",
|
||||
"mcp__ink_exact__get_docs",
|
||||
]
|
||||
|
||||
def test_include_filter_skips_utility_tools_without_capabilities(self):
|
||||
config = {
|
||||
"url": "https://mcp.example.com",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue