Comment créer un serveur MCP pour un produit SaaS ?

On crée un serveur MCP pour un produit SaaS en sept étapes : choisir les tâches que les utilisateurs veulent confier à un assistant, concevoir un outil par tâche, implémenter les outils comme une fine couche au-dessus de votre API existante, ajouter OAuth pour que chaque appel s'exécute avec les permissions de l'utilisateur, sécuriser les outils d'écriture, tout journaliser et tester avec de vrais assistants. Le Model Context Protocol lui-même est la partie facile : les SDK officiels le gèrent en quelques dizaines de lignes.

Ce guide est le complément pratique de notre article qu'est-ce qu'un serveur MCP, qui couvre les notions d'outils, de ressources, de transports et les raisons pour lesquelles un produit peut en avoir besoin. Ici, nous partons du principe que vous avez décidé de le construire et que vous voulez bien le faire. Si vous préférez confier le travail à une équipe qui fait tourner des serveurs MCP en production, consultez nos services de développement de serveurs MCP.

Étape Livrable Durée typique
1. Choisir les tâches Une liste de 5 à 15 tâches utilisateur, classées 1 à 3 jours
2. Concevoir les outils Noms, descriptions, schémas d'entrée, formats de sortie 2 à 5 jours
3. Construire le serveur Serveur distant en Streamable HTTP qui appelle votre API 3 à 10 jours
4. Ajouter OAuth Connexion, scopes, contrôle des permissions par utilisateur 3 à 10 jours
5. Sécuriser l'écriture Brouillons, confirmations, limites 2 à 5 jours
6. Journaliser et surveiller Journal d'audit, métriques par outil, alertes 1 à 3 jours
7. Tester avec des clients Vérifications Inspector, essais avec de vrais assistants 3 à 7 jours

Quels outils votre serveur MCP doit-il exposer en premier ?

Exposez les 5 à 15 tâches que vos utilisateurs demanderaient réellement à un assistant, et non un miroir de votre API REST. "Trouver les factures en retard de ce client" est une tâche. GET /invoices avec onze paramètres de requête est un endpoint. Les modèles choisissent les outils en lisant leurs noms et leurs descriptions : une liste d'outils calquée sur l'intention de l'utilisateur est donc utilisée correctement bien plus souvent.

Une bonne façon de trouver ces tâches : lisez les tickets de support et les appels commerciaux en cherchant des formules comme "est-ce que je peux voir rapidement" ou "je dois toujours copier", puis notez ce qu'un utilisateur taperait dans Claude ou ChatGPT. La plupart des produits SaaS aboutissent à un premier ensemble comme celui-ci :

  • Rechercher et trouver : chercher des enregistrements par texte, statut, responsable ou date.
  • Lire le détail : obtenir un enregistrement avec les champs dont une personne aurait besoin.
  • Résumer l'activité : ce qui a changé cette semaine dans un projet, un compte ou une file de tickets.
  • Créer des brouillons : une tâche, une réponse, une facture ou une publication en brouillon, que l'utilisateur relit dans votre application.
  • Modifier des champs sans risque : statut, assigné, tags, échéance.

Laissez de côté les paramètres d'administration, les changements de facturation, les opérations en masse et la suppression dans la première version.

Comment concevoir des outils MCP que les modèles utilisent correctement ?

Concevez chaque outil comme une petite API publique destinée à un lecteur qui n'a jamais vu votre produit : un nom verbe-nom, une description qui dit ce que fait l'outil et quand l'utiliser, un schéma d'entrée typé avec une description sur chaque champ, et une sortie compacte.

Règle Faible Mieux
Nommer selon l'intention get_entities search_tasks
Dire quand l'utiliser "Gets tasks" "Search tasks in the user's workspace by text, status or assignee. Use before updating a task to find its id."
Contraindre les entrées status: string status: "open" or "in_progress" or "done"
Limiter la sortie Objets complets avec 80 champs id, titre, statut, échéance, URL ; 20 résultats maximum
Erreurs récupérables Error 422 "Project not found. Call list_projects to get valid project ids."

Renvoyez des identifiants et des URL dans les résultats, afin que le modèle puisse enchaîner les appels et que l'utilisateur puisse ouvrir l'enregistrement dans votre application. Gardez des sorties courtes : chaque token renvoyé par un outil est du contexte que le modèle doit lire et que l'utilisateur paie. Marquez les outils en lecture seule avec l'annotation read-only, afin que les clients puissent les traiter comme sûrs.

À quoi ressemble un serveur MCP minimal en TypeScript ?

Voici un serveur distant minimal qui utilise le SDK TypeScript officiel avec Express, avec un outil de lecture et un outil de brouillon. Il crée un serveur par requête avec l'utilisateur authentifié intégré, de sorte que chaque appel d'outil s'exécute avec les permissions de cet utilisateur. L'API du SDK a changé d'une version à l'autre : vérifiez la documentation actuelle du SDK pour les imports et les noms de méthodes exacts avant de copier.

import express from "express";
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { verifyAccessToken, api, type User } from "./app.js"; // votre vérification de jeton et votre client API

function buildServer(user: User): McpServer {
  const server = new McpServer({ name: "acme-projects", version: "1.0.0" });

  server.registerTool(
    "search_tasks",
    {
      title: "Search tasks",
      description:
        "Search tasks in the user's workspace by text, status or assignee. " +
        "Returns up to 20 tasks with id, title, status, due date and URL.",
      inputSchema: {
        query: z.string().min(2).describe("Words to find in task titles and descriptions"),
        status: z.enum(["open", "in_progress", "done"]).optional(),
        limit: z.number().int().min(1).max(20).default(10),
      },
      annotations: { readOnlyHint: true },
    },
    async ({ query, status, limit }) => {
      const tasks = await api.searchTasks(user, { query, status, limit });
      return { content: [{ type: "text", text: JSON.stringify(tasks) }] };
    },
  );

  if (user.scopes.includes("tasks:write")) {
    server.registerTool(
      "create_task_draft",
      {
        title: "Create task draft",
        description:
          "Create a DRAFT task in a project. The user reviews and publishes it in the app. " +
          "Never assigns, notifies or publishes.",
        inputSchema: {
          projectId: z.string().describe("Project id from list_projects"),
          title: z.string().min(3).max(200),
          description: z.string().max(5000).optional(),
        },
        annotations: { readOnlyHint: false, destructiveHint: false },
      },
      async (args) => {
        const draft = await api.createTaskDraft(user, args);
        if (!draft.ok) {
          return { isError: true, content: [{ type: "text", text: draft.error }] };
        }
        return {
          content: [{ type: "text", text: `Draft ${draft.id} created. Review it at ${draft.url}` }],
        };
      },
    );
  }

  return server;
}

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

app.get("/.well-known/oauth-protected-resource", (_req, res) => {
  res.json({
    resource: "https://mcp.example.com/mcp",
    authorization_servers: ["https://auth.example.com"],
    scopes_supported: ["tasks:read", "tasks:write"],
  });
});

app.post("/mcp", async (req, res) => {
  const user = await verifyAccessToken(req.headers.authorization);
  if (!user) {
    res
      .status(401)
      .set("WWW-Authenticate",
        'Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"')
      .end();
    return;
  }

  const server = buildServer(user);
  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);

Remarquez ce qui ne figure pas dans la couche MCP : les règles métier et la logique de permissions. api.searchTasks et api.createTaskDraft appellent la même couche de services ou la même API REST que votre application web, si bien que les règles vivent à un seul endroit. Notez aussi que l'outil d'écriture n'est même pas enregistré pour les utilisateurs qui n'ont pas le scope d'écriture : un modèle ne peut pas appeler un outil qu'il ne voit jamais.

Si votre back end est en PHP, la même structure fonctionne avec un SDK MCP pour PHP (un SDK officiel est en cours de développement avec l'équipe Symfony ; vérifiez son état actuel), ou avec un petit service TypeScript comme celui-ci placé devant votre API PHP. Les équipes Python peuvent utiliser le SDK Python officiel.

Comment ajouter OAuth à un serveur MCP distant ?

Traitez votre serveur MCP comme une ressource protégée par OAuth. Lorsqu'une requête arrive sans jeton valide, renvoyez une erreur 401 qui pointe vers les métadonnées de votre ressource protégée ; le client les lit, trouve votre serveur d'autorisation, connecte l'utilisateur et revient avec un jeton. Votre serveur valide ensuite le jeton à chaque requête et agit au nom de cet utilisateur.

Ce dont vous avez besoin en pratique :

  • Un serveur d'autorisation. Votre fournisseur d'identité existant, s'il prend en charge les flux utilisés par les clients MCP, ou une petite couche OAuth devant vos comptes utilisateurs. Consultez la spécification d'autorisation MCP en vigueur pour les flux requis, y compris la manière dont les clients s'enregistrent.
  • Des scopes alignés sur les groupes d'outils, par exemple tasks:read et tasks:write, pour que les clients puissent accorder un accès en lecture seule.
  • Un contrôle des permissions à chaque appel dans votre API, à partir de l'identité de l'utilisateur contenue dans le jeton. N'utilisez jamais une clé d'administration partagée en coulisses.
  • La révocation depuis la page de paramètres de votre application, pour que les utilisateurs puissent déconnecter un assistant.

Pour un serveur interne utilisé par votre propre équipe ou par vos agents, des clés API à périmètre limité, une par personne, sont un raccourci raisonnable.

Comment sécuriser les outils d'écriture ?

Faites en sorte que les outils d'écriture créent des brouillons ou des modifications réversibles, plafonnez ce qu'un seul appel peut faire, et gardez les actions irréversibles hors du serveur ou derrière une confirmation explicite. C'est le serveur qui applique ces limites, car le modèle lit des textes non fiables (e-mails, documents, pages web) qui peuvent contenir des instructions qui lui sont destinées.

  • Des brouillons plutôt qu'une publication immédiate. Une publication, une réponse ou une facture en brouillon, que l'utilisateur publie dans votre application.
  • Uniquement des suppressions logiques, si la suppression est proposée.
  • Des plafonds. Un enregistrement par appel pour l'écriture, un nombre maximal d'écritures par minute et par utilisateur.
  • Aucun mouvement d'argent et aucun message à des tiers sans étape de confirmation dans votre interface.
  • Des tests d'isolation entre clients. Des tests automatisés qui garantissent qu'une organisation ne peut jamais atteindre les données d'une autre via un outil, quel qu'il soit.

Nous avons appris la règle des brouillons sur nos propres systèmes. Nous faisons tourner en production des serveurs MCP pour notre tableau de projets interne et pour le système de contenu de notre site, utilisés chaque jour par notre équipe et par nos agents IA de développement. Cet usage quotidien nous a appris que les outils d'écriture qui publient immédiatement auraient dû créer des brouillons.

Que faut-il journaliser et surveiller ?

Journalisez chaque appel d'outil avec l'utilisateur, l'organisation, le nom du client, l'outil, les arguments (données personnelles masquées), la taille du résultat, la durée et l'issue. Ce journal répond aux questions du support ("qu'est-ce que mon assistant a modifié ?"), montre quels outils les modèles utilisent mal et constitue votre piste d'audit.

Surveillez le taux d'erreur par outil, le nombre d'appels par utilisateur et par minute, la part d'appels qui se terminent par une erreur dont le modèle n'a pas pu se remettre, et les outils qui ne sont jamais appelés. Un outil jamais choisi a généralement une description qui ne correspond pas à la façon dont les utilisateurs formulent leurs demandes.

Comment tester un serveur MCP avec Claude et ChatGPT ?

Testez en trois couches : vérifications du protocole, tests scriptés des outils et essais avec de vrais assistants.

  1. MCP Inspector. Lancez npx @modelcontextprotocol/inspector et connectez-le à votre serveur. Vérifiez que chaque outil est listé avec le bon schéma, renvoie des résultats valides et des erreurs lisibles pour une entrée incorrecte.
  2. Tests automatisés. Appelez directement les handlers des outils dans votre suite de tests avec des utilisateurs de rôles et d'organisations différents, y compris les cas d'accès croisé entre clients.
  3. De vrais assistants. Ajoutez le serveur comme connecteur personnalisé dans Claude et dans le mode développeur de ChatGPT (les noms de menus changent ; consultez la documentation actuelle de chaque client), ainsi que dans un outil de développement comme Cursor ou Visual Studio Code. Lancez 30 à 50 requêtes réalistes et consignez les résultats dans un tableau comme celui-ci, donné à titre d'illustration :
Requête Outil attendu Claude ChatGPT
"Qu'est-ce qui est en retard dans le projet Apollo ?" search_tasks avec statut et projet correct correct
"Rédige une tâche pour renouveler le certificat SSL" create_task_draft correct mauvais id de projet
"Supprime toutes les tâches terminées" aucun ; expliquer que ce n'est pas possible refusé refusé

Chaque client sélectionne et appelle les outils un peu différemment : une description qui fonctionne dans l'un peut en dérouter un autre. Corrigez les descriptions, relancez l'ensemble des requêtes et conservez-le comme suite de non-régression.

Quelle approche recommandons-nous ?

Notre verdict pour un premier serveur MCP de SaaS : un serveur distant en Streamable HTTP, OAuth avec des scopes de lecture et d'écriture, 5 à 15 outils calqués sur les intentions au-dessus de votre API existante, des brouillons pour chaque écriture, un journal d'audit complet et un jeu de tests lancé sur au moins deux vrais assistants. Commencez en lecture seule si votre modèle de permissions est complexe, et ajoutez l'écriture une fois que vous avez vu comment les utilisateurs s'en servent.

Un premier serveur ciblé de ce type tient généralement dans un seul AI Sprint : 10 000 USD forfaitaires pour 4 semaines. Vous comparez des prestataires ? Consultez notre liste des sociétés de développement de serveurs MCP.

Prochaine étape

Envoyez-nous la documentation de votre API et cinq choses que vos utilisateurs demanderaient à un assistant de faire dans votre produit. Lors d'un appel de 30 minutes, nous vous proposerons un premier ensemble d'outils et signalerons les questions de permissions. Consultez nos services de développement de serveurs MCP ou contactez-nous.

Études de cas

Questions fréquemment posées

Pour un produit doté d'une API propre, un premier serveur MCP distant avec un ensemble ciblé d'outils, OAuth et la journalisation prend généralement 2 à 6 semaines. La couche protocole représente quelques jours de travail. L'essentiel du temps sert à choisir les outils, à rédiger des descriptions que les modèles comprennent, à gérer les autorisations, à sécuriser l'écriture et à tester avec plusieurs clients IA.

Utilisez le langage de votre API existante s'il dispose d'un SDK officiel. Les SDK TypeScript et Python sont les plus matures, et des SDK officiels existent ou sont en cours de développement pour plusieurs autres langages, dont PHP. Comme la plupart des serveurs MCP de SaaS sont une fine couche au-dessus d'une API existante, un petit service TypeScript séparé placé devant un back end PHP ou Java fonctionne aussi très bien.

Pour un serveur que vos clients connectent depuis Claude, ChatGPT ou d'autres assistants, utilisez OAuth. Chaque utilisateur se connecte avec son propre compte, vous disposez de scopes et de la révocation, et c'est ce qu'attendent les principaux clients MCP pour les serveurs distants. Des clés API statiques sont acceptables pour des serveurs internes et pour des outils de développement où chaque ingénieur gère sa propre clé.

Rarement, et jamais par défaut. Commencez par des outils de lecture, puis ajoutez des outils d'écriture qui créent des brouillons ou des modifications réversibles que l'utilisateur confirme dans votre application. Les actions irréversibles, en masse ou qui déplacent de l'argent doivent être exclues ou exiger une étape de confirmation explicite. C'est le serveur, et non le modèle, qui doit appliquer ces limites, car un modèle peut être manipulé par le texte qu'il lit.

Commencez par MCP Inspector pour vérifier que chaque outil est correctement listé et renvoie des résultats et des erreurs valides. Connectez ensuite le serveur à de vrais clients comme Claude et ChatGPT et lancez 30 à 50 requêtes utilisateur réalistes, en vérifiant quel outil a été choisi, avec quels arguments, et si la réponse était juste. Recommencez après chaque modification des noms ou des descriptions d'outils.

Commençons votre projet
Réservez un appel