À la mi-2026, MCP (Model Context Protocol) est la norme de fait pour connecter les LLM (grands modèles de langage) aux outils. Anthropic l’a introduit ; OpenAI, Google et l’ensemble de l’écosystème l’ont adopté. Cursor, Claude Desktop, ChatGPT et les agents personnalisés prennent tous en charge MCP.
Si vous souhaitez que des agents LLM interagissent avec votre service, il vous faut un serveur MCP. Après en avoir construit un ou deux, vous constaterez que le protocole lui-même est peu étendu. L’essentiel du travail d’ingénierie porte sur ce qui l’entoure : conception des schémas, gestion des erreurs, authentification, diffusion continue, performances et observabilité.
Cet article approfondit la création en TypeScript de serveurs MCP adaptés à la production. Il présente les schémas qui résistent à l’utilisation réelle par des agents, au-delà des seuls mécanismes du protocole.
MCP en bref
Le MCP est un protocole client-serveur où :
- Les serveurs exposent des outils, des ressources et des prompts.
- Les clients sont généralement des agents LLM qui les consomment.
Le protocole utilise JSON-RPC 2.0 (la spécification officielle est courte et mérite d’être lue). Les transports sont stdio pour les processus locaux et Streamable HTTP pour les serveurs distants. L’ancien transport HTTP+SSE a été rendu obsolète dans la révision de la spécification du 26 mars 2025 ; considérez donc comme historiques les tutoriels qui reposent sur celui-ci. L’authentification et la sécurité relèvent du protocole ; les principales implémentations prennent en charge OAuth, les clés API et des mécanismes similaires.
Le rôle du serveur consiste à exposer aux LLM des capacités utiles qu’ils peuvent découvrir et utiliser.
La structure de base
Avec le package officiel @modelcontextprotocol/sdk, un serveur minimal utilisant l’API de haut niveau McpServer se présente ainsi :
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-server",
version: "1.0.0",
});
server.registerTool(
"echo",
{
description: "Echo back the provided text.",
inputSchema: { text: z.string() },
},
async ({ text }) => ({
content: [{ type: "text", text }],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
(Le même SDK expose également la classe de bas niveau Server, ainsi que setRequestHandler pour ListToolsRequestSchema / CallToolRequestSchema, si vous souhaitez contrôler entièrement les gestionnaires de requêtes. Pour la plupart des serveurs, McpServer.registerTool reste toutefois plus concis et limite les risques d’erreur.)
C’est l’ossature. Le véritable travail porte sur ce que vous placez dans les gestionnaires d’outils et sur la manière de le faire.
Schéma 1 : philosophie de conception des outils
La première décision : quels outils exposez-vous, à quelle granularité ?
Une erreur courante consiste à exposer telle quelle l’API sous-jacente. Transformer 200 points de terminaison REST en 200 outils est une catastrophe : les modèles deviennent moins performants, les descriptions encombrent le contexte et le protocole se transforme en labyrinthe.
Il vaut mieux concevoir les outils en fonction de la manière dont les agents souhaitent les utiliser. Chaque outil remplit une fonction bien définie, accepte des entrées précises et renvoie des sorties précises.
Quelques principes :
Un concept par outil. N’utilisez pas un outil manage_customer qui exécute 12 opérations. Préférez search_customers, get_customer, update_customer_email et archive_customer, chacun dédié à une tâche.
Granularité appropriée. Si elle est trop fine, l’agent doit multiplier les appels ; si elle est trop large, il ne peut pas exécuter précisément l’action requise. Visez des opérations qu’une personne nommerait naturellement.
Verbes d’action. Utilisez search_documents, et non documents. Le nom des outils doit indiquer leur fonction.
Distinction entre lecture et écriture. Les outils de lecture sont plus sûrs ; ceux d’écriture produisent des effets secondaires. Distinguez-les dans leur nom (list_x et create_x) et traitez-les différemment, notamment en exigeant une confirmation explicite ou des clés d’idempotence.
Agrégation utile. Un outil get_customer_profile qui renvoie en un seul appel le client, ses commandes récentes et ses demandes d’assistance est souvent préférable à trois appels distincts. L’agent obtient tout le contexte en une fois.
Pour un serveur qui expose, par exemple, un système de service client, un ensemble raisonnable comprend entre 8 et 15 outils. Un nombre supérieur est généralement excessif.
Schéma 2 : conception des schémas
Chaque outil possède un schéma d’entrée — les paramètres que le LLM doit fournir — et un résultat. Les schémas ne servent pas seulement à la validation : ils participent à l’ingénierie des prompts.
Voici un exemple de schéma d’entrée avec Zod :
const searchCustomersSchema = z.object({
query: z.string().describe(
"Search term: name, email, or company. Be specific to avoid too many matches."
),
limit: z.number().int().min(1).max(50).default(10).describe(
"Maximum results to return. Default 10, max 50."
),
filters: z.object({
tier: z.enum(["free", "pro", "enterprise"]).optional().describe(
"Filter to specific customer tier"
),
status: z.enum(["active", "trial", "churned"]).optional().describe(
"Filter by customer status"
),
}).optional(),
});
Remarquez :
- Chaque champ possède une méthode
.describe(). Le LLM en lit la description. - Les énumérations sont explicites. Les chaînes libres sont restreintes là où possible.
- Les valeurs par défaut sont judicieuses.
- Les contraintes (min/max, longueur) sont explicites.
- La distinction entre champs facultatifs et obligatoires est claire.
Les descriptions sont essentielles. « Terme de recherche » est peu utile ; « Terme de recherche : nom, adresse e-mail ou entreprise. Soyez précis pour éviter un trop grand nombre de correspondances » fournit au LLM une indication exploitable.
Schéma 3 : forme des résultats
La sortie correspond aux données que le LLM voit et sur lesquelles il agit. Une sortie bien conçue améliore considérablement son comportement.
Sorties structurées.
type SearchResult = {
customers: Customer[];
total_matches: number;
truncated: boolean;
next_page_cursor?: string;
};
Avec contexte.
{
customers: [...],
total_matches: 47,
truncated: true,
next_page_cursor: "abc",
message: "Found 47 matches; showing first 10. Use next_page_cursor to get more."
}
Le champ message fournit des indications lisibles par une personne. Les LLM les exploitent.
Avec gestion des erreurs.
{
error: "ambiguous_query",
message: "Search term 'john' matched 247 customers. Please be more specific.",
suggestion: "Try including a company name or email domain.",
partial_results: [...] // top 3 by relevance, optional
}
L’erreur est structurée et lisible par une machine, mais comprend également un message et une suggestion que le LLM peut interpréter. Celui-ci peut ainsi s’adapter : demander une précision à l’utilisateur ou affiner la requête.
Taille appropriée.
Un outil qui renvoie 10 000 enregistrements est inutilisable. La fenêtre de contexte du LLM ne peut pas les contenir ; même si elle le pouvait, le modèle ne les exploiterait pas efficacement. Utilisez toujours la pagination, la troncature ou la synthèse. Renvoyez suffisamment de données pour permettre au LLM de prendre une décision, et non l’intégralité des données disponibles.
Schéma 4 : sémantique des erreurs
Les outils peuvent échouer. La manière dont ils signalent l’échec au LLM détermine si celui-ci reprend correctement ou aggrave l’erreur.
Catégories d’erreurs.
type ToolError =
| { type: "validation"; message: string; field?: string }
| { type: "auth"; message: string }
| { type: "not_found"; message: string; suggestion?: string }
| { type: "conflict"; message: string; resolution?: string }
| { type: "rate_limit"; message: string; retry_after_seconds: number }
| { type: "service_unavailable"; message: string; retryable: boolean }
| { type: "internal"; message: string; trace_id: string };
Chaque catégorie possède une sémantique distincte. Le LLM doit donc réagir différemment :
validation: corrigez l’entrée et réessayez.not_found: informez l’utilisateur, ou essayez une autre recherche.conflict: demandez une résolution.rate_limit: attendez et réessayez.service_unavailable: essayez une alternative ou informez l’utilisateur.internal: abandonnez et informez l’utilisateur.
Documenter ces comportements dans votre serveur permet au LLM de mieux réagir.
Formatage des erreurs.
Renvoyez les erreurs sous forme de données structurées, avec des messages clairs et exploitables :
{
error: {
type: "validation",
message: "The email address is not in a valid format.",
field: "email",
suggestion: "Provide a valid email address like 'name@example.com'."
}
}
Évitez :
{
error: "Invalid input"
}
Le premier permet au LLM de reprendre correctement ; le second le contraint à deviner.
Schéma 5 : authentification et autorisation
Les serveurs MCP de production nécessitent une authentification. Toute personne capable d’atteindre le serveur peut utiliser ses outils, ce qui pose presque toujours problème.
Authentification : qui appelle ?
Schémas courants :
- Clé API. Simple et courante, elle convient aux communications entre services. Émettez-en une par consommateur et renouvelez-la régulièrement.
- OAuth. Adapté aux systèmes multi-utilisateurs dans lesquels les utilisateurs finaux autorisent les agents. Plus complexe, il constitue néanmoins la solution appropriée dans de nombreux cas d’usage.
- mTLS. Adapté aux environnements hautement sécurisés, avec des certificats TLS mutuels pour les deux parties.
L’implémentation dépend du transport. Avec HTTP, authentifiez la requête avant qu’elle n’atteigne le gestionnaire MCP, dans votre middleware Express, Hono ou Fastify, puis associez l’appelant à la requête :
// Express-style middleware in front of the MCP HTTP endpoint.
app.use("/mcp", async (req, res, next) => {
const apiKey = req.header("x-api-key");
const caller = await authenticate(apiKey);
if (!caller) return res.status(401).send("Unauthorized");
(req as any).caller = caller;
next();
});
Ensuite, dans chaque gestionnaire d’outil, récupérez l’appelant depuis le contexte propre à l’appel (extra), et non depuis les en-têtes bruts. Avec stdio, il n’existe aucun en-tête HTTP ; l’authentification provient généralement de l’environnement du processus ou de fichiers de configuration.
Autorisation : que peut faire l’appelant ?
Une fois authentifié, quels outils l’appelant peut-il utiliser et sur quelles données ?
function authorize(caller: Caller, tool: string, params: any): boolean {
// Caller-level: can this caller use this tool at all?
if (!caller.tools.includes(tool)) return false;
// Data-level: is this caller authorized for this specific data?
if (params.tenant_id && params.tenant_id !== caller.tenant_id) return false;
return true;
}
Ne laissez pas le LLM prendre de décisions d’autorisation : il pourrait être trompé. L’autorisation incombe au serveur ; le LLM ne voit que les données qu’il est autorisé à consulter.
Dans les systèmes mutualisés, chaque appel d’outil est limité à un locataire. Celui-ci est déterminé par l’authentification, et non par les paramètres fournis par le LLM.
Schéma 6 : idempotence
Pour les opérations d’écriture, l’idempotence est importante. Le LLM peut réessayer ou appeler deux fois le même outil dans des contextes différents. Sans idempotence, des doublons apparaissent.
Clés d’idempotence.
L’outil accepte un paramètre idempotency_key. Le serveur vérifie s’il a déjà rencontré cette clé. Si c’est le cas, il renvoie le résultat mis en cache ; sinon, il exécute l’opération et met son résultat en cache.
async function createInvoice(params: {
amount: number;
customer_id: string;
idempotency_key: string;
}) {
const cached = await idempotencyStore.get(params.idempotency_key);
if (cached) return cached;
const invoice = await actuallyCreateInvoice(params);
await idempotencyStore.set(params.idempotency_key, invoice, { ttl: 86400 });
return invoice;
}
Signalez ce comportement au LLM dans la description de l’outil :
"For each unique invoice you create, generate a UUID and pass it as idempotency_key. If you need to retry the operation, use the same UUID to avoid duplicate creation."
Schéma 7 : diffusion continue
Pour les outils qui produisent des sorties volumineuses ou prennent du temps, la diffusion continue de la sortie améliore l’expérience utilisateur. MCP prend en charge les notifications de progression émises depuis un gestionnaire d’outil au moyen de l’argument extra propre à chaque appel :
server.registerTool(
"long_running_task",
{ description: "...", inputSchema: { ... } },
async (input, extra) => {
await extra.sendNotification({
method: "notifications/progress",
params: { progressToken: extra._meta?.progressToken, progress: 0, message: "Starting..." },
});
for (const step of steps) {
await doStep(step);
await extra.sendNotification({
method: "notifications/progress",
params: {
progressToken: extra._meta?.progressToken,
progress: step.index / steps.length,
message: step.name,
},
});
}
return { content: [{ type: "text", text: JSON.stringify({ result: finalResult }) }] };
}
);
Utilisez la diffusion continue pour :
- Les opérations longues (plus de 5 secondes).
- Les sorties volumineuses, afin que le LLM puisse commencer leur traitement avant qu’elles ne soient entièrement disponibles.
- Les opérations dont les résultats intermédiaires méritent d’être affichés.
N’utilisez pas la diffusion continue pour les opérations simples et rapides : elle ajoute de la complexité sans apporter de valeur.
Schéma 8 : mise en cache
De nombreux appels d’outils sollicitent les mêmes données de manière répétée. La mise en cache peut améliorer considérablement les performances et réduire la charge des systèmes dorsaux.
Cache local. Cache dans le processus, par exemple LRU, pour les données fréquemment consultées.
Cache distribué. Redis ou un système similaire pour partager le cache entre les instances du serveur.
Invalidation du cache. Lorsque les données changent, supprimez les entrées concernées. C’est la partie difficile.
Durée de vie (TTL). Les entrées en cache expirent après une durée définie. Adaptez-la au type de données : les profils clients peuvent rester en cache pendant des heures, tandis que les prix ne devraient y rester que quelques minutes.
Pour être utile, le cache suppose que les mêmes appels se répètent. C’est le cas de nombreux serveurs MCP : au cours d’une session, les agents font souvent référence plusieurs fois aux mêmes entités.
Voici un schéma :
async function getCustomerCached(id: string) {
const cached = await cache.get(`customer:${id}`);
if (cached) {
metrics.increment("cache.hit");
return cached;
}
metrics.increment("cache.miss");
const customer = await db.getCustomer(id);
await cache.set(`customer:${id}`, customer, { ttl: 300 });
return customer;
}
Schéma 9 : limitation du débit
Les agents LLM peuvent se montrer étonnamment agressifs : ils peuvent exécuter des boucles, réessayer des appels ou les paralléliser massivement. Un agent défaillant peut provoquer un déni de service sur votre système dorsal.
La limitation du débit par appelant est essentielle :
const limiter = new RateLimiter({
windowMs: 60_000,
max: 100 // 100 calls/minute per caller
});
server.setRequestHandler(CallToolRequestSchema, async (request, context) => {
if (await limiter.exceeded(context.caller.id)) {
return errorResponse("rate_limit", "Too many requests");
}
// ...
});
Au-delà des limites globales, les limites propres à chaque outil sont importantes : certains outils sont coûteux et doivent être strictement limités.
Pour les opérations lourdes de conséquences, comme la création d’enregistrements ou l’envoi de messages, appliquez des limites plus strictes ou exigez un processus de confirmation explicite.
Schéma 10 : ressources
MCP propose des « ressources » : des sources de données en lecture seule que le LLM peut parcourir et référencer. Elles se distinguent des outils, que le LLM appelle activement.
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "doc://my-server/handbook",
name: "Employee Handbook",
mimeType: "text/markdown",
description: "Company employee handbook"
},
// ...
]
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const content = await loadResource(request.params.uri);
return { contents: [{ uri: request.params.uri, mimeType: "text/markdown", text: content }] };
});
Les ressources sont utiles pour :
- Des documents de référence que le LLM pourrait vouloir parcourir.
- Des données de configuration ou de contexte.
- Des tables de correspondance ou des schémas dont le LLM pourrait avoir besoin.
Les ressources sont en lecture seule ; les outils exécutent des actions. Utilisez le concept approprié dans chaque cas.
Schéma 11 : observabilité
Appliquez les mêmes schémas que dans les autres domaines de l’IA en production. Pour votre serveur MCP, instrumentez :
- Chaque appel d’outil : horodatage, appelant, outil, paramètres, résultat, latence et statut.
- Métriques par outil : volume d’appels, latence p50/p95, taux d’erreur.
- Métriques par appelant : identité de l’appelant et fréquence des appels.
- Contexte de traçage : propagez les identifiants de trace de l’appelant jusqu’aux appels aux systèmes dorsaux.
Journaux structurés :
logger.info("tool_call", {
tool: request.params.name,
caller_id: context.caller.id,
trace_id: context.trace_id,
params: redactPII(request.params.arguments),
duration_ms: duration,
status: "success"
});
Transmettez-les à votre plateforme d’observabilité.
Schéma 12 : gestion des versions
Votre serveur MCP évoluera. Les outils changeront, de nouveaux outils seront ajoutés et les anciens deviendront obsolètes.
Gestion des versions du serveur. Le constructeur Server accepte une version. Incrémentez-la lors des changements. Les clients peuvent la détecter.
Gestion des versions des outils. Lorsque la signature d’un outil change de manière incompatible, créez-en une nouvelle version, par exemple search_customers_v2. Conservez l’ancienne version pendant une période d’obsolescence.
Évolution des schémas. Ajoutez des champs facultatifs sans compromettre la compatibilité. La suppression d’un champ ou la modification de son type constitue une rupture de compatibilité.
Obsolescence. Lorsqu’un outil devient obsolète, indiquez-le dans sa description : « DEPRECATED: use search_customers_v2 instead. »
Pour les serveurs MCP de production utilisés par plusieurs clients, la gestion des versions est essentielle. Les serveurs réservés à un usage interne peuvent être plus souples.
Schéma 13 : tests
Comment tester un serveur MCP ?
Tests unitaires. Testez la logique de chaque outil avec des dépendances simulées, au moyen d’outils de test TypeScript classiques.
Tests des schémas. Vérifiez que les schémas valident les données comme prévu et gèrent correctement les cas limites, tels que les champs manquants et les types incorrects.
Tests d’intégration. Démarrez le serveur, envoyez de véritables requêtes MCP et vérifiez les réponses. Le package @modelcontextprotocol/sdk comprend des utilitaires de test.
Tests de bout en bout avec un véritable LLM. Ce sont les tests les plus difficiles, mais aussi les plus précieux. Demandez à un LLM d’utiliser votre serveur MCP pour réaliser des tâches réalistes. Vérifiez qu’il emploie correctement les outils et repérez les problèmes dans leurs descriptions.
Voici un exemple de configuration de test de bout en bout (pseudo-code ; l’intégration exacte dépend du client LLM utilisé : le SDK TypeScript d’Anthropic, celui d’OpenAI ou un framework compatible avec MCP) :
// Start your MCP server as a child process or in-memory transport.
const server = await startTestServer();
// Drive an LLM with the MCP tools attached. The exact API depends on the client.
const result = await runAgent({
mcpServer: server,
systemPrompt: "You are a customer service agent...",
userMessage: "Find the customer Alice and check her open tickets",
});
// Inspect the tool calls captured by the server during the run.
expect(server.callLog.map((c) => c.name)).toEqual([
"search_customers",
"list_tickets",
]);
Les tests de bout en bout détectent dans les descriptions des outils des problèmes que les tests unitaires ne peuvent pas révéler.
Schéma 14 : déploiement
Où se trouve votre serveur MCP ?
Stdio (local). Le serveur s’exécute sous forme de processus et le client l’invoque. Ce transport convient particulièrement aux applications de bureau (Claude Desktop, Cursor) et aux outils locaux.
Streamable HTTP (distant). Le serveur est un service réseau. Ce transport convient particulièrement aux services hébergés, aux infrastructures partagées et à l’accès de plusieurs clients.
Pour les serveurs de production :
- Streamable HTTP est généralement le choix approprié.
- Déployez-le comme tout autre service web : conteneurs, équilibrage de charge et mise à l’échelle automatique.
- TLS est requis.
- Prévoyez des vérifications d’état pour la plateforme de déploiement.
- Arrêt progressif pour laisser les requêtes en cours se terminer.
Schéma 15 : considérations de sécurité
Les serveurs MCP exposent des capacités aux LLM, lesquels peuvent être manipulés. Il en découle plusieurs implications de sécurité :
Injection de prompt par les entrées des outils. Une demande utilisateur peut contenir du texte visant à inciter le LLM à appeler des outils de manière nuisible. Mesures de défense :
- Des descriptions d’outils qui précisent clairement l’usage prévu.
- Des vérifications d’autorisation côté serveur, indépendantes des paramètres choisis par le LLM.
- Des confirmations pour les actions lourdes de conséquences.
Exfiltration de données. Les outils qui renvoient des données peuvent être détournés : le LLM pourrait être incité à restituer abusivement des données sensibles. Mesures de défense :
- Vérifications d’autorisation.
- Journalisation des données consultées et de l’identité de la personne qui les consulte.
- Détection des schémas d’accès inhabituels.
Épuisement des ressources. Les outils qui consomment les ressources des systèmes dorsaux peuvent être détournés. Mesures de défense :
- Limitation de débit.
- Limites de ressources par appel d’outil.
- Disjoncteurs lorsque le système dorsal est dégradé.
Injection dans les sorties des outils. La sortie d’un outil peut contenir du texte qui manipule le LLM lorsqu’il le lit. Mesures de défense :
- Nettoyage des sorties là où possible.
- Prudence avec les outils qui renvoient du contenu généré par les utilisateurs.
Il s’agit de véritables surfaces d’attaque. Traitez les serveurs MCP comme toute API de production et appliquez une défense en profondeur.
Un exemple complet : un petit mais réel serveur MCP
Pour réunir ces éléments, voici un serveur qui expose un petit CRM :
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";
import { db, cache, logger, authenticate } from "./infra.js";
const server = new McpServer({
name: "crm-server",
version: "1.0.0",
});
// === Tool: search_customers ===
server.registerTool(
"search_customers",
{
description: "Search customers by name, email, or company.",
inputSchema: {
query: z.string().describe("Name, email, or company"),
limit: z.number().int().min(1).max(50).default(10),
},
},
async ({ query, limit }, extra) => {
const auth = await authenticate(extra);
const cacheKey = `search:${auth.tenant_id}:${query}:${limit}`;
const cached = await cache.get(cacheKey);
if (cached) return cached;
const customers = await db.searchCustomers({
tenant_id: auth.tenant_id,
query,
limit,
});
const result = {
content: [{
type: "text" as const,
text: JSON.stringify({
customers,
total_matches: customers.length,
truncated: customers.length === limit,
message:
customers.length === limit
? `Showing first ${limit}; there may be more matches.`
: `Found ${customers.length} customer(s).`,
}),
}],
};
await cache.set(cacheKey, result, { ttl: 60 });
logger.info("search_customers", { tenant: auth.tenant_id, query, results: customers.length });
return result;
}
);
// === Tool: get_customer ===
server.registerTool(
"get_customer",
{
description: "Fetch a single customer by id.",
inputSchema: { customer_id: z.string() },
},
async ({ customer_id }, extra) => {
const auth = await authenticate(extra);
const customer = await db.getCustomer(auth.tenant_id, customer_id);
if (!customer) {
return {
isError: true,
content: [{
type: "text" as const,
text: `Customer ${customer_id} not found. Use search_customers to find by name or email.`,
}],
};
}
return { content: [{ type: "text" as const, text: JSON.stringify({ customer }) }] };
}
);
// === Tool: update_customer_email (with idempotency) ===
server.registerTool(
"update_customer_email",
{
description: "Update a customer's email; pass the same idempotency_key on retry.",
inputSchema: {
customer_id: z.string(),
new_email: z.string().email(),
idempotency_key: z
.string()
.describe("UUID for this update; pass the same value on retry to prevent duplicates"),
},
},
async (params, extra) => {
const auth = await authenticate(extra);
// ... idempotency check, validation, update
return { content: [{ type: "text" as const, text: "ok" }] };
}
);
// ... more tools ...
// Wire up a remote transport (Streamable HTTP) on a chosen port via your HTTP server of choice.
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID() });
await server.connect(transport);
Cette structure constitue un point de départ. Ajoutez l’observabilité, la limitation du débit, davantage d’outils et des schémas plus soigneusement élaborés : les fondations sont en place.
Ce qui distingue les serveurs de production des démos
MCP est un protocole peu étendu ; construire un serveur de production représente un véritable travail d’ingénierie. Le bénéfice : votre service devient utilisable par tout agent LLM grâce à une intégration standardisée qui n’exige aucun couplage propre à chaque modèle.
Les schémas essentiels sont les suivants : conception ciblée des outils, schémas tenant compte des prompts, sémantique structurée des erreurs, authentification robuste, idempotence, observabilité et sécurité. En négliger un seul suffit à créer un serveur MCP qui échouera en production.
Intégrez-les dès la conception. Testez le serveur avec de véritables LLM. Améliorez les descriptions des outils de manière itérative. Vous obtiendrez un service qu’un LLM peut utiliser avec autant d’aisance qu’une personne et qui s’adapte à l’écosystème en pleine expansion des agents d’IA.



