refactor(hermes_cli): mcp_picker routes output through catalog _say; _apply_request_chain returns the result; catalog probe-fail ladder tabled
This commit is contained in:
@@ -259,16 +259,10 @@ def _parse_manifest(path: Path) -> CatalogEntry:
|
||||
suggest = _parse_suggest(path, data.get("suggest"))
|
||||
install = _parse_install(path, data.get("install"))
|
||||
return CatalogEntry(
|
||||
name=name,
|
||||
description=description,
|
||||
source=str(data.get("source") or "").strip(),
|
||||
transport=transport,
|
||||
auth=auth,
|
||||
tools=tools,
|
||||
install=install,
|
||||
post_install=str(data.get("post_install") or ""),
|
||||
suggest=suggest,
|
||||
manifest_path=path)
|
||||
name=name, description=description, source=str(data.get("source") or "").strip(),
|
||||
transport=transport, auth=auth, tools=tools, install=install,
|
||||
post_install=str(data.get("post_install") or ""), suggest=suggest, manifest_path=path,
|
||||
)
|
||||
|
||||
|
||||
# Populated by list_catalog(); inspected by the picker / catalog UIs so the user gets actionable
|
||||
@@ -538,7 +532,8 @@ def _apply_tool_selection(
|
||||
_write_tools_filter(name, "exclude", entry.tools.default_excluded)
|
||||
_say(
|
||||
f" Applied manifest exclude list ({len(entry.tools.default_excluded)} entries); "
|
||||
f"everything else stays enabled. {edit_hint}")
|
||||
f"everything else stays enabled. {edit_hint}"
|
||||
)
|
||||
return
|
||||
|
||||
_say(f" Probing '{name}' for available tools...", Colors.CYAN)
|
||||
@@ -550,24 +545,23 @@ def _apply_tool_selection(
|
||||
manifest_default = entry.tools.default_enabled
|
||||
refine_hint = f"Run {configure_hint} after the server is reachable to refine."
|
||||
if prior_selection is not None:
|
||||
_write_tools_filter(name, "include", prior_selection)
|
||||
msg = (
|
||||
f" Couldn't probe server. Kept your previous tool selection "
|
||||
f"({len(prior_selection)} tools). {refine_hint}")
|
||||
mode, values = "include", prior_selection
|
||||
msg = f"Kept your previous tool selection ({len(prior_selection)} tools). {refine_hint}"
|
||||
elif prior_exclude is not None:
|
||||
_write_tools_filter(name, "exclude", prior_exclude)
|
||||
msg = f" Couldn't probe server. Kept your existing exclude list ({len(prior_exclude)} entries)."
|
||||
mode, values = "exclude", prior_exclude
|
||||
msg = f"Kept your existing exclude list ({len(prior_exclude)} entries)."
|
||||
elif manifest_default:
|
||||
_write_tools_filter(name, "include", manifest_default)
|
||||
msg = (
|
||||
f" Couldn't probe server. Applied manifest default "
|
||||
f"({len(manifest_default)} tools). {refine_hint}")
|
||||
mode, values = "include", manifest_default
|
||||
msg = f"Applied manifest default ({len(manifest_default)} tools). {refine_hint}"
|
||||
else:
|
||||
_write_tools_filter(name, "include", None)
|
||||
mode, values = "include", None
|
||||
msg = (
|
||||
" Couldn't probe server; installed with no tool filter (all tools enabled when "
|
||||
f"reachable). Run {configure_hint} after first connect to prune.")
|
||||
_say(msg, Colors.YELLOW)
|
||||
"installed with no tool filter (all tools enabled when "
|
||||
f"reachable). Run {configure_hint} after first connect to prune."
|
||||
)
|
||||
_write_tools_filter(name, mode, values)
|
||||
sep = ";" if values is None else "."
|
||||
_say(f" Couldn't probe server{sep} {msg}", Colors.YELLOW)
|
||||
return
|
||||
|
||||
if not probed:
|
||||
|
||||
@@ -6,12 +6,12 @@ import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
from hermes_cli.colors import Colors, color
|
||||
from hermes_cli.colors import Colors
|
||||
from hermes_cli.cli_output import prompt_yes_no
|
||||
from hermes_cli.curses_ui import curses_single_select
|
||||
from hermes_cli.mcp_catalog import (
|
||||
CatalogEntry, CatalogError, catalog_diagnostics, install_entry, is_enabled, is_installed,
|
||||
list_catalog, installed_servers, remove_server, server_enabled, uninstall_entry,
|
||||
list_catalog, installed_servers, remove_server, server_enabled, uninstall_entry, _say,
|
||||
)
|
||||
from hermes_cli.config import load_config, save_config
|
||||
|
||||
@@ -71,15 +71,15 @@ def _enable_disable(name: str, *, enable: bool) -> None:
|
||||
servers = cfg.get("mcp_servers") or {}
|
||||
server = servers.get(name)
|
||||
if not server:
|
||||
print(color(f" '{name}' is not installed.", Colors.RED))
|
||||
_say(f" '{name}' is not installed.", Colors.RED)
|
||||
return
|
||||
server["enabled"] = enable
|
||||
cfg["mcp_servers"] = servers
|
||||
save_config(cfg)
|
||||
print(color(
|
||||
_say(
|
||||
f" ✓ '{name}' {'enabled' if enable else 'disabled'}. "
|
||||
"Start a new Hermes session for changes to take effect.",
|
||||
Colors.GREEN))
|
||||
"Start a new Hermes session for changes to take effect."
|
||||
)
|
||||
|
||||
|
||||
def _configure_tools(name: str) -> None:
|
||||
@@ -93,12 +93,12 @@ def _configure_tools(name: str) -> None:
|
||||
def _remove_custom(name: str) -> None:
|
||||
"""Remove a non-catalog MCP entry from config.yaml."""
|
||||
if not is_installed(name):
|
||||
print(color(f" '{name}' is not configured.", Colors.RED))
|
||||
_say(f" '{name}' is not configured.", Colors.RED)
|
||||
return
|
||||
if not prompt_yes_no(f"Remove '{name}' from mcp_servers?", default=False):
|
||||
return
|
||||
remove_server(name)
|
||||
print(color(f" ✓ Removed '{name}'", Colors.GREEN))
|
||||
_say(f" ✓ Removed '{name}'")
|
||||
|
||||
|
||||
def _install(entry: CatalogEntry, verb: str) -> bool:
|
||||
@@ -106,7 +106,7 @@ def _install(entry: CatalogEntry, verb: str) -> bool:
|
||||
try:
|
||||
install_entry(entry, enable=True)
|
||||
except CatalogError as exc:
|
||||
print(color(f" ✗ {verb} failed: {exc}", Colors.RED))
|
||||
_say(f" ✗ {verb} failed: {exc}", Colors.RED)
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -115,12 +115,12 @@ def _uninstall(name: str) -> None:
|
||||
if not prompt_yes_no(f"Uninstall '{name}'?", default=False):
|
||||
return
|
||||
if uninstall_entry(name):
|
||||
print(color(
|
||||
_say(
|
||||
f" ✓ Uninstalled '{name}'. "
|
||||
"Credentials in .env preserved — delete manually if no longer needed.",
|
||||
Colors.GREEN))
|
||||
"Credentials in .env preserved — delete manually if no longer needed."
|
||||
)
|
||||
else:
|
||||
print(color(f" '{name}' was not installed", Colors.DIM))
|
||||
_say(f" '{name}' was not installed", Colors.DIM)
|
||||
|
||||
|
||||
def _run_submenu(title: str, actions: list) -> None:
|
||||
@@ -148,7 +148,7 @@ def _handle_row(row: _Row) -> None:
|
||||
return
|
||||
# Catalog row, installed + enabled
|
||||
print()
|
||||
print(color(f" '{row.name}' is already enabled.", Colors.DIM))
|
||||
_say(f" '{row.name}' is already enabled.", Colors.DIM)
|
||||
_run_submenu(f"Action for '{row.name}'", [
|
||||
("Configure tools (probe server + re-pick)", lambda: _configure_tools(row.name)),
|
||||
("Disable (keep config, stop loading on next session)",
|
||||
@@ -162,27 +162,24 @@ def _print_rows_text(rows: List[_Row]) -> None:
|
||||
"""Plain-text catalog dump: `hermes mcp catalog` output and the non-curses fallback."""
|
||||
print()
|
||||
if not rows:
|
||||
print(color(" No MCPs in the catalog or configured.", Colors.DIM))
|
||||
_say(" No MCPs in the catalog or configured.", Colors.DIM)
|
||||
print()
|
||||
return
|
||||
|
||||
print(color(" MCP Catalog + configured servers:", Colors.CYAN + Colors.BOLD))
|
||||
_say(" MCP Catalog + configured servers:", Colors.CYAN + Colors.BOLD)
|
||||
print()
|
||||
print(f" {'Name':<18} {'Status':<24} Description")
|
||||
print(f" {'-' * 18} {'-' * 24} {'-' * 11}")
|
||||
for row in rows:
|
||||
print(f" {_format_row(row)}")
|
||||
print()
|
||||
print(color(" Install: hermes mcp install <name> Picker: hermes mcp", Colors.DIM))
|
||||
_say(" Install: hermes mcp install <name> Picker: hermes mcp", Colors.DIM)
|
||||
# Manifest-version warnings: the user's Hermes is too old to install everything listed.
|
||||
future = [d for d in catalog_diagnostics() if d[1] == "future_manifest"]
|
||||
if future:
|
||||
print()
|
||||
for name, _, _msg in future:
|
||||
print(color(
|
||||
f" ⚠ '{name}' requires a newer Hermes — run `hermes update` "
|
||||
"to install this entry.",
|
||||
Colors.YELLOW))
|
||||
_say(f" ⚠ '{name}' requires a newer Hermes — run `hermes update` to install this entry.", Colors.YELLOW)
|
||||
print()
|
||||
print()
|
||||
|
||||
@@ -215,9 +212,9 @@ def install_by_name(identifier: str) -> int:
|
||||
|
||||
entry = get_entry(identifier)
|
||||
if entry is None:
|
||||
print(color(
|
||||
f" ✗ '{identifier}' is not in the catalog. "
|
||||
"Run `hermes mcp catalog` to see available entries.",
|
||||
Colors.RED))
|
||||
_say(
|
||||
f" ✗ '{identifier}' is not in the catalog. Run `hermes mcp catalog` to see available entries.",
|
||||
Colors.RED,
|
||||
)
|
||||
return 1
|
||||
return 0 if _install(entry, "install") else 1
|
||||
|
||||
@@ -61,8 +61,8 @@ def _safe_copy(payload: Any) -> Any:
|
||||
|
||||
|
||||
def _apply_request_chain(
|
||||
kind: str, payload_key: str, trace: List[Dict[str, Any]], **kwargs: Any
|
||||
) -> Dict[str, Any]:
|
||||
kind: str, payload_key: str, trace: List[Dict[str, Any]], original: Any, **kwargs: Any
|
||||
) -> RequestMiddlewareResult:
|
||||
"""Feed ``kwargs[payload_key]`` through every ``kind`` middleware; each may return ``{payload_key: {...}}``."""
|
||||
from hermes_cli.plugins import invoke_middleware
|
||||
|
||||
@@ -79,7 +79,7 @@ def _apply_request_chain(
|
||||
for key in ("source", "reason", "name")
|
||||
if isinstance(value := result.get(key), str) and value}
|
||||
trace.append(entry or {"source": "plugin"})
|
||||
return current
|
||||
return RequestMiddlewareResult(payload=current, original_payload=original, changed=bool(trace), trace=trace)
|
||||
|
||||
|
||||
def apply_llm_request_middleware(request: Dict[str, Any], **context: Any) -> RequestMiddlewareResult:
|
||||
@@ -90,12 +90,9 @@ def apply_llm_request_middleware(request: Dict[str, Any], **context: Any) -> Req
|
||||
return RequestMiddlewareResult(payload=request, original_payload=request)
|
||||
|
||||
original_request = _safe_copy(request)
|
||||
trace: List[Dict[str, Any]] = []
|
||||
current_request = _apply_request_chain(
|
||||
LLM_REQUEST_MIDDLEWARE, "request", trace,
|
||||
request=_safe_copy(original_request), original_request=original_request, **context)
|
||||
return RequestMiddlewareResult(
|
||||
payload=current_request, original_payload=original_request, changed=bool(trace), trace=trace,
|
||||
return _apply_request_chain(
|
||||
LLM_REQUEST_MIDDLEWARE, "request", [], original_request,
|
||||
request=_safe_copy(original_request), original_request=original_request, **context,
|
||||
)
|
||||
|
||||
|
||||
@@ -126,11 +123,10 @@ def apply_tool_request_middleware(
|
||||
payload=args if not trace else current_args, original_payload=args,
|
||||
changed=bool(trace), trace=trace,
|
||||
)
|
||||
current_args = _apply_request_chain(
|
||||
TOOL_REQUEST_MIDDLEWARE, "args", trace,
|
||||
tool_name=tool_name, args=current_args, original_args=original_args, **context)
|
||||
return RequestMiddlewareResult(
|
||||
payload=current_args, original_payload=original_args, changed=bool(trace), trace=trace)
|
||||
return _apply_request_chain(
|
||||
TOOL_REQUEST_MIDDLEWARE, "args", trace, original_args,
|
||||
tool_name=tool_name, args=current_args, original_args=original_args, **context,
|
||||
)
|
||||
|
||||
|
||||
def run_llm_execution_middleware(
|
||||
|
||||
Reference in New Issue
Block a user