iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
Node.js · Intermédiaire

Créer un serveur MCP en Node.js

Construire un serveur MCP de A à Z : tools, resources, prompts, validation, transports stdio et HTTP — pour connecter Claude à n'importe quel système externe.

Node.jsTypeScriptMCPClaude Code
01Comprendre MCP02Premier serveur MCP03Tools avancés — validation, erreurs, structure04Resources — exposer du contenu05Prompts — templates réutilisables06Transports et déploiement07Projet complet — MCP de gestion de projet
Chapitre 6·20 min

Transports et déploiement

Le transport est le canal de communication entre le client MCP et votre serveur. Deux options principales : stdio (processus local, communication par stdin/stdout) et HTTP Streamable (serveur HTTP, connexions réseau). Chacun a son cas d'usage.

Stdio — transport local

Stdio est le transport par défaut et le plus simple. Claude lance votre serveur comme un processus enfant et communique via stdin/stdout.

import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
 
const transport = new StdioServerTransport()
await server.connect(transport)
// Le processus reste en vie tant que la connexion est ouverte

Avantages :

  • Zéro configuration réseau
  • Sécurité native (pas d'exposition sur le réseau)
  • Démarrage simple dans settings.json

Limites :

  • Un client à la fois
  • Le serveur tourne sur la même machine que le client
  • Pas adapté aux déploiements partagés (équipe)

Quand l'utiliser : outils personnels, scripts locaux, intégrations qui accèdent à la machine locale (fichiers, DB locale, processus).

HTTP Streamable — transport réseau

HTTP Streamable (le transport moderne recommandé par Anthropic) expose le serveur MCP sur une URL HTTP. Les clients se connectent via HTTP, les réponses utilisent Server-Sent Events pour le streaming.

import express from 'express'
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'
 
const app = express()
app.use(express.json())
 
// Une session = un transport
const transports = new Map<string, StreamableHTTPServerTransport>()
 
app.post('/mcp', async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string | undefined
 
  if (sessionId && transports.has(sessionId)) {
    // Session existante — réutiliser le transport
    const transport = transports.get(sessionId)!
    await transport.handleRequest(req, res, req.body)
    return
  }
 
  // Nouvelle session
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => crypto.randomUUID(),
    onsessioninitialized: (id) => {
      transports.set(id, transport)
    },
  })
 
  transport.onclose = () => {
    if (transport.sessionId) {
      transports.delete(transport.sessionId)
    }
  }
 
  const mcpServer = createServer()  // votre fonction qui crée et configure le Server
  await mcpServer.connect(transport)
  await transport.handleRequest(req, res, req.body)
})
 
// Endpoint GET pour SSE (streaming)
app.get('/mcp', async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string
  const transport = transports.get(sessionId)
 
  if (!transport) {
    res.status(404).json({ error: 'Session introuvable' })
    return
  }
 
  await transport.handleRequest(req, res)
})
 
// Endpoint DELETE pour fermer une session
app.delete('/mcp', async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string
  const transport = transports.get(sessionId)
 
  if (transport) {
    await transport.close()
    transports.delete(sessionId)
  }
 
  res.status(200).end()
})
 
app.listen(3000, () => {
  console.error('[mcp] Serveur HTTP démarré sur http://localhost:3000')
})

Installation supplémentaire :

npm install express
npm install -D @types/express

Configurer Claude Code pour HTTP

{
  "mcpServers": {
    "mon-mcp-remote": {
      "url": "http://localhost:3000/mcp"
    }
  }
}

Pour un serveur déployé :

{
  "mcpServers": {
    "mon-mcp-prod": {
      "url": "https://mcp.monapp.fr/mcp",
      "headers": {
        "Authorization": "Bearer ${MON_MCP_TOKEN}"
      }
    }
  }
}

Authentification

Pour les serveurs HTTP exposés, l'authentification est critique.

Bearer token

app.use('/mcp', (req, res, next) => {
  const auth = req.headers.authorization
  if (!auth?.startsWith('Bearer ')) {
    res.status(401).json({ error: 'Token manquant' })
    return
  }
 
  const token = auth.slice(7)
  if (token !== process.env.MCP_SECRET_TOKEN) {
    res.status(403).json({ error: 'Token invalide' })
    return
  }
 
  next()
})

API Key par header custom

app.use('/mcp', (req, res, next) => {
  const apiKey = req.headers['x-api-key']
  if (!apiKey || !validApiKeys.has(apiKey as string)) {
    res.status(403).json({ error: 'API key invalide' })
    return
  }
  next()
})

Dans settings.json côté client :

{
  "mcpServers": {
    "mon-mcp": {
      "url": "https://mcp.monapp.fr/mcp",
      "headers": {
        "x-api-key": "${MCP_API_KEY}"
      }
    }
  }
}

Architecture du serveur pour HTTP

Avec stdio, un serveur = un client. Avec HTTP, plusieurs clients partagent le même serveur — mais chaque session doit être isolée.

// src/server-factory.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { registerTools } from './tools/index.js'
import { registerResources } from './resources/index.js'
 
export function createServer(): Server {
  const server = new Server(
    { name: 'mon-mcp', version: '1.0.0' },
    { capabilities: { tools: {}, resources: {} } }
  )
 
  registerTools(server)
  registerResources(server)
 
  return server
}

Chaque requête de nouvelle session crée un nouveau Server via createServer(). Les sessions sont indépendantes.

Déploiement en production

Dockerfile

FROM node:20-slim
 
WORKDIR /app
 
COPY package*.json ./
RUN npm ci --production
 
COPY dist/ ./dist/
 
ENV NODE_ENV=production
ENV PORT=3000
 
EXPOSE 3000
 
CMD ["node", "dist/index.js"]
# Build et run
npm run build
docker build -t mon-mcp .
docker run -p 3000:3000 -e MCP_SECRET_TOKEN=xxx mon-mcp

Derrière Nginx

location /mcp {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
 
    # Nécessaire pour SSE (streaming)
    proxy_set_header Connection '';
    proxy_buffering off;
    proxy_cache off;
    chunked_transfer_encoding on;
 
    # Headers standard
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
 
    # Timeout long pour les connexions SSE
    proxy_read_timeout 3600s;
}

proxy_buffering off est critique — sans ça, Nginx bufferise le SSE et les messages n'arrivent pas en temps réel.

PM2 pour la persistance

npm install -g pm2
pm2 start dist/index.js --name mon-mcp
pm2 save
pm2 startup  # démarrage au boot

Choisir entre stdio et HTTP

CritèrestdioHTTP Streamable
UsageLocal, personnelPartagé, équipe, remote
SetupMinimalServeur HTTP requis
Sécurité réseauPas d'expositionÀ configurer (HTTPS, auth)
Multi-clientsNonOui
LatenceTrès faibleVariable selon réseau
DéploiementPas nécessaireVPS, Docker, cloud

Règle simple : stdio pour les outils personnels locaux, HTTP pour tout ce qui est partagé ou déployé.

Variables d'environnement dans les deux modes

// Toujours charger depuis process.env — fonctionne en stdio et HTTP
const DB_URL = process.env.DATABASE_URL
if (!DB_URL) {
  console.error('DATABASE_URL manquant')
  process.exit(1)
}

Stdio : les env vars passent via settings.json :

{
  "mcpServers": {
    "mon-mcp": {
      "command": "node",
      "args": ["dist/index.js"],
      "env": {
        "DATABASE_URL": "postgresql://localhost/mydb"
      }
    }
  }
}

HTTP : les env vars viennent du .env ou du système.


Les transports sont maîtrisés. Le dernier chapitre assemble tout en un projet complet : un serveur MCP pour gérer un projet de développement — tickets, utilisateurs, déploiements, métriques.

Précédent
Prompts — templates réutilisables
Suivant
Projet complet — MCP de gestion de projet

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main