Cómo crear un servidor MCP en C# y Java: herramientas, transportes y pruebas
Crea el mismo servidor MCP dos veces, en C# con el paquete NuGet oficial y en Java con Spring AI. Añade un contador de palabras y una herramienta de imágenes que llama a la API de PicassoIA, elige stdio o HTTP y prueba con MCP Inspector y un cliente de IA real.
Un servidor MCP es un pequeño programa que espera un mensaje JSON-RPC, ejecuta una función y devuelve el resultado a un cliente de IA. Eso es todo el truco. Solo parece pesado en C# y Java porque ambos ecosistemas incluyen plantillas de proyecto, inyección de dependencias, anotaciones y herramientas de compilación. Este tutorial lo simplifica. Construirás las mismas dos herramientas dos veces, una en C# con el paquete NuGet oficial ModelContextProtocol y otra en Java con el starter MCP de Spring AI, y luego probarás ambas con MCP Inspector y las conectarás a un cliente real.
Las dos herramientas son deliberadamente sencillas. word_count demuestra que la base funciona, y generate_image llama a la API de PicassoIA, de modo que un cliente de IA como Claude puede pedirle a tu código una imagen y recibir un enlace a cambio. El código sigue la documentación oficial del SDK, pero fija tus propias versiones de paquetes y ejecuta las comprobaciones de la sección de pruebas antes de confiar en él.
Qué hace realmente un servidor MCP
El Model Context Protocol es un estándar abierto que permite a una aplicación de IA (el cliente) comunicarse con programas externos (servidores) de una forma coherente. El cliente se conecta, pregunta al servidor qué sabe hacer y, cuando el modelo lo necesita, invoca esas capacidades. Tu servidor nunca habla directamente con el modelo. Responde a las peticiones, y el cliente gestiona la conversación.
Herramientas, recursos y prompts
Un servidor puede ofrecer tres tipos de capacidades:
Herramientas son funciones que el modelo puede invocar, como generate_image o word_count. Cada una tiene un nombre, una descripción y un esquema JSON para sus entradas.
Recursos son datos de solo lectura identificados por una URI, como un archivo, una fila de una base de datos o un registro.
Prompts son plantillas de mensajes reutilizables que el usuario puede elegir desde un menú.
La mayoría de los servidores solo incluyen herramientas, y las herramientas son lo que construye este tutorial. La descripción que escribas para cada herramienta importa más que el código que contiene, porque el modelo lee ese texto para decidir cuándo llamarla.
💡 Consejo: Escribe las descripciones de las herramientas como si explicaras el trabajo a un compañero nuevo. Di qué hace la herramienta, qué necesita y qué devuelve.
Un protocolo, dos stacks
Ambos SDK ocultan los detalles de JSON-RPC. Declaras un método, describes sus parámetros, y la biblioteca genera el esquema y gestiona el ciclo de la petición. Así se comparan los dos stacks:
Aspecto
C#
Java
Paquete principal
ModelContextProtocol
io.modelcontextprotocol.sdk:mcp o los starters de Spring AI
Declarar una herramienta
Atributo [McpServerTool]
Anotación @Tool (Spring AI)
Transporte stdio
WithStdioServerTransport()
spring-ai-starter-mcp-server
Transporte HTTP
ModelContextProtocol.AspNetCore
spring-ai-starter-mcp-server-webmvc o -webflux
Entorno de ejecución
.NET SDK
Java 17 o posterior
El SDK de C# se mantiene en colaboración con Microsoft, y las integraciones con Spring del SDK de Java ahora están en Spring AI. En ambos casos trabajas con anotaciones e inyección de dependencias, así que el código se lee como cualquier otro servicio en ese lenguaje.
Configurar .NET y Java
Instala lo necesario una sola vez y ambas compilaciones funcionarán en el mismo equipo. También necesitas Node.js, porque MCP Inspector se ejecuta con npx.
Requisitos de .NET
Instala el .NET SDK y compruébalo con dotnet --version. Consulta en la página del paquete ModelContextProtocol de NuGet la versión mínima de framework que usa y elige un SDK que la cumpla. Un editor de código ayuda, aunque basta una terminal sencilla para este proyecto.
Requisitos de Java
Instala JDK 17 o posterior, además de Maven o Gradle. El SDK de Java indica Java 17 como base mínima. Este tutorial usa Maven y un proyecto Spring Boot, porque el starter de Spring AI elimina la mayor parte del código de transporte que, de otro modo, escribirías a mano.
Ambos servidores leen la credencial de PicassoIA desde una variable de entorno. Crea un token de API en picassoia.com/en/api (empieza por pia_sk_) y expórtalo como PICASSOIA_API_TOKEN. Nunca lo pegues en el código fuente, y consulta la página de precios de PicassoIA para ver qué planes incluyen acceso a la API.
Crear el servidor en C#
Crear el proyecto
Ejecuta estos comandos en una carpeta vacía:
dotnet new console -n PicassoMcp
cd PicassoMcp
dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
Después, sustituye el contenido 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();
Hay dos detalles que merecen atención. El logger envía todo a la salida de error estándar, porque la salida estándar pertenece al protocolo. Y WithToolsFromAssembly() examina tu proyecto en busca de clases marcadas como tipos de herramienta, así que nunca registras las herramientas a mano.
Añadir tu primera herramienta
Crea PicassoTools.cs junto a Program.cs. Empieza con la herramienta sencilla:
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;
}
Los tres atributos hacen todo el trabajo. [McpServerToolType] marca la clase, [McpServerTool] marca el método, y cada [Description] se convierte en texto que el modelo lee. Los nombres y tipos de los parámetros se transforman automáticamente en el esquema de entrada.
Llamar a la API de PicassoIA
La API de PicassoIA es asíncrona y sigue el patrón de Replicate. Creas una predicción con POST /v1/models/{owner}/{name}/predictions y luego consultas GET /v1/predictions/{id} hasta que el estado sea succeeded. La autenticación es mediante un token bearer, y una cuenta puede ejecutar 5 predicciones al mismo tiempo. Añade este método dentro de la misma clase, con estas líneas extra using al principio del archivo:
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}'.");
}
}
El parámetro HttpClient se resuelve mediante inyección de dependencias, de modo que el token que configuraste en Program.cs viaja con él. Como el método acepta un CancellationToken, el cliente puede cancelar una generación lenta en lugar de dejarla colgada. El modelo que usa la llamada es PicassoIA Image, y el mismo patrón sirve para los demás modelos que expone la API.
Crear el servidor en Java
La versión en Java tiene las mismas dos herramientas y el mismo comportamiento. Solo cambia el empaquetado.
Añadir el starter de Spring AI
Crea un proyecto Spring Boot, importa la lista de materiales (BOM) de Spring AI y añade después el starter de stdio:
Las tres primeras líneas son tan importantes como la dependencia. Desactivan el servidor web, el banner de inicio y el patrón de log por consola, porque cualquier cosa impresa en la salida estándar rompe el flujo stdio.
Escribir la clase de herramienta
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 y @ToolParam cumplen el mismo papel que los atributos de C#. Spring los convierte en el nombre de la herramienta, la descripción y el esquema de entrada. El método devuelve un Map, que Spring AI serializa a JSON para el cliente.
Los métodos anotados no se exponen hasta que los registras. Añade un bean en tu clase principal:
@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();
}
}
Compílalo con mvn package y obtendrás un JAR ejecutable.
Usar el SDK de Java sin Spring
Si tu proyecto no usa Spring, depende de io.modelcontextprotocol.sdk:mcp e importa mcp-bom para mantener alineadas las versiones de los módulos. El servidor en sí son unas pocas líneas:
StdioServerTransportProvider transport =
new StdioServerTransportProvider(McpJsonDefaults.getMapper());
McpSyncServer server = McpServer.sync(transport)
.serverInfo("picassoia-java", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
Ese fragmento sigue la línea 2.0 del SDK. Añades cada herramienta como un SyncToolSpecification con un nombre, una descripción, un esquema JSON y un manejador de llamadas. Los detalles del builder, como el constructor del transporte, cambian entre versiones, así que copia el fragmento de la herramienta del README que corresponda a tu versión. Los usuarios de Spring obtienen todo esto sin esfuerzo, por eso el starter es el camino más corto.
Transporte stdio o HTTP
El transporte decide quién lanza tu servidor y quién puede acceder a él.
Transporte
Ideal para
C#
Java
Stdio
Herramientas locales que lanza el cliente
WithStdioServerTransport()
spring-ai-starter-mcp-server
HTTP
Servidores remotos o compartidos
WithHttpTransport() y MapMcp()
Starter WebMVC o WebFlux
Con stdio, el cliente inicia tu proceso y se comunica con él a través de la entrada y salida estándar. Es la opción más sencilla y no expone nada a la red, así que empieza por ahí. Con HTTP, un único despliegue atiende a muchos clientes, lo que significa que ahora te encargas de la autenticación, TLS y los límites de uso.
En C#, cambia a HTTP añadiendo el paquete ModelContextProtocol.AspNetCore y modificando el host:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
var app = builder.Build();
app.MapMcp();
app.Run();
En Java, sustituye el starter por spring-ai-starter-mcp-server-webmvc, o por la variante WebFlux para aplicaciones reactivas. La documentación de Spring AI indica /sse y /mcp/message como endpoints por defecto, y spring.ai.mcp.server.stdio=true para ejecutar stdio junto con HTTP.
⚠️ Advertencia: Un servidor HTTP cuya herramienta llama a una API de pago o con límite de uso necesita autenticación antes de salir a internet. Cualquiera que pueda acceder al endpoint puede llamar a la herramienta.
Probar y depurar tu servidor
Ejecutar MCP Inspector
Inspector es una herramienta de navegador que se conecta a tu servidor, lista sus herramientas y te permite llamarlas con argumentos escritos a mano. Compila primero y luego lánzalo con el comando de tu servidor:
Conéctate, abre la pestaña Tools y lista las herramientas. Deberías ver word_count y generate_image, cada una con las descripciones que escribiste. Llama a word_count con one two three y espera 3. Después llama a generate_image con un prompt corto y espera a que vuelva el JSON de la predicción.
Conectar un cliente real
Con Claude Code, un solo comando registra el servidor en C#:
claude mcp add picassoia-dotnet -e PICASSOIA_API_TOKEN=pia_sk_your_token -- dotnet run --project ./PicassoMcp --no-build
Claude Desktop lee un archivo JSON en su lugar. Añade ambos servidores a claude_desktop_config.json:
Reinicia el cliente y pídele que "genere una foto 16:9 de un faro al atardecer". Debería llamar a tu herramienta y devolver la predicción con el enlace a la imagen.
Cinco errores que rompen los servidores
Imprimir en la salida estándar. Un Console.WriteLine o System.out.println olvidado corrompe el flujo JSON-RPC. Registra solo en la salida de error estándar.
Descripciones vagas. Si dos herramientas suenan parecido, el modelo elige la equivocada. Indica la entrada, la salida y los efectos secundarios.
Consultas sin límite. El bucle de C# se detiene al cancelarse y el de Java se detiene tras 60 intentos. Elige un límite que se ajuste a tu modelo más lento.
Secretos codificados en el código. Lee los tokens del entorno, como hacen ambos ejemplos, y nunca los subas al repositorio.
Valores de retorno enormes. Devuelve una URL o un resumen breve, no megabytes de datos que el modelo tenga que leer.
Ten también en cuenta el límite de concurrencia. Con 5 predicciones permitidas a la vez por cuenta, un cliente muy activo que lance muchas llamadas generate_image empezará a recibir errores, así que encola las llamadas o devuelve un mensaje claro.
Cómo usar Sonnet 5 en PicassoIA
Para escribir código repetitivo, un modelo de IA es lo que más tiempo ahorra, y Claude Sonnet 5 en PicassoIA está pensado para programación y tareas de uso de herramientas en varios pasos. Úsalo para redactar herramientas nuevas y después verifica cada nombre de la API con la documentación del SDK.
Abre la página del modelo. Ve a la página de Claude Sonnet 5 en PicassoIA y localiza el cuadro de prompt.
Describe una herramienta. Indica el lenguaje, el SDK y el comportamiento: "Escribe una herramienta MCP en C# con el paquete ModelContextProtocol que convierta una temperatura de Celsius a Fahrenheit".
Define un prompt de sistema una vez. Algo como "Nunca escribas en la salida estándar. Usa los atributos [McpServerTool] y [Description]" mantiene cada respuesta coherente.
Elige el nivel de esfuerzo. Déjalo en low para código repetitivo. Súbelo a high o max cuando el código afecte a varios archivos o cuando un error sea persistente.
Adjunta una captura cuando algo falle. El modelo lee imágenes, así que una captura de un error de Inspector funciona tan bien como el texto pegado.
Prueba el resultado. Pega el código en tu proyecto y ejecútalo con Inspector antes de confiar en él.
Ya tienes dos servidores funcionando que entregan la generación de imágenes a un cliente de IA. El siguiente paso es ver qué puede hacer por sí mismo el modelo que hay detrás. Abre PicassoIA Image, escribe el mismo prompt que enviaste con tu herramienta y compara el resultado. Después cambia la relación de aspecto, reescribe la iluminación y prueba tres variaciones de una escena. Cuando un render esté cerca pero no del todo bien, envíalo a PicassoIA Image Editor Pro y corrige los detalles en lugar de empezar de cero.
Cuanto más experimentes con prompts en Picasso IA, mejores serán las descripciones de tus herramientas y sus argumentos por defecto. Crea tu primera imagen hoy, luego integra el mejor prompt en tu servidor y deja que tu cliente de IA haga el resto.