diff --git a/common/context-hub-mcp-initialize-bad-request-4-causes.md b/common/context-hub-mcp-initialize-bad-request-4-causes.md new file mode 100644 index 0000000..1df6f4d --- /dev/null +++ b/common/context-hub-mcp-initialize-bad-request-4-causes.md @@ -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.