---
title: "Un serveur MCP"
description: "Écrire un petit serveur qui donne à un assistant l'accès à vos données."
url: https://meridal.fr/apprendre/un-serveur-mcp/
language: fr
written: 2026-10-09
verified: 2026-10-09
license: CC BY 4.0
---

[Apprendre](https://meridal.fr/apprendre/) › [Les agents](https://meridal.fr/apprendre/dossier/agents/)

S'exercer · Niveau 3, Construire · 45 minutes

# Un serveur MCP

Un serveur MCP donne à un assistant, Claude Desktop, Claude Code ou un éditeur, l'accès à vos données par des outils que vous écrivez. Quarante-cinq minutes pour en écrire un en Python, en lecture seule, sur un fichier de clients et un fichier de factures fabriqués ; le tester sans modèle, puis le brancher.

Écrit le 9 octobre 2026 · Vérifié le 9 octobre 2026

## Vous saurez

- Écrire un serveur MCP avec le SDK Python officiel, version 2
- Le tester sans modèle, avec un client qui le lance
- Le brancher sur Claude Code ou Claude Desktop
- Garder un serveur sûr : lecture seule, entrées vérifiées, rien sur la sortie standard

## Dans le parcours

[Construction](https://meridal.fr/apprendre/parcours/construire/)

Étape 7 sur 12

- [← Avant Un agent avec deux outils](https://meridal.fr/apprendre/un-agent-avec-deux-outils/)
- [↑ Plus général Connecteurs et MCP](https://meridal.fr/apprendre/connecteurs-et-mcp/)
- [→ Ensuite Évaluation d'un outil d'IA](https://meridal.fr/apprendre/evaluer-un-outil-d-ia/)

L'essentiel

1. Un serveur MCP expose des outils ; l'assistant, client MCP, les découvre et les appelle. La révision actuelle du protocole date du 28 juillet 2026.
2. Le SDK Python officiel, version 2, appelle MCPServer ce qui s'appelait FastMCP ; l'ancien chemin d'import n'existe plus. Un outil, c'est une fonction décorée, décrite par sa docstring. mcp 2.3.0 au 2 octobre 2026.
3. En local, l'assistant lance le serveur et lui parle par stdio : rien ne doit sortir sur la sortie standard hors du protocole ; un print suffit à tout casser.
4. La sécurité est dans le serveur : la spécification demande de valider les entrées, contrôler l'accès, limiter les appels et nettoyer les sorties. Ici, lecture seule.
5. Tout se teste sans modèle : un client du SDK lance le serveur et appelle ses outils, comme le ferait l'assistant, sans rien payer.

## Objectif

Écrire un serveur MCP en Python, avec deux outils en lecture seule : `chercher_client`, qui donne la fiche d'un client, et `factures_en_retard`, qui liste les factures non payées dont l'échéance est passée. Le tester avec un client qui le lance comme le ferait un assistant, puis le brancher sur Claude Code ou Claude Desktop. Compter quarante-cinq minutes, après [l'agent à deux outils](https://meridal.fr/apprendre/un-agent-avec-deux-outils/).

## Données fournies

Deux fichiers fabriqués, séparés par des points-virgules ; les adresses en « .example » ne reçoivent aucun courriel.

`clients.csv`

```
client;ville;contact;encours_eur
Société A;Lyon;compta@societe-a.example;1250.00
Société B;Nantes;factures@societe-b.example;6300.00
Société C;Lille;admin@societe-c.example;0.00
```

`factures.csv`

```
numero;client;montant_eur;echeance;payee
F-2026-097;Société A;1250.00;2026-09-15;non
F-2026-101;Société B;4320.00;2026-08-30;non
F-2026-104;Société C;860.40;2026-09-30;oui
F-2026-115;Société B;1980.00;2026-11-20;non
```

Qui parle à qui

1. L'assistant Claude Desktop, Claude Code, un éditeur : le client MCP, qui lance le serveur
2. stdio des messages JSON-RPC par l'entrée et la sortie standard, sur le même poste
3. serveur.py vos deux outils, en lecture seule ; les journaux sur la sortie d'erreur
4. Les fichiers clients.csv et factures.csv ; demain, votre logiciel

*Le transport stdio, pour un serveur local ; l'autre transport standard, Streamable HTTP, sert un serveur à distance, avec ses propres règles de sécurité.*

## Consignes

1. Préparer le projet uv init suivi-clients uv add "mcp[cli]" **Ce qu'on fait :** Python 3.10 ou plus récent ; le SDK officiel, sous licence MIT.
2. Écrire serveur.py **Ce qu'on fait :** un MCPServer, deux fonctions décorées par @mcp.tool(), une docstring qui dit à quoi sert chaque outil et ce qu'il attend ; des types, pour que le résultat soit structuré.
3. Ne rien écrire sur la sortie standard **Ce qu'on fait :** pas de print : les journaux passent par logging, sur la sortie d'erreur.
4. Vérifier les entrées **Ce qu'on fait :** un client inconnu ou une date mal écrite renvoient un message clair, avec ToolError, au lieu d'une erreur générique.
5. Tester sans modèle uv run essai_client.py **Ce qu'on fait :** le client lance le serveur sur stdio, liste ses outils et les appelle.
6. Le brancher sur un assistant Claude Code : claude mcp add suivi-clients -- uv --directory /chemin/absolu/suivi-clients run serveur.py Claude Desktop : la déclaration plus bas, dans sa configuration **Ce qu'on fait :** le serveur lancé par l'assistant à la demande.
7. Poser la question **Ce qu'on fait :** « Quelles factures sont en retard, et qui faut-il relancer ? » : l'assistant appelle factures_en_retard, puis chercher_client.

## Critères de réussite

- Le client d'essai liste les deux outils
- La fiche de Société B revient structurée : ville, contact, encours
- Un client inconnu renvoie un message clair, sans trace d'erreur
- Au 9 octobre 2026, deux factures sont en retard : F-2026-097 et F-2026-101 la F-2026-104 est payée ; la F-2026-115 n'est pas échue
- Aucun outil n'écrit, et le serveur n'affiche rien sur la sortie standard
- L'assistant appelle les outils et cite les deux factures

## Indications

**Le client se bloque ou reçoit des messages illisibles**

**Cause**Un print écrit sur la sortie standard, qui est réservée au protocole.

**À faire**Remplacer chaque print par logging ; vérifier les bibliothèques qui écrivent sur la sortie standard.

**ImportError sur mcp.server.fastmcp**

**Cause**Un exemple écrit pour la version 1 : en version 2, FastMCP s'appelle MCPServer et l'ancien chemin a disparu.

**À faire**from mcp.server import MCPServer ; puis MCPServer("Suivi clients").

**Une erreur d'outil n'arrive que sous la forme « Error executing tool »**

**Cause**Une exception ordinaire est traitée comme une panne : son texte reste sur le serveur.

**À faire**Lever ToolError pour une erreur prévue : son message part au client, sans trace.

**L'assistant ne voit pas le serveur**

**Cause**Le chemin n'est pas absolu, ou l'assistant n'a pas été relancé.

**À faire**Un chemin absolu dans la commande ; relancer l'assistant ; dans Claude Code, un serveur déclaré dans .mcp.json attend d'être approuvé.

**Le serveur doit servir toute l'équipe, à distance**

**Cause**En Streamable HTTP, le serveur doit vérifier l'en-tête Origin, et l'autorisation suit OAuth 2.1 ; un token reçu ne se transmet jamais à une autre API.

**À faire**Commencer en local ; pour le reste, [la sécurité d'une application d'IA](https://meridal.fr/apprendre/securite-d-une-application-d-ia/).

## Correction commentée

`serveur.py`

```
"""Un serveur MCP en lecture seule sur deux fichiers de l'entreprise : les clients et les factures.
Données fabriquées. Rien ne s'écrit ; rien ne sort sur stdout, réservé au protocole."""
import csv
import logging
from datetime import date
from pathlib import Path
from typing import TypedDict

from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError

ICI = Path(__file__).parent
logging.basicConfig(level=logging.INFO)  # les journaux vont sur stderr
mcp = MCPServer("Suivi clients")

class Fiche(TypedDict):
    client: str
    ville: str
    contact: str
    encours_eur: str

def _lire(nom: str) -> list[dict]:
    with open(ICI / nom, encoding="utf-8", newline="") as f:
        return list(csv.DictReader(f, delimiter=";"))

@mcp.tool()
def chercher_client(nom: str) -> Fiche:
    """Donne la fiche d'un client : ville, contact, encours.

    Args:
        nom: Le nom exact du client, par exemple Société A.
    """
    for ligne in _lire("clients.csv"):
        if ligne["client"] == nom:
            return ligne
    raise ToolError(f"Aucun client nommé {nom}.")  # le message part au modèle, sans trace

@mcp.tool()
def factures_en_retard(au: str = "") -> list[dict]:
    """Liste les factures non payées dont l'échéance est passée.

    Args:
        au: La date de référence, au format AAAA-MM-JJ ; par défaut, aujourd'hui.
    """
    try:
        jour = date.fromisoformat(au) if au else date.today()
    except ValueError:
        raise ToolError("Date attendue au format AAAA-MM-JJ.")
    return [f for f in _lire("factures.csv")
            if f["payee"] == "non" and date.fromisoformat(f["echeance"]) < jour]

if __name__ == "__main__":
    mcp.run(transport="stdio")
```

`essai_client.py`

```
"""Lance le serveur comme le ferait un assistant, sur stdio, et appelle ses deux outils. Aucun modèle."""
import asyncio
import sys

from mcp import Client, StdioServerParameters

async def main() -> None:
    serveur = StdioServerParameters(command=sys.executable, args=["serveur.py"])
    async with Client(serveur) as client:
        outils = await client.list_tools()
        print("Outils :", ", ".join(o.name for o in outils.tools))
        fiche = await client.call_tool("chercher_client", {"nom": "Société B"})
        f = fiche.structured_content
        print(f"Société B : {f['ville']}, {f['contact']}, encours {f['encours_eur']} €")
        inconnu = await client.call_tool("chercher_client", {"nom": "Société Z"})
        print("Société Z :", inconnu.content[0].text if inconnu.is_error else "trouvée")
        retards = await client.call_tool("factures_en_retard", {"au": "2026-10-09"})
        print("En retard au 9 octobre 2026 :")
        for r in retards.structured_content["result"]:
            print(f"  {r['numero']}, {r['client']}, {r['montant_eur']} €, échue le {r['echeance']}")

asyncio.run(main())
```

La sortie`uv run essai_client.py`

```
Outils : chercher_client, factures_en_retard
Société B : Nantes, factures@societe-b.example, encours 6300.00 €
Société Z : Error executing tool chercher_client: Aucun client nommé Société Z.
En retard au 9 octobre 2026 :
  F-2026-097, Société A, 1250.00 €, échue le 2026-09-15
  F-2026-101, Société B, 4320.00 €, échue le 2026-08-30
```

Essayé le 9 octobre 2026 avec Python 3.12.7 et mcp 2.3.0, sans modèle.

**Le brancher sur Claude Desktop** : la déclaration, sur le modèle du tutoriel officiel, avec un chemin absolu.

`claude_desktop_config.json`

```
{
  "mcpServers": {
    "suivi-clients": {
      "command": "uv",
      "args": [
        "--directory",
        "/CHEMIN/ABSOLU/suivi-clients",
        "run",
        "serveur.py"
      ]
    }
  }
}
```

La sortie`Claude Desktop : « Quelles factures sont en retard ? »`

**Pas encore exécuté par Meridal.** Ce qu'on attend : l'assistant appelle factures_en_retard, puis cite F-2026-097 et F-2026-101.

**Pourquoi en lecture seule.** Un assistant qui lit des contenus venus de l'extérieur ne doit pas pouvoir écrire ou envoyer sans une personne ; un outil d'écriture viendra plus tard, avec une validation.

## Extensions

- **Le servir en Streamable HTTP** : `uv run mcp run serveur.py --transport streamable-http` ; puis l'en-tête Origin, l'écoute sur localhost en local, et l'autorisation OAuth 2.1 pour un accès à distance.
- **L'inspecteur MCP** : `uv run mcp dev serveur.py` ouvre une interface pour essayer chaque outil à la main.
- **Votre vrai logiciel** à la place des fichiers : un outil de lecture par question utile, jamais un accès général à la base.
- **Les serveurs déjà publiés** par les éditeurs : [les serveurs MCP existants](https://meridal.fr/apprendre/serveurs-mcp-existants/) ; le projet complet : [un serveur MCP sur un logiciel de suivi des clients](https://meridal.fr/apprendre/projet-serveur-mcp-sur-un-logiciel-clients/).

## Lectures suivantes

↑ Plus général [Connecteurs et MCP Comprendre · Niveau 2 · 15 minutes](https://meridal.fr/apprendre/connecteurs-et-mcp/)

← Avant [Un agent avec deux outils S'exercer · Niveau 3 · 45 minutes](https://meridal.fr/apprendre/un-agent-avec-deux-outils/)

Vous êtes ici

Un serveur MCP

→ Ensuite [Évaluation d'un outil d'IA Faire · Niveau 3 · 14 minutes](https://meridal.fr/apprendre/evaluer-un-outil-d-ia/)

Construction · étape 8 sur 12

↓ Plus concret

Rien avant : c'est un point de départ.

[Tout le dossier « Les agents »](https://meridal.fr/apprendre/dossier/agents/)

Les pages qui renvoient ici

- [Un agent avec deux outils](https://meridal.fr/apprendre/un-agent-avec-deux-outils/)
- [Un agent en entreprise de bout en bout](https://meridal.fr/apprendre/un-agent-en-entreprise/)

**Limites et sources**

Les fichiers sont fabriqués ; le serveur et son client ont tourné sur un poste de Meridal, sans modèle.

La question posée à l'assistant n'a pas été exécutée : elle appelle un modèle payant.

Le SDK et le protocole changent vite ; versions au 2 octobre et au 28 juillet 2026.

1. [SDK Python officiel](https://github.com/modelcontextprotocol/python-sdk/blob/main/README.md) ; [nouveautés de la version 2](https://py.sdk.modelcontextprotocol.io/whats-new/) ; [tutoriel officiel](https://modelcontextprotocol.io/docs/develop/build-server) ; [les outils dans la spécification](https://modelcontextprotocol.io/specification/2026-07-28/server/tools) ; [bonnes pratiques de sécurité](https://modelcontextprotocol.io/specification/2026-07-28/basic/security_best_practices) ; [Claude Code et MCP](https://code.claude.com/docs/en/mcp).
2. Le relevé : `apprendre/recherches/2026-10-08-dev-avance.json` ; le code : `apprendre/pages/agents/serveur-mcp/code/`.

Écrit par Meridal, avec l'aide de l'IA ; chaque fait vérifié à sa source et relu avant publication. Texte et schémas sous licence [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.fr) : reprenez-les en citant Meridal. Le code sous licence MIT.

Service correspondant

Ne plus recopier d'un logiciel à l'autre

1 100 € à 3 400 €, puis sur devis

[Voir le service](https://meridal.fr/offre/#automatisation)

## Un premier échange de 30 minutes

En visio et sans frais : vous décrivez votre situation, nous vous indiquons ce qui est réalisable, avec quels outils et pour quel ordre de prix. Vous pouvez aussi nous écrire ; chaque message reçoit une réponse écrite.

[Réserver 30 minutes](https://meridal.fr/contact/#reserver) [Écrire un message](https://meridal.fr/contact/)
