~ / tutoriais /como-criar-um-servidor-mcp-do-zero $ _

Como criar um servidor MCP do zero: tools, resources e prompts, do stdio ao HTTP

Lucas Souza Lucas Souza 12 min de leitura Tutoriais
Como criar um servidor MCP do zero: tools, resources e prompts, do stdio ao HTTP

Entender o que é MCP é uma coisa. Subir o seu primeiro servidor e ver o agente usá-lo é outra completamente diferente.

Um servidor MCP é o que transforma o seu código numa ferramenta que o Claude (ou qualquer agente compatível) sabe chamar sozinho. Em vez de colar dado no prompt na mão, você expõe uma função, o modelo decide quando usar, chama, lê o retorno e segue o raciocínio. É a diferença entre o agente "saber" e o agente "fazer".

Neste tutorial a gente constrói um servidor MCP do zero em Python, com os três tipos de capability que o protocolo define — tool, resource e prompt — e roda ele de dois jeitos: via stdio (pro Claude Desktop no seu laptop) e via HTTP (pra quando esse server precisa virar serviço de verdade). No fim você pluga no Claude e vê o agente chamar uma função que você mesmo escreveu.

TL;DR

  • O que é: um servidor MCP em Python que expõe uma tool, um resource e um prompt, rodando em stdio e HTTP.
  • Stack: Python 3.10+, SDK oficial mcp (FastMCP), uv pra gerenciar o ambiente.
  • Custo/Acesso: zero. SDK open-source, roda local, sem chave de API pra subir o server.
  • Pré-leitura: se "MCP" ainda soa abstrato, comece por O que é MCP. Aqui a gente parte do princípio que você já sabe o que o protocolo resolve.

O contexto: por que escrever seu próprio servidor MCP?

O Model Context Protocol é o padrão aberto que a Anthropic lançou em novembro de 2024 pra resolver um problema bobo e caro ao mesmo tempo: cada integração entre um modelo e uma ferramenta era reescrita do zero, de um jeito diferente, em cada produto. MCP padroniza esse contrato. Você escreve a integração uma vez, e qualquer host que fala MCP — Claude Desktop, Claude Code, e dezenas de outros clientes — consegue usar.

Tem montanha de servidor MCP pronto por aí. Então por que escrever o seu?

Porque o servidor pronto não conhece o seu domínio. Ele não sabe consultar o seu banco, ele não tem a regra de negócio da sua empresa, ele não expõe o endpoint interno que só existe na sua VPC. No momento que você quer dar ao agente acesso a algo que é seu, não tem atalho: você escreve um servidor MCP. E a boa notícia é que isso é muito mais simples do que parece.

Um servidor MCP pode oferecer três coisas, e dá pra entender cada uma por quem controla a ação:

  • Tool — uma função que o modelo decide chamar. "Consulta esse CEP", "roda essa query", "cria esse ticket". É o tipo mais usado.
  • Resource — um pedaço de dado que o cliente/aplicação decide carregar pro contexto. Tipo um arquivo: os padrões de código do time, um catálogo, a doc de uma API.
  • Prompt — um template reutilizável que o usuário dispara. Aparece como um comando pronto no host ("revisar PR", "gerar changelog").

A regra de quem controla cada um vem direto da doc oficial de conceitos de servidor. Guarda isso, porque é o que evita o erro clássico de transformar tudo em tool.

Pré-requisitos e ferramentas

Checklist curto antes de começar:

  • [ ] Python 3.10 ou superior
  • [ ] uv instalado (o gerenciador de pacotes da Astral — mais rápido que pip e resolve o ambiente sozinho)
  • [ ] Noção básica de Python com type hints (o SDK usa as anotações de tipo pra gerar o schema das tools)
  • [ ] Claude Desktop instalado, se você quiser ver o server rodando num host de verdade no fim

Cria o projeto e instala o SDK:

uv init servidor-mcp
cd servidor-mcp
uv add "mcp[cli]"

O extra [cli] traz o utilitário de linha de comando (mcp dev, mcp install) que a gente usa pra testar e plugar. Se você é time pip, é pip install "mcp[cli]" num virtualenv — o resto do tutorial é idêntico.

Mão na massa: o servidor passo a passo

A gente vai construir um servidor que dá ao agente acesso a um "manual do time" fictício: uma tool que calcula prazo de entrega, um resource que expõe os padrões de código, e um prompt pronto de code review. Cria um arquivo server.py.

Passo 1: o servidor e a primeira tool

O SDK oficial tem uma API de alto nível chamada FastMCP. Você instancia o servidor com um nome e decora funções. As anotações de tipo viram o JSON Schema dos argumentos, e a docstring vira a descrição que o modelo lê pra decidir quando chamar. É por isso que docstring de tool MCP não é firula — é a interface entre o seu código e o raciocínio do agente.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("manual-do-time")

@mcp.tool()
def prazo_entrega(distancia_km: float, expresso: bool = False) -> str:
    """Calcula o prazo de entrega em dias úteis para uma distância em km.

    Use quando o usuário perguntar quanto tempo leva uma entrega.
    """
    base = 1 + distancia_km / 400
    dias = base * (0.5 if expresso else 1.0)
    return f"Prazo estimado: {round(dias, 1)} dias úteis"

Repara em duas coisas. Primeiro: a docstring fala quando usar a tool, não só o que ela faz — isso melhora a decisão do modelo. Segundo: o expresso: bool = False com valor default vira um parâmetro opcional no schema automaticamente. Você não escreveu uma linha de JSON Schema, e o agente já sabe que pode passar expresso=true.

Passo 2: um resource pra expor dado, não ação

Tool é pra ação. Quando você só quer disponibilizar um pedaço de informação pro contexto — sem que o modelo precise "executar" nada — o tipo certo é resource. Resource tem uma URI, e o cliente decide carregar.

@mcp.resource("padroes://codigo")
def padroes_de_codigo() -> str:
    """Os padrões de código que o time segue."""
    return (
        "1. PHP: PSR-12, tipagem estrita em todo arquivo.\n"
        "2. Commits no padrão Conventional Commits.\n"
        "3. Toda feature nova precisa de teste de feature.\n"
        "4. Nada de lógica de negócio no controller."
    )

Dá pra usar template na URI também, com chaves: @mcp.resource("padroes://{linguagem}") recebe linguagem como argumento e te deixa servir conteúdo dinâmico por chave. O ponto mental: resource é leitura, tool é efeito. Misturar os dois é o erro que mais aparece em server iniciante — joga tudo em tool e o agente fica chamando função pra ler dado estático que devia ser resource.

Passo 3: um prompt reutilizável

O terceiro tipo é o prompt: um template que o usuário dispara no host, normalmente como um comando. Bom pra encapsular aquele prompt que todo mundo no time cola na mão toda vez.

@mcp.prompt()
def revisar_pr(diff: str, foco: str = "bugs e segurança") -> str:
    """Gera um prompt de code review a partir de um diff."""
    return (
        f"Revise o diff abaixo com foco em {foco}. "
        f"Seja direto, aponte o problema e a linha, sugira o fix.\n\n"
        f"```diff\n{diff}\n```"
    )

Agora o servidor tem as três capabilities. Falta rodar.

Passo 4: rodar em stdio e plugar no Claude Desktop

O transporte mais simples é o stdio. Funciona assim: o cliente sobe o seu servidor como um subprocesso e conversa via stdin/stdout, uma mensagem JSON-RPC por linha. É o transporte padrão pra rodar local, e o que o Claude Desktop usa.

Adiciona o bloco de execução no fim do server.py:

if __name__ == "__main__":
    mcp.run()

mcp.run() sem argumento já sobe em stdio. Uma regra de ouro do stdio que pega muita gente: nunca escreva nada que não seja mensagem MCP no stdout. Um print() de debug perdido corrompe o protocolo e o cliente desconecta. Log de debug vai pro stderr — o stdout é sagrado.

Antes de plugar em qualquer lugar, testa local com o MCP Inspector, que sobe uma UI web pra você chamar as tools na mão:

uv run mcp dev server.py

Com o server funcionando, instala no Claude Desktop com um comando:

uv run mcp install server.py

Isso registra o servidor no claude_desktop_config.json (o arquivo de config dos MCP servers do Claude Desktop). Reinicia o Claude, e a sua tool aparece. Pergunta "quanto tempo leva uma entrega de 800km no modo expresso?" e você vê o agente chamar prazo_entrega — uma função que você escreveu há cinco minutos.

Passo 5: trocar pra HTTP quando o server vira serviço

stdio é ótimo pro laptop. Mas subprocesso local não escala: você não vai subir um subprocesso por usuário num servidor corporativo. Pra isso existe o Streamable HTTP, o transporte que substituiu o antigo HTTP+SSE (deprecado desde a revisão 2024-11-05 do protocolo). Ele expõe um endpoint HTTP único, que aceita POST e GET, e pode usar Server-Sent Events pra streamar respostas.

A mudança no código é de uma linha — o mesmo servidor, outro transporte:

if __name__ == "__main__":
    mcp.settings.host = "127.0.0.1"
    mcp.settings.port = 8000
    mcp.run(transport="streamable-http")

Sobe em http://localhost:8000/mcp. Esse /mcp é o endpoint MCP — o caminho único que o cliente usa pra falar com o server. Agora qualquer host que aponta pra essa URL conversa com o seu servidor por rede, sem subprocesso nenhum.

E aqui já entra o primeiro alerta de segurança, que a própria spec crava: ao servir por HTTP, o servidor deve validar o header Origin em toda conexão pra evitar ataque de DNS rebinding, e rodando local deve fazer bind só em 127.0.0.1, nunca em 0.0.0.0. Endpoint MCP aberto na rede sem isso é porta destrancada.

Limitações e pontos de atenção

O server que você acabou de subir roda. Mas "roda no meu laptop" e "roda em produção" são dois universos.

  • Autenticação. stdio não pede auth porque o cliente é dono do processo. No instante que você expõe por HTTP, autenticação deixa de ser opcional — a spec exige. O caminho recomendado é OAuth 2.1 com PKCE, schema validado e um gateway no meio, e a gente destrinchou exatamente isso num post separado.
  • stdout é do protocolo. Vale repetir porque é o bug número um do stdio: qualquer biblioteca que faz print ou loga no stdout vai quebrar o canal. Isola o log no stderr.
  • Prompt injection via tool. Se a sua tool retorna texto que veio de fonte externa (um site, um e-mail, um campo de usuário), esse texto entra no contexto do modelo. Trate retorno de tool como entrada não confiável.
  • Header de versão. Em HTTP, o cliente manda o header MCP-Protocol-Version (a revisão atual é 2025-06-18). Se o seu server ignora isso e a spec evolui, você quebra na cara dos clientes novos.

Esse bloco não é pra assustar. É pra deixar claro que o tutorial te leva do zero ao servidor funcional — e que de funcional pra produção tem uma segunda etapa de engenharia que não dá pra pular.

FAQ rápido

Preciso de chave de API pra rodar um servidor MCP? Não. O servidor é o seu código local — não custa nada subir e testar. Chave de API só entra se dentro de uma tool você chamar um serviço pago (um modelo, uma API externa). O protocolo em si é gratuito e open-source.

Posso escrever em outra linguagem além de Python? Sim. Existe SDK oficial em TypeScript, Python, Go, Kotlin, Java e C#. A escolha aqui foi Python por causa do FastMCP, que é a forma mais enxuta de sair do zero. O protocolo é o mesmo independente da linguagem.

Stdio ou HTTP — qual eu uso? stdio pra ferramenta local, pessoal, que roda na máquina do dev (Claude Desktop, Claude Code). HTTP quando o server precisa servir múltiplos clientes pela rede, ou virar um serviço com deploy próprio. Mesmo código, transporte diferente.

Como o agente sabe quando chamar minha tool? Pelo nome, pelos type hints e principalmente pela docstring. O modelo lê a descrição de cada tool e decide com base na intenção do usuário. Docstring vaga = tool subutilizada. Trate a docstring como parte da engenharia, não como comentário.

Conclusão

Você saiu de um arquivo vazio pra um servidor MCP com tool, resource e prompt, rodando em stdio e em HTTP, plugado no Claude. O conceito que parecia abstrato virou função chamada pelo agente na sua frente. E o pulo mental mais importante ficou claro: tool é ação que o modelo dispara, resource é dado que o cliente carrega, prompt é template que o usuário aciona. Errar isso é o que separa um server amador de um bem desenhado.

O próximo degrau é pegar esse server e levar pra valer: autenticação, schema estrito, observabilidade, gateway. É a fronteira entre criar seu primeiro MCP server e fazer ele aguentar produção — e é exatamente esse salto, do prompt isolado até o harness completo de um agente rodando de verdade, que a gente vai construir ao vivo, mão na massa, no workshop Do Prompt ao Harness: construindo um agente de vendas. Servidor MCP é a peça que entrega ferramentas pro agente — saber montar o harness em volta dele é o que faz o produto existir.

Lucas Souza
Lucas Souza

{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.

Você também pode gostar

VirguIA

beer & code assistant

conectando…

Não foi possível iniciar o chat agora.

tocando