Skip to content

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-Method must match the body's method, and Mcp-Name the tool name, resource URI or prompt name; a mismatch is HTTP 400.
  • MCP-Protocol-Version must agree with the version in the request's _meta envelope (-32020 otherwise); an unsupported version is -32022, with the supported versions in error.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.