Model Context Protocol
& FastMCP
Tudo que você precisa saber: o que é, o que você pode fazer, como consumir, como deployar e onde estão os limites.
O que é MCP
MCP (Model Context Protocol) é um protocolo aberto criado pela Anthropic que padroniza como LLMs se conectam a ferramentas, dados e serviços externos. Pense como um "USB-C para IA": qualquer cliente MCP (Claude, ChatGPT, Cursor, Gemini, seu próprio agente) consegue falar com qualquer servidor MCP sem integrações customizadas.
FastMCP
FastMCP é o framework Python padrão de facto para construir servidores MCP. FastMCP 1.0 foi incorporado diretamente no SDK oficial da Anthropic. A versão standalone atual (pip install fastmcp) é baixada ~1 milhão de vezes por dia e alimenta ~70% dos servidores MCP em todos os idiomas. Atualmente na versão 3.x.
Os 3 Primitivos do MCP
Todo servidor MCP expõe alguma combinação de três tipos de componentes:
Funções invocáveis. O LLM decide quando chamar. Fazem ações, buscam dados ao vivo, chamam APIs.
Fontes de dados passivas acessadas por URI. O cliente lê quando precisa de contexto.
Templates de mensagens reutilizáveis que guiam interações do LLM de forma padronizada.
Transportes
MCP suporta três mecanismos de comunicação:
| Transporte | Quando usar | Características |
|---|---|---|
| STDIO | Ferramentas locais, CLI, Claude Desktop | Processo filho, sem rede, padrão para desktop clients |
| Streamable HTTP | Deploy web, múltiplos clientes, produção | HTTP/streaming, suporta auth, CORS, load balancers. Padrão atual. |
| SSE | Legado | Deprecado, ainda suportado por compatibilidade |
# STDIO (default) — local
mcp.run()
# HTTP — remoto
mcp.run(transport="http", host="0.0.0.0", port=8000)
# ASGI app (para Uvicorn/Gunicorn)
app = mcp.http_app()
# uvicorn app:app --host 0.0.0.0 --port 8000
Tools Server
Tools são o primitivo mais importante — funções Python decoradas que o LLM pode invocar. FastMCP gera automaticamente o JSON Schema, valida inputs e trata erros.
Definição básica
from fastmcp import FastMCP
mcp = FastMCP("MeuServidor")
@mcp.tool
def buscar_cliente(cliente_id: str, incluir_historico: bool = False) -> dict:
"""Busca dados de um cliente pelo ID.
Args:
cliente_id: ID único do cliente
incluir_historico: Se True, inclui histórico de pedidos
"""
# sua lógica aqui
return {"id": cliente_id, "nome": "João"}
Tool assíncrona + Context
from fastmcp import FastMCP, Context
@mcp.tool
async def processar_lote(itens: list[str], ctx: Context) -> str:
"""Processa uma lista de itens com progresso reportado."""
for i, item in enumerate(itens):
await ctx.report_progress(i, len(itens), f"Processando {item}")
await ctx.info(f"Item {item} processado")
# ... lógica ...
return "Concluído"
Tipos suportados
FastMCP suporta qualquer tipo Python com suporte a Pydantic: str, int, float, bool, list, dict, datetime, modelos Pydantic, Literal, Union, Optional, Enum etc.
from pydantic import BaseModel
from enum import Enum
class Status(Enum):
ATIVO = "ativo"
INATIVO = "inativo"
class FiltroCliente(BaseModel):
status: Status
limite: int = 10
@mcp.tool
def listar_clientes(filtro: FiltroCliente) -> list[dict]:
"""Lista clientes com filtro estruturado."""
pass
Tags e controle de exposição
# Exponha ou oculte tools por tags
@mcp.tool(tags={"public", "read-only"})
def tool_publica() -> str: ...
@mcp.tool(tags={"admin"})
def tool_admin() -> str: ...
# No servidor: só expõe tags "public"
mcp.enable(tags={"public"}, only=True)
Resources Server
Resources são fontes de dados identificadas por URI. O cliente decide quando ler — eles não são invocados pelo LLM como tools, mas fornecem contexto sob demanda.
Resource estático
@mcp.resource("config://app")
def get_config() -> dict:
"""Configuração atual da aplicação."""
return {"versao": "1.0", "ambiente": "prod"}
Resource Template (URI dinâmico)
@mcp.resource("usuario://{user_id}/perfil")
async def get_perfil(user_id: str) -> str:
"""Perfil de um usuário específico."""
dados = await buscar_usuario(user_id)
return json.dumps(dados)
# Cliente acessa: usuario://abc123/perfil
Resource com conteúdo binário
from fastmcp import Image
@mcp.resource("relatorio://{mes}/grafico")
async def get_grafico(mes: str) -> Image:
img_bytes = await gerar_grafico(mes)
return Image(data=img_bytes, format="png")
Prompts Server
Prompts são templates reutilizáveis de mensagens para guiar interações do LLM de forma padronizada.
@mcp.prompt
def analisar_dados(pontos: list[float], contexto: str = "") -> str:
"""Template para análise de dados numéricos."""
dados_fmt = ", ".join(str(p) for p in pontos)
return f"""Analise estes dados: {dados_fmt}
Contexto adicional: {contexto}
Por favor identifique tendências, outliers e padrões relevantes."""
Context API Server
O parâmetro ctx: Context em qualquer tool ou resource dá acesso a capacidades do servidor durante execução:
| Método | O que faz |
|---|---|
ctx.info(msg) | Loga mensagem nível info para o cliente |
ctx.debug(msg) | Loga mensagem de debug |
ctx.warning(msg) | Loga aviso |
ctx.report_progress(n, total, msg) | Reporta progresso de operação longa |
ctx.read_resource(uri) | Lê um resource de dentro de uma tool |
ctx.sample(...) | Pede ao cliente LLM para gerar texto (sampling) |
ctx.client_id | Identifica o cliente conectado |
ctx.request_id | ID único da requisição atual |
@mcp.tool
async def resumir_doc(uri: str, ctx: Context) -> str:
# Lê resource
conteudo = await ctx.read_resource(uri)
# Pede ao LLM para resumir (sampling)
resumo = await ctx.sample(
f"Resuma em 3 bullets:\n{conteudo[0].text}",
max_tokens=300
)
return resumo.text
Lifespan Server
Lifespan gerencia recursos compartilhados que precisam existir durante toda a vida do servidor (conexões DB, pools HTTP, caches):
from contextlib import asynccontextmanager
from fastmcp import FastMCP
@asynccontextmanager
async def lifespan(server):
# Setup: roda quando servidor inicia
db = await Database.connect("postgresql://...")
redis = await Redis.create(...)
yield {"db": db, "redis": redis} # disponível via ctx.lifespan_context
# Teardown: roda quando servidor para
await db.disconnect()
await redis.close()
mcp = FastMCP("MeuApp", lifespan=lifespan)
@mcp.tool
async def buscar(query: str, ctx: Context) -> list:
db = ctx.lifespan_context["db"]
return await db.fetch(query)
Composição de Servidores Advanced
FastMCP permite montar múltiplos servidores como sub-servidores, criando hierarquias e namespacing:
# Servidor de banco de dados
db_mcp = FastMCP("Database")
@db_mcp.tool
def query(sql: str) -> list: ...
# Servidor de arquivos
files_mcp = FastMCP("Files")
@files_mcp.tool
def ler_arquivo(path: str) -> str: ...
# Servidor principal que compõe os dois
main = FastMCP("Principal")
main.mount(db_mcp, prefix="db")
main.mount(files_mcp, prefix="files")
# Resultado: tools "db_query" e "files_ler_arquivo"
OpenAPI → MCP Server
from fastmcp import FastMCP
# Gera servidor MCP a partir de qualquer OpenAPI spec
mcp = FastMCP.from_openapi(
openapi_url="https://api.meuservico.com/openapi.json",
client=httpx.AsyncClient(base_url="https://api.meuservico.com")
)
# Cada endpoint vira uma tool automaticamente!
FastMCP Client Client
O Client é a interface programática para consumir qualquer servidor MCP — ideal para testes, agentes determinísticos e aplicações que precisam controlar chamadas MCP diretamente.
Conectando a servidores
import asyncio
from fastmcp import Client
# Em memória (mesmo processo — ideal para testes)
client = Client(meu_servidor)
# HTTP remoto
client = Client("https://mcp.meuservico.com/mcp")
# Script local (lança como subprocess via STDIO)
client = Client("./servidor.py", env={"API_KEY": "..."})
# Multi-servidor via config
client = Client({
"mcpServers": {
"db": {"url": "https://db.exemplo.com/mcp"},
"files": {"command": "python", "args": ["./files.py"]}
}
})
Operações disponíveis
async with Client("https://mcp.exemplo.com/mcp") as client:
# Listar
tools = await client.list_tools()
resources = await client.list_resources()
prompts = await client.list_prompts()
# Chamar tool
resultado = await client.call_tool("buscar_cliente", {"cliente_id": "123"})
print(resultado.data)
# Ler resource
conteudo = await client.read_resource("usuario://123/perfil")
print(conteudo[0].text)
# Renderizar prompt
msgs = await client.get_prompt("analisar_dados", {"pontos": [1, 2, 3]})
# Ping
await client.ping()
Integração com LangChain/LangGraph
from langchain_mcp_adapters.client import MultiServerMCPClient
async with MultiServerMCPClient({
"agente_especialista": {
"url": "https://agent.interno/mcp",
"transport": "streamable_http",
"headers": {"Authorization": f"Bearer {token}"}
}
}) as client:
tools = client.get_tools() # LangChain BaseTool list
agent = create_react_agent(llm, tools)
Callbacks do Client Client
from fastmcp import Client
async def on_log(msg):
print(f"[SERVER] {msg.level}: {msg.data}")
async def on_progress(progress, total, msg):
pct = int(progress / total * 100) if total else "?"
print(f"[PROGRESSO] {pct}% — {msg}")
client = Client(
"https://mcp.exemplo.com/mcp",
log_handler=on_log,
progress_handler=on_progress,
timeout=60.0
)
| Callback | Quando dispara |
|---|---|
log_handler | Server envia mensagem de log via ctx.info/debug/warning |
progress_handler | Server chama ctx.report_progress() |
sampling_handler | Server pede ao cliente para gerar texto LLM |
elicitation_handler | Server pede input interativo do usuário |
Sampling Advanced
Sampling permite que o servidor peça ao LLM do cliente para gerar texto — inversão do fluxo normal. Útil para agentes que precisam de raciocínio intermediário sem expor credenciais do LLM no servidor.
# No SERVIDOR: pede ao cliente para gerar texto
@mcp.tool
async def classificar(texto: str, ctx: Context) -> str:
resposta = await ctx.sample(
f"Classifique este texto em: positivo/negativo/neutro.\nTexto: {texto}",
max_tokens=50
)
return resposta.text
# No CLIENTE: provê o handler de sampling
async def sampling_handler(messages, params, context):
response = await llm.ainvoke(messages)
return response.content
client = Client(mcp, sampling_handler=sampling_handler)
Deploy HTTP Produção
Standalone
# server.py
from fastmcp import FastMCP
mcp = FastMCP("MeuServidor")
@mcp.custom_route("/health", methods=["GET"])
async def health(request):
from starlette.responses import JSONResponse
return JSONResponse({"status": "ok"})
if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8000)
# Endpoint MCP: http://host:8000/mcp
ASGI (produção com Uvicorn/ECS)
# app.py
app = mcp.http_app(path="/mcp")
# Dockerfile / ECS task
# uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
Montado em FastAPI
from fastapi import FastAPI
from fastmcp import FastMCP
api = FastAPI()
mcp = FastMCP("MeuMCP")
# Suas rotas REST normais
@api.get("/usuarios")
async def usuarios(): ...
# MCP montado em /mcp
mcp_app = mcp.http_app()
api.mount("/mcp", mcp_app)
# uvicorn main:api
Autenticação Produção
FastMCP suporta múltiplos mecanismos de autenticação para servidores HTTP. Relevante para sua arquitetura com JWT/Bearer:
Bearer Token (JWT via IdP externo — Okta/Auth0)
from fastmcp import FastMCP
from fastmcp.server.auth import BearerAuthProvider
# Valida JWTs emitidos pelo seu IdP via JWKS
auth = BearerAuthProvider(
jwks_uri="https://seu-idp.com/.well-known/jwks.json",
audience="https://sua-api.com",
issuer="https://seu-idp.com/"
)
mcp = FastMCP("SecureServer", auth=auth)
# Acessar claims do token dentro das tools:
@mcp.tool
async def minha_tool(ctx: Context) -> str:
token = ctx.get_access_token()
user_id = token.claims.get("sub")
return f"Olá, {user_id}"
OAuth 2.1 completo
FastMCP tem integrações prontas para: Auth0, Okta, AWS Cognito, Azure Entra ID, Google, GitHub, Discord, Keycloak, Supabase, e mais. Cada uma tem uma página de docs em gofastmcp.com/integrations/.
Client com Bearer
from fastmcp import Client
from fastmcp.client.auth import BearerAuth
client = Client(
"https://mcp.meuservico.com/mcp",
auth=BearerAuth(token="eyJ...")
)
Middleware Server
Middleware intercepta toda mensagem MCP (requests, responses, notifications) — ideal para logging, rate limiting, tracing:
from fastmcp import FastMCP
from fastmcp.server.middleware import Middleware, MiddlewareContext
class LoggingMiddleware(Middleware):
async def on_request(self, ctx: MiddlewareContext, call_next):
print(f"→ {ctx.method} | client={ctx.client_id}")
response = await call_next(ctx)
print(f"← {ctx.method} ok")
return response
mcp = FastMCP("App", middleware=[LoggingMiddleware()])
# CORS para HTTP (necessário apenas para clientes browser)
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware import Middleware as StarletteMiddleware
app = mcp.http_app(middleware=[
StarletteMiddleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"],
allow_headers=["mcp-protocol-version", "mcp-session-id", "Authorization"],
expose_headers=["mcp-session-id"])
])
Background Tasks v2.14+
Operações longas podem rodar assincronamente enquanto o cliente faz polling do resultado — evita timeout de load balancers:
# Habilitar no servidor
mcp = FastMCP("App", tasks=True)
@mcp.tool
async def processar_video(url: str, task=True) -> str:
"""Processa vídeo de forma assíncrona."""
# Retorna imediatamente, roda em background
resultado = await download_e_processar(url)
return resultado
# Cliente: chama e observa progresso
async with client:
task = await client.call_tool("processar_video", {"url": "..."})
resultado = await client.wait_for_task(task.task_id)
OpenAPI → MCP Integração
FastMCP pode gerar um servidor MCP completo a partir de qualquer OpenAPI spec — cada endpoint vira uma tool automaticamente:
import httpx
from fastmcp import FastMCP
# A partir de URL
mcp = FastMCP.from_openapi(
openapi_url="https://api.exemplo.com/openapi.json",
client=httpx.AsyncClient(
base_url="https://api.exemplo.com",
headers={"Authorization": "Bearer ..."}
),
name="ExemploAPI"
)
# A partir de dict/arquivo local
with open("openapi.json") as f:
spec = json.load(f)
mcp = FastMCP.from_openapi(openapi=spec, client=httpx.AsyncClient(...))
Limites e Considerações
⚠️ Sem estado por padrão
MCP servers HTTP são stateless entre requisições. Use session_state_store ou Redis externo para estado de sessão.
⚠️ Timeout de LB
Tools lentas podem causar timeout em ALBs (padrão 60s). Use Background Tasks ou aumente o idle timeout no ALB.
⚠️ Schema de tools
O LLM vê o JSON Schema das tools. Inputs muito complexos (schemas aninhados profundos) podem confundir o modelo. Prefira tipos simples ou Pydantic models bem documentados.
⚠️ Erro handling
Em produção, use mask_error_details=True para não vazar detalhes internos de erro para o LLM/cliente.
⚠️ CORS
Só necessário se o cliente MCP roda no browser. Claude Desktop, agentes LangGraph e API calls não precisam de CORS.
⚠️ SSE deprecado
Transport SSE está deprecado. Use Streamable HTTP (transport="http") para novos deployments.
Limites de tamanho
| Item | Limite / Comportamento |
|---|---|
| Tamanho de payload | Definido pelo servidor HTTP (padrão Uvicorn: sem limite hard) |
| Tools por servidor | Sem limite técnico, mas muitas tools confundem LLMs. Use list_page_size para paginar. |
| Timeout de tool | Configurável no Client(timeout=...). Default: 30s |
| Resources binários | Suporte nativo a bytes/images. Limite de memória da instância. |
| Sessões simultâneas | Limitado pelo número de workers (Uvicorn/Gunicorn) |
Clientes MCP compatíveis
Qualquer servidor FastMCP é automaticamente compatível com:
LLM Hosts
Claude Desktop, Claude Code, ChatGPT, Gemini CLI, Cursor, Goose
SDKs
Anthropic API, OpenAI API, Gemini SDK, Pydantic AI
Agentes
LangChain/LangGraph (via langchain-mcp-adapters), Pydantic AI, qualquer cliente MCP custom
Na sua arquitetura multi-agente AWS
Mapeando o MCP para os três accounts que você opera:
| Account / Componente | Papel MCP | Padrão recomendado |
|---|---|---|
| Supervisor Account LangGraph + Redis |
MCP Client | Usa MultiServerMCPClient do LangChain para chamar specialists como tools. O StateGraph gerencia o fluxo. |
| Agents Account ECS + Specialist Agents |
MCP Server | Cada specialist é um FastMCP rodando como ASGI no ECS. Auth via BearerAuthProvider + JWKS do seu IdP. Exposto via ALB interno. |
| BFFs Account External Actions |
MCP Server | FastMCP com from_openapi() wrappando APIs externas, ou tools custom para integrações específicas. |
Padrão de auth cross-account
# Supervisor → Specialist (via Bearer JWT do IdP)
from langchain_mcp_adapters.client import MultiServerMCPClient
# Token obtido do IdP (Okta/Auth0) com escopo do agente
token = await get_agent_token(scope="specialist:financeiro")
client = MultiServerMCPClient({
"financeiro": {
"url": "https://financeiro-agent.interno/mcp",
"transport": "streamable_http",
"headers": {"Authorization": f"Bearer {token}"}
}
})
# No specialist server: valida o JWT
auth = BearerAuthProvider(
jwks_uri="https://seu-idp.com/.well-known/jwks.json",
audience="specialist:financeiro"
)
mcp = FastMCP("Financeiro", auth=auth)
Lifespan do FastMCP para inicializar a conexão com o OpenSearch Knowledge Base do specialist. O contexto fica disponível em todas as tools via ctx.lifespan_context — evita reconectar a cada chamada.
CLI úteis para dev e debug
# Instalar servidor no Claude Desktop/Code
fastmcp install servidor.py
# Inspecionar tools/resources de um servidor
fastmcp inspect servidor.py
# Chamar tool direto do terminal
fastmcp call servidor.py buscar_cliente --cliente_id=123
# Gerar CLI tipado a partir de um servidor MCP
fastmcp generate-cli servidor.py -o cli_tool.py