docs: add P0 design spec for xdebug-mcp
This commit is contained in:
commit
52529919af
1 changed files with 147 additions and 0 deletions
147
docs/superpowers/specs/2026-05-12-xdebug-mcp-design.md
Normal file
147
docs/superpowers/specs/2026-05-12-xdebug-mcp-design.md
Normal file
|
|
@ -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]]`)
|
||||
Loading…
Reference in a new issue