← voltar ao índice

· 12 min de leitura · por Tiago

MCP em .NET: Guia Prático de Implementação

Se você acompanha o ecossistema de IA generativa nos últimos meses, provavelmente já esbarrou na sigla MCP. O Model Context Protocol nasceu como uma proposta da Anthropic para resolver um problema bem concreto: como conectar modelos de linguagem a ferramentas, dados e sistemas externos de forma padronizada, sem que cada fornecedor de LLM ou cada aplicação precise reinventar seu próprio formato de integração. O protocolo pegou rápido, foi adotado por diversas plataformas e hoje já existe um SDK oficial em .NET, mantido em parceria entre a comunidade e a Microsoft, que torna viável construir servidores e clientes MCP em C# com um esforço razoável.

Neste post vamos entender por que o MCP existe, como ele se encaixa na arquitetura de aplicações .NET, e vamos colocar a mão na massa construindo um servidor MCP simples, expondo ferramentas e recursos, hospedando isso via HTTP em ASP.NET Core, e consumindo tudo isso a partir de um cliente. No caminho, vamos discutir armadilhas reais que aparecem quando esse tipo de integração sai do protótipo e vai para produção.

O problema que o MCP resolve

Antes do MCP, integrar um LLM a ferramentas externas geralmente significava usar o mecanismo de "function calling" ou "tool calling" específico de cada provedor: OpenAI tinha seu formato de funções, outros provedores tinham variações semelhantes mas não idênticas. Isso funcionava bem quando você controlava tanto o modelo quanto a aplicação, mas criava dois problemas sérios. Primeiro, cada nova ferramenta exigia código de integração específico para cada modelo ou cliente que você quisesse suportar. Segundo, não havia um jeito padronizado de expor "capacidades" (ferramentas, dados, prompts reutilizáveis) de forma que qualquer cliente compatível pudesse descobri-las e usá-las sem acordo prévio.

O MCP resolve isso definindo um protocolo de comunicação, baseado em JSON-RPC, entre um "host" (a aplicação que roda o modelo, como um cliente de chat ou um agente) e um ou mais "servidores MCP" que expõem três tipos de capacidades: ferramentas (tools, que são funções que o modelo pode invocar), recursos (resources, que são dados que podem ser lidos e injetados no contexto) e prompts (templates reutilizáveis de instrução). A ideia central é que o servidor MCP não precisa saber nada sobre qual modelo está do outro lado, e o cliente não precisa saber os detalhes de implementação do servidor. Ele apenas descobre, via protocolo, quais ferramentas e recursos estão disponíveis, e o modelo decide quando invocá-los.

Essa separação é exatamente o tipo de desacoplamento que já perseguimos em outras áreas da arquitetura de software: assim como uma API REST bem desenhada esconde detalhes de implementação do consumidor, um servidor MCP esconde a lógica de negócio e o acesso a dados por trás de um contrato padronizado que qualquer LLM compatível consegue consumir.

Por que isso importa para quem trabalha com .NET

Empresas que já têm sistemas legados ou serviços internos robustos em .NET frequentemente querem dar a esses sistemas uma "interface conversacional": permitir que um assistente de IA consulte o status de um pedido, dispare um workflow, ou busque informações em um banco de dados corporativo. Antes do MCP, isso normalmente significava construir uma API REST específica para consumo por LLM, documentá-la em um formato ad hoc, e escrever cola específica para cada ferramenta de IA que a empresa usasse (Claude Desktop, um agente customizado, GitHub Copilot, etc).

Com o SDK oficial ModelContextProtocol para .NET, você pode expor uma aplicação existente (ou uma nova) como um servidor MCP usando atributos e injeção de dependência, do mesmo jeito que já está acostumado a fazer com Minimal APIs ou controllers. Isso significa que o investimento em construir o servidor MCP uma vez é reaproveitado por qualquer cliente que fale o protocolo, seja o Claude Desktop, o Visual Studio Code com extensões de IA, ou um agente próprio construído com Semantic Kernel ou Microsoft.Extensions.AI.

Construindo um servidor MCP mínimo

Vamos começar pelo caso mais simples: um servidor MCP que roda via stdio (entrada e saída padrão), que é o transporte mais comum para uso local, por exemplo quando o Claude Desktop ou o VS Code inicia o processo do seu servidor diretamente.

Primeiro, criamos um projeto de console e adicionamos o pacote:

dotnet new console -n MeuServidorMcp
cd MeuServidorMcp
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting

O uso do Microsoft.Extensions.Hosting não é coincidência: o SDK de MCP em .NET foi desenhado para se integrar naturalmente ao IHostBuilder, o que significa que você ganha de graça injeção de dependência, configuração e logging, exatamente como em qualquer aplicação ASP.NET Core.

using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;

var builder = Host.CreateApplicationBuilder(args);

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

var app = builder.Build();
await app.RunAsync();

Esse trecho faz três coisas importantes. AddMcpServer() registra os serviços internos do protocolo (serialização JSON-RPC, gerenciamento de sessão, descoberta de capacidades). WithStdioServerTransport() diz que a comunicação vai acontecer via entrada e saída padrão do processo, o que é essencial porque muitos clientes MCP locais simplesmente iniciam seu executável e conversam com ele por stdio, sem abrir portas de rede. Já WithToolsFromAssembly() faz uma varredura por reflexão em busca de classes marcadas com atributos específicos do MCP e registra automaticamente qualquer método marcado como ferramenta, sem que você precise registrar cada uma manualmente.

Agora vamos definir uma ferramenta de verdade:

using System.ComponentModel;
using ModelContextProtocol.Server;

[McpServerToolType]
public static class PedidosTools
{
    [McpServerTool, Description("Consulta o status de um pedido pelo número.")]
    public static string ConsultarStatusPedido(
        [Description("Número identificador do pedido")] string numeroPedido)
    {
        // Aqui entraria uma chamada real a um repositório ou serviço.
        var statusFake = numeroPedido.EndsWith("9") ? "Entregue" : "Em transporte";
        return $"O pedido {numeroPedido} está com status: {statusFake}.";
    }
}

Repare que o atributo [McpServerToolType] marca a classe como um contêiner de ferramentas, e [McpServerTool] marca o método específico. As descrições passadas via [Description] não são apenas documentação para humanos: elas são enviadas ao modelo como parte do schema da ferramenta, e é a partir delas que o LLM decide quando e como chamar essa função. Um erro comum aqui é escrever descrições vagas demais, como "Consulta pedido", o que faz o modelo hesitar ou usar a ferramenta de forma incorreta. Quanto mais precisa e específica for a descrição (incluindo o formato esperado do parâmetro), melhor a taxa de acerto do modelo ao decidir invocar a ferramenta.

Com isso, já temos um servidor MCP funcional. Para testá-lo localmente, ferramentas como o Claude Desktop permitem registrar esse executável na configuração local, e o cliente inicia o processo automaticamente quando necessário.

Hospedando o servidor MCP via HTTP em ASP.NET Core

O transporte via stdio é ótimo para uso local, mas em cenários corporativos você geralmente quer expor o servidor MCP como um serviço de rede, acessível remotamente e com autenticação adequada. Para isso, o SDK também suporta hospedagem via ASP.NET Core, usando Server-Sent Events (SSE) ou o transporte HTTP Streamable mais recente do protocolo.

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();

var app = builder.Build();

app.MapMcp("/mcp");

app.Run();

Aqui a diferença central é a troca de WithStdioServerTransport() por WithHttpTransport(), e o uso de MapMcp, que registra os endpoints necessários (tipicamente um endpoint de conexão SSE e um endpoint de envio de mensagens) dentro do pipeline padrão do ASP.NET Core. Isso é poderoso porque significa que o servidor MCP convive no mesmo processo que o resto da sua API: você pode aplicar os mesmos middlewares de autenticação, autorização, rate limiting e observabilidade que já usa no restante da aplicação.

Um ponto que costuma pegar equipes de surpresa é justamente a autenticação. O protocolo MCP em si não define um mecanismo de auth obrigatório, deixando isso a cargo do transporte. Na prática, isso significa que você deve tratar o endpoint MCP como qualquer outro endpoint sensível da sua API: exigir token Bearer, validar escopos OAuth, e nunca expor ferramentas que executam ações destrutivas (como cancelar um pedido ou disparar um pagamento) sem uma camada extra de confirmação ou permissão explícita. É tentador, durante o desenvolvimento, deixar o endpoint MCP anônimo "só para testar", e esquecer de fechar isso antes de ir para produção. Trate esse endpoint com o mesmo rigor que trataria qualquer rota de escrita de dados.

Expondo recursos e prompts

Além de ferramentas, o MCP também permite expor recursos, que são pedaços de dados que o cliente pode ler e injetar no contexto do modelo sem que isso conte como uma "chamada de função" propriamente dita. Isso é útil para documentos, configurações, ou trechos de dados que o modelo deveria simplesmente "ler" antes de responder.

[McpServerResourceType]
public static class DocumentacaoResources
{
    [McpServerResource(UriTemplate = "docs://politica-devolucao")]
    [Description("Política de devolução vigente da empresa.")]
    public static string PoliticaDevolucao()
    {
        return "Devoluções são aceitas em até 30 dias corridos, mediante nota fiscal.";
    }
}

A diferença conceitual entre ferramenta e recurso é sutil, mas importante para o design da sua integração: ferramentas representam ações ou consultas que o modelo decide invocar dinamicamente durante o raciocínio, enquanto recursos representam dados que o host da aplicação pode escolher anexar ao contexto de antemão, muitas vezes sem envolver decisão do modelo. Um erro de design comum é modelar tudo como ferramenta, incluindo dados estáticos que deveriam ser recursos, o que aumenta desnecessariamente o número de "chamadas de função" que o modelo precisa considerar a cada turno, encarecendo a interação e aumentando a chance de escolhas erradas.

Consumindo um servidor MCP a partir de um cliente .NET

Do outro lado, se você está construindo um agente próprio (por exemplo, usando Microsoft.Extensions.AI ou Semantic Kernel) e quer que ele converse com servidores MCP, o SDK também oferece um cliente:

using ModelContextProtocol.Client;

await using var mcpClient = await McpClientFactory.CreateAsync(
    new StdioClientTransport(new()
    {
        Name = "servidor-pedidos",
        Command = "dotnet",
        Arguments = ["run", "--project", "../MeuServidorMcp"]
    }));

var tools = await mcpClient.ListToolsAsync();

foreach (var tool in tools)
{
    Console.WriteLine($"{tool.Name}: {tool.Description}");
}

Esse trecho inicia o processo do servidor MCP como um subprocesso e lista as ferramentas disponíveis. Na prática, esse cliente costuma ser conectado diretamente a um IChatClient do Microsoft.Extensions.AI, de forma que as ferramentas descobertas via MCP sejam automaticamente convertidas para o formato de function calling do modelo que você está usando, permitindo que o LLM decida quando invocá-las durante a conversa. Isso fecha o ciclo: o mesmo servidor MCP que você expôs para o Claude Desktop pode ser consumido pelo seu próprio agente construído em cima da OpenAI, do Azure AI Foundry, ou de qualquer outro provedor compatível com Microsoft.Extensions.AI, sem duplicar lógica de integração.

Armadilhas comuns e trade-offs

Um ponto que aparece cedo em projetos reais é a questão da granularidade das ferramentas. É tentador expor uma única ferramenta genérica, tipo ExecutarConsultaSql(string sql), mas isso é perigoso e também prejudica a qualidade das decisões do modelo. Ferramentas devem ser específicas, com nomes e parâmetros claros, e a validação de entrada deve acontecer no servidor, nunca confiando que o modelo vai sempre enviar dados bem formados. Trate cada ferramenta MCP como um endpoint de API pública: valide, sanitize, e trate exceções de forma que o erro retornado ao modelo seja compreensível e não vaze detalhes internos sensíveis.

Outro ponto de atenção é a latência de descoberta. Em transportes remotos via HTTP, a listagem de ferramentas e recursos acontece em uma chamada separada antes de qualquer invocação real, e se o seu servidor expõe dezenas de ferramentas com schemas complexos, isso pode adicionar latência perceptível ao início de cada conversa. Uma prática comum é agrupar ferramentas por domínio em servidores MCP separados, e deixar o host da aplicação decidir quais servidores conectar dependendo do contexto da conversa, em vez de manter um único servidor monolítico com centenas de capacidades.

Versionamento também merece cuidado. Como as descrições das ferramentas viram parte do "prompt" implícito enviado ao modelo, mudar o comportamento de uma ferramenta sem mudar seu nome pode fazer o modelo continuar chamando-a da forma antiga, gerando resultados inesperados. O ideal é tratar mudanças de contrato de ferramentas com a mesma disciplina de versionamento semântico que você aplicaria a uma API pública, inclusive documentando breaking changes e, quando necessário, criando uma nova ferramenta com nome diferente em vez de alterar silenciosamente o comportamento da existente.

Por fim, vale lembrar que o MCP ainda é um protocolo relativamente jovem, e o SDK oficial em .NET, embora já use convenções maduras de ASP.NET Core, ainda passa por mudanças de API entre versões de pré-lançamento. Fixar a versão do pacote no seu arquivo de projeto e acompanhar o changelog antes de atualizar é uma precaução simples que evita surpresas em builds automatizados.

Quando vale a pena usar MCP

Se sua aplicação só precisa integrar um único modelo a um punhado fixo de funções internas, construir um servidor MCP completo pode ser over-engineering: o mecanismo nativo de function calling do provedor de LLM que você já usa costuma resolver isso mais rápido. O MCP compensa quando você quer que essas capacidades sejam reutilizáveis por múltiplos clientes de IA diferentes (Claude Desktop, agentes internos, extensões de editor), quando você quer desacoplar o time que mantém a integração de dados do time que constrói o agente conversacional, ou quando está construindo uma plataforma que várias equipes vão consumir de formas distintas. Prefira o MCP também quando a governança importa: como o protocolo padroniza descoberta e invocação, fica mais fácil auditar quais ferramentas existem, quem tem acesso a elas, e registrar logs centralizados de uso.

Conclusão

O MCP não é apenas mais uma moda passageira do mundo de IA generativa: ele resolve um problema real de acoplamento entre modelos e sistemas externos, trazendo para esse espaço práticas que a comunidade .NET já domina bem em outros contextos, como injeção de dependência, hospedagem via ASP.NET Core e versionamento de contratos. Construir um servidor MCP em C# hoje é surpreendentemente direto graças ao SDK oficial, mas o sucesso da integração depende menos do código de "plumbing" e mais do cuidado no design das ferramentas expostas: granularidade certa, descrições precisas, validação rigorosa e autenticação adequada. Se você já tem sistemas .NET em produção e está pensando em dar a eles uma camada conversacional, vale a pena começar pequeno, com um servidor expondo duas ou três ferramentas bem definidas, e evoluir a partir daí conforme o protocolo e o SDK amadurecem.

← voltar ao índice