Aller au contenu principal
Leçon 4 sur 14 · Apps SDK et plugins ChatGPT — serveurs MCP et interfaces

Votre premier plugin

Leçon 4 sur 14 Mis à jour le 3 novembre 2026 Développeur
Sur cette page
  1. Deux étapes
  2. Étape 1 : un plugin personnel en cinq minutes
  3. Étape 2 : votre propre serveur
  4. Recharger après chaque modification
  5. Points clés à retenir

Leçon réécrite le 3 novembre 2026 d’après le Quickstart, le MCP server and UI quickstart et la page Connect and test your plugin de la documentation Plugins d’OpenAI. L’ancienne « app météo en 30 minutes » utilisait un SDK inexistant. Les versions des paquets npm ont été vérifiées le 3 novembre 2026 : @modelcontextprotocol/ext-apps 2.x s’appuie sur la v2 du SDK MCP, alors que les exemples d’OpenAI utilisent la v1. Sources : Quickstart · MCP server and UI quickstart · Connect and test.

Deux étapes

Vous allez d’abord brancher un serveur MCP existant, pour voir le parcours complet côté ChatGPT sans écrire une ligne de code. Vous écrirez ensuite votre propre serveur, le testerez en local et le connecterez de la même façon.

Étape 1 : un plugin personnel en cinq minutes

OpenAI met à disposition un serveur MCP d’exemple, public et sans authentification, qui expose un seul outil en lecture : roll_dice, un lancer de dés.

  1. Ouvrez ChatGPT Plugins, sélectionnez le bouton +, puis Add custom MCP server.
  2. Donnez un nom, et indiquez https://tinymcp.dev/api/moldy-aloof-zettabyte/mcp comme Server URL. Choisissez No authentication.
  3. Lisez l’avertissement de risque, sélectionnez I understand and want to continue, puis Create as a plugin.
  4. Le plugin apparaît dans vos plugins personnels : ouvrez-le et installez-le avec le bouton +.
  5. Revenez à l’accueil de ChatGPT, passez de l’onglet Chat à Work, ouvrez une nouvelle conversation, tapez @ et sélectionnez votre plugin.
  6. Demandez-lui de lancer un dé à vingt faces : il doit appeler roll_dice une seule fois avec sides à 20, et renvoyer une valeur entre 1 et 20.

Ne vous arrêtez pas à ce cas nominal. Essayez d’autres tailles de dé, des valeurs invalides, et des demandes qui ne devraient appeler aucun outil. C’est le réflexe de toute la suite du cours : quand le mauvais outil est choisi ou que ses arguments varient, c’est la description et le schéma de l’outil qu’il faut retravailler. Notez enfin que les politiques de votre compte ou de votre espace de travail encadrent l’ajout de serveurs MCP personnalisés : dans une organisation, l’option peut être restreinte.

Étape 2 : votre propre serveur

Installer les SDK officiels

Les SDK officiels du protocole existent en TypeScript (@modelcontextprotocol/sdk) et en Python (mcp). Ce cours suit les exemples d’OpenAI, écrits en TypeScript avec la version 1 du SDK. Épinglez les versions : depuis septembre 2026, @modelcontextprotocol/ext-apps 2.x — le paquet d’interface que vous ajouterez à la leçon 6 — s’appuie sur les nouveaux paquets de la v2 du SDK (@modelcontextprotocol/server…), incompatibles avec les imports de la v1.

mkdir notes-plugin && cd notes-plugin
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk@^1.29 @modelcontextprotocol/ext-apps@^1.7 zod

npm pkg set type=module ajoute "type": "module" au package.json, nécessaire pour les import du serveur.

Écrire le serveur

Ce serveur tient en un fichier, server.js. Il garde des notes en mémoire et expose deux outils : une lecture et une écriture, séparées comme le recommande la leçon 3.

import { createServer } from "node:http";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

const notes = []; // données d'exemple en mémoire : remplacez-les par votre stockage
const schemaNotes = {
  notes: z.array(z.object({ id: z.string(), text: z.string() })),
};

function creerServeur() {
  const server = new McpServer({ name: "notes-demo", version: "0.1.0" });

  server.registerTool(
    "list_notes",
    {
      title: "Lister les notes",
      description: "À utiliser quand l'utilisateur veut revoir les notes enregistrées avec ce plugin.",
      inputSchema: {},
      outputSchema: schemaNotes,
      annotations: { readOnlyHint: true, openWorldHint: false, destructiveHint: false },
    },
    async () => ({
      structuredContent: { notes },
      content: [{ type: "text", text: `${notes.length} note(s) enregistrée(s).` }],
    })
  );

  server.registerTool(
    "add_note",
    {
      title: "Ajouter une note",
      description: "À utiliser quand l'utilisateur demande d'enregistrer une courte note.",
      inputSchema: { text: z.string().min(1).max(500) },
      outputSchema: schemaNotes,
      annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: false },
    },
    async ({ text }) => {
      notes.push({ id: `note-${notes.length + 1}`, text });
      return {
        structuredContent: { notes },
        content: [{ type: "text", text: "Note enregistrée." }],
      };
    }
  );

  return server;
}

const PORT = Number(process.env.PORT ?? 8787);

createServer(async (req, res) => {
  const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
  if (url.pathname !== "/mcp") {
    res.writeHead(404).end("Not Found");
    return;
  }

  // Mode sans état : un serveur et un transport neufs par requête
  const server = creerServeur();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
    enableJsonResponse: true,
  });
  res.on("close", () => {
    transport.close();
    server.close();
  });

  try {
    await server.connect(transport);
    await transport.handleRequest(req, res);
  } catch (error) {
    console.error("Erreur MCP :", error);
    if (!res.headersSent) res.writeHead(500).end("Internal server error");
  }
}).listen(PORT, () => console.log(`Serveur MCP prêt sur http://localhost:${PORT}/mcp`));

Trois détails comptent. Chaque outil déclare un titre, une description d’intention, des schémas et des annotations exactes : c’est sa « fiche » pour le modèle. Chaque résultat combine structuredContent, réutilisable par le modèle, et content, un texte court. Et toute adresse autre que /mcp répond 404 : l’exemple d’OpenAI le fait aussi pour les routes de découverte OAuth que vous n’utilisez pas encore, ce qui évite à ChatGPT d’afficher des erreurs 502 pendant vos essais sans authentification. Le même exemple ajoute une réponse à la requête préalable CORS (OPTIONS) et un GET / pour les sondes de santé ; reprenez-les si votre hébergement les attend.

Tester en local avec MCP Inspector

node server.js
npx @modelcontextprotocol/inspector@latest

Dans l’Inspector, choisissez Streamable HTTP, saisissez http://localhost:8787/mcp et connectez-vous. Vérifiez que l’initialisation réussit, que les deux outils apparaissent avec leurs schémas et leurs annotations, puis appelez-les avec des entrées valides et invalides — un texte vide doit être refusé par le schéma.

Exposer le serveur et le connecter

ChatGPT doit pouvoir joindre votre serveur depuis Internet. Pendant le développement, un tunnel suffit — la documentation cite ngrok :

ngrok http 8787

Vous obtenez une adresse HTTPS publique. Connectez-la comme à l’étape 1, avec le chemin /mcp (par exemple https://<sous-domaine>.ngrok.app/mcp), installez le plugin obtenu, puis invoquez-le par @ : « Enregistre une note : appeler le fournisseur lundi ». ChatGPT affiche les données échangées avec l’outil, ce qui vous permet de contrôler entrées et sorties.

Un tunnel de développement ne convient qu’aux essais : la publication exige un point d’accès HTTPS stable et public (leçon 5).

Recharger après chaque modification

ChatGPT garde en mémoire les métadonnées de votre serveur. Après tout changement de nom, de description, de schéma, d’annotation, d’authentification ou de ressource d’interface : redéployez ou relancez le serveur, ouvrez la connexion sur chatgpt.com/plugins, sélectionnez Refresh, vérifiez que les métadonnées annoncées ont changé, puis ouvrez une nouvelle conversation pour rejouer vos tests. Oublier cette étape, c’est tester l’ancienne version sans le savoir.

Points clés à retenir

  • Un plugin personnel se crée depuis Add custom MCP server, puis s’invoque par @ dans ChatGPT
  • Les SDK officiels : @modelcontextprotocol/sdk (TypeScript) et mcp (Python) ; épinglez la v1 avec ext-apps 1.x pour suivre les exemples d’OpenAI
  • Un serveur minimal : McpServer, registerTool, transport streamable HTTP sur /mcp
  • MCP Inspector pour tester en local, un tunnel pour les essais dans ChatGPT
  • Après toute modification de métadonnées : Refresh, puis nouvelle conversation