mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-16 03:39:29 +02:00
MCP : les serveurs actifs se connectent en parallele au lieu de la file indienne (quatre serveurs npx n'additionnent plus leurs demarrages) et sont prechauffes au lancement du service, donc le premier message ne paie plus le handshake. Une session est publiee dans le pool avant d'aboutir : deux appelants simultanes partagent la meme tentative au lieu de lancer deux process. Interface : la vitesse de generation (prefill/decode) reapparait sous chaque reponse, en fondu, avec un interrupteur pour la masquer ; infobulles " ? " qui n'etaient plus rognees ; espace superflu en haut des cartes du mode agent ; pastilles d'etat redessinees et plaquees a droite ; moteurs renommes en llama.cpp precompile / compile / personnalise ; badge conseille lisible en sombre ; section Service fondue dans Moteur ; plus qu'un enregistrement automatique pour l'acces internet. Documentation : le README dit desormais que Crawl4AI est un serveur a heberger soi-meme, avec la commande Docker.
91 lines
2.5 KiB
Go
91 lines
2.5 KiB
Go
package jean
|
|
|
|
import (
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// TestMCPEnsureDeduplicatesConcurrentConnects vérifie que N appelants simultanés
|
|
// (pré-chauffage + tour de chat + panneau MCP de l'UI) partagent une seule
|
|
// tentative de connexion : sans ça, connecter en parallèle multiplierait les
|
|
// process stdio lancés pour un même serveur.
|
|
func TestMCPEnsureDeduplicatesConcurrentConnects(t *testing.T) {
|
|
t.Setenv("JEAN_HOME", t.TempDir())
|
|
t.Cleanup(mcpCloseAll)
|
|
|
|
// Commande inexistante : la connexion échoue, mais l'entrée du pool est bien
|
|
// partagée — c'est ce qu'on teste.
|
|
cfg := MCPServerConfig{Command: "jean-binaire-inexistant-pour-test", Enabled: true}
|
|
|
|
const callers = 8
|
|
var wg sync.WaitGroup
|
|
got := make([]*mcpSession, callers)
|
|
for i := range got {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
got[i] = mcpMgr.ensure("bidon", cfg)
|
|
}(i)
|
|
}
|
|
wg.Wait()
|
|
|
|
for i, s := range got {
|
|
if s != got[0] {
|
|
t.Fatalf("appelant %d a obtenu une session différente : %p vs %p", i, s, got[0])
|
|
}
|
|
select {
|
|
case <-s.ready:
|
|
default:
|
|
t.Fatalf("appelant %d a reçu une session pas encore prête", i)
|
|
}
|
|
}
|
|
if got[0].err == nil {
|
|
t.Fatal("une commande inexistante devrait produire une erreur de connexion")
|
|
}
|
|
}
|
|
|
|
// TestMCPEndToEnd connecte le serveur MCP de référence (@modelcontextprotocol/
|
|
// server-everything via npx), vérifie la découverte d'outils namespacés puis un
|
|
// appel réel (echo). Skippé si npx est absent (CI sans Node).
|
|
func TestMCPEndToEnd(t *testing.T) {
|
|
if _, err := exec.LookPath("npx"); err != nil {
|
|
t.Skip("npx indisponible — test MCP e2e ignoré")
|
|
}
|
|
t.Setenv("JEAN_HOME", t.TempDir())
|
|
|
|
cfg := MCPServerConfig{
|
|
Command: "npx",
|
|
Args: []string{"-y", "@modelcontextprotocol/server-everything"},
|
|
Enabled: true,
|
|
}
|
|
if err := SetMCPServer("everything", cfg); err != nil {
|
|
t.Fatalf("SetMCPServer: %v", err)
|
|
}
|
|
t.Cleanup(mcpCloseAll)
|
|
|
|
tools := mcpTools()
|
|
if len(tools) == 0 {
|
|
t.Fatal("aucun outil MCP découvert (le serveur s'est-il connecté ?)")
|
|
}
|
|
echoName := mcpExposedName("everything", "echo")
|
|
found := false
|
|
for _, tl := range tools {
|
|
if tl.Function.Name == echoName {
|
|
found = true
|
|
}
|
|
if !strings.HasPrefix(tl.Function.Name, "mcp__everything__") {
|
|
t.Errorf("outil mal namespacé: %s", tl.Function.Name)
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("outil %s introuvable dans %d outils", echoName, len(tools))
|
|
}
|
|
|
|
out := mcpCall(echoName, map[string]any{"message": "bonjour-jean"})
|
|
if !strings.Contains(out, "bonjour-jean") {
|
|
t.Fatalf("echo n'a pas renvoyé le message ; got: %q", out)
|
|
}
|
|
}
|