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 4·20 min

Resources — exposer du contenu

Les resources sont du contenu que Claude peut lire de façon proactive — sans que vous ayez déclenché un tool. Pensez à elles comme des "fichiers virtuels" exposés par votre serveur : logs d'application, configuration système, état d'une base de données, documentation générée dynamiquement.

Différence tools / resources

Tool     → action, effet de bord, résultat dynamique selon les paramètres
Resource → contenu, lecture, données à un instant T
Tool     : create_user(name, email)   → crée l'utilisateur, retourne l'ID
Resource : users://list               → lit la liste des utilisateurs

Les resources sont identifiées par une URI. Claude les lit avec read_resource(uri).

Activer les resources dans le serveur

const server = new Server(
  { name: 'mon-mcp', version: '1.0.0' },
  {
    capabilities: {
      tools: {},
      resources: {},  // ← activer les resources
    },
  }
)

Déclarer les resources disponibles

import {
  ListResourcesRequestSchema,
  ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
 
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: 'app://config',
      name: 'Configuration de l\'application',
      description: 'Variables d\'environnement et configuration actuelle',
      mimeType: 'application/json',
    },
    {
      uri: 'app://logs/recent',
      name: 'Logs récents',
      description: 'Les 100 dernières entrées de log',
      mimeType: 'text/plain',
    },
    {
      uri: 'db://stats',
      name: 'Statistiques base de données',
      description: 'Nombre d\'entrées par table, taille, dernière mise à jour',
      mimeType: 'application/json',
    },
  ],
}))

Implémenter la lecture des resources

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params
 
  if (uri === 'app://config') {
    const config = {
      nodeEnv: process.env.NODE_ENV,
      port: process.env.PORT,
      dbHost: process.env.DB_HOST,
      // Ne jamais exposer les secrets complets
      dbPasswordSet: !!process.env.DB_PASSWORD,
      version: process.env.npm_package_version,
    }
 
    return {
      contents: [
        {
          uri,
          mimeType: 'application/json',
          text: JSON.stringify(config, null, 2),
        },
      ],
    }
  }
 
  if (uri === 'app://logs/recent') {
    const logs = await readRecentLogs(100)  // votre fonction de lecture
    return {
      contents: [
        {
          uri,
          mimeType: 'text/plain',
          text: logs.join('\n'),
        },
      ],
    }
  }
 
  if (uri === 'db://stats') {
    const stats = await getDatabaseStats()
    return {
      contents: [
        {
          uri,
          mimeType: 'application/json',
          text: JSON.stringify(stats, null, 2),
        },
      ],
    }
  }
 
  throw new McpError(ErrorCode.InvalidRequest, `Resource inconnue : ${uri}`)
})

URI Templates — resources dynamiques

Une URI template expose des patterns plutôt que des URIs fixes. Claude peut construire l'URI selon ses besoins.

server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: 'users://list',
      name: 'Liste des utilisateurs',
      mimeType: 'application/json',
    },
  ],
  // URI templates — patterns avec variables
  resourceTemplates: [
    {
      uriTemplate: 'users://{id}',
      name: 'Profil utilisateur',
      description: 'Profil complet d\'un utilisateur par son ID',
      mimeType: 'application/json',
    },
    {
      uriTemplate: 'logs://{service}/{date}',
      name: 'Logs d\'un service à une date',
      description: 'Logs filtrés par service et date (YYYY-MM-DD)',
      mimeType: 'text/plain',
    },
  ],
}))

Le handler gère les URIs dynamiques avec un parser :

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const { uri } = request.params
 
  // URI statique
  if (uri === 'users://list') {
    const users = await db.query('SELECT id, name, email FROM users LIMIT 100')
    return {
      contents: [{
        uri,
        mimeType: 'application/json',
        text: JSON.stringify(users.rows, null, 2),
      }],
    }
  }
 
  // URI dynamique : users://42
  const userMatch = uri.match(/^users:\/\/(\d+)$/)
  if (userMatch) {
    const id = parseInt(userMatch[1])
    const user = await db.query('SELECT * FROM users WHERE id = $1', [id])
 
    if (user.rows.length === 0) {
      throw new McpError(ErrorCode.InvalidRequest, `Utilisateur ${id} introuvable`)
    }
 
    return {
      contents: [{
        uri,
        mimeType: 'application/json',
        text: JSON.stringify(user.rows[0], null, 2),
      }],
    }
  }
 
  // URI dynamique : logs://api/2026-06-24
  const logsMatch = uri.match(/^logs:\/\/([a-z-]+)\/(\d{4}-\d{2}-\d{2})$/)
  if (logsMatch) {
    const [, service, date] = logsMatch
    const logs = await fetchLogs(service, date)
    return {
      contents: [{
        uri,
        mimeType: 'text/plain',
        text: logs,
      }],
    }
  }
 
  throw new McpError(ErrorCode.InvalidRequest, `URI non supportée : ${uri}`)
})

Claude comprend le template et peut construire ses propres URIs :

> lis les logs du service "api" pour hier

Claude construit logs://api/2026-06-23 et lit la resource.

Resource subscriptions — notifications en temps réel

Les clients peuvent s'abonner à une resource et recevoir des notifications quand son contenu change.

const server = new Server(
  { name: 'mon-mcp', version: '1.0.0' },
  {
    capabilities: {
      resources: {
        subscribe: true,    // activer les subscriptions
        listChanged: true,  // notifier quand la liste change
      },
    },
  }
)
 
// Gérer les subscriptions
import {
  SubscribeRequestSchema,
  UnsubscribeRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
 
const subscribers = new Map<string, Set<string>>()  // uri → set de session IDs
 
server.setRequestHandler(SubscribeRequestSchema, async (request) => {
  const { uri } = request.params
  if (!subscribers.has(uri)) {
    subscribers.set(uri, new Set())
  }
  // ... enregistrer l'abonnement
  return {}
})
 
server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
  const { uri } = request.params
  subscribers.delete(uri)
  return {}
})
 
// Notifier les abonnés quand le contenu change
async function notifyResourceUpdated(uri: string) {
  await server.notification({
    method: 'notifications/resources/updated',
    params: { uri },
  })
}
 
// Exemple : surveiller un fichier et notifier
import { watch } from 'fs'
watch('/var/log/app.log', async () => {
  await notifyResourceUpdated('app://logs/recent')
})

Les subscriptions sont utiles pour des métriques en temps réel, des logs streamés, ou tout contenu qui change fréquemment.

MIME types courants

ContenuMIME type
JSON structuréapplication/json
Texte / logstext/plain
Markdowntext/markdown
HTMLtext/html
CSVtext/csv
Image PNG (base64)image/png

Pour les ressources binaires (images), utiliser blob à la place de text :

return {
  contents: [{
    uri,
    mimeType: 'image/png',
    blob: imageBuffer.toString('base64'),  // base64
  }],
}

Notifier que la liste de resources a changé

Si votre serveur ajoute ou retire des resources dynamiquement :

await server.notification({
  method: 'notifications/resources/list_changed',
  params: {},
})

Les clients rechargent la liste des resources disponibles.


Tools pour agir, resources pour lire. Le chapitre suivant ajoute les prompts — des templates de conversation réutilisables que Claude peut instancier avec des variables.

Précédent
Tools avancés — validation, erreurs, structure
Suivant
Prompts — templates réutilisables

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