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),uvpra 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
- [ ]
uvinstalado (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 fazprintou loga no stdout vai quebrar o canal. Isola o log nostderr.- 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.
{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
Como criar seu primeiro MCP server (tool + resource) e plugar no Claude
Tutorial em PT-BR pra escrever um MCP server do zero em Python: uma tool que consulta CEP e um resource que expoe os padroes do time. No fim, voce pluga no Claude e ve o agente chamar uma ferramenta que voce mesmo escreveu.
O que é MCP: o protocolo que virou o padrão de tools dos agentes de IA
O Model Context Protocol é o padrão aberto que conecta modelos de IA a ferramentas e dados sem reescrever integração a cada vez. Entenda o que é MCP, como a arquitetura funciona e por que a indústria adotou em massa.
Scraping, API ou MCP: o trade-off de fontes de dados que define seu agente
Scraping é flexível mas frágil. API é estável mas limitada. MCP padroniza mas exige integração específica. Veja a matriz prática de quando usar cada um para preço, review e estoque no seu agente, e por que o modelo híbrido com fronteira clara é o que aguenta produção.
MCP em produção: OAuth 2.1, schemas validados e o gateway que precisa estar entre você e o agente
MCP local não é MCP em produção. Sair do stdio no laptop pra um servidor MCP servindo agente corporativo exige três mudanças: Streamable HTTP no transporte, OAuth 2.1 com PKCE e Resource Indicators na auth, e JSON Schema 2020-12 estrito nos argumentos. E um gateway corporativo no meio, sempre.