runbook: context-hub MCP initialize Bad Request, 4 causes empilees, resolu (30/07/2026)
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user