MCP com OAuth em Python: proteja tools antes de expor agentes
Guia prático para proteger servidores MCP em Python com OAuth 2.1, escopos, validação de tokens, allowlists de tools, logs e limites seguros para agentes de IA.
Criar um servidor MCP com Python é simples. O problema começa quando a demonstração vira integração real: o agente agora consegue consultar dados internos, acionar APIs, abrir chamados, editar arquivos ou disparar rotinas de negócio. Nesse ponto, a pergunta deixa de ser “como exponho uma tool?” e passa a ser: quem pode chamar essa tool, com qual permissão e com qual trilha de auditoria?
Este guia mostra um caminho prático para proteger servidores MCP em Python com OAuth, escopos, validação de tokens, allowlists e limites operacionais. A ideia não é transformar todo projeto em uma plataforma de identidade gigante, mas evitar o erro comum de publicar tools úteis demais atrás de uma chave estática compartilhada por todo mundo.
Se você está montando portfólio para vagas de IA, backend ou automação, esse é um diferencial forte: muitas empresas já pedem Python, LLMs, RAG, agentes, MCP, APIs e cloud na mesma descrição. Mostrar que você sabe criar a integração e colocar freios corretos passa mais maturidade do que apenas chamar um modelo.
Quando OAuth faz sentido em MCP
OAuth entra no jogo quando o servidor MCP precisa representar usuários, clientes, times ou aplicações diferentes. Exemplos comuns:
- um agente de suporte que consulta tickets do usuário autenticado;
- uma ferramenta de CRM que só deve mostrar contas permitidas para aquele vendedor;
- um servidor de arquivos que separa leitura, escrita e exportação;
- uma tool de deploy que exige aprovação ou escopo específico;
- um agente interno que roda em vários clientes, cada um com seus próprios dados.
Para um script local de estudo, uma variável de ambiente pode bastar. Para uma integração HTTP usada por um cliente MCP remoto, OAuth ajuda a separar autenticação, autorização, expiração e revogação. O ganho prático é simples: você consegue desligar um token, reduzir escopos ou auditar um usuário sem trocar segredo em todos os lugares.
Modelo mental: MCP não substitui autorização
O MCP padroniza como o cliente conversa com servidores de contexto, resources, prompts e tools. Ele não elimina decisões de segurança da sua aplicação. Cada tool continua sendo código Python executando uma ação real.
Pense em três camadas:
- Transporte: como o cliente chega ao servidor, por exemplo Streamable HTTP atrás de HTTPS.
- Identidade: quem está chamando, validado por token OAuth/JWT ou introspecção.
- Autorização da tool: o que aquela identidade pode fazer, considerando escopos, tenant, ambiente e risco.
O erro perigoso é validar que o token existe e depois liberar todas as tools. OAuth só é útil quando o token vira decisão específica: tickets:read pode consultar tickets, mas não deve acionar refund:create; docs:read pode buscar conteúdo, mas não deve editar página em produção.
Desenho de escopos
Comece pequeno. Escopos demais viram burocracia; escopos genéricos demais viram permissão total. Uma divisão inicial boa é por domínio e ação:
| Escopo | Permite | Evite usar para |
|---|---|---|
profile:read | identificar o usuário e tenant | ler dados sensíveis de negócio |
tickets:read | buscar tickets e status | criar, fechar ou escalar tickets |
tickets:write | comentar ou alterar campos simples | reembolsos, cancelamentos, exclusões |
docs:read | consultar base interna | editar documentos |
reports:run | gerar relatório sob demanda | enviar relatório a terceiros |
deploy:request | abrir solicitação de deploy | executar deploy direto |
admin:tools | tarefas administrativas | uso comum por agentes do dia a dia |
Para actions irreversíveis, prefira um fluxo em duas etapas: a tool cria uma solicitação, registra o contexto e exige aprovação humana fora do agente. Essa regra vale para emails em massa, compras, alterações em produção, exclusões e qualquer operação legal ou financeiramente sensível.
Exemplo mínimo com FastAPI validando JWT
O SDK MCP evolui rápido, e a forma exata de plugar middleware depende do transporte usado. O padrão abaixo mostra a parte mais importante: validar o token antes de chamar a função de negócio e transformar escopos em decisão explícita.
from dataclasses import dataclass
from typing import Iterable
import jwt
from fastapi import Depends, FastAPI, Header, HTTPException
app = FastAPI()
JWKS_OR_SECRET = "troque-por-jwks-em-producao"
ISSUER = "https://auth.exemplo.com"
AUDIENCE = "mcp-python-tools"
@dataclass(frozen=True)
class Principal:
subject: str
tenant_id: str
scopes: set[str]
def parse_authorization(authorization: str | None = Header(default=None)) -> Principal:
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Token ausente")
token = authorization.removeprefix("Bearer ").strip()
try:
payload = jwt.decode(
token,
JWKS_OR_SECRET,
algorithms=["HS256"],
issuer=ISSUER,
audience=AUDIENCE,
)
except jwt.PyJWTError as exc:
raise HTTPException(status_code=401, detail="Token inválido") from exc
return Principal(
subject=str(payload["sub"]),
tenant_id=str(payload["tenant_id"]),
scopes=set(str(payload.get("scope", "")).split()),
)
def require_scopes(required: Iterable[str]):
required_set = set(required)
def dependency(principal: Principal = Depends(parse_authorization)) -> Principal:
missing = required_set - principal.scopes
if missing:
raise HTTPException(
status_code=403,
detail=f"Escopos ausentes: {', '.join(sorted(missing))}",
)
return principal
return dependency
@app.get("/internal/tickets/{ticket_id}")
def get_ticket(
ticket_id: str,
principal: Principal = Depends(require_scopes(["tickets:read"])),
):
return {
"ticket_id": ticket_id,
"tenant_id": principal.tenant_id,
"viewer": principal.subject,
}
Em produção, troque segredo local por JWKS do provedor de identidade, valide iss, aud, expiração e rotação de chaves, e registre o sub em logs. O exemplo usa FastAPI porque muitos servidores MCP em Python acabam expondo transportes HTTP ou integrando código de API existente. O mesmo raciocínio vale se você encapsular essa validação em middleware do servidor MCP.
Aplicando autorização dentro das tools
A validação HTTP resolve a entrada, mas cada tool ainda precisa conhecer seu risco. Um padrão simples é declarar os escopos esperados perto da tool:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Operações Internas")
class AuthContext:
def __init__(self, subject: str, tenant_id: str, scopes: set[str]):
self.subject = subject
self.tenant_id = tenant_id
self.scopes = scopes
def require(self, *scopes: str) -> None:
missing = set(scopes) - self.scopes
if missing:
raise PermissionError(f"Escopos ausentes: {', '.join(sorted(missing))}")
@mcp.tool()
def buscar_ticket(ticket_id: str, auth: AuthContext) -> dict:
"""Busca um ticket do tenant atual."""
auth.require("tickets:read")
return carregar_ticket_do_tenant(auth.tenant_id, ticket_id)
@mcp.tool()
def solicitar_reembolso(ticket_id: str, motivo: str, auth: AuthContext) -> dict:
"""Abre uma solicitação de reembolso para aprovação humana."""
auth.require("refund:request")
pedido = criar_pedido_de_aprovacao(
tenant_id=auth.tenant_id,
ticket_id=ticket_id,
motivo=motivo,
solicitado_por=auth.subject,
)
return {"status": "approval_required", "approval_id": pedido.id}
O detalhe importante é que solicitar_reembolso não executa o reembolso. Ela abre um pedido revisável. Essa separação deixa o agente útil sem entregar poder irreversível a uma saída probabilística.
Allowlist por cliente MCP
Mesmo com escopos, vale manter uma allowlist de tools por cliente. Um editor de código pode precisar de tools de busca e documentação; um agente de atendimento pode precisar de tickets e cadastro; uma automação de deploy deve ter um conjunto mínimo e separado.
Uma configuração simples pode ficar assim:
[clients."suporte-web"]
allowed_tools = ["buscar_ticket", "comentar_ticket", "consultar_cliente"]
required_scopes = ["tickets:read", "tickets:write"]
[clients."docs-bot"]
allowed_tools = ["buscar_documento", "listar_documentos"]
required_scopes = ["docs:read"]
[clients."deploy-assistant"]
allowed_tools = ["consultar_release", "abrir_pedido_deploy"]
required_scopes = ["deploy:request"]
A allowlist reduz dano quando alguém cria uma tool nova e esquece de revisar quem pode usá-la. Sem essa camada, toda tool recém-publicada pode aparecer para clientes que nunca deveriam vê-la.
Logs que ajudam sem vazar segredo
Agentes falham de formas estranhas: chamam tool errada, repetem argumento, tentam uma ação sem contexto ou recebem uma resposta incompleta. Você precisa de log suficiente para investigar, mas não deve registrar tokens, senhas, documentos sensíveis ou dados pessoais sem necessidade.
Registre, no mínimo:
- horário da chamada;
subjectetenant_id;- cliente MCP;
- tool chamada;
- escopos usados;
- resultado: sucesso, negado, erro de validação, erro interno;
- identificador de correlação para seguir a requisição.
Evite logar o prompt inteiro por padrão. Em ambientes com dados sensíveis, prefira amostras redigidas, hashes de argumentos ou campos explicitamente permitidos. Segurança de agente também é segurança de observabilidade.
Rate limit e orçamento de tool
OAuth responde “quem pode”. Ele não responde “quantas vezes”. Um agente preso em loop pode chamar a mesma tool centenas de vezes, mesmo com token válido. Por isso, combine autorização com limites:
- limite por usuário e por tenant;
- limite por tool de alto custo;
- timeout por chamada;
- tamanho máximo de resposta;
- número máximo de chamadas por execução do agente;
- bloqueio temporário para erro repetido.
Para tools caras, retorne resultados paginados ou resumidos. Para relatórios demorados, crie uma tarefa assíncrona e devolva um job_id. Isso evita que o servidor MCP vire uma fila invisível sem controle operacional.
Checklist de produção
Antes de expor um servidor MCP com Python para uso real, revise:
- HTTPS obrigatório no transporte remoto.
- Tokens com expiração curta.
- Validação de issuer, audience, assinatura e expiração.
- Escopos específicos por domínio e ação.
- Allowlist de tools por cliente.
- Separação entre leitura, escrita e ações irreversíveis.
- Aprovação humana para operações sensíveis.
- Logs de auditoria sem tokens ou segredos.
- Rate limit por usuário, tenant e tool.
- Testes automatizados cobrindo acesso permitido e negado.
- Rotação de chaves documentada.
- Ambiente de staging separado de produção.
Esse checklist é simples, mas já coloca o projeto acima da maioria dos exemplos copiados de tutorial.
Testes de autorização
Teste autorização como parte da suíte, não como conferência manual. Um exemplo com pytest:
def test_ticket_read_exige_escopo(client, token_factory):
token = token_factory(scopes=["profile:read"])
response = client.get(
"/internal/tickets/TCK-123",
headers={"Authorization": f"Bearer {token}"},
)
assert response.status_code == 403
assert "tickets:read" in response.json()["detail"]
def test_ticket_read_funciona_com_escopo(client, token_factory):
token = token_factory(scopes=["profile:read", "tickets:read"])
response = client.get(
"/internal/tickets/TCK-123",
headers={"Authorization": f"Bearer {token}"},
)
assert response.status_code == 200
assert response.json()["ticket_id"] == "TCK-123"
Também vale testar se tools de escrita não aparecem para clientes somente leitura. Se o servidor gera lista de tools dinamicamente, crie teste de snapshot com allowlist esperada.
Como isso vira projeto de portfólio
Um bom projeto para demonstrar MCP seguro em Python pode ser pequeno:
- servidor MCP com três tools de suporte;
- FastAPI validando JWT;
- escopos
tickets:readetickets:write; - uma tool sensível que apenas abre pedido de aprovação;
- testes de autorização com
pytest; - logs estruturados com
structlogou logging padrão; - Dockerfile e instruções de execução local.
Conecte esse projeto a conteúdos de base como FastAPI, pytest, Pydantic, LlmOps com Python e LangGraph. Para comparação com outras stacks, vale estudar como comunidades de backend tratam autorização e serviços de baixa latência; um bom complemento é o material de Go em produção, especialmente quando tools MCP chamam APIs internas críticas.
Conclusão
MCP torna integrações de IA mais reutilizáveis, mas também aumenta o alcance das ferramentas que um agente pode chamar. Em Python, a parte técnica de criar tools é rápida; a parte profissional é definir identidade, escopos, allowlists, logs, limites e aprovações.
Se o seu servidor MCP só lê dados públicos, mantenha simples. Se ele acessa dados internos, clientes, arquivos, tickets, deploys ou dinheiro, trate OAuth e autorização como parte central da arquitetura. O agente pode continuar flexível, mas o servidor precisa ser previsível.
Equipe Python Brasil
Contribuidor do Python Brasil — Aprenda Python em Português