xdebug-mcp/docs/superpowers/specs/2026-05-12-xdebug-mcp-design.md
2026-05-12 09:20:41 +02:00

5.5 KiB

xdebug-mcp — Design P0

Objectif

Serveur MCP en Go capable de lire et d'analyser des fichiers de sortie de profilage Xdebug (format cachegrind, compressés gzip). Exposé via stdio conformément au protocole MCP.

Stack

  • Langage : Go
  • Protocole MCP : mark3labs/mcp-go
  • Framework CLI/config : forge.lclr.dev/AI/mcp-framework + package généré mcpgen/
  • Format d'entrée : cachegrind v1 (xdebug 3.x), fichiers .gz ou texte brut

Structure du projet

xdebug-mcp/
├── cmd/xdebug-mcp/main.go      # Point d'entrée, bootstrap CLI
├── internal/
│   ├── app/app.go               # Wiring MCP server
│   ├── cachegrind/
│   │   ├── parser.go            # Parser streaming du format cachegrind
│   │   └── model.go             # Structures de données (Profile, Function, Call)
│   ├── cache/
│   │   └── lru.go               # Cache LRU keyed par chemin fichier absolu
│   └── tools/
│       ├── analyze.go           # Tool analyze_profile
│       ├── callers.go           # Tool get_callers
│       └── callees.go           # Tool get_callees
├── mcpgen/                      # Généré par `mcp-framework generate`
│   └── manifest.go
├── mcp.toml
└── go.mod

Modèle de données (cachegrind/model.go)

type Profile struct {
    Cmd       string
    Events    []string            // ex: ["Time_(10ns)", "Memory_(bytes)"]
    Functions []*Function
    ByName    map[string][]*Function // une clé peut matcher plusieurs fichiers
}

type Function struct {
    Name     string
    File     string
    Costs    []int64           // indexé sur Profile.Events
    Calls    []*Call           // appels sortants (callees)
    CalledBy []*Call           // appels entrants (callers)
}

type Call struct {
    Caller   *Function
    Callee   *Function
    Count    int64
    Costs    []int64
}

Les coûts sont agrégés : si une fonction apparaît dans plusieurs blocs fl=/fn=, ses coûts sont sommés. CalledBy est reconstruit à partir des directives calls= pendant le parse. Le parser maintient une table de résolution interne des alias numériques (fl=(N), fn=(N)).

Tools MCP

Le chemin du fichier est toujours un paramètre direct de l'outil — pas de configuration globale de dossier.

analyze_profile

Paramètre Type Défaut Description
file_path string requis Chemin absolu ou relatif du fichier cachegrind (.gz ou texte)
top_n int 20 Nombre de fonctions à retourner

Retourne : stats globales (commande profilée, événements, nombre total de fonctions) + top N fonctions triées par Costs[0] (Time), avec coûts absolus et pourcentage du total.

get_callers

Paramètre Type Défaut Description
file_path string requis Chemin du fichier cachegrind
function_name string requis Nom exact ou sous-chaîne de la fonction cible
top_n int 10 Nombre de callers à retourner

Retourne : liste des fonctions qui appellent function_name, avec nombre d'appels et coûts associés, triés par coût décroissant.

get_callees

Paramètre Type Défaut Description
file_path string requis Chemin du fichier cachegrind
function_name string requis Nom exact ou sous-chaîne de la fonction cible
top_n int 10 Nombre de callees à retourner

Retourne : liste des fonctions appelées par function_name, avec nombre d'appels et coûts associés, triés par coût décroissant.

Résolution du nom : recherche exacte d'abord (ByName), puis recherche contains si aucun résultat exact. Si plusieurs fonctions matchent en mode contains, toutes sont listées avec un avertissement.

Cache LRU (cache/lru.go)

  • Capacité : 2 fichiers simultanés (constante DefaultCapacity = 2)
  • Clé : chemin absolu du fichier
  • Invalidation : comparaison de os.FileInfo.ModTime() à chaque accès
  • Thread-safe : sync.Mutex
  • Structure : map[string]*entry + container/list stdlib pour l'ordre d'éviction
  • Pas de dépendance externe

Gestion d'erreurs

Situation Comportement
Fichier introuvable Erreur avec chemin complet
Fichier non gzippé Tentative de lecture en texte brut
Format cachegrind invalide Erreur avec numéro de ligne
Fonction non trouvée (exact) Message clair + liste des matches contains si disponible
Fichier très gros / OOM Non géré à P0 — limite documentée

Wiring

mcp.toml

binary_name = "xdebug-mcp"

[bootstrap]
description = "MCP server for Xdebug profiling files"

[profiles]
default = "prod"
known = ["dev", "prod"]

cmd/xdebug-mcp/main.go utilise bootstrap.Run avec uniquement le hook MCP branché sur app.RunMCP. La Description est soit fournie par mcpgen (à vérifier après génération), soit hardcodée.

internal/app/app.go :

  1. Instancie cache.New(cache.DefaultCapacity)
  2. Crée le serveur mcp-go
  3. Enregistre les trois tools
  4. Lance server.ServeStdio()

mcpgen/ est généré via mcp-framework generate et doit être regénéré après toute modification de mcp.toml. Il expose au minimum mcpgen.BinaryName.

Limites P0 connues

  • Pas de guard mémoire : un fichier de 325 Mo décompressé est chargé intégralement en RAM
  • Pas de list_profiles : l'appelant doit connaître le chemin du fichier
  • Pas d'auto-update configuré dans mcp.toml
  • Pas de secrets ni de config persistante (pas de [[config.fields]])