ITSkillsCenter
E-commerce

Serveur MCP en Next.js 15 avec mcp-handler : endpoint /api/mcp pas-à-pas

13 min de lecture

📍 Guide principal : Créer un serveur MCP : architecture, primitives, premier déploiement. Ce tutoriel suppose acquis le vocabulaire de MCP (host, client, server, tools) et la spec 2025-11-25 ; en cas de doute, lisez d’abord le guide principal.

Quand on parle de Model Context Protocol côté web, le réflexe Next.js s’impose pour deux raisons. La première : la majorité des SaaS modernes tournent déjà sur Next.js, et exposer une couche MCP au-dessus revient à brancher un agent IA sur un produit existant en quelques heures plutôt qu’à reconstruire un backend dédié. La seconde : Vercel, qui maintient le framework, publie un adaptateur officiel mcp-handler qui transforme une définition de serveur en route handler App Router conforme. On évite tout le boilerplate de transport — on écrit la logique métier, le reste est cuit.

Ce tutoriel construit, étape par étape, un serveur MCP complet sur Next.js 15 en App Router. À la fin, vous aurez un endpoint /api/mcp joignable en HTTPS depuis n’importe quel client compatible (Claude Desktop, VS Code, Cursor, ChatGPT), exposant deux outils utiles — un convertisseur de devises et un agrégateur de météo simulée — et déployé gratuitement sur Vercel. Comptez 45 minutes si vous partez d’un projet vide, une vingtaine si vous greffez la logique sur un Next.js déjà en route.

Prérequis

  • Node.js 20 ou plus récent (vérifiez avec node -v)
  • npm 10+ (livré avec Node)
  • Un compte GitHub et un compte Vercel (gratuit) pour le déploiement final
  • Un client MCP installé localement pour tester (Claude Desktop ou VS Code avec l’extension Copilot Chat)
  • Niveau attendu : intermédiaire (à l’aise avec App Router et les Route Handlers)
  • Temps estimé : 45 minutes pour un projet from scratch

Étape 1 — Initialiser le projet Next.js

On part d’un projet Next.js fraîchement créé pour avoir un terrain neutre, sans dépendances parasites. L’objectif n’est pas de construire une application web complète mais d’avoir le minimum vital : App Router, TypeScript, et un répertoire app/ prêt à recevoir notre route handler MCP. Si vous greffez plutôt sur un projet existant, sautez cette étape et passez directement à l’installation des dépendances.

npx create-next-app@latest mcp-nextjs-server \
  --typescript --app --no-tailwind --no-src-dir --import-alias "@/*"
cd mcp-nextjs-server

Les drapeaux désactivent les options dont nous n’avons pas besoin : pas de Tailwind (on ne fait pas de UI), pas de répertoire src/ (on garde la structure plate par défaut), un alias d’import classique. Une fois la commande terminée, vous obtenez un projet de base avec une page d’accueil minimaliste — que nous laisserons telle quelle, l’endpoint MCP vivra à côté, sans interférer.

Étape 2 — Installer mcp-handler et le SDK

Vercel publie mcp-handler, un adaptateur fin qui prend une définition de serveur MCP au format SDK officiel et la transforme en route handler Next.js capable de servir à la fois Streamable HTTP et SSE. Sous le capot, c’est un wrapper autour du SDK TypeScript officiel : on garde la portabilité d’un serveur standard tout en gagnant l’intégration native avec App Router.

npm install mcp-handler @modelcontextprotocol/sdk@^1.26.0 zod

Note de sécurité : épinglez @modelcontextprotocol/sdk en version 1.26.0 ou plus recente. Les versions antérieures sont sujettes à une vulnerabilite de DNS rebinding (CVE-2025-66414) qui peut permettre à un site malicieux de relayer des appels MCP vers votre serveur local.

Trois paquets, trois rôles. mcp-handler est l’adaptateur Vercel. @modelcontextprotocol/sdk est le SDK officiel — toujours utile car certains imports (types, helpers de transport pour les tests) viennent de là directement. zod sert à décrire les schémas d’entrée des outils ; le SDK les convertit en JSON Schema pour le client. Si vous voyez un avertissement de peer dependency sur la version du SDK, alignez sur celle que mcp-handler demande.

Étape 3 — Créer le route handler MCP

Le route handler vit dans app/api/[transport]/route.ts. Le segment dynamique [transport] est essentiel : c’est lui qui permet à un même fichier de servir l’URL /api/mcp (Streamable HTTP) et /api/sse (SSE legacy) sans dupliquer la logique. La convention vient directement de la documentation officielle de mcp-handler et c’est elle qui rend le déploiement plug-and-play sur Vercel.

Créez l’arborescence puis le fichier :

mkdir -p app/api/\[transport\]
touch app/api/\[transport\]/route.ts

Sous Windows, créez le dossier via l’explorateur ou PowerShell — le shell standard accepte mal les crochets. L’important est d’aboutir à la structure app/api/[transport]/route.ts exactement, casse comprise.

Puis collez ce contenu dans le fichier. Le code définit deux outils : convert_currency qui convertit un montant entre deux devises (avec un taux fixe pour l’exemple, vous remplacerez par un vrai appel d’API ensuite), et get_weather qui retourne une météo simulée pour une ville. Les deux sont volontairement simples pour rester centrés sur la mécanique MCP.

import { createMcpHandler } from "mcp-handler";
import { z } from "zod";

const RATES: Record<string, number> = {
  EUR: 1, USD: 1.08, GBP: 0.85, JPY: 167.4, XOF: 655.957,
};

const handler = createMcpHandler(
  (server) => {
    server.registerTool(
      "convert_currency",
      {
        title: "Conversion de devise",
        description: "Convertit un montant entre deux devises (EUR, USD, GBP, JPY, XOF).",
        inputSchema: {
          amount: z.number().positive(),
          from: z.enum(["EUR", "USD", "GBP", "JPY", "XOF"]),
          to: z.enum(["EUR", "USD", "GBP", "JPY", "XOF"]),
        },
      },
      async ({ amount, from, to }) => {
        const inEur = amount / RATES[from];
        const result = inEur * RATES[to];
        return {
          content: [{
            type: "text",
            text: `${amount} ${from} = ${result.toFixed(2)} ${to}`,
          }],
        };
      },
    );

    server.registerTool(
      "get_weather",
      {
        title: "Météo simulée",
        description: "Retourne une météo de démonstration pour une ville.",
        inputSchema: { city: z.string() },
      },
      async ({ city }) => ({
        content: [{
          type: "text",
          text: `À ${city}, il fait 28°C avec un ciel dégagé.`,
        }],
      }),
    );
  },
  {},
  { basePath: "/api", maxDuration: 60, verboseLogs: true },
);

export { handler as GET, handler as POST };

Trois choses à observer dans ce code. D’abord, basePath: "/api" doit correspondre au chemin réel de votre dossier — si vous mettez le route handler ailleurs (par exemple sous app/v1/[transport]/route.ts), changez basePath en conséquence. Ensuite, maxDuration: 60 est la durée max d’une réponse en secondes, à aligner avec les limites Vercel de votre plan (60s sur Hobby, 300s sur Pro). Enfin, l’export { handler as GET, handler as POST } est obligatoire : POST sert au transport Streamable HTTP, GET sert au mode SSE et à la vérification de santé.

Étape 4 — Lancer le serveur en local

On démarre Next.js en mode développement et on vérifie que la route répond. La commande est celle de tout projet Next.js, rien de spécifique à MCP — c’est précisément le but de l’adaptateur, masquer la complexité du transport derrière la primitive Route Handler.

npm run dev

Vous voyez « Ready in Xms » dans la console et le serveur écoute sur http://localhost:3000. Pour confirmer que la route MCP répond, ouvrez un second terminal et envoyez une requête d’initialisation manuelle avec curl :

curl -i -X POST http://localhost:3000/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'

La réponse doit contenir un en-tête Mcp-Session-Id et un body JSON avec le résultat de l’initialisation, listant les capacités du serveur. Si vous voyez un 404, vérifiez le chemin du fichier (l’erreur la plus fréquente est app/api/mcp/route.ts au lieu de app/api/[transport]/route.ts). Si vous voyez un 500, lancez npm run dev dans un terminal interactif et regardez la stack trace : neuf fois sur dix, c’est un problème de version de SDK ou de schéma Zod mal formé.

Étape 5 — Tester avec l’inspecteur officiel

Plutôt que de continuer à curl à la main, on passe par l’inspecteur officiel maintenu par l’équipe MCP. Il joue les handshakes, liste les outils, valide les schémas et vous laisse invoquer chaque tool via une interface web. C’est l’outil n°1 pour valider qu’un serveur fonctionne avant de le brancher à un vrai client.

npx @modelcontextprotocol/inspector

Au démarrage, l’inspecteur ouvre une page web (généralement http://localhost:6274). Dans le formulaire, choisissez le transport « Streamable HTTP », saisissez l’URL http://localhost:3000/api/mcp et cliquez sur Connect. Le panneau de droite doit lister vos deux outils. Cliquez sur convert_currency, remplissez les champs (par exemple 100 EUR vers XOF), et vous devez voir s’afficher « 100 EUR = 65595.70 XOF ». Si vous obtenez une erreur de connexion, vérifiez que le serveur Next.js tourne toujours et que l’URL n’a pas de barre oblique finale.

Étape 6 — Brancher le serveur à Claude Desktop ou VS Code

Une fois l’inspecteur OK, on passe à un client réel. Pour Claude Desktop, on déclare le serveur via un proxy stdio→HTTP car Claude Desktop privilégie stdio en local et notre serveur est en HTTP. Le SDK fournit un binaire mcp-remote pour ce pont, mais on peut aussi y arriver avec le proxy intégré. La méthode la plus stable consiste à passer par npx mcp-remote :

{
  "mcpServers": {
    "nextjs-demo": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "http://localhost:3000/api/mcp"]
    }
  }
}

Collez ce bloc dans claude_desktop_config.json (sur macOS dans ~/Library/Application Support/Claude/, sur Windows dans %APPDATA%\Claude\), redémarrez Claude Desktop entièrement, et vos deux outils apparaîtront dans la liste des outils disponibles. Pour VS Code, ouvrez la palette (Ctrl+Shift+P) et tapez « MCP: Add server », choisissez « HTTP » et collez l’URL http://localhost:3000/api/mcp — la configuration est plus directe car VS Code parle HTTP nativement.

Étape 7 — Déployer sur Vercel

Le local étant validé, on pousse en ligne. La beauté du combo Next.js + Vercel + mcp-handler est qu’aucune configuration spéciale n’est nécessaire : un push GitHub suivi d’un import dans Vercel et la route est servie en HTTPS, avec un domaine .vercel.app gratuit. Initialisons un repo Git et publions-le.

git init
git add .
git commit -m "Premier serveur MCP sur Next.js"
gh repo create mcp-nextjs-server --public --source=. --push

La commande gh repo create (CLI GitHub) crée le dépôt et le pousse en une seule passe. Si vous n’avez pas gh, créez le dépôt manuellement sur github.com puis pousser avec git remote add origin ... suivi de git push -u origin main. Le résultat est le même : un dépôt public contenant votre serveur, prêt à être importé.

Connectez-vous à vercel.com, cliquez sur « Add New… » puis « Project », sélectionnez le dépôt fraîchement créé. Vercel détecte automatiquement Next.js, propose les paramètres par défaut (rien à changer), et lance le build. Au bout d’une minute environ, le déploiement est en ligne. Notez l’URL — elle ressemble à https://mcp-nextjs-server-xyz.vercel.app. Votre endpoint MCP est joignable à https://mcp-nextjs-server-xyz.vercel.app/api/mcp.

Étape 8 — Vérifier le serveur en production

On rejoue le test d’initialisation contre l’URL Vercel pour confirmer que tout fonctionne avec HTTPS et le routing du CDN. Cette vérification finale est non négociable : un serveur qui marche en local peut casser en production à cause d’une variable d’environnement manquante, d’un timeout trop court ou d’un edge runtime mal aligné.

curl -i -X POST https://mcp-nextjs-server-xyz.vercel.app/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"prod-check","version":"1.0"}}}'

Vous devez recevoir le même type de réponse qu’en local : status 200, en-tête Mcp-Session-Id présent, body JSON avec les capacités. Si vous obtenez un 504 Gateway Timeout, c’est que maxDuration dans votre handler est plus élevé que la limite de votre plan Vercel — passez à 60 sur Hobby. Si vous obtenez un 405 Method Not Allowed, c’est que vous avez oublié l’export POST dans route.ts ; rouvrez le fichier et vérifiez.

Étape 9 — Brancher la production à Claude Desktop

Il ne reste qu’à mettre à jour la config Claude Desktop pour pointer sur l’URL Vercel plutôt que sur localhost. Le bloc devient :

{
  "mcpServers": {
    "nextjs-prod": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://mcp-nextjs-server-xyz.vercel.app/api/mcp"]
    }
  }
}

Redémarrez Claude Desktop, ouvrez un nouveau chat, et demandez « Convertis 50000 XOF en EUR ». Claude détectera l’outil convert_currency, l’invoquera, et vous renverra le résultat. Vous venez de mettre en ligne, depuis zéro, un serveur MCP accessible à n’importe quel agent compatible dans le monde.

Erreurs fréquentes

ErreurCauseSolution
404 sur /api/mcpLe segment dynamique [transport] est mal nomméRenommer le dossier exactement [transport]
504 Gateway TimeoutmaxDuration dépasse la limite du plan VercelRéduire à 60 (Hobby) ou upgrade Pro
Outils invisibles côté clientMauvais transport dans la config (stdio sans mcp-remote)Utiliser mcp-remote comme proxy
Schéma Zod refuséValidation au runtime vs génération du JSON SchemaVérifier les types — éviter z.union trop profond
Cold start lentPremier appel après inactivitéActiver Vercel Cron pour réveiller la route ou passer en Edge Runtime

Pour aller plus loin

FAQ

Mon endpoint MCP doit-il être public ?

Pas nécessairement. Un endpoint MCP HTTPS sans authentification est accessible à toute personne qui en connaît l’URL. Pour limiter l’accès, ajoutez un bearer token simple en attendant d’implémenter OAuth 2.1 complet, ou hébergez derrière un VPN. Le tutoriel sur la sécurisation détaille les options.

Puis-je héberger ailleurs que sur Vercel ?

Oui. mcp-handler publie aussi des adaptateurs pour Hono, Cloudflare Workers, et fonctionne sur tout runtime Node ou Edge compatible avec Next.js. Le code du route handler reste inchangé ; seul l’hébergement diffère. Render, Railway, Fly.io ou un VPS DigitalOcean fonctionnent tous.

Faut-il une base de données ?

Non pour les outils stateless comme une conversion de devise ou un appel d’API tiers. Oui dès que les outils doivent persister, lire ou écrire dans un stockage. Le tutoriel suivant sur PostgreSQL montre exactement comment brancher un pool de connexions Postgres dans un handler MCP.

Quelle différence entre maxDuration et le timeout Streamable HTTP ?

maxDuration est une limite côté Vercel (la fonction est tuée après ce délai). Le timeout côté client MCP est plus court — typiquement 30 secondes. Si votre outil est lent, retournez des progressions intermédiaires via les notifications JSON-RPC plutôt que de bloquer la réponse finale.

Mon serveur peut-il appeler d’autres APIs ?

Oui, c’est même le cas le plus courant. Dans le handler de l’outil, vous pouvez utiliser fetch nativement (Node 20+) pour appeler n’importe quelle API REST. Pensez à mettre les clés d’API dans des variables d’environnement Vercel, jamais dans le code.

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité