---
marp: true
theme: default
paginate: true
style: |
  :root {
    --bleu-nuit: #1A2230;
    --teal: #0F7A6C;
    --cuivre: #B4612A;
    --menthe: #E9F6F3;
    --gris-clair: #F4F7F6;
  }
  section {
    background: var(--gris-clair);
    color: var(--bleu-nuit);
    font-family: 'Segoe UI', 'Helvetica Neue', sans-serif;
  }
  h1, h2 { color: var(--bleu-nuit); }
  h1 { border-bottom: 4px solid var(--teal); padding-bottom: 8px; }
  strong { color: var(--teal); }
  em { color: var(--cuivre); }
  code { background: var(--menthe); color: var(--bleu-nuit); }
  pre { background: var(--bleu-nuit); }
  pre code { background: var(--bleu-nuit); color: var(--menthe); }
  table th { background: var(--teal); color: white; }
  table tr:nth-child(even) { background: var(--menthe); }
  blockquote { border-left: 5px solid var(--cuivre); background: var(--menthe); padding: 10px 16px; }
  section.lead { background: var(--bleu-nuit); color: var(--gris-clair); }
  section.lead h1 { color: var(--menthe); border-bottom-color: var(--cuivre); }
---

<!-- _class: lead -->

# Applied AI — Niveau Avancé
## Session 5 : MCP en profondeur

**Yann Isola** — Formation professionnelle en IA
Préparation *Claude Certified Architect*

🎯 Architecture · Transports · Primitives · Serveurs & Clients · Sécurité

---

# Objectifs de la session

1. Maîtriser l'architecture **Hôte ↔ Client ↔ Serveur** et JSON-RPC 2.0
2. Choisir un **transport** : stdio vs Streamable HTTP
3. Distinguer **Tools / Resources / Prompts** par leur *contrôleur* ← point d'examen n° 1
4. **Construire** un serveur et un client MCP (SDK Python)
5. Appliquer le **modèle de sécurité** et les patrons avancés

> 3 h 30 : 2 h de cours + 90 min d'exercices + quiz

---

# Le problème M×N

**Avant MCP :** chaque application IA × chaque outil = un connecteur spécifique

- 10 applications × 20 outils = **200 intégrations** à écrire et maintenir
- Chaque éditeur réinvente l'authentification, les schémas, les erreurs

**Avec MCP :** M + N

- Chaque outil expose **un** serveur MCP
- Chaque application implémente **un** client MCP
- Tout serveur fonctionne avec tout hôte compatible

> **Analogie officielle : MCP est à l'IA ce que USB-C est au matériel — un port universel.**
> MCP = *Model Context Protocol* (protocole de contexte de modèle), standard ouvert initié par Anthropic ⚠ *(écosystème en évolution rapide)*

---

# Architecture : les trois composants

```
┌──────────────── HÔTE (IDE, app de chat) ────────────────┐
│   ┌──────────┐     ┌──────────┐     ┌──────────┐        │
│   │ Client 1 │     │ Client 2 │     │ Client 3 │        │
│   └────┬─────┘     └────┬─────┘     └────┬─────┘        │
└────────┼────────────────┼────────────────┼──────────────┘
         │ JSON-RPC       │ JSON-RPC       │ JSON-RPC
    ┌────▼─────┐     ┌────▼─────┐     ┌────▼─────┐
    │filesystem│     │  GitHub  │     │PostgreSQL│
    └──────────┘     └──────────┘     └──────────┘
```

**Règle d'or : 1 client ↔ 1 serveur.** 3 serveurs ⇒ 3 clients.

---

# Rôles précis

| Composant | Responsabilités |
|---|---|
| **Hôte** | Embarque le modèle, choisit les serveurs, applique consentement & politiques de sécurité |
| **Client** | Géré par le SDK · session · négociation de capacités · routage des messages |
| **Serveur** | Processus indépendant · expose Tools, Resources, Prompts |

> ❗ Erreur classique : croire qu'un client se connecte à plusieurs serveurs. **Non.** C'est l'hôte qui multiplie les clients.

---

# JSON-RPC 2.0 : la langue commune

**3 types de messages, sur tous les transports :**

| Type | `id` ? | Réponse attendue ? |
|---|---|---|
| Requête | ✅ | ✅ |
| Réponse | ✅ (le même) | — |
| Notification | ❌ | ❌ |

```json
{ "jsonrpc": "2.0", "id": 42, "method": "tools/call",
  "params": { "name": "rechercher_commande",
              "arguments": { "numero": "CMD-2026-0193" } } }
```

---

# Cycle de vie d'une session

1. **`initialize`** — client → serveur : version + **capacités**
2. Serveur → client : ses propres capacités (tools, resources, prompts, subscriptions...)
3. **`notifications/initialized`** — session ouverte
4. **Découverte** : `tools/list` · `resources/list` · `prompts/list`
5. **Opérations** : `tools/call` · `resources/read` · `prompts/get`
6. Fermeture propre du transport

---

# Négociation de capacités — pourquoi ?

**Question d'examen garantie.**

- Chaque partie déclare **ce qu'elle sait faire** au handshake
- On n'utilise que **l'intersection** des capacités
- Un serveur n'envoie jamais une notification que le client n'a pas déclaré supporter

> 🎓 Finalité : **évolution du protocole sans rupture de compatibilité.**
> Client ancien + serveur récent = coopération sur le socle commun.

---

# Transport 1 : stdio

Le serveur = **sous-processus** de l'hôte. JSON-RPC sur stdin/stdout.

- ⚡ Latence minimale, zéro réseau
- 🔒 Aucun port ouvert · permissions héritées de l'OS
- 🔁 Cycle de vie lié à l'hôte
- 📓 **Logs sur stderr — jamais stdout !**

```json
{ "mcpServers": { "commandes": {
    "command": "python",
    "args": ["/opt/mcp/serveur_commandes.py"] } } }
```

---

# Le piège stdio (démo en exercice)

```python
@mcp.tool()
def rechercher(numero: str) -> dict:
    print("debug: étape 2 OK")   # ← 💥 BOUM
    ...
```

- `print()` écrit sur **stdout** → corrompt le flux JSON-RPC
- Le client échoue à analyser les messages → session cassée

✅ Solution : `logging` configuré vers **stderr**, ou fichier.

> Question piège d'examen classique.

---

# Transport 2 : Streamable HTTP

**Remplace SSE (déprécié)** pour les serveurs distants.

- `POST` des messages JSON-RPC vers un point de terminaison unique (`/mcp`)
- Le serveur peut répondre en JSON simple **ou** ouvrir un flux (résultats progressifs, notifications)
- Sessions via en-tête `Mcp-Session-Id`, reprise après coupure
- 🔐 Authentification web : **OAuth 2.1**, jetons *bearer*

Un déploiement → des milliers de clients.

---

# Matrice de décision transport

| Critère | stdio | Streamable HTTP |
|---|---|---|
| Localisation | Locale | Distante / cloud |
| Utilisateurs | 1 | N (mutualisé) |
| Auth | Héritée de l'OS | OAuth 2.1 / jetons |
| Fichiers locaux | ✅ direct | ❌ |
| Déploiement | Avec l'app hôte | Service web opéré |

> 🎓 **Règle de poche :** local mono-utilisateur → stdio · service partagé authentifié → Streamable HTTP · **SSE seul → réponse fausse.**

---

<!-- _class: lead -->

# Les trois primitives
## Le cœur de la certification

**La bonne question n'est pas « que fait-elle ? »
mais « QUI décide de l'invoquer ? »**

---

# Le tableau à mémoriser

| Primitive | Contrôleur | Déclencheur | Analogie |
|---|---|---|---|
| **Tool** | 🤖 **Le modèle** | Le LLM décide pendant son raisonnement | Les mains |
| **Resource** | 🖥️ **L'application** | L'hôte choisit quoi injecter au contexte | Les yeux (lecture seule) |
| **Prompt** | 👤 **L'utilisateur** | Menu / commande slash explicite | Formulaire pré-rempli |

*model-controlled · application-controlled · user-controlled*

---

# Tools — invoqués par le modèle

- Entrée : **JSON Schema** — le modèle sait quels arguments fournir
- Sortie : blocs de contenu structurés (`text`, `image`, ressource incorporée)
- **Effets de bord autorisés** ⇒ consentement utilisateur exigé

```json
{ "name": "rembourser_commande",
  "description": "Rembourse une commande. Refusé > 500 $.",
  "inputSchema": { "type": "object",
    "properties": { "numero": {"type": "string"},
                    "montant": {"type": "number"} },
    "required": ["numero", "montant"] } }
```

---

# Resources — exposées par l'application

- Identifiées par **URI** : `file:///rapports/q2.pdf` · `db://clients/12345` · `api://meteo/paris`
- **Lecture seule** — si ça modifie un état, c'est un Tool
- **Templates** : `db://commandes/{numero}` — familles de ressources
- **Abonnements** : `resources/subscribe` → `notifications/resources/updated`

> Pourquoi « application-controlled » ? L'hôte filtre ce qui entre dans le contexte : protection de la fenêtre **et** de la confidentialité.

---

# Prompts — déclenchés par l'utilisateur

- Modèles d'invites **paramétrés** : nom, description, arguments
- Présentés par l'hôte : commande slash, menu déroulant
- Cas d'usage : flux répétables et structurés

```json
{ "name": "rapport_incident",
  "arguments": [
    { "name": "severite", "required": true },
    { "name": "systeme",  "required": true } ] }
```

Exemples : analyse de PR · revue de contrat · rapport d'incident

---

# Le piège d'examen n° 1

> « Une recherche en base est une **lecture**, donc c'est une **Resource** » — **FAUX** ❌

- Si **le modèle** décide dynamiquement de chercher ⇒ **Tool** (même en lecture)
- Si **l'application** pré-sélectionne la donnée à injecter ⇒ **Resource**

**La frontière passe par le contrôleur, pas par lecture/écriture.**

---

# Arbre de décision

1. Effet de bord / modification d'état ? → **Tool**
2. Donnée à lire, injectée sur décision de l'**application** ? → **Resource**
3. Flux déclenché explicitement par l'**humain**, avec paramètres ? → **Prompt**
4. Le **modèle** doit décider seul d'aller chercher ? → **Tool**, même en lecture

*(Sélecteur interactif dans la page web de la session)*

---

# Serveur MCP en Python — squelette

```python
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("commandes")

@mcp.tool()
def rechercher_commande(numero: str) -> dict:
    """Le modèle décide QUAND appeler — rédigez pour lui."""
    ...

@mcp.resource("db://commandes/{numero}")
def fiche(numero: str) -> str: ...

@mcp.prompt()
def analyse_litige(numero: str, motif: str) -> str: ...

mcp.run()   # stdio par défaut
```
⚠ *API du SDK en évolution — vérifier la version.*

---

# Garde-fou côté serveur : les 500 $

```python
MAX_REMBOURSEMENT = 500.0

@mcp.tool()
def rembourser_commande(numero: str, montant: float) -> dict:
    if montant > MAX_REMBOURSEMENT:
        return { "statut": "refuse", "escalade": True,
                 "raison": "Validation humaine requise" }
    return { "statut": "effectue", ... }
```

> 🛡️ Dans le **prompt** : une *suggestion*, contournable par injection.
> Dans le **serveur** : une *loi*, qui tient même si le modèle est manipulé.
> **Défense en profondeur** — question d'examen.

---

# Normalisation à la frontière

Deux serveurs MCP, deux formats de dates :
`15/03/2026` (interne) · `27 Mar 2026` (transporteur)

```python
def normaliser_date(valeur: str) -> str:
    for fmt in ("%Y-%m-%d", "%d/%m/%Y", "%d %b %Y"):
        try:
            return datetime.strptime(valeur, fmt).date().isoformat()
        except ValueError: continue
    raise ValueError(f"Format inconnu : {valeur!r}")
```

> ✅ ISO 8601 partout, en **code**, à la frontière.
> ❌ Jamais « le modèle devinera » — ambiguïté 03/04 = 3 avril ou 4 mars ?

---

# Client MCP en Python

```python
async with stdio_client(params) as (lecture, ecriture):
    async with ClientSession(lecture, ecriture) as session:
        init = await session.initialize()        # négociation
        outils = await session.list_tools()      # découverte
        resultat = await session.call_tool(      # invocation
            "rembourser_commande",
            {"numero": "CMD-1", "montant": 750.0})
```

- La `ClientSession` gère handshake, `id`, corrélation requête/réponse
- Plusieurs serveurs ⇒ plusieurs sessions + **namespacing** des outils
  (`nordcommerce.rechercher` / `transporteur.statut`)

---

# Modèle de sécurité — 3 piliers

1. **Isolation des serveurs** — processus séparés, client dédié 1:1, **aucune fuite inter-serveurs** : ils ne se parlent jamais, seul l'hôte voit tout
2. **Consentement utilisateur** — tout appel d'outil à effet de bord est présenté et approuvé
3. **Moindre privilège** — jetons à portée réduite, répertoires explicitement autorisés

> Les sorties d'outils tiers = **données non fiables**, jamais des instructions (anti-injection de prompt).

---

# L'écosystème

**50+ serveurs communautaires** ⚠ *(croissance rapide — vérifier)*

filesystem · GitHub · Slack · PostgreSQL · Google Drive · navigateur · mémoire...

> 🧭 Réflexe d'architecte :
> **chercher un serveur existant avant d'en écrire un.**
> On développe pour ses systèmes métier internes, pas pour les intégrations génériques.

---

# Patrons avancés (1/2)

**Enregistrement dynamique d'outils**
- Le serveur ajoute/retire des outils en session
- Émet `notifications/tools/list_changed` → le client relance `tools/list`
- Cas : outils d'admin exposés **après** authentification

**Abonnement aux ressources**
- `resources/subscribe` → `notifications/resources/updated`
- Cas : configuration surveillée, ticket en évolution, tableau de bord

---

# Patrons avancés (2/2)

**Chaînage de prompts**
- Un Prompt MCP orchestre une séquence : recherche → vérification → proposition → escalade

**Normalisation à la frontière**
- Le serveur/client convertit dates, devises, unités vers un canon (ISO 8601)
- Le serveur MCP = **couche anti-corruption** entre systèmes hétérogènes

---

# Synthèse certification — 7 réflexes

1. Client:Serveur = **1:1**, l'hôte orchestre
2. Tout est **JSON-RPC 2.0** (requête / réponse / notification)
3. **stdio** = local · **Streamable HTTP** = distant · SSE = déprécié
4. **Tool = modèle · Resource = application · Prompt = utilisateur**
5. Négociation de capacités = **compatibilité évolutive**
6. Garde-fous métier **côté serveur** (les 500 $ !)
7. Sorties d'outils = données **non fiables** + consentement pour les effets de bord

---

<!-- _class: lead -->

# À vous de jouer 🛠️

**Exercice 1** — Construire le serveur NordCommerce (40 min)
*Tools + garde-fou 500 $ + normalisation de dates + Resource + Prompt*

**Exercice 2** — Client multi-serveurs (30 min)
*2 sessions, namespacing, fusion de données, ISO 8601 partout*

**Exercice 3** — Atelier de décision Tools/Resources/Prompts (20 min)

Puis : **quiz de validation (10 QCM)** — seuil certification : 8/10

---

<!-- _class: lead -->

# Session 5 — terminée ✅

**Prochaine session :** intégration MCP dans les architectures d'agents en production

📄 Guide complet : `doc-prof/guide.md`
🌐 Démo interactive : `webpage/index.html`
❓ Quiz : `quiz/quiz.md`

*Applied AI — Yann Isola*
