Alane Jaunet

Déployer un serveur MCP sur Vercel : le guide complet

Comment j'ai résolu les défis du déploiement d'un serveur MCP (Model Context Protocol) sur Vercel serverless, en contournant les limitations de mcp-handler avec le SDK MCP natif.

Par Alane Jaunet18 mars 2026

Déployer un serveur MCP sur Vercel : le guide complet

Le Model Context Protocol (MCP) permet à Claude et d'autres LLMs d'interagir avec des outils externes via une API standardisée. Mais déployer un serveur MCP sur Vercel serverless n'est pas aussi simple qu'il y paraît.

Voici les problèmes que j'ai rencontrés et comment je les ai résolus.

Le contexte

Mon portfolio expose un serveur MCP avec 38 outils (CRUD projets, articles, compétences, certifications...). L'objectif : permettre à Claude de gérer mon portfolio directement via le MCP connector.

Stack : Next.js App Router + Prisma + Vercel.

Problème 1 : La route dynamique

La bibliothèque mcp-handler (de Vercel) attend une route dynamique app/api/[transport]/route.ts avec basePath: "/api". Cela génère automatiquement :

  • /api/mcp → Streamable HTTP
  • /api/sse → Server-Sent Events
  • /api/message → Messages SSE

Une route statique app/api/mcp/route.ts ne fonctionne pas correctement.

Problème 2 : Le streaming sur Vercel

Sans export const dynamic = "force-dynamic", Vercel bufferise la réponse SSE (ReadableStream) et retourne une erreur 500 avec un body vide. Il faut aussi export const maxDuration = 60 pour éviter les timeouts.

Problème 3 : Le vrai bug — mcp-handler vs serverless

C'est là que ça se complique. mcp-handler crée un seul transport réutilisé entre les requêtes. Or, le SDK MCP v1.27+ a deux modes :

Mode stateless (sans sessionIdGenerator)

Le transport refuse d'être réutilisé :

Stateless transport cannot be reused across requests.

Résultat : seule la première requête fonctionne (cold start), les suivantes crashent.

Mode stateful (avec sessionIdGenerator)

Le transport crée des sessions... en mémoire. Sur Vercel serverless, chaque cold start perd l'état. Claude envoie initialize, obtient un session ID, puis envoie tools/list — mais la session a disparu.

La solution : SDK MCP natif, transport frais par requête

J'ai contourné mcp-handler en utilisant directement le SDK MCP :

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js"; async function handleMcpRequest(request: Request) { // Transport frais par requête = zéro état partagé const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, }); const server = createServer(); await server.connect(transport); return transport.handleRequest(request); } export async function POST(request: Request) { return handleMcpRequest(request); }

Chaque invocation serverless crée son propre serveur et transport. Pas de session, pas d'état partagé, pas de cold start qui casse tout.

Bonus : OAuth2 pour sécuriser le tout

Pour protéger le endpoint MCP, j'ai implémenté un flow OAuth2 complet compatible avec le MCP connector de Claude.ai :

  • Discovery : /.well-known/oauth-protected-resource et /.well-known/oauth-authorization-server
  • Dynamic client registration : Claude s'enregistre automatiquement
  • Authorization code + PKCE S256 : l'utilisateur se connecte via GitHub/email, consent, et Claude reçoit un token
  • Bearer auth sur le endpoint MCP avec fallback CLI token

Le tout stocké en PostgreSQL via Prisma — zéro état en mémoire, 100% compatible serverless.

Résultat

  • 5/5 requêtes : HTTP 200 (cold start et warm)
  • 38 outils détectés par le MCP connector Claude
  • OAuth2 sécurisé avec login GitHub
  • Compatible avec https://domaine.com/api/mcp (Streamable HTTP)

Points clés à retenir

  1. Utilisez app/api/[transport]/route.ts avec basePath: "/api"
  2. Ajoutez export const dynamic = "force-dynamic" et maxDuration
  3. Sur serverless, créez un transport frais par requête au lieu de réutiliser un singleton
  4. Le SDK MCP natif (@modelcontextprotocol/sdk) est plus fiable que mcp-handler pour le serverless
  5. Implémentez OAuth2 si vous voulez sécuriser l'accès depuis Claude.ai