Skip to content

[mcp-server] Hardcoded DNS-rebinding protection blocks cross-host LAN clients (HTTP 421 Invalid Host header) #1470

Description

@sgral

Problem

The vendored mcp/server/fastmcp/server.py:180 (in the mcp Python package shipped with zepai/knowledge-graph-mcp:1.0.2-graphiti-0.28.2) hardcodes enable_dns_rebinding_protection=True whenever host is localhost/127.0.0.1/::1. Combined with the default allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"], any MCP client connecting from a non-localhost hostname is rejected with HTTP 421 Invalid Host header — even if the deployment is behind LAN-only ingress, behind Authentik/CF Access, or on a private overlay network.

In practice this means:

  • Claude Code running on a separate host can't reach Graphiti via http://homelab-host:8765/mcp
  • Cline / Cursor / Continue / etc. running on a dev box can't share the same Graphiti backend that OWU on the homelab itself uses
  • The MCP_SERVER_HOST=0.0.0.0 env var (already supported) implies cross-host intent — but the host-header check makes it cosmetic, not functional

Repro

# from any non-localhost host
curl -X POST http://homelab-host:8765/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}'
# → HTTP 421 "Invalid Host header"

Workaround shipped downstream

1-line sed patch baked into a thin downstream image (ghcr.io/sgral/graphiti-mcp:1.0.2-host-fix):

FROM zepai/knowledge-graph-mcp:1.0.2-graphiti-0.28.2
RUN sed -i \
    's/enable_dns_rebinding_protection=True/enable_dns_rebinding_protection=False/' \
    /app/mcp/.venv/lib/python3.11/site-packages/mcp/server/fastmcp/server.py

This works but is brittle (file path tied to vendored mcp version) and disables protection globally instead of allowing scoped allowlists.

Proposed fix

Expose mcp.server.transport_security.TransportSecuritySettings knobs in Graphiti's config.yaml server: block:

server:
  transport: "http"
  host: "0.0.0.0"
  port: 8000
  transport_security:
    enable_dns_rebinding_protection: true     # default keeps current safety
    allowed_hosts:
      - "localhost:*"
      - "127.0.0.1:*"
      - "my-homelab-host:*"
      - "memory.example.com"

Graphiti's MCP entrypoint would construct TransportSecuritySettings(...) from config and pass through FastMCP(transport_security=...). Default behavior unchanged — operators who need cross-host access add hostnames explicitly.

Happy to send a PR if there's appetite for this shape.

Environment

  • Image: zepai/knowledge-graph-mcp:1.0.2-graphiti-0.28.2
  • Graphiti version: 0.28.2
  • MCP transport: streamable HTTP
  • Deployment: Docker on Proxmox LXC, behind Cloudflare Tunnel + Authentik OIDC

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions