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.

Como criar um servidor MCP em C# e Java: ferramentas, transportes e testes
Cristian Da Conceicao
Fundador do Picasso IA

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.

Mão desenhando setas entre três caixas em um quadro branco para esboçar a arquitetura de um servidor MCP

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:

AspectoC#Java
Pacote principalModelContextProtocolio.modelcontextprotocol.sdk:mcp ou os starters do Spring AI
Declarar uma ferramentaatributo [McpServerTool]anotação @Tool (Spring AI)
Transporte stdioWithStdioServerTransport()spring-ai-starter-mcp-server
Transporte HTTPModelContextProtocol.AspNetCorespring-ai-starter-mcp-server-webmvc ou -webflux
RuntimeSDK do .NETJava 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.

Vista de cima de uma mesa com um notebook, um diagrama em caderno, café e um cabo USB-C

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.

Dedos de um desenvolvedor apoiados em um notebook enquanto escreve código de servidor em C#

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.

Desenvolvedor barbudo trabalhando em uma mesa em pé ao lado de uma janela alta em um projeto Java

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:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-bom</artifactId>
      <version>${spring-ai.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server</artifactId>
  </dependency>
</dependencies>

Defina spring-ai.version com a versão que você quer. Depois adicione src/main/resources/application.properties:

spring.main.web-application-type=none
spring.main.banner-mode=off
logging.pattern.console=
spring.ai.mcp.server.name=picassoia-java
spring.ai.mcp.server.version=1.0.0

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.

Dois engenheiros programando em par em uma mesa comprida, apontando para o mesmo monitor

Transporte stdio ou HTTP

O transporte define quem inicia o seu servidor e quem consegue acessá-lo.

TransporteIdeal paraC#Java
StdioFerramentas locais iniciadas pelo clienteWithStdioServerTransport()spring-ai-starter-mcp-server
HTTPServidores remotos ou compartilhadosWithHttpTransport() 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.

Cabos de rede Ethernet conectados a um painel de distribuição metálico em um rack de equipamentos

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:

dotnet build
npx @modelcontextprotocol/inspector dotnet run --project ./PicassoMcp --no-build

npx @modelcontextprotocol/inspector java -jar target/picassoia-mcp-0.0.1.jar

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.

Patinho de borracha ao lado de um notebook aberto sobre uma mesa de nogueira durante uma sessão tardia de depuração

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:

{
  "mcpServers": {
    "picassoia-dotnet": {
      "command": "dotnet",
      "args": ["run", "--project", "C:/work/PicassoMcp", "--no-build"],
      "env": { "PICASSOIA_API_TOKEN": "pia_sk_your_token" }
    },
    "picassoia-java": {
      "command": "java",
      "args": ["-jar", "C:/work/picassoia-mcp/target/picassoia-mcp-0.0.1.jar"],
      "env": { "PICASSOIA_API_TOKEN": "pia_sk_your_token" }
    }
  }
}

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

  1. 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.
  2. Descrições vagas. Se duas ferramentas soam parecidas, o modelo escolhe a errada. Diga a entrada, a saída e os efeitos colaterais.
  3. 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.
  4. Segredos fixos no código. Leia os tokens do ambiente, como fazem os dois exemplos, e nunca os envie para o repositório.
  5. 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.

Mulher lendo em um tablet, sentada na janela de um café com um caderno e uma xícara de café

  1. Abra a página do modelo. Vá até a página do Claude Sonnet 5 no PicassoIA e encontre a caixa de prompt.
  2. 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."
  3. 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.
  4. 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.
  5. 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.
  6. Teste o resultado. Cole o código no seu projeto e execute-o pelo Inspector antes de confiar nele.
ConfiguraçãoPadrãoMude quando
effortlowA tarefa exige raciocínio mais profundo
max_tokens8192A resposta é cortada no meio
system_promptvazioVocê quer um estilo de código ou um papel fixo
imagenenhumVocê quer compartilhar uma captura de tela ou um diagrama

Se quiser uma segunda opinião sobre o mesmo prompt, compare a saída de Claude Fable 5, GPT 5.6 Sol ou Kimi K2.6.

Experimente a geração de imagens no Picasso IA

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.

Mãos segurando uma fotografia impressa de uma floresta de pinheiros com neblina em frente a uma janela de estúdio

Compartilhe este artigo

Escolha seu idioma