name: fermihdi-hd-mcp description: Set up and use hd_mcp to query a running FermiHDI HD instance through the Model Context Protocol, with or without a dataset schema already in hand. Covers registering the target with an MCP client (Claude Code, Claude Desktop, or any other MCP client), the two available tools, how schema is fetched automatically, and the real auth/mTLS limits of the service today.
Set up and query HD through MCP
hd_mcp is a network service, not a local process — it sits between an MCP client and an HD
instance's query front door (the HD Proxy) and exposes exactly two tools. It has no schema-less
mode: it always fetches the target dataset's schema itself, automatically, so "without a schema"
means the user doesn't have to supply one, not that hd_mcp can answer questions blind.
Step 1: confirm hd_mcp is running and reachable
Image: cr.fermihdi.io/hd/hd_mcp:<tag>. Ports: 8082 MCP (JSON-RPC over plain HTTP POST, no
SSE despite older docs calling it that — one route, /messages), 8083 a separate SSE stream
used only by Data Explore's UI (not the MCP protocol, ignore it here), 8080 health/metrics
(/health, Prometheus). If it's part of a fermihdi-hd-sandbox lab it's already running; for a
standalone deployment, launch it with the env vars in the next step.
Step 2: point it at the right HD Proxy
hd_mcp reads a small proxy list (proxies.yaml, hot-reloaded on change) mapping a short name to a
front-door URL:
sandbox_ipflow: http://mock-auth-proxy:8080
The image ships a lab-only default baked in — always override it for a real deployment, either
by bind-mounting your own file over /app/proxies.yaml (or /etc/hd_mcp/proxies.yaml, a
pre-created alternate mount point) or setting PROXY_CONFIG_PATH. Other env vars: MCP_LISTEN_PORT
(default 8082), MCP_STREAM_PORT (default 8083), FERMIHDI_DATA_RETURN_HOST/_PORT (where SN
results stream back to — port 6987 by default), FERMIHDI_DATA_PLANE_SUBNET/
FERMIHDI_CONTROL_PLANE_SUBNET (pick the right interface when the host has more than one). There
is no credentials field in proxies.yaml at all — a name and a URL are all it takes; auth is
handled at the transport layer (Step 5), not here.
On every reload, hd_mcp fetches that proxy's dataset schema itself — GET <proxy-url>/v1/hd/schema
— and registers it. If that fetch fails for any reason, the whole proxy entry is silently
unusable: no explicit MCP-level error names the cause, the proxy name simply doesn't work from a
tool call. Check hd_mcp's own logs, not the MCP client, when a proxy_name you expect isn't
answering. Only the first dataset in a proxy's schema is ever served — a schema with more than
one dataset silently exposes just Datasets[0].
Step 3: register it with an MCP client
hd_mcp is a plain network service — no stdio wrapper needed, any MCP client on the network can reach it directly:
claude mcp add --transport http hd-instance http://<host>:8082/messages
The /messages path is required. For a different MCP client, use its own "add an HTTP/streamable
MCP server" flow with the same URL.
Step 4: the two tools
| Tool | Args | What it does |
|---|---|---|
execute_hd_query |
proxy_name (required), sql_query (required, DuckDB SQL), start_time/end_time (Unix epoch seconds, optional), query_qos (1–255, optional) |
Fans a real query out to the storage cluster, decodes the results per the dataset's schema, runs sql_query locally against them, returns JSON. No time range means the entire dataset — hd_mcp self-acknowledges the full-dataset scan on the caller's behalf, since there's no one to ask. |
execute_raw_sql_query |
proxy_name, sql_query |
Re-queries the local cache left by the last execute_hd_query call for that proxy — no network round trip. Use it for follow-up questions against data already fetched. |
There is no schema-introspection tool — an MCP client can't ask hd_mcp "what columns does this dataset have" through the protocol itself.
"Without a schema" — how to find the field names first
Since there's no discovery tool, either:
curl <proxy-url>/v1/hd/schemadirectly — the same endpoint hd_mcp itself calls — and read the field names/types before writing SQL, or- once a
proxy_nameis confirmed registered (Step 2), runSELECT * FROM <table> LIMIT 1throughexecute_hd_queryand read the returned JSON's keys empirically.
The table name in FROM is whatever the dataset schema declares as its name.
Step 5: auth — two separate trust boundaries, only one is safe by default
- Client → hd_mcp: hd_mcp enforces no authentication or authorization of its own. It must
sit behind an authenticating front door (the customer's own reverse proxy) that sets the identity
headers the deployment's access policy evaluates — see the front-doors contract. Don't expose
:8082directly to untrusted callers. - hd_mcp → HD Proxy: this is the real gap. hd_mcp's schema-fetch and query-dispatch HTTP
clients present no client certificate at all. Against a real HD Proxy with its mTLS listener
requiring a client cert on every northbound route (including a plain schema fetch), the TLS
handshake fails outright — not a graceful error, a connection failure. This works invisibly in a
lab because the sandbox's target (
mock-auth-proxy) is plain HTTP with no TLS at all. Before pointing hd_mcp at a real, mTLS-enforcing Proxy, confirm the Proxy's front door for this deployment actually terminates TLS itself (so hd_mcp reaches it over plain HTTP internally) — today there is no other way to make this work. If the deployment truly requires hd_mcp to present a client cert to an mTLS-enforcing Proxy directly, that's unbuilt; flag it rather than promising it works.
Troubleshooting
A proxy_name that never answers, or answers with no dataset → check hd_mcp's own logs for the
schema-fetch failure (Step 2) before assuming the query is wrong. Zero rows with a healthy schema
fetch → see fermihdi-hd-troubleshoot's "Query returns 0 rows or hangs" section — the cause is
almost always upstream of hd_mcp (the storage cluster or the query lifecycle), not hd_mcp itself.