Transports¶
stdio (default)¶
edi-mcp (or edi-mcp --transport stdio) serves one client over standard input and
output, the usual setup for Claude Code, Claude Desktop and other local clients. Only
MCP framing goes to stdout; logs go to stderr. The key in the server's environment
(EDI_API_KEY) is used for every call, and the stdio-only download tools, once
released, are offered only here. The SDK's stdio loop serves both protocol eras: a
client that starts with the legacy initialize handshake and a 2026-07-28 client
both work.
Streamable HTTP (stateless)¶
edi-mcp --transport http serves on 127.0.0.1:8080 by default (--bind-address,
--bind-port)1:
| Route | Behaviour |
|---|---|
POST /mcp (and /mcp/) |
JSON-RPC over Streamable HTTP; one response per request (a single SSE frame, or JSON with server.json_response). |
GET /mcp, DELETE /mcp |
405 with Allow: POST: the server sends no server-initiated messages and keeps no sessions. |
GET /healthz |
Liveness, always 200 while the process serves: {"status": "ok", "version", "protocolVersion", "transport"}. |
GET /readyz |
Readiness: 503 {"status": "warming"} during startup work, then 200 {"status": "ready", "degraded", "keyConfigured"}. It never reveals the key. |
The server runs stateless: there is no Mcp-Session-Id, and any instance can answer
any request, so it scales horizontally behind a load balancer. Every response carries
X-Request-Id (yours, if you sent a well-formed one). Request bodies are capped at
1 MiB (server.max_request_body_size).
Header checks¶
For 2026-07-28 requests the SDK checks the HTTP headers against the JSON-RPC body:
Mcp-Methodmust match the body'smethod, andMcp-Namethe tool name, resource URI or prompt name; a mismatch is HTTP 400.MCP-Protocol-Versionmust agree with the version in the request's_metaenvelope (-32020otherwise); an unsupported version is-32022, with the supported versions inerror.data.supported.
A request whose MCP-Protocol-Version header names an older revision is served in
the legacy era, still without a session.
Host and origin checks¶
DNS-rebinding protection is on automatically for loopback binds. For a public
deployment set server.public_base_url (its host and origin join the allow-lists)
and optionally server.allowed_hosts / server.allowed_origins; a request with any
other Host gets 421. Behind a proxy that rewrites Host, set
server.dns_rebinding_protection = false.
Per-request EDI API keys¶
Each HTTP caller sends its own EDI API key in the X-EDI-Key header (configurable
with server.request_key_header; turn it off with server.accept_header_key =
false). The header is honoured only when server.public_base_url starts with
https://, meaning TLS terminates in front of the server, or when
server.allow_insecure_header_key is set for development. Otherwise the header is
stripped before any handler sees it and one warning is logged.
A caller without a key is served from DataONE: the operator's configured key is not
lent to HTTP callers unless server.share_config_key_over_http is set, which is meant
for private deployments only. The MCP-level Authorization header is never forwarded
to EDI. Download tools are never offered over HTTP.
-
MCP specification — Transports. https://modelcontextprotocol.io/specification/2026-07-28/basic/transports ↩