Convertir une API en serveur MCP : REST et OpenAPI étape par étape

Encapsulez une API REST existante dans un serveur MCP que les agents peuvent appeler sans deviner. Générez des outils à partir d’un fichier OpenAPI avec FastMCP, construisez-les à la main en TypeScript, gérez l’authentification et les tâches lentes de génération d’images ou de vidéos, puis testez avec l’Inspector et déployez via stdio ou HTTP.

Convertir une API en serveur MCP : REST et OpenAPI étape par étape
Cristian Da Conceicao
Fondateur de Picasso IA

Votre API REST fonctionne déjà, et pourtant les agents peinent à l’utiliser. Ils devinent les noms de paramètres, butent sur des réponses JSON de 40 Ko et appellent DELETE alors qu’ils voulaient appeler GET. La solution n’est pas un modèle plus intelligent. C’est une couche fine intercalée entre les deux : un serveur MCP qui indique à l’agent exactement quelles actions existent, à quoi ressemble chaque entrée et ce qui revient en sortie. Ce tutoriel montre comment convertir une API en serveur MCP à partir de sa spec OpenAPI, d’abord avec du code généré, puis à la main, en couvrant l’authentification, les tâches lentes, les tests et le déploiement.

Il vous faut trois choses avant de commencer : une API que vous savez déjà appeler avec curl, son fichier OpenAPI 3.x (ou la patience de l’écrire) et un client MCP comme Claude Desktop, Cursor ou VS Code pour essayer le résultat. Une première version fonctionnelle prend un après-midi. Le peaufinage demande le plus de temps, et c’est aussi là que se joue la qualité.

💡 En bref : MCP enveloppe votre API dans des outils. Chaque outil a un nom, une description et un JSON Schema pour son entrée. L’agent choisit ses outils en lisant ces descriptions, donc les descriptions comptent plus que la mécanique HTTP.

Pourquoi encapsuler une API en MCP

REST a été conçu pour des développeurs qui lisent la documentation une fois et écrivent ensuite du code à partir de celle-ci. Un agent fonctionne autrement. Il lit ce que le serveur liste au début d’une session, puis décide uniquement à partir de cette liste quel appel effectuer. Si la liste est vague, il devine. Si elle est énorme, il consomme sa fenêtre de contexte avant que l’utilisateur ait tapé un seul mot.

Opératrice branchant des cordons de liaison sur un standard téléphonique vintage

Un serveur MCP résout les deux problèmes à la manière d’une standardiste : il reçoit une demande claire, la dirige vers la bonne ligne et renvoie une réponse nette.

Ce que voit réellement l’agent

Quand un client se connecte, il demande au serveur la liste de ses outils. Chaque entrée contient un name, un description, un inputSchema écrit en JSON Schema et, en option, une outputSchema ainsi que des annotations. C’est toute la surface visible. L’agent ne voit jamais vos routes, vos verbes HTTP ni vos codes de statut. Il voit des noms, des phrases et des schémas.

REST vers MCP en un coup d’œil

Chaque élément d’une opération OpenAPI trouve sa place côté MCP :

REST / OpenAPIOutil MCP
operationIdOutil name
summary et descriptionOutil description
Paramètres de chemin, de requête et de corpsinputSchema, un seul objet JSON Schema à plat
Schéma de réponse 200outputSchema et contenu structuré
Réponses 4xx et 5xxRésultat avec isError: true et un message lisible
Schéma de sécuritéConfiguration du serveur : jeton d’environnement, ou OAuth pour les serveurs distants
Liens de paginationEntrées explicites cursor et limit

Le contenu structuré et les schémas de sortie sont apparus avec la révision 2025-06-18 de la spec. Vérifiez donc que votre version du SDK les prend en charge avant de vous appuyer sur outputSchema.

Associer les opérations OpenAPI aux outils

Ouvrez la spec et résistez à l’envie de tout exposer. Une API de 120 endpoints devient un serveur de 120 outils, et la seule liste d’outils peut consommer des milliers de tokens à chaque conversation. Commencez petit, nommez bien les choses et décrivez-les comme le ferait un collègue.

Pages imprimées d’une référence d’API surlignées, avec une ligne entourée au crayon

Choisir des opérations, pas des endpoints

Posez quatre questions à chaque endpoint avant d’en faire un outil :

  • Une personne demanderait-elle à un assistant de faire cela en langage courant ?
  • Est-il sans danger de l’appeler deux fois si l’agent réessaie ?
  • La réponse tient-elle en quelques kilo-octets, ou peut-on la réduire jusqu’à ce qu’elle y tienne ?
  • Relève-t-il d’un autre public, comme l’administration, la facturation ou l’outillage interne ?

Tout ce qui échoue à la première ou à la dernière question reste en dehors. Cinq à dix outils bien choisis valent mieux qu’une centaine d’outils bruts. Les parcours en plusieurs étapes méritent un seul outil : si « créer un panier, ajouter des articles, passer la commande » se déroule toujours dans cet ordre, l’agent ne doit voir qu’une seule action place_order.

Nommer et décrire chaque outil

Partez de operationId, puis réécrivez-le sous forme d’un verbe et d’un nom. Une bonne description répond à trois questions : ce que fait l’outil, quand l’utiliser plutôt que ses voisins et ce qu’il renvoie.

GénéréRéécrit
NomOrdersController_findAllsearch_orders
Description« Find all »« Recherchez les commandes par e-mail client, statut ou plage de dates. Renvoie jusqu’à 20 commandes avec id, statut et total. Utilisez get_order pour les lignes de commande. »
Paramètreqemail : « E-mail du client, par exemple ana@example.com »

Transformer les paramètres en schémas

Aplatissez les paramètres de chemin, de requête et de corps en un seul objet. Conservez les énumérations, marquez les champs obligatoires, donnez à chaque propriété une courte description avec une valeur d’exemple et fixez des limites, comme maximum et maxLength, pour que le modèle ne puisse pas demander 10 000 lignes. Une opération OpenAPI de ce type :

/orders/{orderId}:
  get:
    operationId: getOrder
    summary: Fetch one order
    parameters:
      - name: orderId
        in: path
        required: true
        schema: { type: string }

devient cette définition d’outil :

{
  "name": "get_order",
  "description": "Fetch one order by id. Returns status, total and line items. Use search_orders when you only have an email.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string", "description": "Order id, for example ord_8f2c1" }
    },
    "required": ["order_id"]
  }
}

Deux façons de construire le serveur

Vous pouvez générer un serveur directement à partir de la spec en quelques minutes, ou écrire chaque outil à la main. La plupart des équipes font les deux : générer d’abord pour voir la forme générale, puis peaufiner à la main les cinq outils qui comptent.

Développeur qui tape du code à une table de café, à côté d’une fenêtre pluvieuse

Générer avec FastMCP

La bibliothèque Python FastMCP peut construire un serveur directement à partir d’un document OpenAPI :

import os
import httpx
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import RouteMap, MCPType

client = httpx.AsyncClient(
    base_url="https://api.example.com",
    headers={"Authorization": f"Bearer {os.environ['ORDERS_API_TOKEN']}"},
)
spec = httpx.get("https://api.example.com/openapi.json").json()

mcp = FastMCP.from_openapi(
    openapi_spec=spec,
    client=client,
    name="Orders API",
    route_maps=[
        RouteMap(pattern=r"^/admin/.*", mcp_type=MCPType.EXCLUDE),
        RouteMap(tags={"internal"}, mcp_type=MCPType.EXCLUDE),
    ],
)

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

Le serveur lit la spec, crée les outils et transmet chaque appel au client httpx que vous lui passez, c’est aussi là que vit l’en-tête d’authentification. Les règles de routage écartent les routes d’administration et les routes internes avant que l’agent ne les voie.

⚠️ Vérification de version : la correspondance par défaut diffère selon les versions majeures de FastMCP. Les versions récentes transforment chaque opération en outil, tandis que les anciennes versions 2.x associaient certaines routes GET à des ressources. Figez votre version, définissez explicitement les règles de routage et vérifiez le chemin d’import dans la documentation de la version installée.

Quand la génération ne suffit pas

La documentation de FastMCP prévient elle-même que les serveurs soigneusement conçus donnent aux modèles des résultats nettement meilleurs que les serveurs convertis automatiquement, surtout pour les API comptant beaucoup d’endpoints et de paramètres. Vous le constaterez dès le premier test :

  • Des noms comme get_orders_by_id_using_get qu’aucun humain n’écrirait
  • Des descriptions copiées de la documentation développeur, écrites pour des lecteurs qui connaissent déjà le système
  • Des réponses qui renvoient chaque champ, y compris les indicateurs internes
  • Quatre outils qui auraient dû n’en faire qu’un

Corrigez-les dans cet ordre : élaguer, renommer, réécrire les descriptions, réduire les réponses, fusionner les parcours.

Construire à la main en TypeScript

Pour les outils qui comptent, le SDK TypeScript officiel vous donne un contrôle total. Installez @modelcontextprotocol/sdk et zod, puis enregistrez chaque outil avec un schéma et un gestionnaire :

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

const API = "https://api.example.com";
const TOKEN = process.env.ORDERS_API_TOKEN;

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

server.registerTool(
  "get_order",
  {
    title: "Get order",
    description:
      "Fetch one order by id. Returns status, total and line items. Use search_orders when you only have an email.",
    inputSchema: { order_id: z.string().describe("Order id, for example ord_8f2c1") },
    annotations: { readOnlyHint: true },
  },
  async ({ order_id }) => {
    const res = await fetch(`${API}/orders/${encodeURIComponent(order_id)}`, {
      headers: { Authorization: `Bearer ${TOKEN}` },
    });
    if (!res.ok) {
      return {
        isError: true,
        content: [{ type: "text", text: `Orders API returned ${res.status}. Check the id and try again.` }],
      };
    }
    const order = await res.json();
    return { content: [{ type: "text", text: JSON.stringify(order) }] };
  }
);

await server.connect(new StdioServerTransport());

Deux détails font l’essentiel du travail. L’annotation readOnlyHint indique au client que cet appel peut être exécuté sans demande de confirmation, et la branche d’erreur renvoie isError: true avec un message lisible, pour que l’agent puisse réessayer au lieu de rester bloqué.

Authentification, secrets et garde-fous

Cadenas en laiton à côté d’une clé de sécurité matérielle posée sur une étagère en bois

Le serveur détient les identifiants. Le modèle, jamais.

Tenir les jetons hors des prompts

Lisez le jeton d’API depuis une variable d’environnement ou un gestionnaire de secrets au démarrage du processus. Ne l’acceptez jamais comme argument d’outil, ne le répétez jamais dans un message d’erreur et ne journalisez jamais les en-têtes de requête. Créez le jeton le plus restreint que l’API permet : un jeton en lecture seule pour un serveur en lecture seule. Pour les serveurs distants, le flux d’autorisation de MCP repose sur OAuth 2.1 : chaque utilisateur se connecte avec son propre compte, et chaque appel porte ses propres permissions au lieu d’un superutilisateur partagé.

Annoter les outils à risque

Les annotations sont des indications qui aident les clients à décider quand demander une confirmation à l’utilisateur :

AnnotationÀ définir quand
readOnlyHint: trueL’outil ne fait que lire, comme un GET ou une recherche
destructiveHint: trueL’outil supprime ou écrase des données
idempotentHint: trueRépéter l’appel avec la même entrée ne change plus rien
openWorldHint: trueL’outil atteint des systèmes extérieurs au vôtre, comme le web ouvert

Considérez-les comme des indications, non comme une contrainte, car un client ne devrait pas faire confiance aux annotations d’un serveur qu’il ne connaît pas. La vraie protection se trouve de votre côté : livrez la première version en lecture seule, ajoutez les outils d’écriture un à un et donnez aux outils destructeurs une entrée dry_run ou confirm pour que l’agent doive être explicite.

Tâches lentes, interrogation et médias

Chef faisant glisser une assiette dressée sur le passe de cuisine, à côté d’une rampe de tickets de commande

La génération d’images, le rendu de vidéos et l’export de rapports suivent le même schéma : l’API répond immédiatement avec un identifiant de tâche, et le résultat arrive quelques secondes ou quelques minutes plus tard. Un outil qui bloque pendant trois minutes provoquera un délai d’expiration dans la plupart des clients. Une rampe de tickets de commande résout le même problème dans un restaurant : on prend la commande, on remet un ticket, et on appelle le numéro quand le plat est prêt.

Créer, interroger, récupérer

Découpez la tâche en trois outils : un la démarre, un vérifie son état et un l’annule. L’outil de démarrage renvoie un identifiant et une indication sur le moment où revenir vérifier. L’outil de vérification renvoie un petit objet d’état, queued, running, succeeded ou failed, ainsi qu’une URL dès qu’il y a quelque chose à récupérer. Renvoyez des liens, pas le contenu brut des fichiers : une image de 5 Mo collée dans le contexte n’aide personne.

server.registerTool(
  "get_render",
  {
    description:
      "Check a render started with start_render. Call again after next_poll_in_seconds until status is succeeded or failed.",
    inputSchema: { render_id: z.string() },
    annotations: { readOnlyHint: true },
  },
  async ({ render_id }) => {
    const job = await api(`/renders/${render_id}`); // api() is your fetch helper
    const done = job.status === "succeeded" || job.status === "failed";
    const body = {
      status: job.status,
      url: job.output?.[0] ?? null,
      next_poll_in_seconds: done ? null : 5,
    };
    return { content: [{ type: "text", text: JSON.stringify(body) }] };
  }
);

Un exemple réel d’image et de vidéo

Le connecteur propre à PicassoIA suit cette conception. Ses outils generate_image, edit_image, generate_video_picassoia et generate_video_seedance renvoient un predict_id dès qu’un GPU accepte la tâche, accompagné d’un temps estimé. L’agent appelle ensuite get_generation avec le next_poll_in_seconds renvoyé, et répète l’opération jusqu’à ce que le statut soit succeeded ou failed. Un outil cancel_generation arrête une tâche en cours, avec une mise en garde honnête dans ses instructions : une vidéo que le GPU est déjà en train de rendre ne peut plus être annulée.

En dessous se trouve une API REST de type Replicate, à l’adresse https://api.picassoia.com/v1, avec une authentification par jeton bearer. POST /v1/models/{owner}/{name}/predictions crée une tâche, GET /v1/predictions/{id} la lit et POST /v1/predictions/{id}/cancel l’arrête. C’est donc une cible de conversion idéale, et les quatre modèles derrière le connecteur correspondent à ses quatre outils de génération :

ModèleCe qu’il renvoieOutil du connecteur
PicassoIA ImageTexte vers image, sept formatsgenerate_image
PicassoIA Image Editor ProModifications avec jusqu’à trois images de référenceedit_image
Picasso IA VideoClips de 5 secondes à 24 fps avec audio, en 480p ou 720pgenerate_video_picassoia
Seedance 2.5 LiteClips de 5 ou 10 secondes avec audiogenerate_video_seedance

Les limites doivent aussi figurer dans les descriptions d’outils. L’API autorise 5 prédictions simultanées par compte, partagées entre les jetons et les connexions MCP, avec des prompts allant jusqu’à 4 000 caractères. Une bonne description demande donc à l’agent d’attendre qu’une tâche en cours se termine avant d’en lancer une sixième. Vérifiez les conditions actuelles de votre offre sur le site de PicassoIA avant de bâtir un produit sur cette API.

Tester, puis déployer

Deux ingénieurs examinant un écran d’ordinateur portable sur une table en bois

Un agent est un testeur impitoyable : il utilise vos outils de façons que vous n’avez pas prévues. Testez avec des outils adaptés avant de le lui confier.

Lancer le MCP Inspector

Le MCP Inspector est l’interface de débogage officielle. Pointez-le vers votre serveur, par exemple npx @modelcontextprotocol/inspector node dist/server.js, et il liste chaque outil, vous permet d’appeler chacun avec du JSON brut et affiche le résultat exact qu’un client recevrait. Connectez ensuite un vrai client et lancez dix prompts réalistes. Pour chacun, vérifiez trois points : l’agent a-t-il choisi le bon outil, a-t-il correctement rempli les arguments, et la réponse lui a-t-elle donné de quoi répondre ?

Choisir stdio ou HTTP

stdioHTTP diffusable
Fonctionne commeProcessus local lancé par le clientService distant derrière une URL
AuthentificationVariables d’environnement sur la machine de l’utilisateurOAuth ou jetons bearer
Idéal pourOutils personnels et développementÉquipes et API partagées
Attention àNe jamais afficher de journaux sur stdoutTLS, limites de débit, mise à l’échelle horizontale

Vue en contre-plongée d’une allée de baies de serveurs dans une salle de données

Le HTTP diffusable a remplacé l’ancien transport HTTP plus SSE dans la révision 2025-03-26 de la spec. Le protocole continue d’évoluer : figez donc votre version du SDK et lisez son journal des modifications avant chaque mise à jour.

Cinq erreurs courantes

  1. Exposer chaque endpoint. La liste d’outils coûte des tokens à chaque tour de conversation.
  2. Journaliser sur stdout avec un serveur stdio. Stdout transporte le protocole lui-même. Envoyez les journaux vers stderr.
  3. Renvoyer la charge utile complète de l’amont. Ne gardez que les champs dont l’agent a besoin et paginez le reste.
  4. Lever des exceptions. Renvoyez un résultat isError avec un message qui indique quoi essayer ensuite.
  5. Des descriptions qui se chevauchent. Si deux outils se ressemblent, l’agent joue à pile ou face. Indiquez quand privilégier chacun.

Utiliser Claude Sonnet 5 sur PicassoIA

Rédiger vingt descriptions d’outils à la main est fastidieux, et un LLM s’en acquitte très bien quand vous lui donnez des règles. Sur PicassoIA, Claude Sonnet 5 convient bien : sa page de modèle cite les tâches de programmation en plusieurs étapes et d’usage d’outils parmi ses points forts, il accepte un prompt système et vous laisse choisir l’effort de réflexion.

Carnet ouvert avec un croquis au crayon de boîtes reliées, à côté d’une tasse de café

  1. Ouvrez le modèle. Rendez-vous sur la page Claude Sonnet 5 de PicassoIA.
  2. Définissez le prompt système une seule fois. Par exemple : You write MCP tool definitions. For each OpenAPI operation return a verb_noun name, a description that says what the tool does, when to use it and what it returns, and a flat JSON Schema with example values. Never copy internal parameter names.
  3. Collez une opération à la fois dans le champ du prompt, ou un petit groupe d’opérations liées. Une spec complète de 5 Mo produit un résultat confus.
  4. Choisissez le niveau d’effort. low est le plus rapide et désactive la réflexion, medium convient à un lot d’opérations simples, et high se justifie pour les corps de requête imbriqués.
  5. Laissez les tokens maximum à 8192, la valeur par défaut, pour des lots de cinq à huit opérations.
  6. Joignez une capture d’écran si vous n’avez que de la documentation générée. Le champ image en accepte une.
  7. Relisez avant de livrer. Passez chaque brouillon dans l’Inspector et corrigez les noms qui se chevauchent.

💡 Besoin d’une sortie qui doit être un JSON valide à chaque fois ? GPT 5 Structured est conçu pour renvoyer un JSON propre, ce qui convient aux brouillons de schémas que vous comptez charger directement dans votre code.

Construire votre propre boîte à outils pour agents

Choisissez une API, cinq outils et un après-midi libre. Livrez d’abord la version en lecture seule, testez-la avec dix prompts réels, et n’ajoutez qu’ensuite les outils qui écrivent ou suppriment.

Mains posées sur un ordinateur portable, près d’une fenêtre, au crépuscule

Les agents ont besoin de choses à montrer, pas seulement de données à lire. Essayez par vous-même sur Picasso IA : rédigez un prompt dans PicassoIA Image, choisissez 16:9, puis affinez le résultat avec PicassoIA Image Editor Pro, qui accepte jusqu’à trois images de référence. Quand l’image fixe vous convient, animez-la avec Picasso IA Video ou allongez le plan à dix secondes avec Seedance 2.5 Lite. Expérimentez avec vos propres scènes, et quand vous serez prêt, construisez l’outil MCP qui permettra à votre agent d’en faire autant.

Partager cet article

Choisissez votre langue