Créer un serveur MCP pour connecter Claude, n8n et tes API

Construis un serveur MCP en Node.js, expose trois tools sur une API interne, sécurise-le puis connecte-le proprement à Claude Desktop et à un agent n8n.

Photo de Jean-Paul LOVISSOUKPO14 min de lecture
Créer un serveur MCP reliant une API interne à Claude et à n8n

Ton équipe possède une API interne parfaitement utilisable. Elle sait retrouver un contact, lire une commande et créer un ticket. Puis quelqu’un demande : « On peut donner ces trois actions à Claude et à notre agent n8n ? »

La première réponse paraît simple : trois appels HTTP dans chaque outil. C’est aussi le début d’un joli doublon. Tu dois décrire les paramètres deux fois, gérer l’authentification deux fois et corriger chaque changement d’endpoint dans deux intégrations. Au troisième client IA, le connecteur maison ressemble déjà à une multiprise achetée sur un marché.

Créer un serveur MCP place une seule interface entre tes applications IA et l’API métier. Claude, n8n ou un autre client découvrent les mêmes tools. Le serveur valide les paramètres, appelle l’API avec ses propres credentials et renvoie un résultat borné. Le modèle ne voit jamais le token interne. Un seul majordome pour tous les invités, un peu comme Jarvis chez Iron Man dans Marvel : l’assistant répond à toutes les demandes sans jamais laisser traîner les clés de l’armure.

On va construire ce serveur en TypeScript avec trois tools : rechercher_contact, lire_commande et creer_ticket. Il fonctionnera en local avec Claude Desktop, puis sur un VPS en Streamable HTTP pour n8n. La partie authentification distinguera volontairement deux cas, une clé Bearer pour un client interne maîtrisé et OAuth pour un connecteur Claude distant.

Pourquoi créer un serveur MCP change l’architecture

Pour éviter de dupliquer les trois appels HTTP dans chaque client, il faut placer leur contrat dans une couche commune.

MCP sépare le client qui héberge le modèle du serveur qui expose des capacités. Le client ouvre une connexion, négocie une version du protocole, récupère la liste des tools puis transmet les appels choisis par le modèle. Le serveur exécute le code. L’API interne reste derrière lui.

Diagramme du processus : Claude Desktop, Serveur MCP, Agent n8n, Autre client MCP, Tool rechercher_contact, Tool lire_commande, Tool creer_ticket, API interne.

Un tool n’est pas une route HTTP repeinte. Il possède un nom stable, une description destinée au modèle, un schéma d’entrée et un résultat. Cette description compte autant que le code. Si lire_commande prétend « gérer les commandes », le modèle ne sait pas s’il peut lire, modifier ou annuler. Écris plutôt : « Lit une commande existante par son identifiant, sans la modifier. »

Cette discipline rejoint l’architecture décrite dans l’article sur le système multi-agents n8n. L’agent choisit une capacité, mais le serveur garde les contrôles déterministes : validation des identifiants, délai maximal, permissions et format de réponse.

La spécification MCP actuelle définit deux transports standards :

  • stdio, pour un serveur local lancé comme sous-processus par le client
  • Streamable HTTP, pour un serveur indépendant joignable sur le réseau

L’ancien transport HTTP avec un endpoint SSE séparé est conservé pour compatibilité. Ne démarre pas un nouveau projet dessus. C’est la cassette audio du protocole : ça se lit encore, ça ne s’achète plus.

Choisir la bonne version du SDK TypeScript

Le rôle du serveur et ses deux transports sont maintenant définis. Avant d’écrire le projet, il reste à choisir la version du SDK qui implémente ces transports.

Le calendrier du SDK demande un choix explicite. À la date de publication de cet article, la branche v2 est encore en bêta. Le dépôt officiel du SDK maintient donc la v1 comme version de production tant que la v2 n’est pas stable.

Le code de cet article utilise donc @modelcontextprotocol/sdk v1. Verrouille sa version dans ton package-lock.json. Quand la v2 sera stable, suis le guide de migration officiel au lieu de remplacer les imports au hasard. La v2 découpe le SDK en plusieurs paquets, ce n’est pas un simple changement de numéro.

Il te faut :

  • Node.js 22 LTS
  • Un nom de domaine dont tu contrôles le DNS pour le déploiement
  • Une API de test ou un environnement de staging
  • Docker et Caddy sur le VPS
  • Claude Desktop pour le test local
  • Une instance n8n récente avec le nœud MCP Client Tool

N’utilise jamais les credentials de production pendant la construction. Le tool creer_ticket écrit réellement. Une faute dans un prompt ne mérite pas d’ouvrir 87 tickets au support à 2 h 13.

Initialiser le serveur MCP Node.js

La version du SDK étant fixée, tu peux initialiser le projet sans risquer de mélanger des imports v1 et v2.

Pour créer un serveur MCP proprement, commence dans un dossier séparé de l’application métier. Le serveur est une frontière de sécurité, pas un fichier de plus dans le backend historique.

mkdir api-interne-mcp
cd api-interne-mcp
npm init -y
npm install @modelcontextprotocol/sdk express dotenv zod
npm install --save-dev typescript @types/express @types/node
mkdir src

Le package.json compile le TypeScript puis démarre le JavaScript produit :

{
  "name": "api-interne-mcp",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/http.js",
    "start:stdio": "node dist/stdio.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1",
    "dotenv": "^17",
    "express": "^5",
    "zod": "^4"
  },
  "devDependencies": {
    "@types/express": "^5",
    "@types/node": "^22",
    "typescript": "^5"
  }
}

Utilise NodeNext. Sans cette configuration, TypeScript accepte parfois un import que Node refuse ensuite avec ERR_MODULE_NOT_FOUND.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "rootDir": "src",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"]
}

Garde les secrets dans .env, jamais dans le code ni dans le schéma des tools.

INTERNAL_API_URL=https://staging-api.example.com
INTERNAL_API_TOKEN=remplacer-par-un-token-de-staging
MCP_BEARER_TOKEN=remplacer-par-une-cle-longue-et-aleatoire
MCP_PUBLIC_HOST=mcp.example.com
PORT=3000

Ajoute .env à .gitignore. Ce conseil semble scolaire jusqu’au matin où GitHub t’envoie une alerte de secret exposé.

Isoler l’appel à l’API interne

Le projet compile et ses secrets sont isolés. Il faut maintenant créer l’unique passage entre les futurs tools et l’API métier.

Créer un serveur MCP ne justifie pas de répéter fetch() dans chaque tool. Centralise le délai maximal, les headers et la lecture des erreurs dans src/api.ts.

import "dotenv/config";

const apiUrl = process.env.INTERNAL_API_URL;
const apiToken = process.env.INTERNAL_API_TOKEN;

if (!apiUrl || !apiToken) {
  throw new Error("INTERNAL_API_URL et INTERNAL_API_TOKEN sont obligatoires");
}

export async function apiRequest(
  path: string,
  init: RequestInit = {},
): Promise<unknown> {
  const response = await fetch(new URL(path, apiUrl), {
    ...init,
    headers: {
      accept: "application/json",
      authorization: `Bearer ${apiToken}`,
      "content-type": "application/json",
      ...init.headers,
    },
    signal: AbortSignal.timeout(8_000),
  });

  const text = await response.text();
  const data = text ? JSON.parse(text) : null;

  if (!response.ok) {
    throw new Error(
      `API interne ${response.status}: ${JSON.stringify(data)}`,
    );
  }

  return data;
}

Le délai de 8 secondes n’est pas une vérité universelle. C’est une limite de départ. Mesure ensuite le percentile 95 de ton API et règle le timeout au-dessus, sans laisser une requête suspendue pendant trois minutes.

Ne renvoie pas toute la réponse si elle contient des notes privées, des marges commerciales ou des données personnelles inutiles. Le meilleur filtre se trouve dans l’API métier. À défaut, construis un objet de sortie explicite dans le tool.

Exposer trois tools sur la même API

Le client HTTP centralisé fournit désormais les délais, les headers et les erreurs dont chaque capacité a besoin. Les trois tools peuvent donc partager cette base sans dupliquer le transport.

Pour créer un serveur MCP compatible avec plusieurs transports, place les tools dans src/server.ts. La fonction fabrique une instance que l’on pourra connecter à stdio ou à HTTP sans dupliquer le métier.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import * as z from "zod/v4";
import { apiRequest } from "./api.js";

function jsonResult(data: unknown) {
  return {
    content: [{ type: "text" as const, text: JSON.stringify(data) }],
  };
}

function errorResult(error: unknown) {
  const message = error instanceof Error ? error.message : "Erreur inconnue";
  return {
    content: [{ type: "text" as const, text: message }],
    isError: true,
  };
}

export function createServer() {
  const server = new McpServer({
    name: "api-interne",
    version: "1.0.0",
  });

  server.registerTool(
    "rechercher_contact",
    {
      title: "Rechercher un contact",
      description:
        "Recherche un contact par email. Lecture seule, aucune modification.",
      inputSchema: {
        email: z.string().email().describe("Email exact du contact"),
      },
      annotations: {
        readOnlyHint: true,
        openWorldHint: false,
      },
    },
    async ({ email }) => {
      try {
        const data = await apiRequest(
          `/contacts?email=${encodeURIComponent(email)}`,
        );
        return jsonResult(data);
      } catch (error) {
        return errorResult(error);
      }
    },
  );

  server.registerTool(
    "lire_commande",
    {
      title: "Lire une commande",
      description:
        "Lit une commande existante par son identifiant. Ne la modifie pas.",
      inputSchema: {
        orderId: z
          .string()
          .regex(/^CMD-[0-9]{6}$/)
          .describe("Identifiant au format CMD-123456"),
      },
      annotations: {
        readOnlyHint: true,
        openWorldHint: false,
      },
    },
    async ({ orderId }) => {
      try {
        return jsonResult(
          await apiRequest(`/orders/${encodeURIComponent(orderId)}`),
        );
      } catch (error) {
        return errorResult(error);
      }
    },
  );

  server.registerTool(
    "creer_ticket",
    {
      title: "Créer un ticket support",
      description:
        "Crée un ticket support après confirmation explicite de l’utilisateur.",
      inputSchema: {
        subject: z.string().min(5).max(120),
        description: z.string().min(20).max(4_000),
        priority: z.enum(["low", "normal", "high"]).default("normal"),
        confirmed: z
          .literal(true)
          .describe("Vrai seulement après confirmation de l’utilisateur"),
      },
      annotations: {
        readOnlyHint: false,
        destructiveHint: false,
        idempotentHint: false,
        openWorldHint: true,
      },
    },
    async ({ subject, description, priority }) => {
      try {
        return jsonResult(
          await apiRequest("/tickets", {
            method: "POST",
            body: JSON.stringify({ subject, description, priority }),
          }),
        );
      } catch (error) {
        return errorResult(error);
      }
    },
  );

  return server;
}

Le confirmed: true n’est pas une preuve cryptographique. Il force tout de même le client et le prompt à traiter la confirmation comme une donnée visible. Pourtant, un modèle peut remplir lui-même ce champ. Pour une action coûteuse ou irréversible, place l’approbation hors du tool : file humaine, écran de validation ou workflow n8n bloqué.

La documentation du SDK v1 recommande registerTool() avec un schéma Zod et permet de signaler une erreur métier avec isError: true. Une exception brute transformée en erreur JSON-RPC ne donne pas toujours au modèle assez d’information pour corriger son appel.

Tester le serveur dans Claude Desktop avec stdio

La fonction qui crée le serveur contient maintenant le métier sans dépendre d’un transport. Pour vérifier les tools localement, il suffit de la connecter à stdio.

Créer un serveur MCP local demande seulement un transport stdio. Ajoute src/stdio.ts :

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createServer } from "./server.js";

const server = createServer();
const transport = new StdioServerTransport();

await server.connect(transport);

Compile :

npm run build
npm run start:stdio

La seconde commande semble ne rien faire. C’est normal. Le processus attend des messages JSON-RPC sur l’entrée standard. Surtout, n’ajoute pas de console.log() sur stdout. La spécification réserve ce canal aux messages MCP. Envoie tes journaux sur stderr avec console.error(). Un console.log() oublié et le client reçoit du charabia au milieu du JSON-RPC : piège classique, tout le monde y passe une fois.

Pour une intégration locale de développement, ajoute le serveur dans la configuration locale de Claude Desktop. Sur Windows, utilise un chemin absolu avec des doubles antislashs :

{
  "mcpServers": {
    "api-interne": {
      "command": "node",
      "args": ["C:\\mcp\\api-interne-mcp\\dist\\stdio.js"],
      "env": {
        "INTERNAL_API_URL": "https://staging-api.example.com",
        "INTERNAL_API_TOKEN": "token-de-staging"
      }
    }
  }
}

Redémarre Claude Desktop, ouvre les tools et vérifie que les trois noms apparaissent. Demande d’abord : « Recherche le contact test@example.com ». N’attaque pas le test par creer_ticket. Commencer par l’écriture complique le diagnostic pour rien. La lecture d’abord : si elle répond, ça passe crème, et l’écriture attendra son tour.

Anthropic recommande désormais les extensions DXT pour distribuer un serveur local. La configuration JSON reste utile au développement, mais une équipe ne devrait pas copier manuellement des secrets dans dix ordinateurs.

Servir MCP en Streamable HTTP

Le test stdio valide les tools et l’accès à l’API sur la machine locale. Pour rendre exactement les mêmes capacités accessibles à n8n, seule la couche de transport doit changer.

Créer un serveur MCP distant demande un endpoint unique /mcp. Cette version reste stateless : chaque requête crée son serveur et son transport, puis les ferme. C’est suffisant pour trois tools synchrones et beaucoup plus simple à répliquer.

Crée src/http.ts :

import "dotenv/config";
import { timingSafeEqual } from "node:crypto";
import type { NextFunction, Request, Response } from "express";
import { createMcpExpressApp } from
  "@modelcontextprotocol/sdk/server/express.js";
import { hostHeaderValidation } from
  "@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js";
import { StreamableHTTPServerTransport } from
  "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { createServer } from "./server.js";

const port = Number(process.env.PORT ?? 3000);
const publicHost = process.env.MCP_PUBLIC_HOST;
const bearerToken = process.env.MCP_BEARER_TOKEN;

if (!publicHost || !bearerToken) {
  throw new Error("MCP_PUBLIC_HOST et MCP_BEARER_TOKEN sont obligatoires");
}

function bearerAuth(req: Request, res: Response, next: NextFunction) {
  const header = req.header("authorization");
  const supplied = header?.startsWith("Bearer ") ? header.slice(7) : "";
  const expected = Buffer.from(bearerToken);
  const received = Buffer.from(supplied);

  if (
    expected.length !== received.length ||
    !timingSafeEqual(expected, received)
  ) {
    res.status(401).json({ error: "unauthorized" });
    return;
  }

  next();
}

const app = createMcpExpressApp({ host: "0.0.0.0" });
app.use(
  hostHeaderValidation([publicHost, "localhost", "127.0.0.1"]),
);

app.get("/health", (_req, res) => {
  res.status(200).json({ status: "ok" });
});

app.post("/mcp", bearerAuth, async (req, res) => {
  const server = createServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
    enableJsonResponse: true,
  });

  res.on("close", () => {
    void transport.close();
    void server.close();
  });

  try {
    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);
  } catch (error) {
    console.error(error);
    if (!res.headersSent) {
      res.status(500).json({
        jsonrpc: "2.0",
        error: { code: -32603, message: "Internal server error" },
        id: null,
      });
    }
  }
});

app.get("/mcp", bearerAuth, (_req, res) => {
  res.sendStatus(405);
});

app.delete("/mcp", bearerAuth, (_req, res) => {
  res.sendStatus(405);
});

app.listen(port, "0.0.0.0", () => {
  console.error(`MCP écoute sur le port ${port}`);
});

Un GET qui renvoie 405 est autorisé lorsqu’un serveur ne propose pas de flux SSE indépendant. Le client envoie les appels par POST et reçoit ici une réponse JSON. Si tu ajoutes des notifications, de la reprise ou des tâches longues, passe à des sessions stateful et conserve les transports par identifiant de session.

Sécuriser le serveur MCP sans se mentir

Le transport HTTP rend le serveur joignable à distance, ce qui crée une frontière de sécurité absente de stdio.

Créer un serveur MCP accessible sur Internet oblige à traiter l’authentification comme une fonction centrale. La clé Bearer ci-dessus convient à n8n si tu contrôles les deux serveurs. Elle ne constitue pas l’authentification MCP complète pour un connecteur Claude distant.

La spécification d’autorisation MCP s’appuie sur OAuth 2.1. Le serveur MCP agit comme resource server. Il publie des métadonnées de ressource protégée, indique son authorization server dans une réponse 401, vérifie les scopes et refuse un token dont l’audience ne correspond pas. Le diagramme suivant replace ces contrôles dans l’échange complet entre le client, le serveur MCP et le serveur OAuth.

Diagramme du processus décrit dans cette section.

Ne transmets jamais le token reçu de Claude directement à l’API interne. C’est du token passthrough, précisément le genre de raccourci qui casse la séparation des permissions. Le serveur MCP valide le token du client puis utilise sa propre credential, limitée aux opérations utiles.

Pour Claude distant, branche le middleware OAuth officiel du SDK à un fournisseur comme Keycloak, Auth0 ou ton serveur d’identité. L’exemple simpleStreamableHttp.ts du dépôt officiel montre mcpAuthMetadataRouter() et requireBearerAuth(). Je déconseille d’écrire toi-même un authorization server dans ce tutoriel. OAuth bricolé est pire qu’une absence d’auth, parce qu’il donne une impression de sécurité. Un faux cadenas rassure le propriétaire, jamais le cambrioleur.

Déployer le serveur MCP sur un VPS

L’authentification définit maintenant qui peut entrer. Le déploiement doit encore limiter ce qui est exposé et chiffrer le transport.

Pour créer un serveur MCP déployable sans traîner tout l’atelier, le conteneur final n’a besoin ni des sources TypeScript ni des dépendances de développement.

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build

FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/http.js"]

Le docker-compose.yml n’expose le port que sur la boucle locale. Caddy sera le seul point d’entrée public.

services:
  mcp:
    build: .
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "127.0.0.1:3000:3000"
    read_only: true
    security_opt:
      - no-new-privileges:true

Le service restant limité à la boucle locale, Caddy peut maintenant recevoir le trafic public et terminer TLS :

mcp.example.com {
  encode zstd gzip
  reverse_proxy 127.0.0.1:3000
}

Pointe le DNS vers le VPS, ouvre uniquement les ports 80 et 443, puis démarre :

docker compose up -d --build
curl https://mcp.example.com/health

Une réponse {"status":"ok"} valide le conteneur, le reverse proxy, le DNS et TLS. Quatre étages contrôlés en un seul curl, du travail propre. Elle ne teste ni MCP ni l’API interne. Utilise ensuite le MCP Inspector officiel pour initialiser le protocole, lister les tools et exécuter les deux tools de lecture.

Si ce VPS héberge aussi n8n, ne mélange pas leurs fichiers .env. Une sauvegarde ou une migration de l’instance n8n suit son propre cycle. La procédure pour migrer n8n vers PostgreSQL sans perdre les credentials couvre cette partie, et le montage complet du serveur partagé, reverse proxy, sauvegardes nocturnes et mises à jour épinglées, est décrit dans le guide pour auto-héberger toute ta stack d’automatisation sur un VPS.

Connecter le serveur distant à Claude

Le serveur est désormais accessible en HTTPS et son contrôle de santé répond. Tu peux donc configurer le premier client distant.

Les connecteurs distants Claude sont configurés dans les réglages des connecteurs, avec l’URL complète :

https://mcp.example.com/mcp

La documentation Claude sur les connecteurs MCP précise que la connexion part de l’infrastructure Anthropic, même depuis Claude Desktop. Ton endpoint doit donc être accessible publiquement ou autorisé depuis les plages IP publiées par Anthropic.

Cette différence explique un échec fréquent : curl fonctionne depuis ton ordinateur, mais Claude affiche que le serveur est inaccessible. Le domaine pointe vers une IP privée, le firewall n’autorise que ton bureau ou le certificat TLS est émis par une autorité interne.

Pour le serveur protégé, termine l’intégration OAuth avant d’ajouter le connecteur. Dans les réglages avancés, Claude peut utiliser un client ID et un client secret si ton authorization server ne prend pas en charge l’enregistrement dynamique. N’enlève pas bearerAuth « juste cinq minutes » sur une API qui crée des tickets. Les scanners trouvent les nouveaux domaines plus vite que ton café ne refroidit. Ce raccourci, c’est non.

Connecter le serveur MCP à n8n

Une fois l’accès distant validé avec Claude, n8n peut consommer le même contrat sans réimplémenter les appels à l’API.

Dans n8n, ajoute un nœud AI Agent, puis connecte un MCP Client Tool sur son port Tool. La documentation du MCP Client Tool confirme qu’il consomme les tools exposés par un serveur externe. Configure :

Server URL: https://mcp.example.com/mcp
Authentication: Bearer Auth
Bearer token: valeur de MCP_BEARER_TOKEN
Tools to include: rechercher_contact, lire_commande

Commence avec les deux tools de lecture. Cette première passe isole la découverte et l’authentification avant d’introduire une action d’écriture. Exécute directement le MCP Client Tool pour vérifier la découverte. Ajoute ensuite creer_ticket et une validation humaine dans le workflow.

Diagramme du processus : Chat Trigger, AI Agent, MCP Client Tool, Répondre, Validation humaine, Appel creer_ticket, Écriture demandée ?.

Dans le message système, recopie les noms exacts :

Utilise rechercher_contact uniquement avec un email exact.
Utilise lire_commande uniquement avec un identifiant CMD- suivi de 6 chiffres.
Avant creer_ticket, présente le sujet, la description et la priorité.
N'appelle creer_ticket qu'après une confirmation explicite.

Le serveur MCP n’est pas un remplaçant du routage par orchestrateur et sous-agents. Il standardise l’accès aux tools. L’orchestrateur décide encore quel spécialiste doit les utiliser et le workflow décide quelles actions nécessitent un humain.

Ce qui va te bloquer

Le serveur fonctionne maintenant avec ses deux clients. Lorsqu’un test échoue, pars du transport concerné, puis remonte vers l’authentification, le tool et enfin l’API métier.

Claude Desktop affiche « Server disconnected »

Avec stdio, vérifie le chemin absolu vers dist/stdio.js, la présence de Node dans le PATH de l’application et les variables d’environnement. Lance exactement la commande de la configuration dans un terminal. Si le processus écrit une bannière sur stdout, supprime-la ou déplace-la vers stderr.

Le client reçoit 401 Unauthorized

Teste la casse du header et l’espace après Bearer. Vérifie surtout que tu n’as pas mis INTERNAL_API_TOKEN dans n8n à la place de MCP_BEARER_TOKEN. Ces deux secrets protègent deux frontières différentes.

Le navigateur affiche 405 Method Not Allowed

Ouvrir /mcp dans un navigateur envoie un GET ordinaire. Notre endpoint refuse ce GET, conformément au mode stateless sans flux SSE. Ce n’est pas un test MCP. Utilise Inspector, Claude ou le MCP Client Tool.

n8n renvoie fetch failed

Le conteneur n8n résout et appelle l’URL depuis son propre réseau. localhost:3000 désigne le conteneur n8n, pas ton VPS ni le conteneur MCP. Utilise le domaine HTTPS ou le nom du service Docker si les deux services partagent un réseau privé.

Le tool existe, mais le modèle ne l’appelle jamais

La description est souvent trop large ou le nom cité dans le message système ne correspond pas. Commence par exécuter le tool directement. Si l’appel direct fonctionne, le transport n’est plus le suspect. Reviens au nom, à la description et au prompt de l’agent.

L’API renvoie 422, mais MCP affiche une erreur vague

Ne jette pas le corps de réponse. apiRequest() inclut le statut et le JSON retourné. Filtre les secrets avant de journaliser, puis conserve un identifiant de corrélation. Une erreur utile dit quel champ est invalide, pas seulement que « quelque chose s’est mal passé ».

Le serveur marche sur une requête puis plante en charge

La version stateless crée une instance par requête. C’est volontaire. Si tu ajoutes des sessions, ne stocke pas les transports dans une simple Map sur trois réplicas sans affinité de session. Utilise un stockage partagé ou un routage cohérent, comme le documente le SDK.

Les limites de ce serveur MCP

Les diagnostics précédents couvrent les erreurs de configuration. Même lorsque tous les tests passent, cette implémentation conserve des limites d’architecture.

Le code ne fournit pas un authorization server OAuth. Il protège le endpoint avec une clé Bearer pour n8n et utilise stdio pour Claude Desktop local. Un connecteur Claude distant manipulant des données privées doit recevoir la couche OAuth décrite plus haut.

Les trois tools supposent aussi que l’API métier applique ses propres permissions. MCP ne corrige pas un endpoint /contacts qui renvoie tout le fichier client à n’importe quel token. Réduis les scopes et les champs en amont.

Enfin, une interface commune ne garantit pas un comportement identique entre clients. Claude peut demander une confirmation avant un tool, tandis qu’un agent n8n exécutera selon ton workflow. Teste chaque client avec le même jeu de 18 requêtes : 6 valides, 6 ambiguës et 6 interdites. Compare les appels, pas seulement les réponses finales.

La prochaine extension naturelle consiste à transformer une API CRM sans intégration native en tools spécialisés. Garde cette logique dans le serveur, pas dans trois prompts différents. Si tu bloques sur un comportement de client ou une variante d’authentification, la communauté lesnocodeurs est l’endroit adapté pour comparer les configurations et les messages d’erreur.

Questions fréquentes

À quoi sert un serveur MCP personnalisé ?+
Un serveur MCP personnalisé traduit les capacités de ton application en tools, resources ou prompts qu’un client compatible peut découvrir et appeler. Il évite de recoder un connecteur différent pour Claude, n8n ou un autre agent. Le serveur garde aussi les secrets de l’API interne hors du contexte envoyé au modèle.
Quel transport choisir pour un serveur MCP distant ?+
Utilise Streamable HTTP pour un serveur distant. Le transport stdio reste adapté aux intégrations locales où le client lance lui-même le processus. HTTP avec SSE séparé appartient à l’ancien protocole et ne devrait servir qu’à maintenir la compatibilité avec un client qui ne sait pas encore parler Streamable HTTP.
Peut-on sécuriser un serveur MCP avec une simple clé API ?+
Une clé Bearer convient pour un client maîtrisé comme une instance n8n interne, à condition d’utiliser HTTPS, une rotation régulière et un secret distinct de celui de l’API métier. Pour un connecteur MCP distant dans Claude, implémente le flux OAuth prévu par la spécification, avec découverte des métadonnées et validation de l’audience.
Comment connecter un serveur MCP à Claude Desktop ?+
Pour un développement local, Claude Desktop peut lancer le serveur par stdio depuis sa configuration locale ou une extension DXT. Pour un serveur distant, ajoute son URL dans les réglages des connecteurs Claude. Le serveur doit être joignable depuis l’infrastructure Anthropic et utiliser OAuth s’il protège des données ou des actions.
Comment utiliser les tools MCP dans n8n ?+
Ajoute un nœud MCP Client Tool sous un AI Agent, renseigne l’URL complète du endpoint, par exemple https://mcp.example.com/mcp, puis configure l’authentification Bearer ou Header. N’expose à l’agent que les tools nécessaires. Teste chaque tool directement avant de laisser le modèle choisir quand l’appeler.
Sujets :mcpclauden8nnode.jsapiagents ia