Ao terminar este artigo você terá um servidor MCP escrito em Python com duas ferramentas, uma que consulta um CEP fictício e outra que soma os itens de um pedido. Você vai saber testá-lo com o cliente do SDK oficial, sem nenhum modelo de IA envolvido, e conectá-lo ao Claude Code ou ao Claude Desktop.
O que é MCP, em um parágrafo
O Model Context Protocol (MCP) é um padrão aberto para um aplicativo de IA conversar com ferramentas e dados externos. Em vez de cada aplicativo inventar a sua forma de chamar uma função, o servidor MCP descreve as ferramentas que oferece (nome, descrição, parâmetros) e qualquer cliente compatível, como o Claude Code ou o Claude Desktop, consegue listá-las e chamá-las. O modelo decide quando usar uma ferramenta, e o servidor executa. Se você já viu o conceito de chamada de função, o artigo sobre function calling mostra a base, e o MCP padroniza a forma de oferecer essas funções.
Passo 1: instale o SDK
Crie um ambiente virtual e instale o pacote oficial mcp:
python -m venv .venv
source .venv/bin/activate # no Windows: .venv\Scripts\activate
pip install mcp
Atenção à versão. No teste deste artigo, o pip install mcp instalou a versão 2.3.0, e nela a classe do servidor se chama MCPServer, importada de mcp.server.mcpserver. Em tutoriais mais antigos você vai ver FastMCP, importada de mcp.server.fastmcp, que pertence à série 1.x. Se copiar código antigo, o erro será No module named 'mcp.server.fastmcp'. A mensagem do próprio SDK sugere fixar mcp<2 para manter código da série 1.
Passo 2: escreva o servidor
Salve como servidor.py:
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("loja-didatica")
# Base fictícia: não são CEPs reais.
CEPS = {
"00000-001": {"logradouro": "Rua dos Exemplos", "bairro": "Centro", "cidade": "Cidade Modelo", "uf": "MG"},
"00000-002": {"logradouro": "Avenida do Teste", "bairro": "Jardim Fictício", "cidade": "Cidade Modelo", "uf": "MG"},
"00000-003": {"logradouro": "Travessa da Amostra", "bairro": "Vila Exemplo", "cidade": "Outra Cidade", "uf": "SP"},
}
@mcp.tool()
def consultar_cep(cep: str) -> dict:
"""Consulta um CEP fictício na base local e devolve o endereço.
Use o formato 00000-000. Se o CEP não existir, devolve um erro descritivo.
"""
normalizado = "".join(c for c in cep if c.isdigit())
if len(normalizado) != 8:
return {"erro": "CEP deve ter 8 dígitos, por exemplo 00000-001"}
chave = f"{normalizado[:5]}-{normalizado[5:]}"
return CEPS.get(chave, {"erro": f"CEP {chave} não encontrado na base"})
@mcp.tool()
def somar_pedido(itens: list[dict]) -> dict:
"""Soma os itens de um pedido.
Cada item precisa de 'preco' (reais, número) e 'quantidade' (inteiro).
Devolve o total em reais, com duas casas decimais.
"""
total = 0.0
for i, item in enumerate(itens, start=1):
try:
preco = float(item["preco"])
quantidade = int(item["quantidade"])
except (KeyError, TypeError, ValueError):
return {"erro": f"item {i} inválido: use preco e quantidade"}
if preco < 0 or quantidade < 0:
return {"erro": f"item {i} com valor negativo"}
total += preco * quantidade
return {"total": round(total, 2), "itens": len(itens)}
if __name__ == "__main__":
mcp.run(transport="stdio")
O que importa nesse código:
@mcp.tool()registra a função como ferramenta. O nome da função vira o nome da ferramenta.- A docstring vira a descrição que o modelo lê para decidir quando usar a ferramenta. Escreva como se explicasse a uma pessoa nova na equipe.
- Os tipos (
cep: str,itens: list[dict]) viram o esquema dos parâmetros. - Em caso de entrada ruim, a ferramenta devolve um erro descritivo em vez de falhar. O modelo lê esse texto e pode corrigir a chamada.
mcp.run(transport="stdio")faz o servidor conversar pela entrada e saída padrão, que é como clientes locais iniciam um servidor.- A base de CEPs é fictícia. Não use CEPs reais em exemplos nem copie dados de clientes para o código.
Nunca imprima nada com print em um servidor stdio. A saída padrão é o canal do protocolo, e texto solto quebra a conversa. Para registrar mensagens, escreva na saída de erro.
Passo 3: teste com o cliente do SDK
Antes de ligar a um aplicativo de IA, teste a parte que é sua. O SDK traz um cliente que inicia o servidor, lista as ferramentas e chama cada uma. Salve como cliente_teste.py:
import asyncio
import json
import sys
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command=sys.executable, args=["servidor.py"])
async with stdio_client(params) as (leitura, escrita):
async with ClientSession(leitura, escrita) as sessao:
await sessao.initialize()
ferramentas = await sessao.list_tools()
print("ferramentas:", [t.name for t in ferramentas.tools])
r = await sessao.call_tool("consultar_cep", {"cep": "00000001"})
print("cep:", r.content[0].text)
r = await sessao.call_tool("consultar_cep", {"cep": "99999-999"})
print("cep ruim:", r.content[0].text)
r = await sessao.call_tool("somar_pedido", {"itens": [
{"preco": 19.9, "quantidade": 3}, {"preco": 5, "quantidade": 2}]})
print("pedido:", r.content[0].text)
assert json.loads(r.content[0].text)["total"] == 69.7
asyncio.run(main())
Rode python cliente_teste.py. A saída esperada, executada em container, foi:
ferramentas: ['consultar_cep', 'somar_pedido']
cep: {
"logradouro": "Rua dos Exemplos",
"bairro": "Centro",
"cidade": "Cidade Modelo",
"uf": "MG"
}
cep ruim: {
"erro": "CEP 99999-999 não encontrado na base"
}
pedido: {
"total": 69.7,
"itens": 2
}
A conta do pedido confere de cabeça: 3 unidades de R$ 19,90 dão 59,70, mais 2 de R$ 5,00 dão 10,00, total 69,70. Esse teste roda sem modelo de IA e sem custo. Teste de ferramenta é teste de função comum.
O projeto MCP também mantém uma ferramenta visual de teste, o MCP Inspector, que roda com npx @modelcontextprotocol/inspector. Ela não foi executada na redação deste artigo, então siga a documentação oficial se quiser usá-la.
Passo 4: conecte ao Claude Code
No Claude Code, o comando para adicionar um servidor local é (use caminhos absolutos):
claude mcp add loja-didatica -- /caminho/para/.venv/bin/python /caminho/para/servidor.py
claude mcp list
O -- separa as opções do Claude Code do comando do servidor. Dentro de uma sessão, o comando /mcp mostra o estado. Por padrão o servidor fica disponível só para você, naquele projeto. Com --scope project, a configuração vai para um arquivo .mcp.json na raiz do projeto, que a equipe pode versionar. Depois, peça em linguagem natural: "consulte o CEP 00000-002" ou "some um pedido com 2 itens de R$ 10,00 e 1 de R$ 7,50". O Claude Code pede aprovação antes de usar a ferramenta, conforme as suas permissões.
Passo 5: conecte ao Claude Desktop
No Claude Desktop, abra Configurações, a aba Desenvolvedor, e escolha editar a configuração. O arquivo claude_desktop_config.json fica em ~/Library/Application Support/Claude/ no macOS e em %APPDATA%\Claude\ no Windows. Adicione:
{
"mcpServers": {
"loja-didatica": {
"command": "/caminho/para/.venv/bin/python",
"args": ["/caminho/para/servidor.py"]
}
}
}
Feche o Claude Desktop por completo e abra de novo. Os caminhos precisam ser absolutos. Se o servidor não aparecer, os logs de MCP ficam em ~/Library/Logs/Claude (macOS) ou %APPDATA%\Claude\logs (Windows), e o arquivo mcp-server-NOME.log traz a saída de erro do seu servidor.
A conexão com Claude Code e Claude Desktop foi descrita a partir da documentação oficial e não foi executada aqui, porque ambos exigem conta e aplicativo instalado.
Cuidados de segurança
Um servidor MCP roda com as permissões da sua conta. Alguns hábitos evitam problemas.
- Conecte só servidores em que você confia. A documentação do Claude Code avisa que servidores que buscam conteúdo externo podem expor você a injeção de prompt.
- Dê a cada ferramenta o menor poder possível. Nosso servidor só lê uma lista local e soma números. Uma ferramenta que escreve em disco ou chama um sistema de pagamento merece confirmação humana.
- Valide as entradas. O modelo pode passar argumentos estranhos, como no tratamento de itens inválidos em
somar_pedido. - Trate o texto devolvido como dado. Se uma ferramenta devolve conteúdo de terceiros, ele pode conter instruções escondidas. O artigo segurança em aplicações com LLM explica esse risco.
Resumo
- MCP padroniza como uma IA descobre e chama ferramentas.
- Com o SDK atual (2.x), o servidor usa
MCPServer; em tutoriais antigos apareceFastMCP. - A docstring e os tipos são o contrato que o modelo lê.
- Teste a ferramenta com o cliente do SDK, sem IA e sem custo.
- Use caminhos absolutos ao registrar o servidor e conecte só o que você confia.
Fontes
- Model Context Protocol, "Connect to local MCP servers" (Claude Desktop, local do arquivo de configuração, logs), consultado em 09/10/2026: modelcontextprotocol.io/docs/develop/connect-local-servers
- Anthropic, "Connect Claude Code to tools via MCP" (
claude mcp add, escopos,.mcp.json, aviso de segurança), consultado em 09/10/2026: code.claude.com/docs/en/mcp - MCP Python SDK, guia de migração para a versão 2 (
MCPServer), consultado em 09/10/2026: py.sdk.modelcontextprotocol.io/v2/migration
Versões testadas
Em 09/10/2026, em container python:3.12-slim (Python 3.12.15) com 2 CPUs, SDK mcp 2.3.0: o cliente_teste.py iniciou o servidor.py por stdio, listou as duas ferramentas e chamou ambas com sucesso. Não foram executados o MCP Inspector, a conexão ao Claude Code e a conexão ao Claude Desktop.