Construire des serveurs MCP en .NET
Cette skill t'aide à écrire des serveurs MCP et des clients basiques de qualité production en C#/.NET en utilisant les packages NuGet officiels ModelContextProtocol, maintenus par Microsoft et le projet MCP. Elle cible la ligne stable 1.x et la spec actuelle (2025-11-25).
Quand cette skill gagne son salaire
Le SDK .NET MCP a connu des années de packages en preview (0.x-preview) avant d'atteindre 1.0. Sans aide, le modèle tend à :
- Épingler une version preview obsolète qui ne compile pas contre les samples actuels.
- Rater les features récentes de la spec (elicitation URL mode, MCP Apps, content blocks structurés).
- Se tromper sur les détails du transport HTTP (stateful/stateless, buffering proxy, wiring OAuth).
- Oublier le piège stdout/stderr de STDIO.
Si la tâche en fait partie, charge la référence correspondante et suis-la. Si c'est vraiment trivial (ex. « renomme cette méthode tool »), tu n'as pas besoin de tout lire — les règles cardinales ci-dessous sont le minimum.
Modèle mental en 30 secondes
Un serveur MCP .NET est une app ordinaire Microsoft.Extensions.Hosting (ou WebApplication) qui wiring un serveur MCP par DI :
builder.Services
.AddMcpServer()
.WithStdioServerTransport() // OU .WithHttpTransport(...)
.WithToolsFromAssembly() // discover [McpServerToolType] classes
.WithPrompts<MyPrompts>() // optional
.WithResources<MyResources>(); // optional
Les primitives sont des méthodes C# ordinaires sur des classes marquées d'attributs ([McpServerToolType] + [McpServerTool], [McpServerPromptType] + [McpServerPrompt], [McpServerResourceType] + [McpServerResource]). Les paramètres se lient depuis JSON-RPC ; le SDK construit le JSON Schema à partir de la signature plus les attributs [Description].
Les features server-to-client (sampling, elicitation, roots, notifications log/progress) sont des méthodes sur l'IMcpServer injecté.
Arbre de décision → quelles références charger
Charge toujours references/packages.md si tu crées un nouveau projet ou que tu es incertain de la version actuelle du package.
| Tâche | Charge |
|---|---|
| Nouveau serveur STDIO | references/transport-stdio.md |
| Nouveau serveur HTTP (Streamable) | references/transport-http.md |
| Ajouter/modifier un tool | references/tool-primitive.md |
| Ajouter/modifier un prompt | references/prompt-primitive.md |
| Ajouter/modifier une resource | references/resource-primitive.md |
| Poser une question à l'utilisateur en cours de tool | references/elicitation.md |
| Appeler le LLM du client depuis un tool | references/sampling.md |
| Lire les project roots de l'utilisateur | references/roots.md |
| Retourner une UI interactive | references/mcp-apps.md |
| Complétions d'arguments, notifications log/progress, filters, instructions serveur | references/server-features.md |
| Écrire un programme .NET qui consomme un serveur MCP | references/client.md |
| MCP Inspector, tests en mémoire, mocks, CI | references/testing.md |
Pour les tâches multi-primitive, charge plusieurs à la fois. Pour les édits triviaux dans un fichier existant, tu n'en as généralement pas besoin.
Règles cardinales (s'appliquent toujours ; elles préviennent les pannes les plus fréquentes)
- Épingle le package stable actuel, pas une preview. Utilise
ModelContextProtocol/ModelContextProtocol.AspNetCore/ModelContextProtocol.Coreau dernier 1.x. Si tu te trouves à écrire0.3-previewou0.4-preview, arrête et vérifie sur NuGet — les APIs preview ont des différences breaking. - Les serveurs STDIO ne doivent pas écrire sur stdout. Stdout est le canal JSON-RPC. Configure
LogToStandardErrorThreshold = LogLevel.Traceavant toute chose et ne fais jamaisConsole.WriteLinedepuis un tool. - HTTP est stateful par défaut. Pour les déploiements horizontalement scalés sans trafic server-initiated, définis
options.Stateless = true. Les features server-to-client (sampling, elicitation, roots, notifications non sollicitées) nécessitent HTTP stateful ou STDIO —Stateless = trueles cassera à l'exécution. - SSE seul est deprecated. Utilise Streamable HTTP. Active le SSE legacy (
EnableLegacySse = true) seulement pour un ancien client que tu dois supporter, et signale-le. - Toujours
[Description]les tools et paramètres. C'est ce que le LLM voit quand il choisit et façonne les appels. Les descriptions vagues sont la raison #1 pour laquelle les tools ne sont pas utilisés. - Affiche la ligne d'enregistrement chaque fois que tu ajoutes une primitive. Une nouvelle classe
[McpServerPromptType]sans.WithPrompts<...>()(ou.WithPromptsFromAssembly()) est invisible. - N'invente pas d'APIs. Si tu doutes qu'une méthode existe, dis-le et consulte la référence API — les mauvais noms de méthode causent des défaillances silencieuses.
Style de travail
- Fais des changements minimaux et additifs. Ajoute une méthode à la classe tool existante plutôt que de restructurer le projet.
- Pour les setups non triviaux, lance
dotnet build. Attrape les usings manquants, les typos d'attributs et les désaccords TFM avant que l'utilisateur ne les voie. - Confirme le transport + version .NET + primitives avant de scaffolder si le contexte ne les rend pas déjà évidents. Défaut sur .NET 10 pour les nouveaux projets.
Quand l'utilisateur est bloqué
Parcours cette checklist avant de deviner :
- STDIO : quelque chose écrit sur stdout (logger sink,
Console.WriteLine, bannière de library). - HTTP 404 : désaccord de path —
app.MapMcp()est root,app.MapMcp("/mcp")la met sous/mcp. - Tool n'apparaît pas :
[McpServerToolType]manquant sur la classe, ou pas de.WithToolsFromAssembly()/.WithTools<T>()enregistré. - Args non liés : les noms de paramètres doivent correspondre aux clés JSON-RPC
arguments; les types complexes se lient viaSystem.Text.Json. - Sampling/elicitation/roots qui échouent : le transport est HTTP stateless, ou le client n'annonce pas la capability.
Toujours bloqué ? Pointe l'utilisateur vers l'exemple EverythingServer — il exerce chaque feature.