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
This commit is contained in:
Claude committed 2026-10-15 19:56:11 +00:00
1 parent 3fc36a3acc
commit 090f15615e
20 files changed
+3670 -2

No files matched your search

+15
View File
@@ -87,6 +87,9 @@ pub struct Config {
pub system: Option<Arc<crate::system::System>>,
/// Hub des projets tenu par la façade (`LOKI_PROJECTS=rust`).
pub projects: bool,
/// Serveurs MCP (pool de sessions, configuration, catalogue) tenus par la
/// façade (`LOKI_MCP=rust`) ; `None` : au Go.
pub mcp: Option<Arc<crate::mcp::Mcp>>,
}
struct Proxy {
@@ -124,6 +127,7 @@ pub async fn serve(
engine_ctl: cfg.engine_ctl,
system: cfg.system,
projects: cfg.projects,
mcp: cfg.mcp,
internal_token: cfg.internal_token.clone(),
},
});
@@ -192,6 +196,12 @@ impl Proxy {
d.shutdown_fast().await;
return plain(StatusCode::NO_CONTENT, "");
}
if from_go
&& let Some(m) = &self.ctx.mcp
&& crate::mcp::is_internal(path)
{
return crate::mcp::serve_internal(&self.ctx, m, req).await;
}
// L'API interne du Go n'est jamais joignable de l'extérieur.
if path.starts_with("/internal/") {
return plain(StatusCode::NOT_FOUND, "404 page not found");
@@ -216,6 +226,11 @@ impl Proxy {
{
return chatapi::serve(&self.ctx, chat, req, from_go).await;
}
if let Some(m) = &self.ctx.mcp
&& crate::mcp::handles(path)
{
return crate::mcp::serve(&self.ctx, m, req, from_go).await;
}
if self.ctx.projects && crate::projects_api::handles(path) {
return crate::projects_api::serve(&self.ctx, req, from_go).await;
}