Aller au contenu

La passerelle HTTP

La même passerelle sert MCP en Streamable HTTP : un service réseau mutualisé au lieu d’un processus par agent. Chaque initialize ouvre une session, avec sa propre instance du serveur enveloppé, sa propre chaîne d’audit et sa propre identité ; le jeton arrive à chaque requête dans l’en-tête Authorization, là où le SSO d’entreprise le place. Supprimer la session ferme sa chaîne ; le ledger la scelle ensuite comme n’importe quel autre WAL, sous <chain-id>-<session>.

Fenêtre de terminal
cargo run -p obsign-proxy -- \
--http 127.0.0.1:8080 \
--policy /tmp/demo/policy-bundle.json \
--trusted-keys /tmp/demo/trusted-keys.json \
--identity-bundle /tmp/demo/identity-bundle.json \
--wal /tmp/demo/wal --chain-id demo --env prod \
-- ./target/debug/mock-mcp-server

Ouvrir une session et l’exercer :

Fenêtre de terminal
TOKEN=$(cat /tmp/demo/token.jwt)
SID=$(curl -si http://127.0.0.1:8080/mcp \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
| tr -d '\r' | awk 'tolower($1)=="mcp-session-id:" {print $2}')
curl -s http://127.0.0.1:8080/mcp \
-H "Authorization: Bearer $TOKEN" -H "Mcp-Session-Id: $SID" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"delete_production_db","arguments":{}}}'
# → isError: refused by policy, recorded, never reached the server
curl -s -X DELETE http://127.0.0.1:8080/mcp -H "Mcp-Session-Id: $SID"
# → the WAL now holds the complete session (chain demo-$SID),
# ready for an obsign-ledger pass to seal

La couche HTTP est écrite à la main sur std::net, sans exécutif asynchrone ni framework web. La liste des dépendances fait partie du produit, et le sous-ensemble de HTTP/1.1 dont ce transport a besoin est plus petit que l’arbre de n’importe quel framework. Le HTTP entrant ne touche pas à l’invariant « aucun appel réseau », qui interdit les dépendances sortantes (récupération de JWKS, allers-retours vers le ledger) : identité et politiques arrivent toujours sous forme de fichiers signés.

Le transport est en HTTP nu par le même argument (aucune pile TLS dans l’arbre auditable), et le jeton bearer ne doit donc jamais traverser un réseau en clair : TLS se termine dans un reverse proxy placé devant, et la passerelle n’écoute que là où TLS se termine.

Les configurations nginx et Caddy testées, le contrat du proxy (mise en tampon SSE, délais d’attente, liste d’Origin autorisées) et les sondes pour contrôler un déploiement : TLS devant la passerelle.