Files
nas-runbooks/common/context-hub-mcp-initialize-bad-request-4-causes.md
T

47 lines
3.1 KiB
Markdown

# context-hub -- MCP initialize Bad Request : 4 causes empilees (30/07/2026)
**Symptome initial** : Gemini "Error: calling initialize: sending initialize: Bad Request", connexion MCP impossible.
## Cause 1 -- redirection HTTPS vers HTTP
uvicorn lance sans --proxy-headers, donc il ignore X-Forwarded-Proto du reverse-proxy.
La redirection Starlette /mcp -> /mcp/ (trailing slash) se construit avec le scheme HTTP percu en interne,
alors que la requete externe est en HTTPS. Location: http://... au lieu de https://... -- un client MCP
strict refuse ce downgrade silencieux.
Fix : CMD Dockerfile -> uvicorn ... --proxy-headers --forwarded-allow-ips=*
## Cause 2 -- httpx manquant (revele par --no-cache)
app/git_sync.py importe httpx directement, jamais dans requirements.txt -- marchait par hasard
via dependance transitive du package mcp. Un rebuild --no-cache a resolu les dependances a neuf
et n a pas retire httpx cette fois -> ModuleNotFoundError au chargement de l app, crash loop.
Fix : httpx>=0.27.0 ajoute explicitement a requirements.txt.
## Cause 3 -- mcp 2.0.0 casse l API v1 utilisee par le code
mcp>=1.2.0 sans plafond -> pip installe 2.0.0 (sorti tres recemment, breaking rewrite officiel).
Server.list_tools() (API bas niveau v1.x) n existe plus en v2 -> AttributeError au chargement.
Fix : mcp>=1.2.0,<2.0.0 (recommandation officielle du projet pour qui n a pas migre).
## Cause 4 -- transport SSE legacy incompatible clients modernes (LA cause du Bad Request)
app/routes/mcp.py montait SseServerTransport (spec 2024-11-05, deux endpoints : GET pour ouvrir
un flux SSE + recuperer un session_id, POST /messages?session_id=... ensuite). Les clients MCP
recents (Gemini/Antigravity inclus) parlent Streamable HTTP (spec 2025-03-26+) : un seul POST
direct sans poignee de main prealable. Le serveur recevait ce POST sans session_id -> 400 Bad
Request (log : "mcp.server.sse: Received request without session_id").
Fix : migration vers StreamableHTTPSessionManager (mcp.server.streamable_http_manager, fallback
import mcp.server.streamable_http si version differente), mode stateless=True (pas de besoin de
resumabilite/streaming long pour ce service de lookup de regles/ports), json_response=True.
L auth X-API-Key et le contextvar current_agent_var (lu par tous les handlers call_tool) sont
preserves a l identique -- seule la couche transport a change.
Cote main.py : @app.on_event("startup") remplace par un lifespan= unifie (init_db() +
async with session_manager.run()), necessaire car le session manager doit rester actif toute
la duree de vie du process, pas juste au demarrage.
Comportement residuel attendu, pas un bug : DELETE /mcp/ -> 405 Method Not Allowed en mode
stateless (pas de session a terminer). Sans impact sur la connexion.
## Lecon generale
Un --no-cache sur un projet avec des bornes de version ouvertes (mcp>=1.2.0 sans plafond,
requirements.txt incomplet) peut faire remonter plusieurs regressions latentes d un coup.
Traiter chaque nouvelle erreur de log comme une couche distincte plutot que de chercher un
fix unique -- ici il a fallu 4 iterations successives avant la resolution complete.