---
title: "Zig e MCP: Como Criar um Servidor JSON-RPC para Agentes de IA"
url: "https://ziglang.com.br/artigos/zig-mcp-servidor-json-rpc-agentes-ia/"
markdown_url: "https://ziglang.com.br/artigos/zig-mcp-servidor-json-rpc-agentes-ia.MD"
description: "Guia prático para entender onde Zig encaixa no Model Context Protocol: transporte stdio, mensagens JSON-RPC, schemas, ferramentas, segurança e arquitetura para agentes de IA."
date: "2026-05-18"
author: ""
---

# Zig e MCP: Como Criar um Servidor JSON-RPC para Agentes de IA

Guia prático para entender onde Zig encaixa no Model Context Protocol: transporte stdio, mensagens JSON-RPC, schemas, ferramentas, segurança e arquitetura para agentes de IA.


O Model Context Protocol, conhecido como MCP, virou uma das formas mais práticas de conectar agentes de IA a ferramentas reais: arquivos, bancos, APIs internas, documentação, filas, navegadores, dashboards e qualquer outro recurso que precise sair do prompt e entrar no sistema. Para quem trabalha com Zig, a pergunta natural é: faz sentido escrever um servidor MCP em Zig?

A resposta curta é: sim, quando o servidor precisa ser pequeno, previsível, fácil de distribuir e cuidadoso com recursos. Um servidor MCP não precisa ser um monólito com framework pesado. Na forma mais comum, ele conversa por `stdio`, recebe mensagens JSON-RPC, declara ferramentas disponíveis e executa chamadas com entrada e saída estruturadas. Isso combina bem com Zig: binário único, controle explícito de memória, parsing previsível e boa integração com processos de sistema.

Este artigo mostra a arquitetura de um servidor MCP em Zig. Não é uma implementação completa do protocolo, porque o ecossistema ainda se move rápido e cada cliente pode ter detalhes próprios. A ideia é dar o mapa: transporte, mensagens, parsing JSON, registro de ferramentas, segurança e pontos onde Zig ajuda ou atrapalha. Para a base técnica, vale cruzar com a referência de [`std.json`](/stdlib/std-json/), a receita de [parsear JSON em Zig](/receitas/zig-parse-json/), o guia de [servidor HTTP em produção](/artigos/zig-http-server-producao/) e o artigo sobre [parsing e serialização de alta performance](/artigos/zig-processamento-dados-parsing-serializacao/).

## O que um servidor MCP faz

Um servidor MCP expõe capacidades para um cliente de IA. Em vez de o modelo "inventar" uma resposta sobre um repositório, ele pode chamar uma ferramenta como `buscar_arquivo`, `consultar_issue`, `listar_tarefas`, `rodar_teste` ou `pegar_metricas`. O servidor descreve essas ferramentas com nome, descrição e schema de entrada. O cliente decide quando chamar. O servidor executa e devolve um resultado estruturado.

Na prática, o ciclo fica assim:

1. O cliente inicia o processo do servidor.
2. Cliente e servidor trocam uma mensagem de inicialização.
3. O servidor lista ferramentas, recursos ou prompts disponíveis.
4. O cliente envia uma chamada de ferramenta.
5. O servidor valida argumentos, executa a ação e devolve conteúdo.
6. O cliente usa o resultado como contexto para a próxima etapa.

Esse desenho é simples, mas exige disciplina. O servidor vira uma fronteira de segurança. Se ele tem uma ferramenta para ler arquivo, precisa limitar diretórios. Se chama uma API interna, precisa proteger token. Se executa comando, precisa ter allowlist. MCP não é desculpa para dar um shell irrestrito a qualquer agente.

## Por que Zig é interessante para MCP

A maior vantagem de Zig aqui não é "ser mais rápido" em abstrato. Um servidor MCP normalmente passa mais tempo esperando I/O do que queimando CPU. O ganho real está em distribuição e previsibilidade.

Um servidor escrito em Zig pode virar um binário pequeno, sem runtime pesado, fácil de empacotar para Linux, macOS e Windows. Isso importa quando a ferramenta precisa rodar na máquina do usuário, dentro de um container mínimo ou em um ambiente de automação onde instalar Node, Python, dependências nativas e versões compatíveis aumenta atrito.

Zig também força decisões explícitas sobre alocação. Em um protocolo que recebe JSON externo, isso é útil. Você decide o allocator, limita tamanho de mensagem, separa arena por request e libera tudo no fim da chamada. Essa estratégia reduz vazamentos em servidores long-running e torna falhas mais fáceis de investigar.

O trade-off: você vai escrever mais infraestrutura. Ecossistemas como TypeScript e Python já têm SDKs MCP mais maduros. Em Zig, talvez você precise montar transporte, tipos, validação de schema e despacho de ferramentas com menos ajuda. Para um produto rápido, isso pesa. Para um binário operacional que deve viver anos, pode compensar.

## Transporte: comece por stdio

O transporte mais simples para MCP é `stdio`: o cliente abre o processo do servidor, escreve mensagens no `stdin` e lê respostas do `stdout`. Isso evita porta TCP, TLS, autenticação HTTP e firewall. Para ferramentas locais, é o caminho natural.

Em Zig, a estrutura básica é um loop de leitura:

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const stdin = std.io.getStdIn().reader();
    const stdout = std.io.getStdOut().writer();

    while (true) {
        var arena = std.heap.ArenaAllocator.init(allocator);
        defer arena.deinit();

        const line = stdin.readUntilDelimiterOrEofAlloc(
            arena.allocator(),
            '\n',
            1024 * 1024,
        ) catch |err| {
            try writeError(stdout, null, "read_error", @errorName(err));
            continue;
        };

        const payload = line orelse break;
        try handleMessage(arena.allocator(), stdout, payload);
    }
}
```

O detalhe importante é o limite: `1024 * 1024`. Nunca leia entrada ilimitada. Se uma ferramenta precisa processar payload grande, desenhe um protocolo específico ou passe caminho de arquivo validado, não enfie megabytes arbitrários no JSON.

Alguns clientes usam framing por cabeçalho `Content-Length`, parecido com Language Server Protocol, em vez de uma mensagem por linha. Se for o seu caso, a arquitetura não muda: primeiro leia cabeçalhos, descubra tamanho, aplique limite, leia o corpo e envie para o mesmo `handleMessage`.

## JSON-RPC: o envelope antes da ferramenta

MCP usa JSON-RPC como formato de chamada. Antes de pensar em `buscarArquivo`, você precisa tratar o envelope: `jsonrpc`, `id`, `method` e `params`.

Um request típico parece assim:

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "ler_arquivo",
    "arguments": {
      "path": "src/main.zig"
    }
  }
}
```

Em Zig, uma abordagem pragmática é parsear primeiro como árvore JSON dinâmica, validar o envelope e só depois converter `params` para tipos específicos. Isso evita criar uma struct rígida cedo demais, especialmente enquanto você ainda está ajustando métodos.

```zig
fn handleMessage(
    allocator: std.mem.Allocator,
    writer: anytype,
    payload: []const u8,
) !void {
    var parsed = try std.json.parseFromSlice(
        std.json.Value,
        allocator,
        payload,
        .{},
    );
    defer parsed.deinit();

    const root = parsed.value;
    const obj = root.object;

    const method = obj.get("method") orelse
        return writeError(writer, null, "invalid_request", "method ausente");

    if (!std.mem.eql(u8, method.string, "tools/call")) {
        return writeError(writer, obj.get("id"), "method_not_found", "método não suportado");
    }

    try handleToolCall(allocator, writer, obj);
}
```

O código acima é propositalmente incompleto. Em produção, você validaria `jsonrpc == "2.0"`, preservaria o `id`, diferenciaria request de notification, trataria tipos inesperados sem `panic` e garantiria que erros também seguem o formato JSON-RPC.

## Registro de ferramentas

Um servidor MCP fica mais fácil de manter quando ferramentas são registradas em uma tabela, não espalhadas em um `if` gigante. Em Zig, você pode usar uma struct com nome, descrição, schema e função de execução.

```zig
const Tool = struct {
    name: []const u8,
    description: []const u8,
    input_schema_json: []const u8,
    run: *const fn (std.mem.Allocator, std.json.Value) anyerror!ToolResult,
};

const tools = [_]Tool{
    .{
        .name = "ler_arquivo",
        .description = "Lê um arquivo permitido dentro do workspace.",
        .input_schema_json = \\
        \\\\{"type":"object","properties":{"path":{"type":"string"}},"required":["path"]}
        ,
        .run = runReadFile,
    },
};
```

O schema é útil para o cliente escolher argumentos corretos. Mesmo assim, não confie nele como segurança. Schema ajuda o modelo a chamar melhor; validação no servidor impede dano.

Para `tools/list`, o servidor percorre essa tabela e devolve a lista. Para `tools/call`, busca por `name`, valida `arguments` e chama a função correspondente. Se a ferramenta não existe, erro claro. Se os argumentos estão errados, erro claro. Se a execução falha, erro claro sem vazar segredo.

## Segurança: allowlist antes de capacidade

O erro mais perigoso em servidores MCP é confundir "útil" com "liberado". Uma ferramenta chamada `run_command` parece conveniente até receber `rm -rf`, `curl | sh` ou uma leitura acidental de token. Em vez disso, modele ferramentas pequenas e específicas.

Prefira:

- `ler_arquivo_permitido` com raiz fixa e extensão permitida;
- `buscar_no_repositorio` usando busca textual controlada;
- `rodar_testes` que executa um comando pré-definido;
- `consultar_metricas` contra endpoint específico;
- `listar_issues` com token lido do ambiente e nunca retornado.

Evite:

- shell arbitrário;
- path absoluto sem normalização;
- escrita fora de um diretório de trabalho;
- logs com tokens, headers ou corpo completo de resposta;
- chamadas de rede para domínio fornecido pelo modelo sem allowlist.

Essa mentalidade é parecida com a de produção em servidores HTTP: limite de body, timeouts, logs úteis e separação de responsabilidade. A diferença é que no MCP o cliente é um agente que pode tentar caminhos criativos. Seja explícito.

## Memória por request com arena

Um padrão bom para MCP em Zig é usar uma arena por mensagem. Você aloca parsing, strings temporárias e resposta dentro da arena, executa a chamada e libera tudo no fim. Para ferramentas que retornam conteúdo grande, imponha limite e escreva streaming quando fizer sentido.

```zig
while (true) {
    var arena = std.heap.ArenaAllocator.init(parent_allocator);
    defer arena.deinit();

    const request_allocator = arena.allocator();
    const payload = try readMessage(request_allocator, stdin);
    try handleMessage(request_allocator, stdout, payload);
}
```

Isso não substitui cuidado com recursos externos. Arquivos precisam ser fechados. Processos filhos precisam ter timeout. Sockets precisam de deadline. Mas reduz uma classe comum de vazamento: pequenas alocações por request que nunca são liberadas.

## Quando usar HTTP em vez de stdio

`stdio` é ótimo para ferramenta local. HTTP ou SSE entram quando o servidor precisa ser compartilhado por vários clientes, rodar remoto ou integrar com uma plataforma interna. Nesse caso, reaproveite o que já vale para qualquer API: autenticação, rate limit, TLS no proxy, observabilidade e health check.

Se você for por esse caminho, não misture o protocolo MCP com uma API pública sem fronteira. Coloque o servidor atrás de proxy, registre logs estruturados, limite payloads e trate cada ferramenta como operação sensível. O guia de [Zig HTTP Server em produção](/artigos/zig-http-server-producao/) cobre essa camada operacional.

## Zig, Rust, Go e o espaço de ferramentas de IA

Hoje, grande parte dos exemplos de MCP aparece em TypeScript ou Python porque esses ecossistemas estão colados no produto e têm SDKs oficiais mais visíveis. Rust e Go tendem a aparecer quando a preocupação é distribuir agentes, CLIs e servidores robustos. Zig entra em uma faixa ainda mais específica: ferramentas pequenas, baixo overhead, controle de memória e cross-compilation sem cerimônia.

Se você está comparando caminhos, veja também a perspectiva de <a href="https://rustlang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust Lang Brasil</a>. Rust entrega mais bibliotecas prontas e um modelo de segurança de memória mais restritivo. Zig entrega mais simplicidade de linguagem, controle explícito e uma história de build muito direta. Para servidores MCP internos, a melhor escolha depende menos de benchmark e mais de quem vai manter a ferramenta daqui a seis meses.

## Checklist para um servidor MCP em Zig

Antes de considerar o servidor pronto, passe por esta lista:

- Entrada com limite de tamanho.
- Parser JSON com erro recuperável, sem `panic` para input externo.
- Envelope JSON-RPC validado.
- `id` preservado nas respostas.
- `tools/list` gerado a partir de registro central.
- Schemas de entrada pequenos e claros.
- Validação real no servidor, não só no schema.
- Ferramentas específicas, sem shell arbitrário.
- Paths normalizados e presos a uma raiz permitida.
- Tokens lidos do ambiente e nunca enviados ao cliente.
- Timeout para I/O, rede e processos filhos.
- Logs sem segredo e com contexto suficiente para debug.
- Testes com mensagens válidas, inválidas e parcialmente formadas.
- Build multiplataforma se o servidor será distribuído.

Esse checklist vale mais do que um exemplo bonito. Um servidor MCP vira parte da superfície operacional do seu ambiente. Se ele roda no laptop de um desenvolvedor, pode acessar código e credenciais locais. Se roda no cluster, pode alcançar APIs internas. A qualidade da fronteira importa.

## Conclusão

Zig é uma boa opção para servidores MCP quando você quer um binário enxuto, auditável e previsível. O protocolo combina com a filosofia da linguagem: mensagens explícitas, dados estruturados, poucos componentes mágicos e responsabilidade clara sobre memória e I/O.

O caminho mais saudável é começar pequeno: um transporte `stdio`, duas ou três ferramentas bem delimitadas, parsing JSON robusto, erros claros e testes de contrato. Depois, se a ferramenta provar valor, você adiciona HTTP, observabilidade, release multiplataforma e integração com CI/CD. Para isso, o guia de [GitHub Actions para releases Zig](/artigos/zig-github-actions-release-multiplataforma/) fecha bem o ciclo: servidor local primeiro, distribuição automatizada depois.

MCP não muda a regra central de sistemas: capacidade sem limite vira incidente. Zig ajuda justamente porque obriga você a desenhar esses limites cedo.
