feat: MCP toolsets in WebUI + onboarding fix for non-standard providers — v0.50.63

Squash-merges PR #578 (rebased from #574 by @renheqiang + #575 by @nesquena-hermes). MCP server toolsets now included in WebUI sessions; onboarding wizard no longer fires for non-standard providers. 1331 tests pass. Nathan override applied for self-built #575.
This commit is contained in:
nesquena-hermes
2026-04-15 23:39:07 -07:00
committed by GitHub
parent 45426bdcd1
commit a512f2020e
8 changed files with 263 additions and 15 deletions

View File

@@ -342,7 +342,6 @@ MAX_UPLOAD_BYTES = 20 * 1024 * 1024
# ── File type maps ───────────────────────────────────────────────────────────
IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico", ".bmp"}
MD_EXTS = {".md", ".markdown", ".mdown"}
OFFICE_EXTS = {".xls", ".xlsx", ".doc", ".docx"}
CODE_EXTS = {
".py",
".js",
@@ -404,7 +403,19 @@ _DEFAULT_TOOLSETS = [
"web",
"webhook",
]
CLI_TOOLSETS = get_config().get("platform_toolsets", {}).get("cli", _DEFAULT_TOOLSETS)
def _resolve_cli_toolsets(cfg=None):
"""Resolve CLI toolsets using the agent's _get_platform_tools() so that
MCP server toolsets are automatically included, matching CLI behaviour."""
if cfg is None:
cfg = get_config()
try:
from hermes_cli.tools_config import _get_platform_tools
return list(_get_platform_tools(cfg, "cli"))
except Exception:
# Fallback: read raw list from config (MCP toolsets will be missing)
return cfg.get("platform_toolsets", {}).get("cli", _DEFAULT_TOOLSETS)
CLI_TOOLSETS = _resolve_cli_toolsets()
# ── Model / provider discovery ───────────────────────────────────────────────

View File

@@ -210,6 +210,22 @@ def _provider_api_key_present(
and str(custom_cfg.get("api_key") or "").strip()
):
return True
# For providers not in _SUPPORTED_PROVIDER_SETUPS (e.g. minimax-cn, deepseek,
# xai, etc.), ask the hermes_cli auth registry — it knows every provider's env
# var names and can check os.environ for a valid key.
# Exclude known OAuth/token-flow providers — those are handled separately by
# _provider_oauth_authenticated() and should not be short-circuited here.
_known_oauth = {"openai-codex", "copilot", "copilot-acp", "qwen-oauth", "nous"}
if provider not in _SUPPORTED_PROVIDER_SETUPS and provider not in _known_oauth:
try:
from hermes_cli.auth import get_auth_status as _gas
status = _gas(provider)
if isinstance(status, dict) and status.get("logged_in"):
return True
except Exception:
pass
return False
@@ -288,11 +304,13 @@ def _status_from_runtime(cfg: dict, imports_ok: bool) -> dict:
elif provider in _SUPPORTED_PROVIDER_SETUPS:
provider_ready = _provider_api_key_present(provider, cfg, env_values)
else:
# Unknown / OAuth provider (e.g. openai-codex, copilot, qwen-oauth).
# These do not use a plain API key; auth lives in auth.json or a
# credential pool managed by hermes_cli.
provider_ready = _provider_oauth_authenticated(
provider, _get_active_hermes_home()
# Unknown provider — may be an OAuth flow (openai-codex, copilot, etc.)
# OR an API-key provider not in the quick-setup list (minimax-cn, deepseek,
# xai, etc.). Check both: api key presence first (covers the majority of
# third-party providers), then OAuth auth.json.
provider_ready = (
_provider_api_key_present(provider, cfg, env_values)
or _provider_oauth_authenticated(provider, _get_active_hermes_home())
)
chat_ready = bool(_HERMES_FOUND and imports_ok and provider_ready)

View File

@@ -29,7 +29,7 @@ from api.config import (
STREAMS_LOCK,
CANCEL_FLAGS,
SERVER_START_TIME,
CLI_TOOLSETS,
_resolve_cli_toolsets,
_INDEX_HTML_PATH,
get_available_models,
IMAGE_EXTS,
@@ -2070,7 +2070,7 @@ def _handle_chat_sync(handler, body):
api_key=_api_key,
platform="cli",
quiet_mode=True,
enabled_toolsets=CLI_TOOLSETS,
enabled_toolsets=_resolve_cli_toolsets(),
session_id=s.session_id,
)
workspace_ctx = f"[Workspace: {s.workspace}]\n"

View File

@@ -16,7 +16,7 @@ from typing import Optional
logger = logging.getLogger(__name__)
from api.config import (
STREAMS, STREAMS_LOCK, CANCEL_FLAGS, AGENT_INSTANCES, CLI_TOOLSETS,
STREAMS, STREAMS_LOCK, CANCEL_FLAGS, AGENT_INSTANCES,
LOCK, SESSIONS, SESSION_DIR,
_get_session_agent_lock, _set_thread_env, _clear_thread_env,
resolve_model_provider,
@@ -804,9 +804,10 @@ def _run_agent_streaming(session_id, msg_text, model, workspace, stream_id, atta
from api.config import get_config as _get_config
_cfg = _get_config()
# Per-profile toolsets (fall back to module-level CLI_TOOLSETS)
_pt = _cfg.get('platform_toolsets', {})
_toolsets = _pt.get('cli', CLI_TOOLSETS) if isinstance(_pt, dict) else CLI_TOOLSETS
# Per-profile toolsets — use _resolve_cli_toolsets() so MCP
# server toolsets are included, matching native CLI behaviour.
from api.config import _resolve_cli_toolsets
_toolsets = _resolve_cli_toolsets(_cfg)
# Fallback model from profile config (e.g. for rate-limit recovery)
_fallback = _cfg.get('fallback_model') or None