From 52529919afed066671c3bc7445282a34f796d9f4 Mon Sep 17 00:00:00 2001 From: thibaud-leclere Date: Tue, 12 May 2026 09:20:41 +0200 Subject: [PATCH] docs: add P0 design spec for xdebug-mcp --- .../specs/2026-05-12-xdebug-mcp-design.md | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-12-xdebug-mcp-design.md diff --git a/docs/superpowers/specs/2026-05-12-xdebug-mcp-design.md b/docs/superpowers/specs/2026-05-12-xdebug-mcp-design.md new file mode 100644 index 0000000..e6f4a17 --- /dev/null +++ b/docs/superpowers/specs/2026-05-12-xdebug-mcp-design.md @@ -0,0 +1,147 @@ +# 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`) + +```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`** +```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]]`)