Comment créer un serveur MCP en C# et Java : outils, transports et tests
Construisez deux fois le même serveur MCP, en C# avec le package NuGet officiel et en Java avec Spring AI. Ajoutez un compteur de mots et un outil d’image qui appelle l’API PicassoIA, choisissez stdio ou HTTP, puis testez avec le MCP Inspector et un vrai client d’IA.
Un serveur MCP est un petit programme qui attend un message JSON-RPC, exécute une fonction et renvoie le résultat à un client d’IA. C’est tout le principe. Il ne paraît lourd qu’en C# et Java, parce que ces écosystèmes arrivent avec des modèles de projet, de l’injection de dépendances, des annotations et des outils de build. Ce tutoriel va à l’essentiel. Vous allez construire deux fois les mêmes deux outils : une fois en C# avec le package NuGet officiel ModelContextProtocol, une fois en Java avec le starter MCP de Spring AI. Vous les testerez ensuite tous les deux avec le MCP Inspector, puis vous les connecterez à un vrai client.
Les deux outils sont volontairement simples. word_count vérifie que la plomberie fonctionne, et generate_image appelle l’API PicassoIA, si bien qu’un client d’IA comme Claude peut demander une image à votre code et récupérer un lien en retour. Le code suit la documentation officielle du SDK, mais épinglez vos propres versions de package et lancez les vérifications de la section test avant de vous fier à quoi que ce soit.
Ce que fait réellement un serveur MCP
Le Model Context Protocol est un standard ouvert qui permet à une application d’IA (le client) de dialoguer de manière cohérente avec des programmes externes (les serveurs). Le client se connecte, demande au serveur ce qu’il sait faire, puis appelle ces capacités lorsque le modèle juge qu’il en a besoin. Votre serveur ne parle jamais directement au modèle. Il répond aux requêtes, et c’est le client qui gère la conversation.
Outils, ressources et prompts
Un serveur peut proposer trois types de capacités :
Outils : ce sont des fonctions que le modèle peut appeler, comme generate_image ou word_count. Chacun possède un nom, une description et un schéma JSON pour ses entrées.
Ressources : des données en lecture seule, adressées par une URI, comme un fichier, une ligne de base de données ou un journal.
Prompts : des modèles de messages réutilisables que l’utilisateur peut choisir dans un menu.
La plupart des serveurs n’exposent que des outils, et ce sont eux que ce tutoriel construit. La description que vous écrivez pour chaque outil compte plus que le code qu’il contient, car le modèle lit ce texte pour décider quand l’appeler.
💡 Astuce : Rédigez les descriptions d’outils comme si vous briefiez un nouveau collègue. Dites ce que fait l’outil, ce dont il a besoin et ce qu’il renvoie.
Un protocole, deux environnements
Les deux SDK masquent les détails de JSON-RPC. Vous déclarez une méthode, décrivez ses paramètres, et la bibliothèque construit le schéma et gère le cycle de la requête. Voici la correspondance entre les deux environnements :
Aspect
C#
Java
Package principal
ModelContextProtocol
io.modelcontextprotocol.sdk:mcp ou les starters Spring AI
Déclarer un outil
Attribut [McpServerTool]
Annotation @Tool (Spring AI)
Transport stdio
WithStdioServerTransport()
spring-ai-starter-mcp-server
Transport HTTP
ModelContextProtocol.AspNetCore
spring-ai-starter-mcp-server-webmvc ou -webflux
Environnement d’exécution
SDK .NET
Java 17 ou plus récent
Le SDK C# est maintenu en collaboration avec Microsoft, et les intégrations Spring du SDK Java se trouvent désormais dans Spring AI. Dans les deux cas, vous travaillez avec des annotations et de l’injection de dépendances, si bien que le code se lit comme n’importe quel service dans ce langage.
Installer .NET et Java
Installez ce dont vous avez besoin une seule fois, et les deux builds pourront tourner sur le même ordinateur. Vous avez aussi besoin de Node.js, car le MCP Inspector s’exécute avec npx.
Prérequis .NET
Installez le SDK .NET et vérifiez-le avec dotnet --version. Consultez la page du package ModelContextProtocol sur NuGet pour connaître le framework minimal visé, puis choisissez un SDK qui le satisfait. Un éditeur de code aide, même si un simple terminal suffit pour ce projet.
Prérequis Java
Installez le JDK 17 ou plus récent, ainsi que Maven ou Gradle. Le SDK Java indique Java 17 comme version de référence. Ce tutoriel utilise Maven et un projet Spring Boot, car le starter Spring AI supprime la plupart du code de transport que vous devriez sinon écrire à la main.
Les deux serveurs lisent l’identifiant PicassoIA dans une variable d’environnement. Créez un token API sur picassoia.com/en/api (il commence par pia_sk_) et exportez-le en tant que PICASSOIA_API_TOKEN. Ne le collez jamais dans le code source, et consultez la page tarifaire de PicassoIA pour voir quelles offres incluent l’accès à l’API.
Construire le serveur C#
Créer le projet
Exécutez ces commandes dans un dossier vide :
dotnet new console -n PicassoMcp
cd PicassoMcp
dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
Remplacez ensuite le contenu de Program.cs :
using System.Net.Http.Headers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateApplicationBuilder(args);
// Standard output carries the protocol, so every log line goes to standard error.
builder.Logging.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services.AddSingleton(_ =>
{
var client = new HttpClient { BaseAddress = new Uri("https://api.picassoia.com/v1/") };
var token = Environment.GetEnvironmentVariable("PICASSOIA_API_TOKEN");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return client;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
Deux détails méritent attention. Le logger envoie tout vers la sortie d’erreur standard, car la sortie standard appartient au protocole. Et WithToolsFromAssembly() parcourt votre projet à la recherche des classes marquées comme types d’outils, si bien que vous n’enregistrez jamais les outils à la main.
Ajouter votre premier outil
Créez PicassoTools.cs à côté de Program.cs. Commencez par l’outil simple :
using System.ComponentModel;
using ModelContextProtocol.Server;
[McpServerToolType]
public static class PicassoTools
{
[McpServerTool(Name = "word_count")]
[Description("Counts the words in a piece of text and returns the number.")]
public static int WordCount([Description("The text to count")] string text) =>
// A null separator splits on any whitespace.
text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;
}
Les trois attributs font tout le travail. [McpServerToolType] marque la classe, [McpServerTool] marque la méthode, et chaque [Description] devient un texte que le modèle lit. Les noms et les types des paramètres deviennent automatiquement le schéma d’entrée.
Appeler l’API PicassoIA
L’API PicassoIA est asynchrone et suit le modèle de Replicate. Vous créez une prédiction avec POST /v1/models/{owner}/{name}/predictions, puis vous interrogez GET /v1/predictions/{id} jusqu’à ce que le statut indique succeeded. L’authentification se fait par bearer token, et un compte peut lancer 5 prédictions en même temps. Ajoutez cette méthode dans la même classe, avec ces lignes using supplémentaires en haut du fichier :
using System.Net.Http.Json;
using System.Text.Json;
using ModelContextProtocol;
[McpServerTool(Name = "generate_image")]
[Description("Generates an image with PicassoIA and returns the finished prediction as JSON.")]
public static async Task<string> GenerateImage(
HttpClient client,
[Description("What the image should show")] string prompt,
[Description("Aspect ratio such as 16:9 or 1:1")] string aspectRatio = "16:9",
CancellationToken cancellationToken = default)
{
var created = await client.PostAsJsonAsync(
"models/picassoia/picassoia-image/predictions",
new { input = new { prompt, aspect_ratio = aspectRatio } },
cancellationToken);
created.EnsureSuccessStatusCode();
using var createdDoc = JsonDocument.Parse(
await created.Content.ReadAsStringAsync(cancellationToken));
var id = createdDoc.RootElement.GetProperty("id").GetString();
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
var json = await client.GetStringAsync($"predictions/{id}", cancellationToken);
using var doc = JsonDocument.Parse(json);
var status = doc.RootElement.GetProperty("status").GetString();
if (status == "succeeded") return json;
if (status is "failed" or "canceled")
throw new McpException($"The generation ended with status '{status}'.");
}
}
Le paramètre HttpClient est résolu par l’injection de dépendances, si bien que le token configuré dans Program.cs l’accompagne. Comme la méthode accepte un CancellationToken, le client peut interrompre une génération lente au lieu de la laisser bloquée. Le modèle derrière cet appel est PicassoIA Image, et le même schéma fonctionne pour les autres modèles que l’API expose.
Construire le serveur Java
La version Java propose les mêmes deux outils et le même comportement. Seul l’empaquetage change.
Ajouter le starter Spring AI
Créez un projet Spring Boot, importez le BOM de Spring AI, puis ajoutez le starter stdio :
Les trois premières lignes ont autant d’importance que la dépendance. Elles désactivent le serveur web, la bannière de démarrage et le motif de journalisation sur la console, car tout ce qui est affiché sur la sortie standard casse le flux stdio.
Écrire la classe d’outil
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
@Service
public class PicassoTools {
private final RestClient client;
public PicassoTools(RestClient.Builder builder,
@Value("${PICASSOIA_API_TOKEN}") String token) {
this.client = builder
.baseUrl("https://api.picassoia.com/v1")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.build();
}
@Tool(description = "Counts the words in a piece of text and returns the number.")
public int wordCount(@ToolParam(description = "The text to count") String text) {
return text.isBlank() ? 0 : text.trim().split("\\s+").length;
}
@Tool(description = "Generates an image with PicassoIA and returns the finished prediction.")
public Map<String, Object> generateImage(
@ToolParam(description = "What the image should show") String prompt,
@ToolParam(description = "Aspect ratio such as 16:9 or 1:1", required = false)
String aspectRatio) throws InterruptedException {
String ratio = aspectRatio == null ? "16:9" : aspectRatio;
Map<String, Object> created = client.post()
.uri("/models/picassoia/picassoia-image/predictions")
.contentType(MediaType.APPLICATION_JSON)
.body(Map.of("input", Map.of("prompt", prompt, "aspect_ratio", ratio)))
.retrieve()
.body(new ParameterizedTypeReference<>() {});
String id = (String) created.get("id");
for (int attempt = 0; attempt < 60; attempt++) {
Thread.sleep(5000);
Map<String, Object> prediction = client.get()
.uri("/predictions/{id}", id)
.retrieve()
.body(new ParameterizedTypeReference<>() {});
String status = (String) prediction.get("status");
if ("succeeded".equals(status)) return prediction;
if ("failed".equals(status) || "canceled".equals(status)) {
throw new IllegalStateException("The generation ended with status " + status);
}
}
throw new IllegalStateException("The generation took longer than five minutes.");
}
}
@Tool et @ToolParam jouent le même rôle que les attributs C#. Spring les transforme en nom d’outil, en description et en schéma d’entrée. La méthode renvoie un Map, que Spring AI sérialise en JSON pour le client.
Les méthodes annotées ne sont pas exposées tant que vous ne les enregistrez pas. Ajoutez un bean dans votre classe principale :
@SpringBootApplication
public class PicassoMcpApplication {
public static void main(String[] args) {
SpringApplication.run(PicassoMcpApplication.class, args);
}
@Bean
ToolCallbackProvider picassoTools(PicassoTools tools) {
return MethodToolCallbackProvider.builder().toolObjects(tools).build();
}
}
Compilez avec mvn package et vous obtenez un JAR exécutable.
Utiliser le SDK Java seul
Si votre projet n’utilise pas Spring, dépendez de io.modelcontextprotocol.sdk:mcp et importez mcp-bom pour aligner les versions des modules. Le serveur lui-même tient en quelques lignes :
StdioServerTransportProvider transport =
new StdioServerTransportProvider(McpJsonDefaults.getMapper());
McpSyncServer server = McpServer.sync(transport)
.serverInfo("picassoia-java", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
Cet extrait suit la ligne 2.0 du SDK. Vous ajoutez chaque outil sous forme de SyncToolSpecification avec un nom, une description, un schéma JSON et un gestionnaire d’appel. Les détails du builder, comme le constructeur du transport, diffèrent d’une version à l’autre. Copiez donc l’extrait d’outil depuis le README correspondant à votre version. Les utilisateurs de Spring bénéficient de tout cela sans effort, ce qui fait du starter la voie la plus courte.
Transport stdio ou HTTP
Le transport détermine qui lance votre serveur et qui peut l’atteindre.
Transport
Idéal pour
C#
Java
Stdio
Outils locaux lancés par le client
WithStdioServerTransport()
spring-ai-starter-mcp-server
HTTP
Serveurs distants ou partagés
WithHttpTransport() et MapMcp()
Starter WebMVC ou WebFlux
Avec stdio, le client démarre votre processus et communique avec lui via l’entrée et la sortie standard. C’est l’option la plus simple, et rien n’est exposé au réseau, donc commencez par là. Avec HTTP, un seul déploiement sert de nombreux clients, ce qui signifie que vous devez désormais gérer l’authentification, le TLS et les limites de débit.
En C#, passez en HTTP en ajoutant le package ModelContextProtocol.AspNetCore et en modifiant l’hôte :
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
var app = builder.Build();
app.MapMcp();
app.Run();
En Java, remplacez le starter par spring-ai-starter-mcp-server-webmvc, ou par la variante WebFlux pour les applications réactives. La documentation Spring AI indique /sse et /mcp/message comme points de terminaison par défaut, et spring.ai.mcp.server.stdio=true pour faire tourner stdio en parallèle de HTTP.
⚠️ Avertissement : Un serveur HTTP dont l’outil appelle une API payante ou soumise à des limites de débit doit être authentifié avant toute mise en ligne. Quiconque peut atteindre le point de terminaison peut appeler l’outil.
Tester et déboguer votre serveur
Lancer le MCP Inspector
L’Inspector est un outil de navigateur qui se connecte à votre serveur, liste ses outils et vous permet de les appeler avec des arguments saisis à la main. Compilez d’abord, puis lancez-le avec la commande de votre serveur :
Connectez-vous, ouvrez l’onglet Tools et listez les outils. Vous devriez voir word_count et generate_image, chacun avec les descriptions que vous avez écrites. Appelez word_count avec one two three et attendez-vous à 3. Appelez ensuite generate_image avec un prompt court et attendez que le JSON de la prédiction revienne.
Connecter un vrai client
Avec Claude Code, une seule commande enregistre le serveur C# :
claude mcp add picassoia-dotnet -e PICASSOIA_API_TOKEN=pia_sk_your_token -- dotnet run --project ./PicassoMcp --no-build
Claude Desktop lit à la place un fichier JSON. Ajoutez les deux serveurs dans claude_desktop_config.json :
Redémarrez le client et demandez-lui de « générer une photo 16:9 d’un phare au coucher du soleil ». Il devrait appeler votre outil et renvoyer la prédiction avec le lien de l’image.
Cinq erreurs qui cassent les serveurs
Afficher sur la sortie standard. Un Console.WriteLine ou System.out.println égaré corrompt le flux JSON-RPC. Journalisez uniquement sur la sortie d’erreur standard.
Descriptions vagues. Si deux outils se ressemblent, le modèle choisit le mauvais. Nommez l’entrée, la sortie et les effets de bord.
Interrogation sans limite. La boucle C# s’arrête à l’annulation, et la boucle Java s’arrête après 60 tentatives. Choisissez une limite adaptée à votre modèle le plus lent.
Secrets codés en dur. Lisez les tokens depuis l’environnement, comme le font les deux exemples, et ne les ajoutez jamais à un commit.
Valeurs de retour énormes. Renvoyez une URL ou un résumé court, pas plusieurs Mo de données que le modèle doit lire.
Pensez aussi à la limite de concurrence. Avec 5 prédictions autorisées simultanément par compte, un client bavard qui enchaîne de nombreux appels generate_image commencera à voir des erreurs. Mettez donc les appels en file d’attente ou renvoyez un message clair.
Comment utiliser Sonnet 5 sur PicassoIA
Écrire du code répétitif est l’étape où un modèle d’IA fait gagner le plus de temps, et Claude Sonnet 5 sur PicassoIA est conçu pour les tâches de code et d’utilisation d’outils en plusieurs étapes. Utilisez-le pour rédiger de nouveaux outils, puis vérifiez chaque nom d’API dans la documentation du SDK.
Ouvrez la page du modèle. Allez sur la page Claude Sonnet 5 de PicassoIA et repérez la zone de prompt.
Décrivez un outil. Indiquez le langage, le SDK et le comportement : « Écrivez un outil MCP en C# avec le package ModelContextProtocol qui convertit une température entre Celsius et Fahrenheit. »
Définissez un prompt système une fois. Quelque chose comme « N’écrivez jamais sur la sortie standard. Utilisez les attributs [McpServerTool] et [Description] » garde chaque réponse cohérente.
Choisissez le niveau d’effort. Laissez-le sur low pour le code répétitif. Montez-le à high ou max quand le code touche plusieurs fichiers ou qu’un bug résiste.
Joignez une capture d’écran en cas d’échec. Le modèle lit les images, donc une capture d’une erreur de l’Inspector fonctionne aussi bien que du texte collé.
Testez le résultat. Collez le code dans votre projet et exécutez-le avec l’Inspector avant de vous y fier.
Paramètre
Valeur par défaut
Modifiez-le quand
effort
low
La tâche demande un raisonnement plus approfondi
max_tokens
8192
La réponse est coupée
system_prompt
vide
Vous voulez un style de code ou un rôle fixe
image
aucun
Vous voulez partager une capture d’écran ou un schéma
Vous disposez maintenant de deux serveurs fonctionnels qui confient la génération d’images à un client d’IA. L’étape suivante consiste à voir ce que le modèle derrière eux sait faire seul. Ouvrez PicassoIA Image, saisissez le même prompt que celui envoyé par votre outil, et comparez le résultat. Changez ensuite le format, retravaillez l’éclairage et essayez trois variations d’une même scène. Quand un rendu est proche sans être juste, envoyez-le vers PicassoIA Image Editor Pro pour corriger les détails au lieu de tout recommencer.
Plus vous expérimentez avec les prompts sur Picasso IA, plus vos descriptions d’outils et vos arguments par défaut gagneront en qualité. Créez votre première image dès aujourd’hui, puis intégrez le meilleur prompt à votre serveur et laissez votre client d’IA faire le reste.