feat(dashboard): surface full per-MCP catalog detail; fix pip-install doc (#48520)

The dashboard MCP catalog only showed name/description/transport and a
non-clickable source. Users couldn't see what an entry connects to or runs
before installing — the exact detail the docs trust model tells them to vet.

- /api/mcp/catalog now returns transport target (url, or command+args),
  auth_type, git install source/ref + bootstrap commands, default-enabled
  tool hint, and post-install guidance per entry.
- McpPage renders the endpoint URL (http) or command+args (stdio), the git
  install source/ref, a collapsible bootstrap-commands list, setup notes,
  and the source as a clickable link when it's a URL.
- Docs: drop the 'uv pip install -e .[mcp]' quick-start step (Hermes does
  not support pip installs; MCP ships with the standard install) and note
  the dashboard now surfaces this detail.
- Strengthen the catalog endpoint test to assert the new inspection fields.
This commit is contained in:
Teknium 2026-06-18 09:40:56 -07:00 committed by GitHub
parent 4af16b5da2
commit c37fdec2d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 141 additions and 13 deletions

View file

@ -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],

View file

@ -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"})

View file

@ -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;

View file

@ -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}
</Badge>
<Badge tone="outline">
{entry.source === "official" ? "official" : entry.source}
</Badge>
<Badge tone="outline">auth: {entry.auth_type}</Badge>
{isHttpUrl(entry.source) ? (
<a
href={entry.source}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-primary underline underline-offset-2 hover:opacity-80"
>
source
</a>
) : (
entry.source && (
<Badge tone="outline">{entry.source}</Badge>
)
)}
{entry.installed && (
<Badge tone="success">Installed</Badge>
)}
@ -722,6 +738,67 @@ export default function McpPage() {
{entry.description}
</p>
)}
{/* Connection detail: what the agent actually talks to. */}
{entry.transport === "http" && entry.url && (
<p className="mt-1 text-xs text-muted-foreground">
<span className="font-medium">Endpoint:</span>{" "}
<code className="font-mono">{entry.url}</code>
</p>
)}
{entry.transport === "stdio" && entry.command && (
<p className="mt-1 text-xs text-muted-foreground break-all">
<span className="font-medium">Runs:</span>{" "}
<code className="font-mono">
{[entry.command, ...entry.args].join(" ")}
</code>
</p>
)}
{/* Git bootstrap surfaced so users see what gets cloned/run
before they install (matches the docs trust model). */}
{entry.install_url && (
<p className="mt-1 text-xs text-muted-foreground break-all">
<span className="font-medium">Installs from:</span>{" "}
{isHttpUrl(entry.install_url) ? (
<a
href={entry.install_url}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline underline-offset-2 hover:opacity-80"
>
{entry.install_url}
</a>
) : (
<code className="font-mono">{entry.install_url}</code>
)}
{entry.install_ref && (
<span> @ {entry.install_ref}</span>
)}
</p>
)}
{entry.bootstrap.length > 0 && (
<details className="mt-1 text-xs text-muted-foreground">
<summary className="cursor-pointer select-none">
Bootstrap commands ({entry.bootstrap.length})
</summary>
<ul className="mt-1 ml-3 list-disc space-y-0.5">
{entry.bootstrap.map((cmd, i) => (
<li key={`${entry.name}-bs-${i}`} className="break-all">
<code className="font-mono">{cmd}</code>
</li>
))}
</ul>
</details>
)}
{entry.post_install && (
<details className="mt-1 text-xs text-muted-foreground">
<summary className="cursor-pointer select-none">
Setup notes
</summary>
<p className="mt-1 whitespace-pre-wrap">
{entry.post_install.trim()}
</p>
</details>
)}
{entryDiags.map((d, i) => (
<p
key={`${entry.name}-diag-${i}`}

View file

@ -20,12 +20,7 @@ If you have ever wanted Hermes to use a tool that already exists somewhere else,
## Quick start
1. Install MCP support (already included if you used the standard install script):
```bash
cd ~/.hermes/hermes-agent
uv pip install -e ".[mcp]"
```
1. MCP support ships with the standard install — no extra step needed.
2. Add an MCP server to `~/.hermes/config.yaml`:
@ -132,7 +127,12 @@ the hermes-agent repo, so Nous has reviewed each entry before it shipped —
Manifests live at
[`optional-mcps/<name>/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