.NETC#MCPDevSecOpsAI Agents

Ajoutez des politiques, un contrôle d'identité et des garde-fous CI à un serveur MCP C# pour limiter les outils exposés et tracer les décisions.

Yva Hajatiana
13 août 2026
10 min de lecture
Partager :X / TwitterLinkedIn
Composition éditoriale abstraite sur la sécurité et l'automatisation logicielle

Sécuriser un serveur MCP C# avec des politiques DevOps

Un serveur Model Context Protocol (MCP) met des outils à la disposition d'un agent. Cette capacité est utile pour consulter un build, rechercher une documentation interne ou déclencher un déploiement. Elle devient risquée si une nouvelle méthode est exposée sans revue, si tous les agents possèdent les mêmes droits ou si la réponse d'un outil contient elle-même des instructions hostiles.

Depuis mai 2026, l'extension Microsoft.AgentGovernance.Extensions.ModelContextProtocol ajoute une couche de gouvernance au SDK MCP C#. Elle applique une politique avant l'exécution d'un outil, analyse les définitions au démarrage et assainit les réponses textuelles. L'objectif de ce tutoriel est de construire un petit serveur MCP gouverné, puis d'en faire une règle vérifiable par la CI.

Ce n'est pas un mécanisme de conformité automatique. Une politique technique ne remplace ni l'authentification, ni la revue des privilèges, ni la validation des actions de production. Elle rend toutefois les décisions d'accès explicites, versionnées et testables.

Ce que nous allons protéger

Notre serveur de démonstration n'expose qu'un outil echo. Ce choix volontairement simple permet de vérifier la chaîne complète sans donner à un exemple de blog le pouvoir de modifier une infrastructure.

Le même modèle s'applique ensuite à des outils DevOps réels : lire l'état d'un build, chercher les échecs d'une pipeline ou créer un déploiement. La différence importante est de séparer les capacités par niveau de risque.

CatégorieExemplePolitique initiale recommandée
Lectureobtenir l'état d'un buildautoriser explicitement
Écriture réversiblecréer un commentaire de pull requestautoriser à une identité dédiée
Action de productiondéployer, supprimer, modifier un secretrefuser par défaut, approbation hors modèle

Le refus par défaut évite qu'une méthode ajoutée lors d'une évolution du serveur devienne utilisable simplement parce qu'elle a été découverte par le SDK.

Prérequis

Il faut disposer de :

  • .NET 8 ou une version plus récente ;
  • un terminal et le SDK .NET ;
  • les bases d'un serveur MCP C# ;
  • un client MCP compatible avec le transport stdio, par exemple Visual Studio Code.

L'extension est annoncée en Public Preview. Épinglez les versions NuGet dans une application importante, consultez les notes de version avant toute mise à jour et validez les comportements de sécurité dans votre environnement.

Créer le projet et installer les packages

Créez un projet console, puis ajoutez le SDK MCP officiel et l'extension de gouvernance :

dotnet new console -n GovernedMcpServer
cd GovernedMcpServer
dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.AgentGovernance.Extensions.ModelContextProtocol

La documentation du toolkit cible .NET 8 et intègre l'extension au builder IMcpServerBuilder. Conservez le SDK MCP et l'extension dans la même stratégie de mise à jour : la politique doit être évaluée avec les mêmes versions que le serveur qu'elle protège.

Écrire une politique minimale et lisible

Ajoutez le fichier policies/mcp.yaml. Il exprime d'abord le refus global, puis l'exception attendue. Cette forme est plus sûre qu'une liste implicite de capacités autorisées.

apiVersion: governance.toolkit/v1
version: "1.0"
name: ci-tools-policy
default_action: deny
rules:
  - name: allow-echo-in-development
    condition: "tool_name == 'echo'"
    action: allow
    priority: 10

La condition porte ici sur le nom de l'outil, pas sur une demande en langage naturel. Si le serveur expose demain restart-production, il sera bloqué tant qu'une règle dédiée ne l'autorise pas.

Pour une vraie plateforme, placez les politiques dans le dépôt qui les gouverne, soumettez-les à la revue de code et donnez à chaque règle un nom qui explique sa décision. Une politique impossible à comprendre en pull request est difficile à auditer en incident.

Brancher la gouvernance au serveur MCP

Remplacez le contenu de Program.cs par ce code :

using AgentGovernance.Extensions.ModelContextProtocol;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using ModelContextProtocol.Server;

var builder = Host.CreateApplicationBuilder(args);

builder.Logging.AddConsole(options =>
{
    options.LogToStandardErrorThreshold = LogLevel.Trace;
});

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithGovernance(options =>
    {
        options.PolicyPaths.Add("policies/mcp.yaml");
        options.ServerName = "governed-ci-tools";

        // Démonstration locale uniquement : en production, exigez une identité authentifiée.
        options.RequireAuthenticatedAgentId = false;
        options.DefaultAgentId = "did:mcp:local-developer";
    });

builder.Services.AddSingleton<McpServerTool>(
    new EchoTool(
        "echo",
        "Répète un message de diagnostic sans appeler de service externe.",
        _ => "Diagnostic reçu."));

await builder.Build().RunAsync();

La méthode WithGovernance ne rend pas un outil sûr par magie. Elle ajoute quatre points de contrôle autour des outils du serveur : analyse des métadonnées au démarrage, évaluation de la politique avant chaque appel, gouvernance des handlers de secours et assainissement des blocs textuels retournés au client.

L'extension encapsule la collection finale d'outils. L'ordre dans lequel les modules de votre application enregistrent leurs outils est donc moins fragile, mais tous les outils restent soumis à la même politique finale.

Ajouter un outil sans donner de privilège d'infrastructure

Créez EchoTool.cs :

using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;

internal sealed class EchoTool : McpServerTool
{
    private readonly Func<RequestContext<CallToolRequestParams>, string> handler;

    public EchoTool(
        string name,
        string description,
        Func<RequestContext<CallToolRequestParams>, string> handler)
    {
        this.handler = handler;
        ProtocolTool = new Tool
        {
            Name = name,
            Description = description
        };
    }

    public override Tool ProtocolTool { get; }

    public override IReadOnlyList<object> Metadata => Array.Empty<object>();

    public override ValueTask<CallToolResult> InvokeAsync(
        RequestContext<CallToolRequestParams> request,
        CancellationToken cancellationToken = default)
    {
        return ValueTask.FromResult(new CallToolResult
        {
            Content =
            [
                new TextContentBlock
                {
                    Text = handler(request)
                }
            ]
        });
    }
}

L'attribut de description n'est pas un commentaire anodin : il sera transmis au client et potentiellement au modèle. Écrivez-le comme une interface de sécurité. Il doit décrire une opération précise, ne pas demander de secret et ne contenir aucune instruction de contrôle destinée au modèle.

Compilez le projet :

dotnet build

Pour l'essayer dans VS Code, ajoutez un serveur à .vscode/mcp.json :

{
  "servers": {
    "governed-ci-tools": {
      "type": "stdio",
      "command": "dotnet",
      "args": ["run", "--project", "./GovernedMcpServer.csproj"]
    }
  }
}

Demandez ensuite à votre client d'appeler echo avec un message court. L'appel est autorisé parce que le nom de l'outil correspond exactement à la règle YAML.

Remplacer l'identité de démonstration par une identité vérifiée

Le fallback did:mcp:local-developer rend la démo exécutable sans fournisseur d'identité. Il ne doit pas être une solution de production : une identité partagée ne permet pas d'attribuer une action à un appelant.

La documentation actuelle de l'extension demande une identité d'agent authentifiée par défaut. Résolvez-la depuis les claims validés par votre hôte HTTP ou votre fournisseur d'identité :

using AgentGovernance.Extensions.ModelContextProtocol;
using System.Security.Claims;

builder.Services
    .AddMcpServer()
    .WithGovernance(options =>
    {
        options.PolicyPaths.Add("policies/mcp.yaml");
        options.AgentIdResolver = static principal =>
            principal.FindFirst("agent_id")?.Value
            ?? principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    });

N'utilisez pas une valeur fournie librement dans les paramètres de l'outil comme identité. Le serveur doit relier l'identité à une authentification déjà vérifiée, puis la politique doit limiter les outils de cette identité.

Une règle peut alors restreindre une capacité à un agent précis :

- name: allow-trusted-build-reader
  condition: "tool_name == 'get_build_status' and agent_did == 'did:mcp:build-reader'"
  action: allow
  priority: 20

Dans cette configuration, créer l'outil get_build_status ne suffit pas : l'agent appelant doit aussi posséder le DID prévu. Gardez les identités de lecture, de publication et de production séparées.

Faire échouer tôt une définition d'outil douteuse

L'analyse au démarrage est utile contre une classe de risques souvent négligée : les métadonnées des outils. Un outil dont la description contient « ignore les instructions précédentes » peut influencer un agent avant même l'appel de sa méthode.

Avec les valeurs par défaut annoncées par l'extension, ScanToolsOnStartup, FailOnUnsafeTools, SanitizeResponses, l'audit et les métriques sont activés. Une définition considérée risquée peut donc empêcher l'initialisation au lieu d'être discrètement proposée à un client.

Ne baissez pas le seuil ou ne désactivez pas l'échec fermé seulement pour débloquer un déploiement. Corrigez d'abord le nom, la description ou le schéma signalé ; si c'est un faux positif justifié, documentez l'exception, son propriétaire et sa date de révision.

Traiter la réponse d'un outil comme une entrée non fiable

Même un outil autorisé peut lire un ticket, un log ou une page wiki contenant une injection de prompt. L'assainissement textuel de l'extension tente de retirer certains motifs, notamment des balises de type système, des formulations de contournement d'instructions, des indices d'identifiants et des URL d'exfiltration.

Ce filtre est une défense complémentaire, pas une preuve que la sortie est sûre. Conservez ces règles d'exploitation :

  • ne retournez que les champs nécessaires au modèle ;
  • filtrez les secrets avant toute sérialisation ;
  • imposez une validation humaine pour les mutations à fort impact ;
  • exécutez les actions de production avec une identité de workload limitée ;
  • enregistrez l'identité, l'outil, la décision et le résultat, sans journaliser les secrets.

Les outils MCP transforment le modèle en orchestrateur. Ils ne doivent pas transformer le modèle en source d'autorisation.

Ajouter un garde-fou dans la CI

La valeur DevOps apparaît lorsque la politique et le serveur évoluent ensemble. Le pipeline doit au minimum restaurer, compiler et tester le projet avant de publier une image ou un package.

Voici une base GitHub Actions, à adapter au nom de votre solution :

name: Validate governed MCP server

on:
  pull_request:
    paths:
      - "src/GovernedMcpServer/**"
      - "src/GovernedMcpServer/policies/**"
      - ".github/workflows/governed-mcp.yml"

jobs:
  verify:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: "8.0.x"
      - run: dotnet restore src/GovernedMcpServer/GovernedMcpServer.csproj
      - run: dotnet build src/GovernedMcpServer/GovernedMcpServer.csproj --configuration Release --no-restore
      - run: dotnet test src/GovernedMcpServer.Tests/GovernedMcpServer.Tests.csproj --configuration Release

Ajoutez un test qui charge McpServerOptions et vérifie qu'un outil non autorisé reste bloqué par la politique. Testez aussi qu'une définition contenant un motif d'injection fait échouer l'initialisation. Ces tests sont plus utiles qu'un simple contrôle de syntaxe YAML : ils prouvent que l'assemblage de packages réellement déployé applique la règle attendue.

Ne donnez pas de jeton de déploiement à ce job de validation. Une CI qui vérifie les politiques a besoin de lire le dépôt et de restaurer les dépendances ; elle n'a pas besoin de permissions de production.

Checklist avant de connecter des outils de production

  • La politique commence-t-elle par default_action: deny ?
  • Chaque outil possède-t-il une description factuelle, sans secret ni instruction au modèle ?
  • L'identité vient-elle de claims authentifiés plutôt que des arguments du client ?
  • Les identités ont-elles des capacités séparées pour lecture, écriture et production ?
  • Les outils exposés et les politiques sont-ils revus dans la même pull request ?
  • Les sorties sont-elles limitées, assainies et sans données sensibles ?
  • Les décisions d'autorisation sont-elles traçables dans l'observabilité ?
  • Une approbation indépendante du modèle protège-t-elle les actions irréversibles ?

Ressources officielles

Microsoft annonce l'extension MCP d'Agent Governance Toolkit pour .NET et décrit son intégration avec le SDK MCP C#. Le tutoriel C# de l'extension dans le dépôt Microsoft détaille la politique YAML, le contrôle des outils et les options disponibles.

Pour créer ou vérifier le serveur de base, consultez le guide Microsoft .NET : créer un serveur MCP en C#. La documentation .NET du toolkit précise notamment la résolution d'identité et le comportement de repli.

Une politique versionnée ne rend pas un agent infaillible. Elle donne à l'équipe une frontière claire : l'agent peut proposer et appeler les capacités explicitement prévues ; le système, lui, conserve la décision et la preuve de ce qui a été autorisé.

Mots-clés :.NETC#MCPDevSecOpsAI Agents
Y

Yva Hajatiana

Articles techniques sur l'ingénierie logicielle, .NET, le cloud et l'intelligence artificielle appliquée aux applications.