Files
Loki/docs/superpowers/specs/2026-10-15-rust-migration-lot20-mcp-design.md
T
Claude 090f15615e Lot 20 : serveurs MCP tenus par la façade (LOKI_MCP=rust)
La façade tient les serveurs MCP quand LOKI_MCP=rust (expérimental ; le
Go reste le défaut) : configuration (state/mcp), catalogue embarqué (le
même fichier que le Go, copié dans l'étape Rust de l'image), état et
routes /api/mcp*. Un client MCP en Rust (mcp/client.rs) parle JSON-RPC
sur stdio et en Streamable HTTP (réponses JSON ou SSE, session suivie,
en-têtes configurés, ping du serveur servi). Le pool suit mcp_client.go :
session gardée entre les tours, connexions parallèles et partagées,
session invalidée pendant sa connexion jamais republiée, délais npx/uvx,
appel borné avec une reconnexion, sortie plafonnée à la fenêtre.

Un seul pool, celui de la façade : le Go, qui exécute encore les outils
de l'agent, lui demande les outils MCP, leurs appels et le
pré-chauffage (/internal/facade/v1/mcp/*, facade_mcp.go).

Parité : parity_mcp_test.go tire du Go les fonctions pures, seize
mutations de configuration, et un vrai serveur du SDK MCP Go (onze
outils) : ce qu'il met sur le fil et ce que le client Go en tire (état,
outils annoncés, appels). tests/mcp.rs le rejoue contre un faux serveur
qui resert ces réponses, et vérifie stdio, reconnexion, routes, catalogue
et session invalidée de bout en bout ; TestFacadeMCP côté Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
2026-10-15 19:56:11 +00:00

4.2 KiB

Lot 20 — serveurs MCP tenus par la façade

Contexte

Le Go tenait les serveurs MCP (Model Context Protocol) :

  • mcp_config.go : la configuration, dans state/mcp ;
  • mcp_catalog.go : le catalogue embarqué ;
  • mcp_client.go : le pool de sessions, avec le SDK MCP Go ;
  • les routes /api/mcp* de web_api.go.

Le pool sert deux usages : l'état affiché dans l'interface, et les outils mcp__<serveur>__<outil> annoncés au modèle et exécutés pour l'agent.

Lot 20 : la façade tient tout cela quand LOKI_MCP=rust (mcp/, expérimental). Sans la variable, rien ne change.

Route Rôle
GET /api/mcp état de chaque serveur, connexion tentée pour les activés (en parallèle)
GET /api/mcp/catalog catalogue embarqué (le même fichier que le Go), {workspace} résolu, entrées déjà configurées marquées, lanceur (npx, uvx) présent ou non
POST /api/mcp/save ajoute ou remplace (actif par défaut) ; nom contrôlé, un seul transport
POST /api/mcp/delete, /api/mcp/toggle, /api/mcp/tool retire, active ou éteint, masque ou démasque un outil
POST /api/mcp/test reconnexion forcée

Client MCP en Rust

mcp/client.rs parle JSON-RPC 2.0 sur deux transports, comme le SDK Go :

  • stdio : un processus local, un message par ligne ;
  • Streamable HTTP : un POST par message, réponse en JSON ou en flux SSE, session suivie par Mcp-Session-Id, en-têtes configurés ajoutés.

La session s'ouvre par initialize puis notifications/initialized. Elle sert tools/list (première page, comme le Go) et tools/call. Un ping du serveur reçoit sa réponse.

Le pool (mcp/mod.rs) suit mcp_client.go :

  • une session gardée d'un tour à l'autre ;
  • une entrée publiée avant la connexion, pour que deux appelants partagent la même tentative ;
  • une session invalidée pendant sa connexion n'est jamais republiée ;
  • délai de 3 min pour npx et uvx (premier téléchargement), 20 s sinon ;
  • un appel borné à 120 s, une reconnexion si la session est morte ;
  • la sortie plafonnée à la fenêtre de contexte (12 000 au moins), une erreur d'outil signalée, une sortie vide rendue « [ok] (aucune sortie) ».

Le Go

Un seul pool, celui de la façade. Le Go, qui exécute encore les outils de l'agent, lui demande les outils à annoncer, leurs appels et le pré-chauffage (/internal/facade/v1/mcp/{tools,call,prewarm}, facade_mcp.go), quelle que soit la conversation qui tourne. Son propre pool reste vide.

L'image copie internal/loki/mcp_catalog.json dans l'étape de compilation Rust : un seul catalogue pour les deux binaires.

Écarts assumés

  • Les erreurs de transport (connexion refusée, flux coupé) ont les messages du client Rust. Une erreur JSON-RPC et un binaire introuvable ont ceux du Go.
  • Un contenu « autre » (lien de ressource, ressource intégrée) est rendu en JSON dans l'ordre des champs du SDK. Les annotations éventuelles sont dans l'ordre alphabétique.

Parité

internal/loki/parity_mcp_test.go tire du Go :

  • les fonctions pures : noms exposés, schémas, libellés d'appel, serveur d'origine d'un outil, ligne du préambule, délais et leurs messages, aplatissement de neuf résultats ;
  • la lecture de la configuration et seize mutations avec leurs erreurs et la base après chacune ;
  • ce qu'un vrai serveur du SDK MCP Go met sur le fil en Streamable HTTP : onze outils, et le résultat de chacun ;
  • ce que le client Go en tire : état de quatre serveurs (joignable, binaire introuvable, désactivé, mal configuré), outils annoncés (un masqué), appels.

tests/mcp.rs rejoue les fonctions et la configuration. Un faux serveur resert ce que le serveur du SDK a répondu (appels en SSE) : l'état, les outils et les appels tirés par la façade doivent être ceux du Go, sur une seule session. Sont aussi vérifiés de bout en bout :

  • le transport stdio : environnement, ping du serveur, serveur mort en plein appel puis reconnexion ;
  • les routes de configuration, en-têtes compris ;
  • le catalogue ;
  • une session invalidée pendant sa connexion, sans processus laissé derrière.

Une mutation de chaque règle fait échouer les tests. Côté Go, TestFacadeMCP vérifie le relais, les outils, les appels, le pré-chauffage et une façade injoignable.