Exemples de serveurs MCP en Python et TypeScript (code GitHub) qui fonctionnent avec les SDK actuels

Des exemples de serveurs MCP en Python et TypeScript, prêts à copier, écrits d'après les dépôts SDK officiels sur GitHub. Chaque exemple fonctionne via stdio ou Streamable HTTP, et les deux derniers encapsulent une API d'images et de vidéos pour qu'un assistant génère des médias depuis une fenêtre de chat.

Exemples de serveurs MCP en Python et TypeScript (code GitHub) qui fonctionnent avec les SDK actuels
Cristian Da Conceicao
Fondateur de Picasso IA

Un serveur MCP est un petit programme qui fournit à un assistant d’IA une liste d’outils qu’il peut appeler. Le moyen le plus rapide de comprendre son fonctionnement est de lire quelques serveurs déjà opérationnels. Cet article rassemble des exemples de serveurs MCP en Python et TypeScript, écrits à partir des dépôts SDK officiels sur GitHub. Vous pouvez donc copier un fichier, le lancer et voir un assistant l’utiliser en quelques minutes.

Tout ce qui suit s’appuie sur la documentation des SDK en date d’octobre 2026, ce qui compte, car les deux SDK ont récemment atteint une deuxième version majeure. En Python, FastMCP est devenu MCPServer. En TypeScript, le code du serveur a été déplacé dans son propre paquet @modelcontextprotocol/server. La première moitié de l’article construit un serveur simple dans chaque langage. La seconde ajoute des outils qui génèrent des images et des vidéos, ce qui fait passer MCP du stade de démo à celui d’un outil qui fait gagner un vrai temps de travail.

Mains d’un développeur tapant sur un clavier, à côté d’un ordinateur portable et d’un schéma dessiné à la main

Ce que fait un serveur MCP

Le Model Context Protocol (MCP) est un standard ouvert qui permet à un client d’IA, comme une application de chat, un IDE ou un agent, d’appeler du code que vous avez écrit. Le client ouvre une connexion, demande à votre serveur ce qu’il propose, et laisse le modèle décider quand utiliser chaque élément. Les messages circulent en JSON-RPC, et votre serveur n’appelle jamais le modèle lui-même. Il attend qu’on le sollicite, exécute la fonction et renvoie un résultat.

Outils, ressources et prompts

Chaque serveur est construit à partir de trois types de briques :

  • Les outils sont des fonctions que le modèle peut appeler, comme add ou generate_image. Ils peuvent avoir des effets de bord, c’est pourquoi ce sont eux qu’il faut concevoir avec le plus de soin.
  • Les ressources sont des données en lecture seule identifiées par une URI, comme greeting://alice ou un chemin de fichier. Le client les lit pour donner du contexte au modèle.
  • Les prompts sont des modèles de messages réutilisables qu’une personne choisit dans un menu, par exemple une demande de revue de code.

💡 Astuce : commencez par les outils. La plupart des serveurs sur GitHub n’exposent que des outils, et un modèle choisit plus sûrement le bon outil dans une courte liste d’outils aux noms clairs que dans une longue liste d’outils vagues.

Quelle branche du SDK installer

Les deux SDK officiels proposent désormais une branche actuelle et une branche de maintenance. Choisissez la bonne avant de copier du code, car les imports diffèrent.

PythonTypeScript
Actuelle (v2)pip install "mcp[cli]", classe MCPServernpm install @modelcontextprotocol/server
Maintenance (v1.x)pip install "mcp[cli]<2", classe FastMCPnpm install @modelcontextprotocol/sdk zod
Dépôt GitHubmodelcontextprotocol/python-sdkmodelcontextprotocol/typescript-sdk

La branche Python v1 ne reçoit plus que des correctifs de sécurité. Les nouveaux projets doivent donc démarrer sur la v2, et c’est ce que font les exemples Python ci-dessous. Si vous maintenez un serveur Python ancien, la migration consiste à remplacer from mcp.server.fastmcp import FastMCP par from mcp.server.mcpserver import MCPServer et à renommer l’appel au constructeur. Les décorateurs restent les mêmes.

Pour TypeScript, les exemples complets utilisent le paquet v1.x que la plupart des serveurs existants importent aujourd’hui. Une version courte en v2 du même serveur suit, pour que vous voyiez précisément ce qui change.

Panneau de brassage réseau avec des câbles bleus et gris soigneusement acheminés

Exemple Python avec MCPServer

Python offre le chemin le plus court entre rien et un serveur fonctionnel, car les annotations de type et les docstrings deviennent le schéma de l’outil. Il n’y a aucun JSON Schema à écrire à la main.

Le fichier du serveur

Installez avec uv add "mcp[cli]" (ou pip install "mcp[cli]"), puis enregistrez ceci sous server.py :

from mcp.server.mcpserver import MCPServer

mcp = MCPServer("Demo")


@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b


@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"


@mcp.prompt()
def review_code(code: str) -> str:
    """Ask for a code review that lists bugs first."""
    return f"Please review this code and list bugs first:\n\n{code}"


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

La fonction add devient un outil dont le schéma d’entrée est généré à partir de a: int, b: int. La docstring devient la description que le modèle lit pour décider s’il doit l’appeler. greeting est un modèle de ressource : un client qui lit greeting://Ada reçoit en retour Hello, Ada!.

Lancer et inspecter

uv run mcp dev server.py        # opens the MCP Inspector in your browser
uv run mcp run server.py        # plain stdio, for a client to launch
uv run mcp run server.py --transport streamable-http   # HTTP instead

Commencez par l’Inspector. Il liste chaque outil, vous propose un formulaire pour les arguments et affiche le trafic JSON-RPC brut, ce qui est la façon la plus rapide de repérer un mauvais schéma. Une fois que tout fonctionne, enregistrez le serveur auprès d’un client. Dans Claude Code, cela tient en une ligne :

claude mcp add demo -- uv run mcp run server.py

Vue en contre-plongée d’un développeur debout devant un bureau avec deux écrans, dans un loft lumineux

Exemple TypeScript avec Zod

TypeScript demande un peu plus de formalisme, car vous décrivez les entrées avec Zod au lieu des annotations de type. En contrepartie, vous obtenez une validation à l’exécution et des arguments typés dans votre gestionnaire.

Le fichier du serveur en v1.x

Installez le paquet avec npm install @modelcontextprotocol/sdk zod et définissez "type": "module" dans package.json. Écrivez le serveur comme une fonction afin que les deux transports puissent la réutiliser. Enregistrez ceci sous src/build-server.ts :

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

export function buildServer(): McpServer {
  const server = new McpServer({ name: "demo", version: "1.0.0" });

  server.registerTool(
    "add",
    {
      title: "Add numbers",
      description: "Add two numbers",
      inputSchema: { a: z.number(), b: z.number() },
    },
    async ({ a, b }) => ({
      content: [{ type: "text", text: String(a + b) }],
    })
  );

  return server;
}

Puis un point d’entrée de trois lignes pour stdio, dans src/stdio.ts :

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { buildServer } from "./build-server.js";

await buildServer().connect(new StdioServerTransport());

Compilez avec tsc, puis vérifiez avec npx @modelcontextprotocol/inspector node dist/stdio.js. Les ressources et les prompts suivent la même forme via registerResource et registerPrompt. Le dépôt v1.x fournit aussi src/examples/server/simpleStreamableHttp.ts, un exemple complet avec outils, ressources, prompts, journalisation et OAuth optionnel, qui mérite d’être lu une fois que votre serveur dépasse le simple jouet.

Le même serveur en v2

import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";

const server = new McpServer({ name: "demo", version: "1.0.0" });

server.registerTool(
  "add",
  {
    description: "Add two numbers",
    inputSchema: z.object({ a: z.number(), b: z.number() }),
  },
  async ({ a, b }) => ({
    content: [{ type: "text", text: String(a + b) }],
  })
);

await server.connect(new StdioServerTransport());

Trois éléments changent : le nom du paquet, le sous-chemin d’import /stdio, et le schéma, qui devient un z.object(...) complet issu de zod/v4 au lieu d’un simple objet de champs. Le corps du gestionnaire reste identique. Le service via HTTP en v2 passe par de petits paquets d’adaptation, comme @modelcontextprotocol/express : lisez donc le README de ce paquet avant de porter un serveur HTTP.

Homme tapant sur un ordinateur portable devant la vitre d’un café, sous la pluie

Stdio ou Streamable HTTP

Le transport est la seule décision qui change la façon de déployer. Le code des outils reste identique.

stdioStreamable HTTP
Qui lance le serveurLe client le lance comme processus enfantVous le faites tourner, les clients se connectent par URL
Idéal pourOutils locaux, IDE, applications de bureauServeurs partagés ou distants, équipes
AuthentificationHérite de votre utilisateur et de votre environnementVous l’ajoutez (jetons ou OAuth)
Journalisationstderr uniquementLa sortie normale convient
Mise à l’échelleUn processus par clientMise à l’échelle web classique

Processus local via stdio

Un serveur stdio est le bon choix par défaut pour tout ce qui touche à votre propre machine, comme des fichiers, une base de données locale ou un script. Le client le démarre, communique via son stdin et son stdout, et l’arrête à la fin de la session. Il n’y a pas de port à sécuriser.

Serveur distant via HTTP

Streamable HTTP permet à un seul serveur en fonctionnement de répondre à de nombreux clients. Cette version Express sans état crée un nouveau serveur pour chaque requête, ce qui évite un état de session partagé. Enregistrez-la sous src/http.ts :

import express from "express";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { buildServer } from "./build-server.js";

const app = express();
app.use(express.json());

app.post("/mcp", async (req, res) => {
  const server = buildServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });
  res.on("close", () => {
    transport.close();
    server.close();
  });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

app.listen(3000, "127.0.0.1");

Enregistrez-le avec claude mcp add --transport http demo http://127.0.0.1:3000/mcp. En Python, l’équivalent tient en une ligne : mcp.run(transport="streamable-http", host="127.0.0.1", port=9000). Liez-le à localhost, sauf si quelque chose devant le serveur gère l’authentification, car un port MCP ouvert est une porte ouverte sur chaque outil que vous avez enregistré.

Vue aérienne d’une allée de centre de données avec des rangées de baies serveurs noires

Exemple : des outils qui génèrent des images

Les outils deviennent plus intéressants lorsque le résultat n’est pas un nombre. La génération d’images et de vidéos fait de bons cas d’étude, car elle est lente, asynchrone et renvoie une URL plutôt que du texte. Les exemples ci-dessous appellent l’API PicassoIA, qui suit un style proche de Replicate : créer une prédiction, l’interroger, lire le résultat.

  • URL de base et authentification : https://api.picassoia.com/v1 avec l’en-tête Authorization: Bearer pia_sk_...
  • Création : POST /models/{owner}/{name}/predictions avec le corps {"input": {...}}
  • Interrogation : GET /predictions/{id} jusqu’à ce que le statut soit succeeded, failed ou canceled
  • Délais : la réponse de création inclut eta.next_poll_in_seconds, un intervalle d’interrogation qu’il vaut la peine de respecter

Outil Python avec interrogation

import asyncio
import os

import httpx
from mcp.server.mcpserver import MCPServer

API = "https://api.picassoia.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['PICASSOIA_API_TOKEN']}"}

mcp = MCPServer("picassoia-media")
slots = asyncio.Semaphore(5)  # the account allows 5 concurrent predictions


async def run_prediction(model: str, payload: dict, timeout_s: int = 600) -> list[str]:
    async with slots, httpx.AsyncClient(headers=HEADERS, timeout=30) as http:
        created = await http.post(
            f"{API}/models/{model}/predictions", json={"input": payload}
        )
        created.raise_for_status()
        prediction = created.json()

        waited = 0
        while prediction["status"] in ("starting", "processing"):
            if waited >= timeout_s:
                raise TimeoutError(f"Prediction {prediction['id']} is still running")
            delay = (prediction.get("eta") or {}).get("next_poll_in_seconds", 3)
            await asyncio.sleep(delay)
            waited += delay
            polled = await http.get(f"{API}/predictions/{prediction['id']}")
            polled.raise_for_status()
            prediction = polled.json()

    if prediction["status"] != "succeeded":
        raise RuntimeError(f"Prediction {prediction['status']}: {prediction.get('error')}")
    output = prediction["output"]
    return output if isinstance(output, list) else [output]


@mcp.tool()
async def generate_image(prompt: str, aspect_ratio: str = "16:9") -> str:
    """Generate one image from a text prompt and return its URL."""
    urls = await run_prediction(
        "picassoia/picassoia-image",
        {"prompt": prompt, "aspect_ratio": aspect_ratio, "num_outputs": 1},
    )
    return urls[0]


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

Deux détails comptent ici. La boucle utilise asyncio.sleep, afin que le serveur reste réactif aux autres requêtes pendant qu’une tâche tourne, et elle suit l’intervalle suggéré par l’API au lieu de la solliciter en continu. Le sémaphore vous maintient sous la limite de concurrence du compte. Le modèle PicassoIA Image accepte prompt, aspect_ratio, seed, num_outputs (1 ou 2), output_format et output_quality. Pour les retouches, faites pointer le même helper vers PicassoIA Image Editor Pro.

Photographe examinant de grands tirages de paysages côtiers sur un plan de travail en bois

Outil TypeScript pour la vidéo

Le même schéma fonctionne en TypeScript. Cette version ajoute un outil vidéo à la fonction buildServer vue précédemment :

const API = "https://api.picassoia.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.PICASSOIA_API_TOKEN}`,
  "Content-Type": "application/json",
};

async function runPrediction(model: string, input: Record<string, unknown>) {
  const created = await fetch(`${API}/models/${model}/predictions`, {
    method: "POST",
    headers,
    body: JSON.stringify({ input }),
  });
  if (!created.ok) throw new Error(`Create failed: ${created.status}`);
  let prediction = await created.json();

  while (["starting", "processing"].includes(prediction.status)) {
    const delay = prediction.eta?.next_poll_in_seconds ?? 5;
    await new Promise((resolve) => setTimeout(resolve, delay * 1000));
    const polled = await fetch(`${API}/predictions/${prediction.id}`, { headers });
    prediction = await polled.json();
  }
  if (prediction.status !== "succeeded") {
    throw new Error(`Prediction ${prediction.status}`);
  }
  return [prediction.output].flat() as string[];
}

server.registerTool(
  "generate_video",
  {
    title: "Generate video",
    description: "Make a short clip with synchronized audio from a text prompt",
    inputSchema: {
      prompt: z.string().max(4000),
      duration: z.union([z.literal(5), z.literal(10)]).default(5),
      resolution: z.enum(["480p", "720p"]).default("720p"),
    },
  },
  async ({ prompt, duration, resolution }) => {
    const [url] = await runPrediction("picassoia/seedance-2.5-lite", {
      prompt,
      duration,
      resolution,
    });
    return { content: [{ type: "text", text: url }] };
  }
);

La vidéo est plus lente. La page du modèle Seedance 2.5 Lite indique des exemples d’exécution d’environ 100 à 190 secondes, un délai suffisamment long pour que certains clients abandonnent un appel d’outil unique. Une conception plus sûre répartit le travail en deux : start_video renvoie immédiatement la prédiction id, et check_video prend cet identifiant et renvoie soit le statut, soit l’URL finale.

C’est ainsi que se comporte le connecteur PicassoIA pour claude.ai. Ses outils de génération renvoient un predict_id et un délai d’attente suggéré, et get_generation est appelé jusqu’à ce que la tâche réussisse ou échoue. Le même modèle accepte aussi un image facultatif comme première image, un seed, un aspect_ratio et un indicateur save_audio pour les clips sans son.

💡 Astuce : gardez le résultat de l’outil léger. Renvoyez l’URL et un résumé d’une ligne, pas le fichier. L’assistant n’a besoin que d’un lien à afficher ou à transmettre.

Monteur vidéo dans une salle de montage sombre, travaillant sur une timeline avec une molette de défilement

Utiliser PicassoIA avec MCP

Il existe deux façons d’atteindre PicassoIA depuis un assistant : le connecteur prêt à l’emploi, ou votre propre serveur qui encapsule l’API, comme dans les exemples ci-dessus.

Installation pas à pas

  1. Choisissez la voie. Pour un client de chat, ajoutez le connecteur PicassoIA dans vos paramètres claude.ai. Il expose generate_image, edit_image, generate_video_picassoia, generate_video_seedance, get_generation, cancel_generation, list_models, get_account et list_generations. Pour votre propre code d’agent, utilisez la voie API.
  2. Créez un token API. Connectez-vous à la page API de PicassoIA et créez-en un. Il commence par pia_sk_, et un compte en détient au maximum deux. Consultez la page des tarifs pour savoir quel forfait inclut l’accès à l’API avant de construire dessus.
  3. Stockez-le dans l’environnement. Exécutez export PICASSOIA_API_TOKEN=pia_sk_... dans votre shell. Gardez-le hors des fichiers source et de toute configuration client que vous versionnez.
  4. Choisissez un modèle dans le tableau ci-dessous.
  5. Encapsulez et testez. Collez un outil issu des exemples, lancez-le dans l’Inspector avec un prompt simple, puis enregistrez-le auprès de votre client.
ModèleÀ utiliser pour
PicassoIA ImageTexte vers image, une ou deux sorties par appel
PicassoIA Image Editor ProRetouche et combinaison d’images
PicassoIA VideoTexte ou image vers vidéo
Seedance 2.5 LiteTexte ou image vers vidéo avec audio synchronisé, 5 ou 10 secondes

Quel modèle décide quand appeler vos outils ? N’importe quel LLM capable d’appeler des outils. Le catalogue de grands modèles de langage de PicassoIA propose notamment Claude Sonnet 5, GPT 5.6 Sol, Kimi K2.6 et Gemini 3.5 Flash, parmi beaucoup d’autres. Essayez-en plusieurs sur le même serveur et comparez leur fiabilité pour choisir le bon outil.

Les limites à connaître

  • 5 prédictions simultanées par compte, partagées entre les jetons et les connexions MCP
  • 10 Mo de taille maximale du corps de requête
  • 4 000 caractères maximum par prompt
  • 3 heures avant qu’une prédiction n’expire
  • 2 tokens API par compte

Les erreurs qui cassent les serveurs MCP

La plupart des premiers essais échoués remontent à l’un de ces trois problèmes. Chacun est facile à éviter une fois qu’on sait où regarder.

Journaliser sur stdout

Avec stdio, la sortie standard est le canal du protocole. Un print() ou console.log() égaré corrompt le flux JSON-RPC, et le client signale une erreur d’analyse ou un serveur qui ne s’est jamais connecté. Envoyez plutôt les journaux vers la sortie d’erreur standard :

import sys

print("polling prediction", file=sys.stderr)   # safe
# print("polling prediction")                   # breaks stdio

En TypeScript, utilisez console.error("polling prediction").

Main entourant une ligne sur une page imprimée de journaux d’erreurs, à côté d’un ordinateur portable

Appels bloquants et noms vagues

Un time.sleep(30) dans un outil asynchrone fige toutes les autres requêtes du même serveur. Utilisez await asyncio.sleep en Python et une minuterie utilisée avec await en TypeScript, comme le font les exemples. Les noms comptent tout autant : un outil appelé do_task ne donne rien au modèle pour choisir, alors que generate_image avec une docstring claire lui indique exactement quand faire appel à l’outil.

Secrets et charges volumineuses

Lisez les tokens depuis les variables d’environnement, jamais depuis les fichiers source, et ne les renvoyez jamais dans un résultat d’outil. Pour la sortie, renvoyez des URL plutôt que du base64. Une seule image en base64 peut atteindre plusieurs mégaoctets et saturer la fenêtre de contexte du modèle, et l’API PicassoIA limite de toute façon les corps de requête à 10 Mo.

Gros plan d’un cadenas en laiton sur la porte d’une cage de serveurs en acier

Essayez dès aujourd’hui sur PicassoIA

Copiez server.py ou la paire TypeScript, lancez-le dans l’Inspector, et vous disposez d’un serveur MCP fonctionnel en moins de dix minutes. Donnez-lui ensuite quelque chose de visuel à faire. Les serveurs de référence officiels sur GitHub sont un bon endroit pour découvrir d’autres schémas, et les deux dépôts SDK contiennent un dossier d’exemples.

Ouvrez PicassoIA Image et rédigez votre propre prompt, retouchez une photo avec Image Editor Pro, ou animez une image avec Seedance 2.5 Lite. Quoi que vous construisiez, la boucle reste la même : décrire, soumettre, interroger, vérifier.

Essayez de créer vos propres images avec Picasso IA, et lorsqu’un prompt fonctionne, encapsulez-le dans un outil afin que votre assistant puisse le reproduire à la demande.

Partager cet article

Choisissez votre langue