Skip to main content
Glama
BNJ02

dijkstra-mcp-server

by BNJ02
README.md
# Serveur MCP Dijkstra

Serveur MCP minimal (un fichier) exposant l'algorithme de Dijkstra à un agent LLM.
Support de cours : le point intéressant n'est pas l'algo mais **comment l'agent transmet le graphe**.

## Format du graphe

Une arête par ligne, trois champs séparés par des espaces :

```
<noeud_a> <noeud_b> <cout>
```

- graphe **non orienté** : `Paris Dijon 315` crée `Paris->Dijon` **et** `Dijon->Paris`
- coûts **positifs ou nuls** (contrainte de Dijkstra)
- lignes vides et lignes commençant par `#` ignorées

Pourquoi ce format plutôt que du JSON : un LLM l'écrit sans erreur de syntaxe (rien à
équilibrer), il se lit tel quel au tableau, et il se parse en quelques lignes.
Le graphe voyage **dans l'appel d'outil** — le serveur ne stocke rien.

## Outils

| Outil | Arguments | Retour |
|---|---|---|
| `shortest_path` | `graph`, `start`, `end` | chemin optimal, coût total, détail des tronçons |
| `all_distances` | `graph`, `start` | coût minimal **et chemin** vers **tous** les nœuds, trié |

`all_distances` montre que Dijkstra construit l'arbre complet des plus courts chemins,
pas seulement une paire départ/arrivée. Il renvoie aussi les chemins : sans ça, un agent
à qui on demande un tableau récapitulatif rappelle `shortest_path` une fois par
destination. Leçon de design MCP : **un outil qui ne rend qu'une partie de la réponse
attendue se fait contourner.**

## Exemple

Graphe (coûts de trajet) :

```
Paris Lyon 550
Paris Dijon 315
Dijon Lyon 195
Lyon Marseille 315
Dijon Geneve 200
Geneve Marseille 420
```

`shortest_path(graph, "Paris", "Marseille")` :

```
Chemin le moins coûteux : Paris -> Dijon -> Lyon -> Marseille
Coût total : 825
Détail des tronçons :
  Paris -> Dijon : 315
  Dijon -> Lyon : 195
  Lyon -> Marseille : 315
```

Le détour par Dijon (825) bat la route directe Paris–Lyon–Marseille (550+315 = 865)
et la route par Genève (315+200+420 = 935) : l'arête directe évidente n'est pas la bonne.

## Prompts de démonstration

À coller tels quels dans une session Claude Code une fois le serveur enregistré.

**Prompt 1 — une paire départ/arrivée** (déclenche `shortest_path`, un seul appel) :

```
Avec le serveur MCP dijkstra, trouve-moi le trajet le moins cher de Paris à Marseille.

Paris Lyon 550
Paris Dijon 315
Dijon Lyon 195
Lyon Marseille 315
Dijon Geneve 200
Geneve Marseille 420
```

Attendu : `Paris -> Dijon -> Lyon -> Marseille`, coût **825**.

**Prompt 2 — toutes les destinations** (déclenche `all_distances`, un seul appel) :

```
Avec dijkstra, donne-moi le coût minimal depuis Paris vers toutes les autres villes.

Paris Lyon 550
Paris Dijon 315
Dijon Lyon 195
Lyon Marseille 315
Dijon Geneve 200
Geneve Marseille 420
```

Attendu : un tableau des 5 villes avec coût **et** chemin.

> Observé en cours : tant que `all_distances` ne renvoyait que les coûts, l'agent
> l'ignorait et appelait `shortest_path` **4 fois** pour reconstituer les chemins.
> C'est exactement le piège décrit plus haut — un outil qui ne rend qu'une partie de
> la réponse attendue se fait contourner. Bon moment pour faire la démo des deux
> versions côte à côte.

**Prompt 3 — sans graphe fourni** (l'agent doit produire lui-même le bon format) :

```
Utilise le serveur dijkstra pour trouver le chemin le moins coûteux
de A à F dans un petit graphe de 6 nœuds de ton choix.
```

## Carte

`carte.py` produit deux figures dans `images/` à partir du **même** graphe que le serveur
(constante `GRAPHE_EXEMPLE` dans `server.py`) :

| Fichier | Usage |
|---|---|
| `images/carte_graphe.png` / `.svg` | l'énoncé : 5 villes, 6 arêtes, les coûts |
| `images/carte_solution.png` / `.svg` | la correction : `Paris → Dijon → Lyon → Marseille` en rouge |

Le chemin surligné est **calculé par l'algorithme**, jamais codé en dur : changer un coût
dans `GRAPHE_EXEMPLE` et relancer suffit à mettre la carte à jour.

Les contours de la France et de la Suisse sont téléchargés une fois puis mis en cache dans
`data/` (2 ko au total) — le script tourne offline ensuite.

```bash
.venv/bin/python carte.py
```

## Installation / enregistrement

Deux environnements, deux besoins :

- **le serveur** tourne avec le venv MCP partagé `/home/bnj/mcp_servers/.venv`
  (dépendance unique : `fastmcp>=3.4.2`, déjà présent) ;
- **la carte** tourne avec le `.venv` local du projet, qui ajoute matplotlib sans polluer
  le venv partagé :

```bash
uv venv .venv
uv pip install --python .venv/bin/python -r requirements-carte.txt
```

Enregistrement du serveur dans Claude Code :

```bash
claude mcp add dijkstra -- /home/bnj/mcp_servers/.venv/bin/python /home/bnj/Documents/djikstra_mcp_server/server.py
```

## Tests

```bash
/home/bnj/mcp_servers/.venv/bin/python test_server.py
```

Couvre : parsing, optimalité du chemin, les deux outils, les erreurs (nœud inconnu,
coût négatif, ligne malformée, graphe déconnecté) et un appel bout-en-bout via un
client MCP en mémoire.