IA & agents

Skills et MCP

Étendre un agent : skills, serveurs MCP, outils et ressources.

Vérifié en septembre 2026 · .NET 10, Angular 22.1 · environ 17 min

Un agent ne sait faire que ce que son hôte lui présente : des messages, et la liste des outils qu'il a le droit de demander. Le cours Agents de ce topic décrit la boucle qui exécute ces demandes ; celui-ci traite de ce qu'on branche dessus. Deux mécanismes se partagent le travail. Un serveur MCP est un programme séparé qui expose des outils, des ressources et des prompts à travers un protocole standard, si bien qu'un même serveur sert tout hôte qui parle ce protocole. Une skill est un dossier d'instructions et de fichiers que l'agent lit lui-même quand la tâche le demande, sans protocole ni processus. Le premier ajoute des capacités, la seconde un savoir-faire ; les deux ajoutent du texte au contexte du modèle, et un serveur local ou un script de skill s'exécute avec les droits de l'utilisateur.

Hôte, client, serveur

MCP, le Model Context Protocol, distingue trois rôles. L'hôte est l'application que l'utilisateur a devant lui — un éditeur, un assistant en terminal, une application de conversation : il tient la conversation, parle au modèle et décide de ce qui est permis. Pour chaque serveur configuré, il crée un client, et chaque client parle à exactement un serveur. Le serveur expose des capacités : c'est un processus local que l'hôte lance, ou un service distant.

Le modèle ne parle jamais à un serveur. Quand il demande un outil, c'est l'hôte qui reçoit la demande, la confie au client concerné et remet le résultat dans la conversation. La spécification en fait un principe : un serveur ne lit pas la conversation entière, ne voit pas les autres serveurs et ne reçoit que ce dont il a besoin.

Un serveur propose trois primitives, qui se distinguent par qui décide de s'en servir.

PrimitiveQui la déclencheExemple
Toolsle modèle, pendant la bouclechercher des commandes, lancer un build
Resourcesl'hôte ou l'utilisateur, qui les joint au contextele contenu d'un fichier, le schéma d'une base
Promptsl'utilisateur, souvent par une commande à barre oblique« résumer ce client »

Côté hôte, la liste des serveurs est une configuration. Le dépôt de ce site en contient une, lue par Claude Code : un serveur distant joint en HTTP, et un serveur local que l'hôte lance en exécutant une commande. Le fichier n'est pas normalisé par le protocole : VS Code lit .vscode/mcp.json avec une clé servers, Claude Code et d'autres lisent .mcp.json avec mcpServers ; chaque entrée décrit un transport et de quoi l'ouvrir.

{
  "mcpServers": {
    "microsoft-learn": {
      "type": "http",
      "url": "https://learn.microsoft.com/api/mcp"
    },
    "angular": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "@angular/cli@latest",
        "mcp"
      ],
      "env": {}
    }
  }
}

Un serveur MCP en C#

Le SDK officiel pour .NET est le paquet NuGet ModelContextProtocol, en version 2.2.0 en septembre 2026, bâti sur l'hôte générique de Microsoft.Extensions.Hosting. Un serveur est un programme console qui enregistre MCP dans le conteneur, choisit un transport et déclare des classes marquées d'attributs. Celui-ci expose les trois primitives autour d'un dépôt de commandes en mémoire.

// Paquets : ModelContextProtocol 2.2.0 et Microsoft.Extensions.Hosting 10.0.
using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol;
using ModelContextProtocol.Server;

var builder = Host.CreateApplicationBuilder(args);

// En stdio, stdout EST le canal du protocole : tous les journaux partent sur
// stderr, que le client peut afficher ou ignorer.
builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);

builder.Services.AddSingleton<DepotCommandes>();

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<OutilsCommandes>()
    .WithResources<RessourcesCommandes>()
    .WithPrompts<PromptsCommandes>();

await builder.Build().RunAsync();

// Un outil : une fonction que le MODELE decide d'appeler. Le nom, la
// description et le schema d'entree publies par tools/list sont deduits de
// cette signature ; la description est tout ce que le modele saura de l'outil.
[McpServerToolType]
public sealed class OutilsCommandes(DepotCommandes depot)
{
    [McpServerTool(Name = "chercher_commandes", ReadOnly = true)]
    [Description("Liste les commandes du client, les plus recentes en premier.")]
    public IReadOnlyList<Commande> Chercher(
        [Description("Identifiant du client, par exemple C-042")] string client,
        [Description("Nombre maximal de commandes rendues")] int limite = 5)
    {
        // Le message d'une McpException parvient au modele, qui peut corriger
        // son appel. Celui de toute autre exception est remplace par
        // « An error occurred invoking 'chercher_commandes'. »
        if (!depot.Connait(client))
            throw new McpException($"Client inconnu : {client}. Format attendu : C-000.");
        return depot.ParClient(client).Take(limite).ToList();
    }
}

// Une ressource : une donnee identifiee par une URI, que l'HOTE (ou
// l'utilisateur) choisit de joindre au contexte. Le modele ne l'appelle pas.
[McpServerResourceType]
public sealed class RessourcesCommandes
{
    [McpServerResource(UriTemplate = "commandes://statuts", Name = "statuts", MimeType = "text/plain")]
    [Description("Les statuts possibles des commandes et leur sens.")]
    public static string Statuts() =>
        "ouverte : payee, pas encore expediee\nexpediee : remise au transporteur\nannulee : remboursee";
}

// Un prompt : un template de messages que l'UTILISATEUR declenche, souvent par
// une commande a barre oblique dans l'hote.
[McpServerPromptType]
public sealed class PromptsCommandes
{
    [McpServerPrompt(Name = "resumer_client")]
    [Description("Prepare une demande de resume pour un client.")]
    public static string Resumer([Description("Identifiant du client")] string client) =>
        $"Resume l'activite du client {client} a partir de ses commandes, en trois lignes.";
}

public sealed record Commande(string Numero, string Client, decimal Montant, string Statut);

public sealed class DepotCommandes
{
    private static readonly Commande[] Table =
    [
        new("CMD-1043", "C-042", 129.90m, "expediee"),
        new("CMD-1017", "C-042", 45.00m, "annulee"),
        new("CMD-1002", "C-007", 18.50m, "ouverte"),
    ];

    public bool Connait(string client) => Table.Any(c => c.Client == client);

    public IEnumerable<Commande> ParClient(string client) =>
        Table.Where(c => c.Client == client).OrderByDescending(c => c.Numero);
}

Le SDK lit la signature de la méthode pour produire la définition de l'outil. Les [Description] deviennent la description de l'outil et de chaque paramètre, les types deviennent un schéma JSON, et un paramètre doté d'une valeur par défaut n'est pas requis : limite sort avec "default": 5, hors de required. La valeur rendue est sérialisée en JSON dans un bloc de texte. Les dépendances du constructeur viennent du conteneur, comme pour un contrôleur ASP.NET Core.

La description n'est pas un commentaire pour développeurs (cours Agents) : c'est le seul texte que le modèle lira pour décider d'appeler l'outil, et avec quels arguments. Un paramètre dont le format n'est pas donné produit des appels mal formés. ReadOnly = true publie l'annotation readOnlyHint : un indice que l'hôte peut utiliser pour alléger la confirmation quand l'utilisateur l'a permis, pas une garantie, et la spécification demande de tenir les annotations pour non fiables quand le serveur ne l'est pas.

Le fil : JSON-RPC sur stdio ou HTTP

Tous les messages MCP sont du JSON-RPC 2.0. Une requête porte un id, une method et des params ; la réponse reprend le même id et porte soit result, soit error ; une notification n'a pas d'id et n'attend rien. C'est l'id, et non l'ordre d'arrivée, qui relie une réponse à sa requête : un serveur peut traiter plusieurs requêtes à la fois. Ce format voyage sur deux transports standard.

En stdio, l'hôte lance le serveur comme sous-processus, lui écrit sur son entrée standard et lit sa sortie standard. Chaque message tient sur une ligne, sans saut de ligne à l'intérieur. C'est le transport des serveurs locaux, sans réseau ni authentification propre au protocole : le processus hérite des droits de l'utilisateur qui a lancé l'hôte, et ses identifiants lui viennent de l'environnement. En Streamable HTTP, le serveur est un service indépendant qui expose un seul point de terminaison ; chaque message est un POST, et la réponse est soit un objet JSON, soit un flux Server-Sent Events qui porte des notifications de progression puis la réponse finale. Chaque POST recopie aussi une partie du corps dans des en-têtes — MCP-Protocol-Version, Mcp-Method, Mcp-Name — pour qu'une passerelle puisse router sans lire le JSON. Un serveur HTTP doit valider l'en-tête Origin, faute de quoi une page web peut l'atteindre par rebond DNS ; un serveur local ne devrait écouter que sur 127.0.0.1, pour rester hors de portée du réseau local.

stdio a une règle qui se paie cher : la sortie standard n'appartient qu'au protocole. La spécification interdit au serveur d'y écrire autre chose qu'un message MCP et lui laisse stderr pour ses journaux. Une trace de débogage oubliée dans un outil suffit à la violer.

using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;

var builder = Host.CreateApplicationBuilder(args);

// Les journaux partent bien sur stderr...
builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<Horloge>();

await builder.Build().RunAsync();

[McpServerToolType]
public sealed class Horloge
{
    [McpServerTool(Name = "heure_utc"), Description("Rend l'heure UTC courante.")]
    public static string HeureUtc()
    {
        // ...mais cette trace de debogage, elle, part sur stdout.
        Console.Write("heure_utc appele... ");
        return DateTime.UtcNow.ToString("O");
    }
}

// Ce que le client lit sur stdout, en une seule ligne :
// heure_utc appele... {"result":{"content":[{"type":"text","text":"2026-09-25T17:46:12.6407920Z"}],...},"id":1,"jsonrpc":"2.0"}
// La ligne n'est pas du JSON : la reponse est perdue, et le client attend
// toujours l'id 1.

La trace n'a pas de fin de ligne : elle se colle devant la réponse, et le client lit une ligne qui n'est pas du JSON. La réponse est perdue sans erreur côté serveur, et le client attend jusqu'à son délai d'expiration. Un Console.WriteLine, qui laisse la réponse sur sa propre ligne, est moins brutal mais pas plus correct : il ne marche que si le client ignore les lignes parasites, ce que chaque SDK décide à sa façon. Le journal console par défaut de l'hôte générique fait la même faute, puisqu'il écrit sur stdout tant qu'on ne lui dit pas le contraire. La forme juste envoie tout sur stderr.

using System.ComponentModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;

var builder = Host.CreateApplicationBuilder(args);

// Tous les niveaux vont sur stderr : stdout ne porte plus que le protocole.
builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<Horloge>();

await builder.Build().RunAsync();

// Une trace passe par le journal, jamais par Console.
[McpServerToolType]
public sealed class Horloge(ILogger<Horloge> journal)
{
    [McpServerTool(Name = "heure_utc"), Description("Rend l'heure UTC courante.")]
    public string HeureUtc()
    {
        journal.LogInformation("heure_utc appele");
        return DateTime.UtcNow.ToString("O");
    }
}

// stdout : {"result":{"content":[{"type":"text","text":"2026-09-25T17:46:27.8691218Z"}],...},"id":1,"jsonrpc":"2.0"}
// stderr : info: Horloge[0]
//                heure_utc appele

Découvrir, lister, appeler

La révision 2026-07-28 de la spécification, en vigueur en septembre 2026, a rendu le protocole sans état. Il n'y a plus de poignée de main : chaque requête porte dans params._meta la version du protocole, les capacités du client et son identité, et le serveur accepte ou refuse chaque requête isolément. Tout serveur implémente server/discover, qui rend ses versions, ses capacités et son nom. Voici l'échange réel avec le serveur précédent, réindenté.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "server/discover",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "sonde",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}
{
  "result": {
    "supportedVersions": ["2026-07-28"],
    "capabilities": {
      "logging": {},
      "prompts": { "listChanged": true },
      "resources": { "listChanged": true },
      "tools": { "listChanged": true }
    },
    "ttlMs": 0,
    "cacheScope": "private",
    "resultType": "complete",
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "ServeurMcp",
        "version": "1.0.0.0"
      }
    }
  },
  "id": 1,
  "jsonrpc": "2.0"
}

Le client n'est pas tenu de passer par là : il peut lister directement, dans le même format.

{
  "result": {
    "tools": [
      {
        "name": "chercher_commandes",
        "description": "Liste les commandes du client, les plus recentes en premier.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "client": {
              "description": "Identifiant du client, par exemple C-042",
              "type": "string"
            },
            "limite": {
              "description": "Nombre maximal de commandes rendues",
              "type": "integer",
              "default": 5
            }
          },
          "required": ["client"]
        },
        "annotations": {
          "readOnlyHint": true
        }
      }
    ],
    "ttlMs": 0,
    "cacheScope": "private",
    "resultType": "complete",
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "ServeurMcp",
        "version": "1.0.0.0"
      }
    }
  },
  "id": 2,
  "jsonrpc": "2.0"
}

La signature C# y est devenue un schéma. Deux champs que la révision impose aux listes règlent leur mise en cache : ttlMs dit combien de temps la réponse reste fraîche, 0 la déclarant périmée aussitôt, et cacheScope dit qui peut la conserver — public autorise un cache partagé entre utilisateurs, private le limite au même contexte d'autorisation. L'hôte transmet name, description et inputSchema au modèle comme définition d'outil, comme pour un outil déclaré directement dans une API de conversation. Quand le modèle le demande, l'hôte l'appelle.

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {
        "name": "sonde",
        "version": "1.0.0"
      },
      "io.modelcontextprotocol/clientCapabilities": {}
    },
    "name": "chercher_commandes",
    "arguments": {
      "client": "C-042",
      "limite": 1
    }
  }
}
{
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[{\"numero\":\"CMD-1043\",\"client\":\"C-042\",\"montant\":129.90,\"statut\":\"expediee\"}]"
      }
    ],
    "resultType": "complete",
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "ServeurMcp",
        "version": "1.0.0.0"
      }
    }
  },
  "id": 3,
  "jsonrpc": "2.0"
}

Le protocole distingue deux sortes d'échec, et la distinction sert au modèle. Un outil inconnu ou une requête mal formée donne une erreur JSON-RPC : le serveur répond -32602, Unknown tool: 'inconnu'. Un outil qui échoue en s'exécutant rend un résultat ordinaire marqué "isError": true, dont l'hôte remet le texte au modèle pour qu'il corrige son appel. Le SDK C# ne transmet que le message d'une McpException : avec C-999, le modèle lit An error occurred invoking 'chercher_commandes': Client inconnu : C-999. Format attendu : C-000. Toute autre exception est réduite au début de cette phrase, sans son message — c'est ce que rend un appel sans client —, si bien qu'une chaîne de connexion glissée dans ce message ne quitte pas le serveur. Une erreur qui aide le modèle se lève donc exprès.

Ces règles sont récentes. Jusqu'à la révision 2025-11-25, une session s'ouvrait par une requête initialize, qui négociait version et capacités, puis la notification notifications/initialized, avant tout tools/list. En septembre 2026, les SDK officiels parlent les deux générations, mais pas avec les mêmes réglages. Le serveur C# répond aussi à un initialize envoyé à la main, avec "protocolVersion": "2025-11-25". Le SDK TypeScript v2, les paquets @modelcontextprotocol/client et @modelcontextprotocol/server, stable depuis fin juillet 2026, implémente la nouvelle révision, mais son client reste sur l'ancienne tant qu'on ne lui demande pas versionNegotiation: { mode: 'auto' } ; l'ancien paquet @modelcontextprotocol/sdk 1.x ne connaît que 2025-11-25. En stdio, un client qui veut joindre les deux générations commence par server/discover et se replie sur initialize si la réponse n'est pas moderne ; en HTTP, il tente une requête moderne et inspecte le corps d'un éventuel 400.

Les skills : des instructions chargées à la demande

Une skill est un dossier qui contient un fichier SKILL.md et, au besoin, des scripts, des références et des modèles. Le format a été conçu par Anthropic puis publié comme standard ouvert sur agentskills.io ; Codex, Gemini CLI, Cursor et GitHub Copilot, entre autres, lisent le même fichier. Il commence par un en-tête YAML à deux champs obligatoires, suivi d'un corps en Markdown : name, en minuscules, chiffres et tirets, 64 caractères au plus et, selon la spécification ouverte, égal au nom du dossier ; description, 1024 caractères au plus.

migrations-ef/SKILL.md
---
name: migrations-ef
description: Ajoute et relit des migrations Entity Framework Core selon les conventions de l'équipe. À utiliser quand on demande de créer, renommer ou relire une migration, ou quand une entité ou un DbContext change.
---

# Migrations Entity Framework Core

## Ajouter une migration

1. Nommer la migration d'après l'intention, au présent : `AjouterDateLivraison`,
   jamais `Migration12`.
2. Lancer `dotnet ef migrations add <Nom> --project src/Infrastructure`.
3. Passer le fichier généré à `scripts/operations-destructives.sh` et recopier
   sa sortie dans la réponse. Un code de sortie non nul signifie que le
   fichier n'a pas été lu : ne rien conclure.

## Si le script signale une opération

- `RenameColumn` ou `RenameTable` : lire `references/CONVENTIONS.md` avant
  toute modification ; un renommage se fait en deux migrations.
- `AlterColumn` : vérifier que le nouveau type ne tronque aucune valeur
  existante, et le dire dans la réponse.
- `DropColumn` ou `DropTable` : ne rien appliquer, demander confirmation.

Ne jamais lancer `dotnet ef database update` : l'application des migrations
passe par le pipeline.
$ find migrations-ef -type f | sort
migrations-ef/SKILL.md
migrations-ef/references/CONVENTIONS.md
migrations-ef/scripts/operations-destructives.sh

$ cat migrations-ef/scripts/operations-destructives.sh
#!/usr/bin/env bash
# Liste les operations qui detruisent ou renomment des donnees dans une migration.
# Usage : scripts/operations-destructives.sh <fichier de migration .cs>
set -euo pipefail
[ -f "$1" ] || { echo "Fichier introuvable : $1" >&2; exit 2; }
grep -nE 'migrationBuilder\.(DropTable|DropColumn|RenameColumn|RenameTable|AlterColumn)\(' "$1" \
  || echo "Aucune operation destructive."

$ migrations-ef/scripts/operations-destructives.sh Migrations/20260925_RenommerNom.cs
5:        migrationBuilder.RenameColumn(name: "Nom", table: "Clients", newName: "RaisonSociale");
6:        migrationBuilder.DropColumn(name: "Fax", table: "Clients");

$ migrations-ef/scripts/operations-destructives.sh Migrations/absente.cs; echo "code $?"
Fichier introuvable : Migrations/absente.cs
code 2

Le mécanisme tient dans le chargement progressif. Au démarrage, l'agent ne reçoit que le nom et la description de chaque skill installée, une centaine de jetons chacune selon la documentation d'Anthropic. Quand une demande correspond à une description, il lit le corps de SKILL.md avec ses propres outils de lecture, et c'est alors seulement que ces instructions entrent dans le contexte. Les fichiers annexes ne coûtent rien tant qu'ils ne sont pas lus, et un script exécuté n'apporte que sa sortie. Encore faut-il que chaque description dise ce que fait la skill et quand s'en servir : c'est le seul texte sur lequel le modèle décide de l'ouvrir.

Au-delà de ces deux champs, l'en-tête varie selon l'hôte. Claude Code accepte par exemple disable-model-invocation, qui réserve la skill à une invocation explicite par l'utilisateur — le bon réglage pour une skill qui déploie —, et allowed-tools, que la spécification ouverte marque expérimental.

Skill ou outil MCP

SkillOutil MCP
Naturedu texte et des fichiersune fonction dans un autre processus
Qui l'exécutel'agent, avec ses propres outilsle serveur, appelé par l'hôte
Contratune description en langue naturelleun nom et un schéma JSON d'entrée
Portéeles hôtes qui savent lire le dossiertout hôte qui parle MCP
Coût en contextela description, puis le corps une fois chargéla définition, dès qu'elle est présentée au modèle

Une skill convient à une manière de faire — des conventions, une procédure, un script de vérification — qui s'appuie sur des outils que l'agent a déjà. Un serveur MCP convient à un accès que l'agent n'a pas : une API interne, une base, un service dont on ne veut pas confier les identifiants à un shell. Les deux se combinent souvent, la skill disant quand et comment appeler les outils d'un serveur. Côté contexte, chaque outil présenté au modèle occupe de la place à chaque tour, qu'il serve ou non ; certains hôtes n'en présentent qu'un index et chargent la définition à la demande, comme la recherche d'outils de Claude Code.

Un serveur MCP est du code tiers

Un serveur stdio est un programme que l'hôte lance avec les droits de l'utilisateur : il lit ses fichiers et ses variables d'environnement, il a son réseau. Ajouter une entrée à .mcp.json revient à installer un logiciel, et une skill qui contient des scripts aussi ; la documentation d'Anthropic demande de n'installer que des skills de source sûre et de les auditer entièrement, scripts compris. C'est pourquoi Claude Code demande une approbation avant d'utiliser les serveurs du .mcp.json d'un dépôt, et VS Code une décision de confiance au premier démarrage d'un serveur.

Le code n'est pas le seul vecteur. Tout ce qu'un serveur renvoie — descriptions d'outils, résultats, contenus de ressources — entre dans le contexte comme du texte que le modèle lit, et peut contenir des instructions : c'est l'injection indirecte, que traite le cours Agents. La liste d'outils peut changer en cours de route, et les annotations ne sont que déclaratives. La spécification pose donc en principe que l'hôte obtient le consentement de l'utilisateur avant d'invoquer un outil, et lui recommande d'en montrer les arguments avant l'appel.

Dans la configuration, deux fautes reviennent. La première est le jeton écrit dans un fichier versionné. La seconde est plus discrète, et le fichier du dépôt la commet en septembre 2026 : npx -y @angular/cli@latest résout à chaque démarrage la dernière version publiée du paquet et l'exécute sans rien demander — la 22.2.0, publiée le 23 septembre 2026 avec Angular 22.2, alors que le projet utilise la 22.1.8. Le code lancé change sans que personne l'ait relu, et il écrit : sans option, ce serveur expose run_target et devserver_start, qui exécutent des cibles du projet.

{
  "mcpServers": {
    "tickets": {
      "type": "http",
      "url": "https://tickets.exemple.fr/mcp",
      "headers": {
        "Authorization": "Bearer tk_live_9f2c41d07ab8e6"
      }
    },
    "angular": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@angular/cli@latest", "mcp"]
    }
  }
}

La forme juste épingle une version, restreint le serveur à ce dont on a besoin — ng mcp --read-only n'enregistre que les outils en lecture, six sur neuf — et lit le jeton dans une variable d'environnement. La syntaxe ${VAR} est celle de Claude Code ; VS Code a ses propres variables d'entrée, sur le même principe.

{
  "mcpServers": {
    "tickets": {
      "type": "http",
      "url": "https://tickets.exemple.fr/mcp",
      "headers": {
        "Authorization": "Bearer ${TICKETS_JETON}"
      }
    },
    "angular": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@angular/[email protected]", "mcp", "--read-only"]
    }
  }
}

--read-only reste une promesse du code qu'on lance : il ne dispense pas l'hôte de demander confirmation. Le jeton, lui, doit porter les droits minimaux, un compte en lecture seule pour une base par exemple, car tout ce que le serveur peut faire avec, un modèle abusé par une injection peut le lui demander.

Ce cours vous a servi ? Offrir un café Signaler une erreur