Como criar um servidor MCP em C# e Java: ferramentas, transportes e testes
Crie o mesmo servidor MCP duas vezes, em C# com o pacote NuGet oficial e em Java com o Spring AI. Adicione um contador de palavras e uma ferramenta de imagem que chama a API do PicassoIA, escolha stdio ou HTTP e depois teste com o MCP Inspector e um cliente de IA real.
Um servidor MCP é um pequeno programa que espera uma mensagem JSON-RPC, executa uma função e envia o resultado de volta para um cliente de IA. É esse o truque inteiro. Ele só parece pesado em C# e Java porque os dois ecossistemas vêm com modelos de projeto, injeção de dependência, anotações e ferramentas de build. Este tutorial descomplica tudo isso. Você vai criar as mesmas duas ferramentas duas vezes, uma em C# com o pacote NuGet oficial ModelContextProtocol e outra em Java com o starter MCP do Spring AI, e depois testar as duas com o MCP Inspector e conectá-las a um cliente real.
As duas ferramentas são propositalmente simples. word_count comprova que a infraestrutura funciona, e generate_image chama a API do PicassoIA, para que um cliente de IA como o Claude possa pedir uma imagem ao seu código e receber um link de volta. O código segue a documentação oficial do SDK, mas fixe as versões dos pacotes no seu projeto e execute as verificações da seção de testes antes de confiar em qualquer parte dele.
O que um servidor MCP realmente faz
O Model Context Protocol é um padrão aberto que permite a uma aplicação de IA (o cliente) conversar com programas externos (os servidores) de forma consistente. O cliente se conecta, pergunta ao servidor o que ele sabe fazer e depois chama essas capacidades quando o modelo decide que precisa delas. Seu servidor nunca fala diretamente com o modelo. Ele responde às requisições, e o cliente cuida da conversa.
Ferramentas, recursos e prompts
Um servidor pode oferecer três tipos de capacidade:
Ferramentas são funções que o modelo pode chamar, como generate_image ou word_count. Cada uma tem um nome, uma descrição e um esquema JSON para suas entradas.
Recursos são dados somente leitura endereçados por uma URI, como um arquivo, uma linha de banco de dados ou um log.
Prompts são modelos de mensagem reutilizáveis que o usuário pode escolher em um menu.
A maioria dos servidores oferece apenas ferramentas, e são elas que este tutorial constrói. A descrição que você escreve para cada ferramenta importa mais do que o código dentro dela, porque o modelo lê esse texto para decidir quando chamá-la.
💡 Dica: Escreva as descrições das ferramentas como se estivesse orientando um novo colega de equipe. Diga o que a ferramenta faz, do que ela precisa e o que devolve.
Um protocolo, dois ecossistemas
Os dois SDKs escondem os detalhes do JSON-RPC. Você declara um método, descreve seus parâmetros, e a biblioteca monta o esquema e cuida do ciclo de requisição. Veja como as duas tecnologias se comparam:
Aspecto
C#
Java
Pacote principal
ModelContextProtocol
io.modelcontextprotocol.sdk:mcp ou os starters do Spring AI
Declarar uma ferramenta
atributo [McpServerTool]
anotação @Tool (Spring AI)
Transporte stdio
WithStdioServerTransport()
spring-ai-starter-mcp-server
Transporte HTTP
ModelContextProtocol.AspNetCore
spring-ai-starter-mcp-server-webmvc ou -webflux
Runtime
SDK do .NET
Java 17 ou mais recente
O SDK de C# é mantido em colaboração com a Microsoft, e as integrações do SDK de Java com Spring agora vivem no Spring AI. Nos dois casos você trabalha com anotações e injeção de dependência, então o código se parece com qualquer outro serviço daquela linguagem.
Configurar .NET e Java
Instale o necessário uma vez e as duas versões vão rodar no mesmo notebook. Você também precisa do Node.js, porque o MCP Inspector roda por meio do npx.
Requisitos do .NET
Instale o SDK do .NET e confirme com dotnet --version. Consulte a página do pacote ModelContextProtocol no NuGet para ver a versão mínima do framework que ele usa e escolha um SDK que a atenda. Um editor de código ajuda, mas um terminal simples basta para este projeto.
Requisitos do Java
Instale o JDK 17 ou mais recente, além do Maven ou do Gradle. O SDK de Java indica o Java 17 como base. Este tutorial usa Maven e um projeto Spring Boot, porque o starter do Spring AI elimina a maior parte do código de transporte que você escreveria à mão.
Os dois servidores leem a credencial do PicassoIA de uma variável de ambiente. Crie um token de API em picassoia.com/en/api (ele começa com pia_sk_) e exporte-o como PICASSOIA_API_TOKEN. Nunca o cole no código-fonte, e confira a página de preços do PicassoIA para ver quais planos incluem acesso à API.
Criar o servidor em C#
Criar o projeto
Execute estes comandos em uma pasta vazia:
dotnet new console -n PicassoMcp
cd PicassoMcp
dotnet add package ModelContextProtocol
dotnet add package Microsoft.Extensions.Hosting
Depois substitua o conteúdo 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();
Dois detalhes merecem atenção. O logger envia tudo para a saída de erro padrão, porque a saída padrão pertence ao protocolo. E WithToolsFromAssembly() varre o projeto em busca de classes marcadas como tipos de ferramenta, então você nunca registra ferramentas manualmente.
Adicionar sua primeira ferramenta
Crie PicassoTools.cs ao lado de Program.cs. Comece com a ferramenta simples:
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;
}
Os três atributos fazem todo o trabalho. [McpServerToolType] marca a classe, [McpServerTool] marca o método, e cada [Description] vira um texto que o modelo lê. Os nomes e tipos dos parâmetros se transformam automaticamente no esquema de entrada.
Chamar a API do PicassoIA
A API do PicassoIA é assíncrona e segue o padrão do Replicate. Você cria uma previsão com POST /v1/models/{owner}/{name}/predictions e depois consulta GET /v1/predictions/{id} até que o status mostre succeeded. A autenticação é feita com um token bearer, e uma conta pode executar 5 previsões ao mesmo tempo. Adicione este método dentro da mesma classe, com estas linhas extras using no topo do arquivo:
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}'.");
}
}
O parâmetro HttpClient é resolvido pela injeção de dependência, então o token que você configurou em Program.cs acompanha a chamada. Como o método aceita um CancellationToken, o cliente pode interromper uma geração lenta em vez de deixá-la travada. O modelo por trás da chamada é o PicassoIA Image, e o mesmo padrão funciona com os outros modelos que a API expõe.
Criar o servidor em Java
A versão em Java tem as mesmas duas ferramentas e o mesmo comportamento. Só muda o empacotamento.
Adicionar o starter do Spring AI
Crie um projeto Spring Boot, importe o bill of materials do Spring AI e depois adicione o starter stdio:
As três primeiras linhas são tão importantes quanto a dependência. Elas desligam o servidor web, o banner de inicialização e o padrão de log no console, porque qualquer coisa impressa na saída padrão quebra o fluxo stdio.
Escrever a classe da ferramenta
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 e @ToolParam cumprem o mesmo papel dos atributos do C#. O Spring os transforma em nome da ferramenta, descrição e esquema de entrada. O método retorna um Map, que o Spring AI serializa em JSON para o cliente.
Métodos anotados não ficam expostos até você registrá-los. Adicione um bean na sua classe 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();
}
}
Gere o build com mvn package e você obtém um JAR executável.
Usar o SDK Java puro
Se o seu projeto não usa Spring, adicione a dependência io.modelcontextprotocol.sdk:mcp e importe mcp-bom para manter as versões dos módulos alinhadas. O servidor em si tem poucas linhas:
StdioServerTransportProvider transport =
new StdioServerTransportProvider(McpJsonDefaults.getMapper());
McpSyncServer server = McpServer.sync(transport)
.serverInfo("picassoia-java", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
Esse trecho segue a linha 2.0 do SDK. Você adiciona cada ferramenta como um SyncToolSpecification com nome, descrição, esquema JSON e um handler de chamada. Detalhes do builder, como o construtor do transporte, mudam entre versões, então copie o trecho da ferramenta do README correspondente à sua versão. Quem usa Spring recebe tudo isso de graça, por isso o starter é o caminho mais curto.
Transporte stdio ou HTTP
O transporte define quem inicia o seu servidor e quem consegue acessá-lo.
Transporte
Ideal para
C#
Java
Stdio
Ferramentas locais iniciadas pelo cliente
WithStdioServerTransport()
spring-ai-starter-mcp-server
HTTP
Servidores remotos ou compartilhados
WithHttpTransport() e MapMcp()
Starter WebMVC ou WebFlux
Com stdio, o cliente inicia o seu processo e conversa com ele pela entrada e saída padrão. É a opção mais simples e nada fica exposto à rede, então comece por ela. Com HTTP, uma única implantação atende muitos clientes, o que significa que agora você é responsável por autenticação, TLS e limites de requisição.
Em C#, para mudar para HTTP, adicione o pacote ModelContextProtocol.AspNetCore e altere o host:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddMcpServer()
.WithHttpTransport()
.WithToolsFromAssembly();
var app = builder.Build();
app.MapMcp();
app.Run();
Em Java, troque o starter por spring-ai-starter-mcp-server-webmvc, ou pela variante WebFlux para apps reativos. A documentação do Spring AI lista /sse e /mcp/message como os endpoints padrão, e spring.ai.mcp.server.stdio=true para executar stdio junto com HTTP.
⚠️ Aviso: Um servidor HTTP cuja ferramenta chama uma API paga ou com limite de requisições precisa de autenticação antes de ir para a internet. Qualquer pessoa que consiga acessar o endpoint pode chamar a ferramenta.
Testar e depurar o servidor
Executar o MCP Inspector
O Inspector é uma ferramenta de navegador que se conecta ao seu servidor, lista as ferramentas e permite chamá-las com argumentos digitados à mão. Faça o build primeiro e depois inicie o Inspector com o comando do seu servidor:
Conecte-se, abra a aba Tools e liste as ferramentas. Você deve ver word_count e generate_image, cada uma com as descrições que você escreveu. Chame word_count com one two three e espere 3. Depois chame generate_image com um prompt curto e aguarde o JSON da previsão voltar.
Conectar um cliente real
No Claude Code, um único comando registra o servidor em C#:
claude mcp add picassoia-dotnet -e PICASSOIA_API_TOKEN=pia_sk_your_token -- dotnet run --project ./PicassoMcp --no-build
O Claude Desktop lê um arquivo JSON. Adicione os dois servidores em claude_desktop_config.json:
Reinicie o cliente e peça para ele "gerar uma foto 16:9 de um farol ao pôr do sol". Ele deve chamar sua ferramenta e devolver a previsão com o link da imagem.
Cinco erros que quebram servidores
Imprimir na saída padrão. Um Console.WriteLine ou System.out.println perdido corrompe o fluxo JSON-RPC. Registre logs apenas na saída de erro padrão.
Descrições vagas. Se duas ferramentas soam parecidas, o modelo escolhe a errada. Diga a entrada, a saída e os efeitos colaterais.
Consulta sem limite. O loop em C# para no cancelamento e o loop em Java para depois de 60 tentativas. Escolha um limite que combine com o seu modelo mais lento.
Segredos fixos no código. Leia os tokens do ambiente, como fazem os dois exemplos, e nunca os envie para o repositório.
Valores de retorno enormes. Devolva uma URL ou um resumo curto, não megabytes de dados que o modelo precisa ler.
Lembre-se também do teto de concorrência. Com 5 previsões permitidas ao mesmo tempo por conta, um cliente tagarela que dispara muitas chamadas generate_image vai começar a ver erros, então enfileire as chamadas ou devolva uma mensagem clara.
Como usar o Sonnet 5 no PicassoIA
Escrever código repetitivo é onde um modelo de IA mais economiza tempo aqui, e o Claude Sonnet 5 no PicassoIA foi feito para tarefas de programação e uso de ferramentas em várias etapas. Use-o para rascunhar novas ferramentas e depois verifique cada nome de API na documentação do SDK.
Abra a página do modelo. Vá até a página do Claude Sonnet 5 no PicassoIA e encontre a caixa de prompt.
Descreva uma ferramenta. Informe a linguagem, o SDK e o comportamento: "Escreva uma ferramenta MCP em C# usando o pacote ModelContextProtocol que converte uma temperatura entre Celsius e Fahrenheit."
Defina um prompt de sistema uma vez. Algo como "Nunca escreva na saída padrão. Use os atributos [McpServerTool] e [Description]" mantém todas as respostas coerentes.
Escolha o nível de esforço. Deixe em low para código repetitivo. Aumente para high ou max quando o código tocar vários arquivos ou quando um bug for teimoso.
Anexe uma captura de tela quando algo falhar. O modelo lê imagens, então uma captura de um erro do Inspector funciona tão bem quanto texto colado.
Teste o resultado. Cole o código no seu projeto e execute-o pelo Inspector antes de confiar nele.
Configuração
Padrão
Mude quando
effort
low
A tarefa exige raciocínio mais profundo
max_tokens
8192
A resposta é cortada no meio
system_prompt
vazio
Você quer um estilo de código ou um papel fixo
image
nenhum
Você quer compartilhar uma captura de tela ou um diagrama
Agora você tem dois servidores funcionando que entregam a geração de imagens a um cliente de IA. O próximo passo é ver o que o modelo por trás deles consegue fazer sozinho. Abra o PicassoIA Image, digite o mesmo prompt que você enviou pela sua ferramenta e compare o resultado. Depois mude a proporção, reescreva a iluminação e teste três variações de uma mesma cena. Quando uma renderização estiver perto, mas não certa, envie-a para o PicassoIA Image Editor Pro e corrija os detalhes em vez de começar do zero.
Quanto mais você experimentar prompts no Picasso IA, melhores ficarão as descrições das suas ferramentas e os argumentos padrão. Crie sua primeira imagem hoje, depois coloque o melhor prompt no seu servidor e deixe o cliente de IA fazer o resto.