Como criar seu primeiro MCP server (tool + resource) e plugar no Claude
Você já plugou um MCP server pronto no seu Claude. O do GitHub, o do Postgres, o do Slack. Funciona, é mágico, e aí bate a pergunta: e se eu quiser expor as minhas coisas? Sua API interna, seus padrões de time, aquele banco que só você entende.
O Model Context Protocol é o padrão aberto que a Anthropic lançou em novembro de 2024 para conectar modelos a dados e ferramentas externas sem cada integração virar um projeto à parte (anúncio oficial). Consumir MCP server dos outros é fácil. Construir o seu é onde a coisa fica interessante — e é exatamente o que falta em português.
Neste tutorial você vai escrever um MCP server do zero em Python: uma tool que consulta CEP de verdade e um resource que expõe os padrões de engenharia do seu time. No fim, você pluga no Claude e vê o agente chamar uma ferramenta que você escreveu.
TL;DR
- O que é: um MCP server é um processo que expõe capacidades (tools, resources, prompts) para qualquer cliente que fale o Model Context Protocol — Claude, Cursor, e por aí vai.
- Stack: Python 3.10+, SDK oficial
mcp[cli],uvpra gerenciar o projeto,httpxpra chamada HTTP. - Custo/Acesso: tudo open-source e gratuito. O exemplo usa a ViaCEP, que não pede chave.
- Repositório/Link útil: tutorial oficial de build server e o Python SDK.
O contexto: por que um MCP server importa?
Antes do MCP, conectar modelo a ferramenta era o que os engenheiros chamavam de problema M×N. Tem M modelos e N ferramentas? Você escrevia M vezes N integrações na mão. Dez aplicações falando com cem ferramentas davam mil adaptadores possíveis, cada um do seu jeito.
O MCP corta isso. Vira M+N. Você escreve um servidor uma vez, e qualquer cliente que fala o protocolo consome. É a mesma ideia do USB-C: antes, cada aparelho tinha um conector; depois, uma porta só pra tudo.
Se você quer o panorama do protocolo antes de pôr a mão no código, a gente já destrinchou o que é MCP e por que virou padrão. Aqui o foco é construir.
Um MCP server expõe três tipos de capacidade. Vale gravar a tabela, porque a diferença entre elas é a parte que mais confunde quem está começando (docs oficiais):
| Building block | O que é | Quem controla |
|---|---|---|
| Tools | Funções que o modelo chama pra agir (escrever no banco, bater numa API, mandar e-mail). | O modelo |
| Resources | Dados read-only que entram como contexto (arquivo, schema, doc). | A aplicação |
| Prompts | Templates de instrução que o usuário dispara de propósito. | O usuário |
Guarde isso: tool é ação, resource é contexto. A gente vai construir uma de cada e a distinção vai ficar concreta.
Pré-requisitos e ferramentas
Antes de começar, você precisa de:
- [ ] Python 3.10 ou superior (o SDK exige a partir da 1.2.0).
- [ ]
uvinstalado — é o gerenciador de pacotes e venv que a doc oficial recomenda. - [ ] Conhecimento básico de Python e async. Nada além de
async def. - [ ] Um cliente MCP pra testar no fim: Claude for Desktop ou o MCP Inspector (já vem no SDK).
Sem uv? Uma linha resolve:
curl -LsSf https://astral.sh/uv/install.sh | sh
Mão na massa: seu primeiro MCP server
Passo 1: o esqueleto do projeto
Cria o projeto, o ambiente e instala o SDK com as ferramentas de linha de comando:
uv init meu-mcp
cd meu-mcp
uv add "mcp[cli]" httpx
O mcp[cli] traz o SDK e o comando mcp (a gente usa ele pra testar daqui a pouco). O httpx é só pra fazer a chamada HTTP da nossa tool. Agora cria o arquivo servidor.py.
Passo 2: instanciar o FastMCP
O coração é uma linha. O FastMCP é a classe que faz o trabalho chato — ele lê seus type hints e suas docstrings e gera as definições de tool e resource automaticamente. Você escreve Python normal; ele monta o JSON Schema.
import httpx
from mcp.server.fastmcp import FastMCP
# O nome é como o servidor aparece no cliente.
mcp = FastMCP("ferramentas-do-time")
VIACEP = "https://viacep.com.br/ws"
Passo 3: expor uma tool (a ação)
Tool é o que o modelo chama quando decide fazer alguma coisa. Decorou com @mcp.tool(), virou tool. O nome da função, os parâmetros tipados e a docstring viram a descrição que o modelo lê pra decidir se chama.
Vamos fazer uma que consulta CEP de verdade na ViaCEP — sem chave, sem firula:
@mcp.tool()
async def consultar_cep(cep: str) -> str:
"""Consulta um CEP brasileiro e devolve o endereço.
Args:
cep: CEP com 8 dígitos, com ou sem traço (ex: 01310-100)
"""
cep = cep.replace("-", "").strip()
async with httpx.AsyncClient() as client:
resp = await client.get(f"{VIACEP}/{cep}/json/", timeout=10.0)
if resp.status_code != 200:
return f"Erro ao consultar o CEP {cep}."
dados = resp.json()
if dados.get("erro"):
return f"CEP {cep} nao encontrado."
return (
f"{dados['logradouro']}, {dados['bairro']} - "
f"{dados['localidade']}/{dados['uf']} (CEP {dados['cep']})"
)
Repara que aquela docstring não é enfeite. É ela que o modelo usa pra entender o que a ferramenta faz e o que mandar em cep. Docstring ruim, tool que o modelo chama errado. Trate a descrição como parte do contrato, não como comentário.
Passo 4: expor um resource (o contexto)
Agora a outra metade. Resource é dado read-only que a aplicação injeta como contexto — o modelo não "chama" um resource pra agir, ele lê. Cada resource tem uma URI única, e o FastMCP suporta URI com parâmetro (o chamado resource template).
Vamos expor os padrões de engenharia do time. O cliente pede padroes://commits, padroes://testes, e recebe o texto:
PADROES = {
"commits": "Use Conventional Commits. Mensagem no imperativo, em portugues.",
"testes": "Todo PR sobe com teste. Pest pra unit, sem mock de banco.",
"branches": "feature/<ticket>-descricao. Nada de push direto na main.",
}
@mcp.resource("padroes://{area}")
def padrao_do_time(area: str) -> str:
"""Devolve o padrao de engenharia do time para uma area."""
return PADROES.get(area, f"Sem padrao definido para '{area}'.")
O {area} na URI é o parâmetro. padroes://testes casa com area="testes". É o mesmo padrão de um resource template tipo weather://forecast/{cidade} da doc oficial — URI dinâmica, contexto sob demanda.
Passo 5: rodar e testar
Fecha o arquivo dizendo qual transporte usar. Pra rodar local, é stdio (o cliente conversa com o servidor por stdin/stdout):
if __name__ == "__main__":
mcp.run(transport="stdio")
Não precisa subir nada pra inspecionar. O SDK traz o MCP Inspector, uma UI que conecta no seu servidor e deixa você listar e disparar tools e resources na mão:
uv run mcp dev servidor.py
Abre no navegador, lista a consultar_cep e o padroes://{area}, e você testa cada um antes de plugar em qualquer modelo. Esse passo economiza muita dor: se quebra aqui, o problema é seu servidor, não o cliente.
Passo 6: plugar no Claude
Com o servidor funcionando, registra ele no Claude for Desktop. Abre o claude_desktop_config.json e adiciona seu servidor na chave mcpServers:
{
"mcpServers": {
"ferramentas-do-time": {
"command": "uv",
"args": [
"--directory",
"/caminho/absoluto/para/meu-mcp",
"run",
"servidor.py"
]
}
}
}
Use o caminho absoluto da pasta (roda pwd que ele te dá). Salva, reinicia o Claude for Desktop, e pronto: peça "consulta o CEP 01310-100" e veja o agente chamar a sua tool. Detalhe pra quem está no Linux como eu: o Claude for Desktop ainda não tem build pra Linux, então o Inspector do passo 5 é o seu caminho de teste — ou plugue num cliente que rode no seu SO.
Tool ou resource: qual usar?
Essa é a decisão que separa quem entendeu MCP de quem só copiou o exemplo.
Pergunte: o modelo precisa decidir chamar isso, ou é contexto que a aplicação já sabe que quer entregar?
Se é uma ação com efeito — consultar uma API, gravar no banco, abrir um ticket — é tool. O modelo controla, e por isso tool geralmente passa por aprovação do usuário antes de executar.
Se é um dado estável que serve de pano de fundo — a documentação do projeto, o schema do banco, os padrões do time — é resource. A aplicação controla quando e como injeta. Não tem efeito colateral, então é seguro ler à vontade.
O erro clássico do iniciante é transformar tudo em tool. Aí o modelo fica chamando função pra ler dado parado, gastando turno e abrindo brecha de erro onde não precisava. Contexto read-only é resource. Ponto.
Limitações e pontos de atenção
Antes de levar isso a sério, três armadilhas:
- Nunca escreva em stdout num servidor stdio. Aquele
print()de debug corrompe as mensagens JSON-RPC e quebra o servidor inteiro, do nada. Logue em stderr (print(..., file=sys.stderr)) ou em arquivo. É o bug número um de quem está começando. - Tool é superfície de ataque. Se sua tool roda SQL, deleta arquivo ou bate numa API com credencial, o modelo agora tem esse poder. Valide entrada, limite escopo, e trate aprovação do usuário como parte do design — não como detalhe. E quando esse servidor sair do seu laptop pra valer, o jogo muda: é o que MCP em produção exige — OAuth, schema validado e gateway no meio.
- Descrição é contrato. Modelo não lê seu código, lê a docstring e os nomes. Tool mal descrita é tool chamada na hora errada, com argumento errado. Capricha nessa parte como se fosse a API pública que é.
FAQ rápido
Preciso saber async pra escrever um MCP server?
Pra tool que faz I/O (HTTP, banco), async def é o caminho natural e o FastMCP lida com isso. Mas tool e resource síncronos também funcionam — o padrao_do_time do exemplo é def normal.
Funciona só com Claude? Não. O M de MCP é de protocolo aberto. O mesmo servidor pluga em qualquer cliente que fale MCP — Cursor, Zed, sua própria aplicação via SDK. Foi esse o ponto desde o começo.
Posso usar TypeScript em vez de Python? Pode. O SDK oficial tem versões em TypeScript, Java, C#, Kotlin e Ruby, todas com o mesmo conceito de tools e resources. Escolhi Python aqui pela curva mais curta.
Como debugo quando o modelo não chama minha tool?
Quase sempre é descrição. Abra o MCP Inspector (uv run mcp dev), confirme que a tool aparece com a descrição certa, e reescreva a docstring deixando explícito quando usar.
O próximo passo
Você saiu do "consumo MCP dos outros" pro "exponho o meu". Escreveu um servidor com uma tool que age e um resource que dá contexto, testou no Inspector e plugou no Claude. Isso é a base de tudo: um agente interno da sua empresa não é outra coisa senão um modelo com MCP servers bem desenhados em volta.
E é aí que o jogo muda. A parte difícil não é o decorator — é decidir o que vira tool, o que vira resource, onde mora a credencial, como o agente erra com segurança. Isso é arquitetura, não prompt bonito. Se você quer ver esse tipo de decisão sendo tomada com código rodando e dor de produção na mesa, é o que a gente destrincha no Workshop Arquitetando Soluções de IA, um workshop prático de como montar soluções de software com agents de IA.
Agora pega o servidor.py, troca a ViaCEP pela sua API interna e o padroes:// pela doc do seu projeto. O esqueleto é o mesmo. O que muda é o problema que ele resolve.
{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
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.
Hands-on: construindo um agente de ofertas em 80 linhas com Claude, tool use e um reranker
Tutorial reproduzivel em Python: agent loop com Claude, busca na web, rerank do Cohere e saida em JSON estruturado. Esqueleto de 80 linhas para voce expandir e levar para producao.
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.
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.