diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index ed619979bfb..fcda37d6dfe 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -7593,17 +7593,35 @@ async def list_mcp_catalog(profile: Optional[str] = None): } for entry in catalog_entries: auth = entry.auth + transport = entry.transport + install = entry.install entries.append({ "name": entry.name, "description": entry.description, "source": entry.source, - "transport": entry.transport.type, + "transport": transport.type, "auth_type": getattr(auth, "type", "none"), # Env vars the user must supply (names + prompts only, never values). "required_env": [ {"name": e.name, "prompt": e.prompt, "required": e.required} for e in getattr(auth, "env", []) or [] ], + # Transport details so the UI can show exactly what connects/runs. + # The trust model (docs: user-guide/features/mcp) tells users to + # inspect command/args/url and the install bootstrap before + # installing — surface them rather than hiding them in the repo. + "command": transport.command, + "args": list(transport.args or []), + "url": transport.url, + # Git bootstrap (present only for entries that clone + build). + "install_url": install.url if install else None, + "install_ref": install.ref if install else None, + "bootstrap": list(install.bootstrap) if install else [], + # Default tool pre-selection hint and post-install guidance. + "default_enabled": list(entry.tools.default_enabled) + if entry.tools.default_enabled is not None + else None, + "post_install": entry.post_install or "", "needs_install": entry.install is not None, "installed": installed_state.get(entry.name, (False, False))[0], "enabled": installed_state.get(entry.name, (False, False))[1], diff --git a/tests/hermes_cli/test_dashboard_admin_endpoints.py b/tests/hermes_cli/test_dashboard_admin_endpoints.py index 3eb2ca37d2d..6650d055a42 100644 --- a/tests/hermes_cli/test_dashboard_admin_endpoints.py +++ b/tests/hermes_cli/test_dashboard_admin_endpoints.py @@ -94,9 +94,31 @@ class TestMcpEndpoints: body = r.json() assert "entries" in body and "diagnostics" in body # The shipped optional-mcps/ catalog has at least one entry; each must - # carry the install/enabled status fields the UI relies on. + # carry the install/enabled status fields plus the inspection detail + # the dashboard renders (transport target, install source, guidance) so + # users can vet an entry before installing. for e in body["entries"]: - assert {"name", "transport", "installed", "enabled", "needs_install"} <= set(e) + assert { + "name", + "transport", + "auth_type", + "installed", + "enabled", + "needs_install", + "command", + "args", + "url", + "install_url", + "install_ref", + "bootstrap", + "default_enabled", + "post_install", + } <= set(e) + # http entries expose a url; stdio entries expose a command. + if e["transport"] == "http": + assert e["url"] + elif e["transport"] == "stdio": + assert e["command"] def test_catalog_install_unknown_404(self): r = self.client.post("/api/mcp/catalog/install", json={"name": "no-such-mcp-xyz"}) diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 9b5b7afc835..ec03997b6c6 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -1301,6 +1301,17 @@ export interface McpCatalogEntry { transport: "http" | "stdio"; auth_type: "api_key" | "oauth" | "none"; required_env: Array<{ name: string; prompt: string; required: boolean }>; + // Transport details — what actually connects (http) or runs (stdio). + command: string | null; + args: string[]; + url: string | null; + // Git bootstrap (only set for entries that clone + build locally). + install_url: string | null; + install_ref: string | null; + bootstrap: string[]; + // Default tool pre-selection (null = all tools pre-checked) + guidance text. + default_enabled: string[] | null; + post_install: string; needs_install: boolean; installed: boolean; enabled: boolean; diff --git a/web/src/pages/McpPage.tsx b/web/src/pages/McpPage.tsx index 29088a6fc6a..cc933645b71 100644 --- a/web/src/pages/McpPage.tsx +++ b/web/src/pages/McpPage.tsx @@ -26,6 +26,10 @@ import { cn, themedBody } from "@/lib/utils"; type Transport = "http" | "stdio"; +function isHttpUrl(value: string): boolean { + return /^https?:\/\//i.test(value.trim()); +} + function truncateText(value: string, maxLength: number): string { return value.length > maxLength ? value.slice(0, maxLength) + "..." : value; } @@ -707,9 +711,21 @@ export default function McpPage() { > {entry.transport} - - {entry.source === "official" ? "official" : entry.source} - + auth: {entry.auth_type} + {isHttpUrl(entry.source) ? ( + + source ↗ + + ) : ( + entry.source && ( + {entry.source} + ) + )} {entry.installed && ( Installed )} @@ -722,6 +738,67 @@ export default function McpPage() { {entry.description}

)} + {/* Connection detail: what the agent actually talks to. */} + {entry.transport === "http" && entry.url && ( +

+ Endpoint:{" "} + {entry.url} +

+ )} + {entry.transport === "stdio" && entry.command && ( +

+ Runs:{" "} + + {[entry.command, ...entry.args].join(" ")} + +

+ )} + {/* Git bootstrap — surfaced so users see what gets cloned/run + before they install (matches the docs trust model). */} + {entry.install_url && ( +

+ Installs from:{" "} + {isHttpUrl(entry.install_url) ? ( + + {entry.install_url} + + ) : ( + {entry.install_url} + )} + {entry.install_ref && ( + @ {entry.install_ref} + )} +

+ )} + {entry.bootstrap.length > 0 && ( +
+ + Bootstrap commands ({entry.bootstrap.length}) + + +
+ )} + {entry.post_install && ( +
+ + Setup notes + +

+ {entry.post_install.trim()} +

+
+ )} {entryDiags.map((d, i) => (

/manifest.yaml`](https://github.com/NousResearch/hermes-agent/tree/main/optional-mcps) on GitHub. The picker also prints the manifest's `source:` URL at install -time so you can quickly verify the upstream repo. +time so you can quickly verify the upstream repo. The web dashboard's MCP +page surfaces the same detail per catalog entry — transport, auth type, the +endpoint URL (HTTP) or command + args (stdio), the git install source/ref and +bootstrap commands, and setup notes — with the `source:` rendered as a +clickable link, so you can inspect exactly what an entry connects to or runs +before clicking Install. ### Manifest version compatibility