fix: harden MCP endpoint after code review

Critical: the MCP SDK auto-enables DNS-rebinding protection when its host
parameter is left at the 127.0.0.1 default, hard-coding a loopback-only Host
allow-list. Every remote client (the only deployment this feature targets) was
refused with HTTP 421 before auth or the tool ran. Now driven by a new
MCP_ALLOWED_HOSTS setting, with '*' as an explicit, logged opt-out.

Also bounds query/top_k to match AskRequest (top_k is amplified 4x downstream,
so an unbounded value was a resource-exhaustion vector), decodes the
Authorization header as latin-1 per the ASGI spec instead of raising a 500 on
malformed bytes, and returns WWW-Authenticate on 401 per RFC 7235.

Moves the psycopg2 import guard into backend/tests/conftest.py: duplicated
across four test modules, it only worked because of alphabetical collection
order, and any earlier-sorting package would have reintroduced a live
connection attempt against the production database.

Registers the mcp module in the authoritative backend architecture doc.

84 backend tests pass. Verified against a live server: allowed remote Host
returns a valid initialize result, unknown Host returns 421, missing token
returns 401 with WWW-Authenticate.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
wangwei
2026-07-29 17:11:54 +08:00
co-authored by Copilot
parent bd3dc38d1d
commit 49ee50c104
13 changed files with 330 additions and 51 deletions
+14
View File
@@ -198,6 +198,20 @@ class Settings(BaseSettings):
description="Comma-separated allowed CORS origins. Never use * in production.",
)
# ── MCP ───────────────────────────────────────────────────────────────────
# The MCP SDK enables DNS-rebinding protection whenever the transport is
# bound to a loopback host, which rejects any Host header not in this list
# with HTTP 421. Deployments reachable by a real hostname/IP must list it
# here or every remote MCP client is refused before the handler runs.
mcp_allowed_hosts: str = Field(
default="127.0.0.1:*,localhost:*,[::1]:*",
description=(
"Comma-separated Host header values accepted by the MCP endpoint. "
"A ':*' suffix matches any port. Set to '*' to disable DNS-rebinding "
"protection entirely (not recommended)."
),
)
@lru_cache
def get_settings() -> Settings:
"""Return settings."""