mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-22 22:48:11 +02:00
Repris d'AJEAN c05a0a9f et e44b6139. Les deux délais MCP, jusqu'ici codés en dur (20 s pour se connecter, 120 s pour un appel d'outil), deviennent des réglages globaux : clé bkState mcp_timeouts, bornes 1 à 3600 s, routes GET /api/mcp/timeouts et POST /api/mcp/timeouts/save. L'enregistrement ferme les sessions ouvertes pour que le nouveau délai de connexion s'applique dès la suivante. Un lancement npx/uvx garde au moins mcpConnectTimeoutCold. Un appel prend le réglage global, toujours remplacé par le callTimeoutSec du serveur, puis allongé par l'argument timeout de l'outil. Le plafond d'un appel suit la borne des réglages (1 h au lieu de 30 min) : un délai réglé à 3600 s n'est plus rogné en silence. Une valeur rangée hors bornes ou illisible retombe sur la borne ou le défaut. Le délai par serveur passe par /api/mcp/save (callTimeoutSec, 0 à 3600, vide = réglage global), revient dans /api/mcp et n'est plus perdu à l'enregistrement. L'interface gagne deux lignes dans la carte « Serveurs MCP », au style des réglages (sans bordure, un seul enregistrement par modification), et un champ « délai d'un appel d'outil » dans la fenêtre d'édition d'un serveur. index.html régénéré par tools/assemble-ui. Quand la façade tient les serveurs MCP (LOKI_MCP=rust), elle sert aussi les nouvelles routes : c'est son pool qu'il faut fermer quand les délais changent. Le journal de parité MCP couvre la lecture des délais, leur effet sur la connexion et l'appel, et les deux routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
111 lines
3.4 KiB
Go
111 lines
3.4 KiB
Go
package loki
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// facade_mcp.go — serveurs MCP tenus par la façade Rust (LOKI_MCP=rust, lot 20
|
|
// de la migration ; expérimental, le Go reste le défaut) : configuration,
|
|
// catalogue, état et pool de sessions (rust/loki-rs/src/mcp).
|
|
//
|
|
// Un seul pool : celui de la façade. Le Go lui demande les outils MCP à
|
|
// annoncer au modèle, et leurs appels pour les tours qu'il exécute encore
|
|
// (sous-agents, tâches IA, discussion rendue au Go) via
|
|
// /internal/facade/v1/mcp/* ; son propre pool reste vide. Les tours de la
|
|
// façade appellent son pool sans passer par ici (lot 36).
|
|
|
|
const facadeMCPEnv = "LOKI_MCP"
|
|
|
|
// facadeMCP : la façade tient-elle les serveurs MCP ?
|
|
func facadeMCP() bool {
|
|
return strings.EqualFold(strings.TrimSpace(os.Getenv(facadeMCPEnv)), "rust") &&
|
|
facadeAddr() != "" && internalToken() != ""
|
|
}
|
|
|
|
// facadeMCPRoutes : les routes que la façade sert alors (mcp/mod.rs).
|
|
var facadeMCPRoutes = map[string]bool{
|
|
"/api/mcp": true,
|
|
"/api/mcp/catalog": true,
|
|
"/api/mcp/save": true,
|
|
"/api/mcp/delete": true,
|
|
"/api/mcp/toggle": true,
|
|
"/api/mcp/tool": true,
|
|
"/api/mcp/test": true,
|
|
// Les délais globaux : la façade tient le pool, c'est elle qui doit
|
|
// fermer ses sessions quand ils changent.
|
|
"/api/mcp/timeouts": true,
|
|
"/api/mcp/timeouts/save": true,
|
|
}
|
|
|
|
// facadeMCPJSON appelle l'API interne MCP de la façade et décode sa réponse.
|
|
// Délai large, simple garde-fou : la façade borne elle-même la connexion
|
|
// (1 h au plus) et l'appel, nouvel essai compris (mcpCallTimeoutMax chacun).
|
|
func facadeMCPJSON(path string, body any, out any) error {
|
|
b, err := json.Marshal(body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req, err := http.NewRequest(http.MethodPost, "http://"+facadeAddr()+path, bytes.NewReader(b))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set(internalTokenHeader, internalToken())
|
|
resp, err := (&http.Client{Timeout: mcpTimeoutMaxSec*time.Second + 2*mcpCallTimeoutMax}).Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode >= 300 {
|
|
return fmt.Errorf("façade : %s", resp.Status)
|
|
}
|
|
return json.NewDecoder(resp.Body).Decode(out)
|
|
}
|
|
|
|
// facadeMCPTools : les outils MCP à annoncer, tenus par la façade.
|
|
func facadeMCPTools() []Tool {
|
|
var out struct {
|
|
Tools []Tool `json:"tools"`
|
|
}
|
|
if err := facadeMCPJSON("/internal/facade/v1/mcp/tools", map[string]any{}, &out); err != nil {
|
|
fmt.Fprintf(os.Stderr, "[mcp] outils demandés à la façade : %v\n", err)
|
|
return nil
|
|
}
|
|
return out.Tools
|
|
}
|
|
|
|
// facadeMCPCall : un outil MCP exécuté par la façade.
|
|
func facadeMCPCall(name string, args map[string]any) string {
|
|
if args == nil {
|
|
args = map[string]any{}
|
|
}
|
|
var out struct {
|
|
Result string `json:"result"`
|
|
}
|
|
if err := facadeMCPJSON("/internal/facade/v1/mcp/call", map[string]any{"name": name, "args": args}, &out); err != nil {
|
|
return "[erreur MCP] façade injoignable : " + err.Error()
|
|
}
|
|
return out.Result
|
|
}
|
|
|
|
// facadeMCPPrewarm : la façade ouvre ses sessions en tâche de fond.
|
|
func facadeMCPPrewarm() {
|
|
go func() {
|
|
var out map[string]any
|
|
// La façade démarre le Go puis attend qu'il réponde : elle peut ne pas
|
|
// écouter encore au tout premier instant.
|
|
for i := 0; i < 10; i++ {
|
|
if facadeMCPJSON("/internal/facade/v1/mcp/prewarm", map[string]any{}, &out) == nil {
|
|
return
|
|
}
|
|
time.Sleep(time.Second)
|
|
}
|
|
}()
|
|
}
|