fix: configure MCP transport security with env-driven allowed hosts
All checks were successful
ci/woodpecker/push/build Pipeline was successful

FastMCP auto-enables DNS rebinding protection when host=127.0.0.1
(the default). Production requests from brain.woltje.com were rejected
with 421 Invalid Host header because the allowed_hosts list was empty.

Added MCP_ALLOWED_HOSTS config field (comma-separated). When set,
DNS rebinding protection is enabled with those hosts; when empty,
protection is disabled. Set MCP_ALLOWED_HOSTS=brain.woltje.com in
Portainer stack env.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 19:14:32 -06:00
parent 679fda8491
commit c02d1d8974
2 changed files with 12 additions and 1 deletions

View File

@@ -19,5 +19,10 @@ class Settings(BaseSettings):
port: int = 8000 port: int = 8000
log_level: str = "info" log_level: str = "info"
# MCP transport security — comma-separated allowed Host header values.
# Set to the public hostname (e.g. "brain.woltje.com") in production.
# Empty disables DNS rebinding protection.
mcp_allowed_hosts: str = ""
settings = Settings() settings = Settings()

View File

@@ -6,6 +6,7 @@ import logging
from fastapi import Depends, FastAPI, HTTPException, Security from fastapi import Depends, FastAPI, HTTPException, Security
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings
from src import brain, db from src import brain, db
from src.config import settings from src.config import settings
@@ -29,7 +30,12 @@ def require_api_key(credentials: HTTPAuthorizationCredentials = Security(bearer)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# MCP server # MCP server
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
mcp = FastMCP("openbrain", stateless_http=True) _allowed_hosts = [h.strip() for h in settings.mcp_allowed_hosts.split(",") if h.strip()]
_transport_security = TransportSecuritySettings(
enable_dns_rebinding_protection=bool(_allowed_hosts),
allowed_hosts=_allowed_hosts,
)
mcp = FastMCP("openbrain", stateless_http=True, transport_security=_transport_security)
@mcp.tool() @mcp.tool()