DEV Community

Cover image for 🧩 Minha Primeira Comunicação com MCP e .NET – Parte 2
Danilo O. Pinheiro, dopme.io
Danilo O. Pinheiro, dopme.io

Posted on

🧩 Minha Primeira Comunicação com MCP e .NET – Parte 2

Integração Completa com gRPC

Nesta segunda parte da série "Minha Primeira Comunicação com MCP e .NET", exploramos como realizar uma integração completa com gRPC, permitindo que o MCP (Model Context Protocol) comunique-se com aplicações .NET de maneira eficiente, tipada e de alta performance.


🚀 Introdução

O MCP (Model Context Protocol) surge como uma camada de interoperabilidade entre modelos de linguagem, agentes e aplicações corporativas. No ecossistema .NET, a integração com gRPC é uma escolha natural, combinando tipagem forte, baixa latência e eficiência binária via Protocol Buffers — características ideais para comunicação entre processos e serviços distribuídos.

Este artigo demonstra, de forma arquitetural e prática, como criar uma ponte robusta entre MCP e aplicações .NET via gRPC, garantindo integração segura e escalável para sistemas modernos.


⚙️ O que é gRPC e por que usá-lo?

O gRPC (Google Remote Procedure Call) é um framework de comunicação que utiliza Protocol Buffers (protobuf) como formato de serialização binária. Ele substitui o tradicional REST em cenários que exigem alta performance, streaming bidirecional e comunicação eficiente entre microsserviços.

Benefícios principais:

  • 🚀 Performance superior: comunicação até 7x mais rápida que JSON/REST
  • 🔒 Contrato tipado: validação em tempo de compilação entre cliente e servidor
  • ⚙️ Streaming nativo: suporte a comunicação unidirecional e bidirecional
  • 🌐 Multiplataforma: compatibilidade com C#, Python, Go, Java, Node.js e mais
  • 📦 Payload compacto: serialização binária reduz tráfego de rede

🧠 Arquitetura da Integração MCP + gRPC + .NET

A integração segue uma arquitetura de orquestração bidirecional onde o MCP atua como cliente inteligente:

┌─────────────┐         ┌──────────────────┐         ┌─────────────────┐
│             │         │                  │         │                 │
│  MCP Agent  │ ◄────► │  gRPC Server     │ ◄────► │  Domain Layer   │
│  (Cliente)  │         │  (.NET 8)        │         │  Business Logic │
│             │         │                  │         │                 │
└─────────────┘         └──────────────────┘         └─────────────────┘
     ▲                           ▲                           ▲
     │                           │                           │
  Contexto                   Validação                   Persistência
  Semântico                  & Roteamento                 & Regras
Enter fullscreen mode Exit fullscreen mode

Fluxo de comunicação:

  1. O MCP Agent envia comandos contextualizados ou dados estruturados
  2. O Servidor gRPC valida, autentica e roteia as mensagens
  3. A camada de domínio aplica regras de negócio e retorna resultados
  4. Respostas retornam via stream ou chamadas unitárias

🏗️ Implementação Passo a Passo

1️⃣ Criar o Projeto Base

dotnet new grpc -n MCPPipeline.GrpcServer
cd MCPPipeline.GrpcServer
Enter fullscreen mode Exit fullscreen mode

O template gera automaticamente:

  • Estrutura de Protos/ para definição de contratos
  • Pasta Services/ para implementações
  • Configuração Kestrel otimizada para HTTP/2

2️⃣ Definir o Contrato Protobuf

Crie o arquivo Protos/mcp.proto:

syntax = "proto3";

option csharp_namespace = "MCPPipeline.Grpc";

package mcp;

// Serviço principal de comunicação MCP
service MCPService {
  // Comando único com resposta direta
  rpc SendCommand (MCPRequest) returns (MCPResponse);

  // Streaming bidirecional para múltiplas operações
  rpc StreamUpdates (stream MCPRequest) returns (stream MCPResponse);

  // Health check para monitoramento
  rpc HealthCheck (HealthRequest) returns (HealthResponse);
}

message MCPRequest {
  string command = 1;           // Comando a ser executado
  string payload = 2;           // Dados contextuais (JSON)
  map<string, string> metadata = 3;  // Metadados adicionais
  int64 timestamp = 4;          // Timestamp da requisição
}

message MCPResponse {
  string result = 1;            // Resultado da operação
  string status = 2;            // Status: OK, ERROR, PROCESSING
  string error_message = 3;     // Mensagem de erro (se houver)
  int64 processing_time_ms = 4; // Tempo de processamento
}

message HealthRequest {}

message HealthResponse {
  string status = 1;
  string version = 2;
}
Enter fullscreen mode Exit fullscreen mode

Configure o .csproj para incluir o arquivo proto:

<ItemGroup>
  <Protobuf Include="Protos\mcp.proto" GrpcServices="Server" />
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

3️⃣ Implementar o Serviço gRPC

using Grpc.Core;
using MCPPipeline.Grpc;
using System.Diagnostics;

namespace MCPPipeline.GrpcServer.Services;

public class MCPServiceImpl : MCPService.MCPServiceBase
{
    private readonly ILogger<MCPServiceImpl> _logger;
    private readonly IMCPCommandHandler _commandHandler;

    public MCPServiceImpl(
        ILogger<MCPServiceImpl> logger,
        IMCPCommandHandler commandHandler)
    {
        _logger = logger;
        _commandHandler = commandHandler;
    }

    public override async Task<MCPResponse> SendCommand(
        MCPRequest request, 
        ServerCallContext context)
    {
        var sw = Stopwatch.StartNew();

        try
        {
            _logger.LogInformation(
                "Comando recebido: {Command} | Payload: {Payload}", 
                request.Command, 
                request.Payload);

            // Processar comando via handler de domínio
            var result = await _commandHandler.ExecuteAsync(
                request.Command, 
                request.Payload,
                context.CancellationToken);

            sw.Stop();

            return new MCPResponse
            {
                Result = result,
                Status = "OK",
                ProcessingTimeMs = sw.ElapsedMilliseconds
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Erro ao processar comando: {Command}", request.Command);

            return new MCPResponse
            {
                Status = "ERROR",
                ErrorMessage = ex.Message,
                ProcessingTimeMs = sw.ElapsedMilliseconds
            };
        }
    }

    public override async Task StreamUpdates(
        IAsyncStreamReader<MCPRequest> requestStream,
        IServerStreamWriter<MCPResponse> responseStream,
        ServerCallContext context)
    {
        await foreach (var request in requestStream.ReadAllAsync(context.CancellationToken))
        {
            _logger.LogInformation("Stream recebido: {Command}", request.Command);

            var response = new MCPResponse
            {
                Result = $"Processando → {request.Command}",
                Status = "PROCESSING"
            };

            await responseStream.WriteAsync(response);
        }
    }

    public override Task<HealthResponse> HealthCheck(
        HealthRequest request, 
        ServerCallContext context)
    {
        return Task.FromResult(new HealthResponse
        {
            Status = "Healthy",
            Version = "1.0.0"
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

4️⃣ Implementar o Command Handler (Domain Layer)

public interface IMCPCommandHandler
{
    Task<string> ExecuteAsync(string command, string payload, CancellationToken ct);
}

public class MCPCommandHandler : IMCPCommandHandler
{
    private readonly ILogger<MCPCommandHandler> _logger;

    public MCPCommandHandler(ILogger<MCPCommandHandler> logger)
    {
        _logger = logger;
    }

    public async Task<string> ExecuteAsync(
        string command, 
        string payload, 
        CancellationToken ct)
    {
        return command switch
        {
            "GetStatus" => await GetSystemStatusAsync(ct),
            "ProcessData" => await ProcessDataAsync(payload, ct),
            "AnalyzeContext" => await AnalyzeContextAsync(payload, ct),
            _ => throw new InvalidOperationException($"Comando desconhecido: {command}")
        };
    }

    private async Task<string> GetSystemStatusAsync(CancellationToken ct)
    {
        await Task.Delay(10, ct); // Simula processamento
        return "Sistema operacional | Uptime: 99.9%";
    }

    private async Task<string> ProcessDataAsync(string payload, CancellationToken ct)
    {
        _logger.LogInformation("Processando dados: {Payload}", payload);
        await Task.Delay(50, ct);
        return $"Dados processados com sucesso: {payload.Length} caracteres";
    }

    private async Task<string> AnalyzeContextAsync(string payload, CancellationToken ct)
    {
        await Task.Delay(100, ct);
        return $"Análise contextual concluída | Tokens: {payload.Split(' ').Length}";
    }
}
Enter fullscreen mode Exit fullscreen mode

5️⃣ Configurar o Servidor no Program.cs

using MCPPipeline.GrpcServer.Services;

var builder = WebApplication.CreateBuilder(args);

// Adicionar serviços gRPC
builder.Services.AddGrpc(options =>
{
    options.EnableDetailedErrors = true;
    options.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4MB
});

// Registrar handlers de domínio
builder.Services.AddScoped<IMCPCommandHandler, MCPCommandHandler>();

var app = builder.Build();

// Mapear serviços gRPC
app.MapGrpcService<MCPServiceImpl>();

// Endpoint HTTP para verificação
app.MapGet("/", () => Results.Ok(new 
{ 
    Service = "MCP gRPC Server", 
    Status = "Running",
    Version = "1.0.0"
}));

app.Run();
Enter fullscreen mode Exit fullscreen mode

Configuração do appsettings.json:

{
  "Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http2"
    }
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Grpc": "Debug"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

6️⃣ Criar Cliente MCP (Simulação)

using Grpc.Net.Client;
using MCPPipeline.Grpc;

// Configurar canal gRPC
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new MCPService.MCPServiceClient(channel);

// Exemplo 1: Comando único
var response = await client.SendCommandAsync(new MCPRequest
{
    Command = "GetStatus",
    Payload = "Kernel v2.0",
    Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
});

Console.WriteLine($"✅ Resposta: {response.Result}");
Console.WriteLine($"⏱️  Tempo: {response.ProcessingTimeMs}ms");

// Exemplo 2: Streaming bidirecional
using var streamingCall = client.StreamUpdates();

// Enviar múltiplos comandos
await streamingCall.RequestStream.WriteAsync(new MCPRequest 
{ 
    Command = "ProcessData", 
    Payload = "Sample data batch 1" 
});

await streamingCall.RequestStream.WriteAsync(new MCPRequest 
{ 
    Command = "AnalyzeContext", 
    Payload = "Context information" 
});

await streamingCall.RequestStream.CompleteAsync();

// Receber respostas
await foreach (var streamResponse in streamingCall.ResponseStream.ReadAllAsync())
{
    Console.WriteLine($"📥 Stream: {streamResponse.Result}");
}
Enter fullscreen mode Exit fullscreen mode

🔗 Integração com MCP Kernel

Para integrar com o Semantic Kernel ou outro framework MCP:

public class MCPGrpcPlugin
{
    private readonly MCPService.MCPServiceClient _grpcClient;

    public MCPGrpcPlugin(MCPService.MCPServiceClient grpcClient)
    {
        _grpcClient = grpcClient;
    }

    [KernelFunction, Description("Executa comando no servidor MCP via gRPC")]
    public async Task<string> ExecuteCommandAsync(
        [Description("Comando a executar")] string command,
        [Description("Dados contextuais")] string payload)
    {
        var response = await _grpcClient.SendCommandAsync(new MCPRequest
        {
            Command = command,
            Payload = payload,
            Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
        });

        return response.Status == "OK" 
            ? response.Result 
            : $"Erro: {response.ErrorMessage}";
    }
}
Enter fullscreen mode Exit fullscreen mode

Registro no Kernel:

var kernel = Kernel.CreateBuilder()
    .AddOpenAIChatCompletion("gpt-4", apiKey)
    .Build();

var grpcChannel = GrpcChannel.ForAddress("https://localhost:5001");
var grpcClient = new MCPService.MCPServiceClient(grpcChannel);

kernel.Plugins.AddFromObject(new MCPGrpcPlugin(grpcClient), "MCPCommands");
Enter fullscreen mode Exit fullscreen mode

🔒 Boas Práticas e Segurança

Autenticação e Segurança

// Adicionar autenticação JWT
builder.Services.AddAuthentication("Bearer")
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true
        };
    });

builder.Services.AddAuthorization();
Enter fullscreen mode Exit fullscreen mode

Observabilidade

// OpenTelemetry para tracing distribuído
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddAspNetCoreInstrumentation()
        .AddGrpcClientInstrumentation()
        .AddOtlpExporter());
Enter fullscreen mode Exit fullscreen mode

Resiliência

// Polly para retry e circuit breaker
services.AddGrpcClient<MCPService.MCPServiceClient>(o =>
{
    o.Address = new Uri("https://localhost:5001");
})
.AddPolicyHandler(Policy
    .HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .WaitAndRetryAsync(3, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
Enter fullscreen mode Exit fullscreen mode

📊 Quando Usar gRPC com MCP

Microsserviços internos com alta frequência de comunicação

Streaming de dados em tempo real (logs, métricas, eventos)

Pipelines de IA que exigem baixa latência (<10ms)

Comunicação entre agentes distribuídos em Kubernetes

APIs internas onde tipagem forte previne erros em produção

Evite usar quando:

  • Precisar de suporte em navegadores web (sem HTTP/2 full)
  • Integração com sistemas legados sem suporte a Protobuf
  • APIs públicas onde REST/JSON é mais apropriado

🎯 Conclusão

A integração entre MCP e gRPC no .NET cria uma base sólida para sistemas inteligentes e distribuídos, unindo a velocidade e confiabilidade do gRPC com a inteligência contextual do Model Context Protocol. Essa combinação é ideal para pipelines de IA corporativos onde precisão, performance e interoperabilidade são essenciais.

Próximos passos:

  • Implementar autenticação mTLS para produção
  • Adicionar cache distribuído (Redis) para respostas frequentes
  • Configurar balanceamento de carga com health checks gRPC
  • Instrumentar com OpenTelemetry para observabilidade completa

Na Parte 3 desta série, exploraremos observabilidade e tracing distribuído entre MCP e .NET usando OpenTelemetry e Datadog.


🤝 Conecte-se Comigo

Se você trabalha com .NET moderno e quer dominar arquitetura, C#, observabilidade, DevOps ou interoperabilidade:

💼 LinkedIn
✍️ Medium

📬 contato@dopme.io

📬 devsfree@devsfree.com.br


📚 Referências:


² Porque eis que teus inimigos fazem tumulto, e os que te odeiam levantaram a cabeça. ³ Tomaram astuto conselho contra o teu povo, e consultaram contra os teus escondidos. Salmos 83:2,3

Top comments (0)