MCP (Model Context Protocol): Do Zero ao Avançado com Caso de Uso Real
Arquitetura, primitivas, servidor em Python, testes com Inspector e client próprio.

MCP (Model Context Protocol): Do Zero ao Avançado com Caso de Uso Real
Se você trabalha com AI Agents em produção, já esbarrou nesse problema: cada integração é uma gambiarra diferente. Um agent que acessa banco de dados usa uma API. Outro que lê arquivos usa outra. Um terceiro que manda e-mail, mais uma. Cada ferramenta tem seu formato, seu protocolo, sua autenticação.
O MCP resolve isso. É um protocolo aberto que padroniza como aplicações de AI se conectam a sistemas externos. Pense num USB-C pra AI: um conector universal.
Neste artigo eu vou cobrir tudo. Da teoria à prática, com código que você roda no seu terminal sem depender de nenhum produto específico.
O que é MCP
MCP (Model Context Protocol) é um protocolo open-source criado pela Anthropic que define como aplicações de AI (Claude, ChatGPT, VS Code Copilot, Cursor) se conectam a fontes de dados, ferramentas e workflows externos.
Antes do MCP, cada aplicação implementava suas integrações do zero. O resultado: fragmentação. Cada vendor com seu SDK, seu formato, seu jeito de expor ferramentas. O MCP propõe um padrão. Você implementa uma vez, e qualquer client compatível consegue usar.
Hoje o protocolo é suportado por Claude Desktop, ChatGPT, VS Code, Cursor, MCPJam e dezenas de outros clients.
Arquitetura: Host, Client e Server
A arquitetura do MCP tem três participantes:
MCP Host: a aplicação de AI que orquestra tudo. Exemplos: Claude Desktop, VS Code, Cursor.
MCP Client: um componente dentro do Host que mantém conexão com um MCP Server específico. Cada conexão tem seu próprio client.
MCP Server: o programa que expõe funcionalidades (dados, ferramentas, prompts) pro client consumir.
Na prática, quando o VS Code se conecta a dois MCP Servers diferentes (digamos, um de filesystem e um do Sentry), ele cria dois MCP Clients internos, cada um com sua conexão dedicada.
Duas camadas
O protocolo opera em duas camadas:
Data Layer: define o formato das mensagens usando JSON-RPC 2.0. Aqui ficam o lifecycle management (inicialização, negociação de capabilities, encerramento), as primitivas (tools, resources, prompts) e notificações.
Transport Layer: define como as mensagens trafegam. Dois mecanismos:
STDIO: comunicação via stdin/stdout entre processos locais. Zero overhead de rede. Ideal pra servidores que rodam na mesma máquina.
Streamable HTTP: comunicação via HTTP POST com Server-Sent Events opcionais. Suporta autenticação OAuth, API keys e headers customizados. Pra servidores remotos.
As 3 Primitivas do MCP
Todo MCP Server expõe funcionalidades através de três primitivas. Entender a diferença entre elas é fundamental.
Tools (controladas pelo modelo)
Tools são funções que o LLM pode chamar ativamente. O modelo decide quando usá-las com base no contexto da conversa.
Exemplos: buscar voos, enviar mensagem, criar evento no calendário, consultar banco de dados.
Cada tool tem um schema JSON que define seus inputs e outputs. O client lista as tools disponíveis (tools/list) e o modelo chama as que precisa (tools/call).
{
"name": "consultarCEP",
"description": "Consulta endereço a partir de um CEP brasileiro",
"inputSchema": {
"type": "object",
"properties": {
"cep": {
"type": "string",
"description": "CEP com 8 dígitos"
}
},
"required": ["cep"]
}
}
Resources (controladas pela aplicação)
Resources são fontes de dados passivas. Elas provêem acesso somente-leitura a informações que a aplicação pode usar como contexto.
Cada resource tem um URI único (ex: file:///docs/README.md, db://users/schema) e declara seu MIME type. Existem dois padrões de descoberta:
Direct Resources: URIs fixos que apontam pra dados específicos.
Resource Templates: URIs dinâmicos com parâmetros. Ex:
api://clientes/{id}/pedidos.
Prompts (controlados pelo usuário)
Prompts são templates pré-construídos que orientam o modelo a trabalhar com tools e resources de forma específica. O usuário escolhe qual prompt usar.
Exemplo: um prompt "Analise este repositório" que instrui o modelo a ler a estrutura de arquivos, verificar dependências e gerar um relatório de code review.
| Primitiva | Quem controla | Acesso | Exemplo |
|---|---|---|---|
| Tools | Modelo (LLM) | Leitura e escrita | Enviar e-mail, criar registro |
| Resources | Aplicação | Somente leitura | Ler arquivo, schema do banco |
| Prompts | Usuário | Template | "Resuma minha reunião" |
Mão na massa: criando um MCP Server em Python
Chega de teoria. Vou construir um MCP Server que consulta a API ViaCEP (pública e gratuita) pra buscar endereços a partir de CEPs brasileiros. Um caso de uso simples, mas que mostra toda a mecânica do protocolo.
Setup do projeto
Você precisa de Python 3.10+ e do uv (gerenciador de pacotes da Astral):
# Instalar uv (se ainda não tem)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Criar o projeto
uv init mcp-cep
cd mcp-cep
# Criar e ativar ambiente virtual
uv venv
source .venv/bin/activate
# Instalar dependências
uv add "mcp[cli]" httpx
O servidor completo
Crie o arquivo server.py:
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# Inicializa o servidor
mcp = FastMCP("cep-brasil")
VIACEP_BASE = "https://viacep.com.br/ws"
async def buscar_cep_api(cep: str) -> dict[str, Any] | None:
"""Faz a requisição pra API ViaCEP."""
cep_limpo = cep.replace("-", "").replace(".", "").strip()
async with httpx.AsyncClient() as client:
try:
response = await client.get(
f"{VIACEP_BASE}/{cep_limpo}/json/",
timeout=10.0
)
response.raise_for_status()
data = response.json()
if "erro" in data:
return None
return data
except Exception:
return None
@mcp.tool()
async def consultar_cep(cep: str) -> str:
"""Consulta um endereço completo a partir de um CEP brasileiro.
Args:
cep: CEP com 8 dígitos (aceita formato 01001-000 ou 01001000)
"""
data = await buscar_cep_api(cep)
if not data:
return f"CEP {cep} não encontrado ou inválido."
return (
f"CEP: {data['cep']}\n"
f"Logradouro: {data.get('logradouro', 'N/A')}\n"
f"Complemento: {data.get('complemento', '')}\n"
f"Bairro: {data.get('bairro', 'N/A')}\n"
f"Cidade: {data.get('localidade', 'N/A')}\n"
f"Estado: {data.get('uf', 'N/A')}\n"
f"DDD: {data.get('ddd', 'N/A')}"
)
@mcp.tool()
async def buscar_endereco(uf: str, cidade: str, logradouro: str) -> str:
"""Busca CEPs a partir de um endereço parcial.
Args:
uf: Sigla do estado com 2 letras (ex: SP, RJ, PR)
cidade: Nome da cidade
logradouro: Nome da rua (mínimo 3 caracteres)
"""
if len(logradouro) < 3:
return "O logradouro precisa ter no mínimo 3 caracteres."
url = f"{VIACEP_BASE}/{uf}/{cidade}/{logradouro}/json/"
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, timeout=10.0)
response.raise_for_status()
resultados = response.json()
except Exception:
return "Erro ao buscar endereço."
if not resultados:
return "Nenhum endereço encontrado."
saida = []
for r in resultados[:5]:
saida.append(
f"CEP: {r['cep']} | {r.get('logradouro', '')} - "
f"{r.get('bairro', '')}, {r.get('localidade', '')}/{r.get('uf', '')}"
)
return "\n".join(saida)
@mcp.resource("cep://info")
def info_resource() -> str:
"""Informações sobre o servidor de CEP."""
return (
"Servidor MCP de consulta de CEPs brasileiros.\n"
"Fonte de dados: ViaCEP (viacep.com.br)\n"
"Tools disponíveis: consultar_cep, buscar_endereco"
)
def main():
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
Alguns pontos importantes:
FastMCPusa type hints e docstrings pra gerar os schemas das tools automaticamente. Sem JSON Schema na mão.@mcp.tool()registra a função como tool. Nome, descrição e parâmetros são extraídos do código.@mcp.resource()registra um resource com URI fixo.O transport
stdioé o mais simples pra desenvolvimento local.
Testando o servidor
Agora vem a parte mais importante: como testar isso sem depender de nenhum produto pago ou específico. Duas opções.
Opção 1: MCP Inspector (visual, zero config)
O MCP Inspector é a ferramenta oficial de teste. Roda no browser, não precisa instalar nada além do Node.js:
npx @modelcontextprotocol/inspector uv --directory /caminho/pro/mcp-cep run python server.py
Isso abre uma interface web onde você:
Vê todas as tools e resources registradas
Testa cada tool com inputs customizados
Inspeciona os schemas gerados
Monitora as mensagens JSON-RPC trocadas
Verifica erros e logs
É a forma mais rápida de validar se o servidor tá funcionando. Você vê o consultar_cep listado, coloca um CEP, clica pra executar e vê o resultado. Sem precisar de Claude, ChatGPT ou qualquer outro client.
Exemplo da consulta via MCP com MCP Inspector
Tools
Consultar CEP
Resultado da consulta
Opção 2: Client Python (programático, completo)
Pra quem quer integrar de verdade, o SDK do MCP permite construir seu próprio client. Crie um arquivo client.py:
import asyncio
import sys
from contextlib import AsyncExitStack
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
class MCPClient:
def __init__(self):
self.session: ClientSession | None = None
self.exit_stack = AsyncExitStack()
async def connect(self, server_script: str):
"""Conecta ao MCP Server via STDIO."""
server_params = StdioServerParameters(
command="python",
args=[server_script],
env=None
)
stdio_transport = await self.exit_stack.enter_async_context(
stdio_client(server_params)
)
read_stream, write_stream = stdio_transport
self.session = await self.exit_stack.enter_async_context(
ClientSession(read_stream, write_stream)
)
await self.session.initialize()
# Lista as tools disponíveis
response = await self.session.list_tools()
print("Tools disponíveis:")
for tool in response.tools:
print(f" - {tool.name}: {tool.description}")
async def call_tool(self, name: str, args: dict) -> str:
"""Chama uma tool no servidor."""
result = await self.session.call_tool(name, args)
return result.content[0].text
async def cleanup(self):
await self.exit_stack.aclose()
async def main():
client = MCPClient()
try:
await client.connect("server.py")
# Testa consulta de CEP
print("\n--- Consultando CEP 01001-000 ---")
resultado = await client.call_tool(
"consultar_cep", {"cep": "01001-000"}
)
print(resultado)
# Testa busca por endereço
print("\n--- Buscando 'Paulista' em São Paulo ---")
resultado = await client.call_tool(
"buscar_endereco",
{"uf": "SP", "cidade": "São Paulo", "logradouro": "Paulista"}
)
print(resultado)
finally:
await client.cleanup()
if __name__ == "__main__":
asyncio.run(main())
Instale a dependência do client e rode:
uv add mcp
python client.py
Saída esperada:
Tools disponíveis:
- consultar_cep: Consulta um endereço completo a partir de um CEP brasileiro.
- buscar_endereco: Busca CEPs a partir de um endereço parcial.
--- Consultando CEP 01001-000 ---
CEP: 01001-000
Logradouro: Praça da Sé
Complemento: lado ímpar
Bairro: Sé
Cidade: São Paulo
Estado: SP
DDD: 11
--- Buscando 'Paulista' em São Paulo ---
CEP: 01310-100 | Avenida Paulista - Bela Vista, São Paulo/SP
CEP: 01310-000 | Avenida Paulista - Bela Vista, São Paulo/SP
...
Nenhuma dependência de produto externo. Você criou o servidor e o client, e eles se comunicam via protocolo MCP puro.
Integrando com AI Hosts
Depois de validar que o servidor funciona, aí sim você conecta num Host de verdade. O MCP é suportado nativamente por Claude Desktop, VS Code (Copilot), Cursor, ChatGPT e outros. Cada um tem sua forma de configurar, mas o server é o mesmo. A documentação de cada client explica como apontar pro seu servidor.
Esse é o ponto forte do protocolo: você escreve o server uma vez e qualquer client compatível consome.
Indo além: conceitos avançados
Transporte remoto com Streamable HTTP
O exemplo acima usa STDIO (local). Pra expor seu MCP Server remotamente, use Streamable HTTP:
def main():
mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)
Isso sobe um servidor HTTP que aceita conexões de clients remotos. Você pode colocar atrás de um reverse proxy, adicionar autenticação OAuth, e escalar horizontalmente.
Notificações e progresso
Pra operações longas, o MCP suporta notificações de progresso. O servidor pode enviar updates pro client enquanto processa, em vez de deixar o usuário esperando sem feedback.
Múltiplos servidores
Um MCP Host pode se conectar a vários servidores simultaneamente. Cada um expõe suas próprias tools e resources. O modelo vê todas as tools disponíveis e decide quais usar com base no contexto.
Isso permite composição: um server de CEP + um server de banco de dados + um server de e-mail = um agent que consulta endereço, verifica cadastro no banco e notifica o cliente. Tudo padronizado.
Segurança e controle
O protocolo prevê mecanismos de controle:
Aprovação por tool: o client pode pedir confirmação do usuário antes de executar cada tool.
Permissões granulares: pré-aprovar operações seguras (leitura) e exigir aprovação pra escrita.
Logs de atividade: registrar todas as execuções de tools com resultados.
Quando usar (e quando não usar) MCP
Use quando:
Você quer que seu AI Agent acesse dados ou execute ações em sistemas externos
Precisa de uma integração que funcione com múltiplos clients (Claude, ChatGPT, VS Code)
Quer padronizar as integrações do seu time em vez de cada um inventar seu formato
Não use quando:
Seu caso é uma API simples que só precisa de uma chamada HTTP direta
Você não tem um MCP Host/Client na arquitetura
O overhead do protocolo não se justifica pra uma integração pontual
Ecossistema atual
O MCP já tem um ecossistema considerável de servidores prontos: filesystem, PostgreSQL, GitHub, Slack, Google Drive, entre outros. A lista completa fica no repositório oficial.
Os SDKs oficiais cobrem Python e TypeScript. A comunidade já tem implementações em Go, Rust e Java.
Pra fechar
O MCP é a cola que faltava entre AI Agents e o mundo real. Em vez de cada aplicação reinventar como conectar um modelo a ferramentas externas, agora existe um padrão. Você implementa um MCP Server uma vez e qualquer client compatível consome.
O protocolo é simples o suficiente pra criar um servidor funcional em menos de 100 linhas de Python, mas robusto o suficiente pra rodar em produção com autenticação, transporte HTTP e múltiplos servidores simultâneos.
Se você tá construindo AI Agents ou qualquer aplicação que precisa conectar LLMs a sistemas externos, vale estudar o MCP agora. O ecossistema tá crescendo rápido e quem sair na frente vai ter vantagem.
Todo o código deste artigo foi montado para rodar localmente com Python + uv e serve como base prática para adaptar ao seu ambiente.
A documentação oficial fica em modelcontextprotocol.io.
Me segue no LinkedIn pra mais conteúdo sobre AI Agents, arquitetura e engenharia de software na prática.


