Pular para o conteúdo
EngenhariaIntermediário

MCP na prática: seu primeiro servidor em Python

Crie um servidor MCP em Python com duas ferramentas, teste com o cliente do SDK oficial e conecte ao Claude Code ou ao Claude Desktop, com cuidados de segurança.

Por Equipe IAUAI Estudos · 2 de outubro de 2026 · 11 min de leitura · Revisado em 9 de outubro de 2026

Nesta página

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 aparece FastMCP.
  • 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

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.

Aprenda jogando

Roteia o Pacote

Encanador de rede: gire os tubos até conectar a origem a todos os destinos.

Jogar Roteia o Pacote

Teste seu conhecimento

Teste o que você aprendeu sobre MCP em Python

Pergunta 1 de 6

Qual é o papel da docstring de uma ferramenta em um servidor MCP?

Um conteúdo prático por quinzena

Deixe o seu e-mail para receber um conteúdo prático de IA e cloud a cada quinzena. Sem spam, e você pede a exclusão quando quiser.

Continue lendo