Agentes de IA para segurança
O que é MCP? O Model Context Protocol explicado
MCP é o protocolo aberto que liga apps de IA a ferramentas e dados. Veja papéis, primitivas, transportes e segurança com um laboratório isolado.
Como funciona o uso de ferramentas: o modelo devolve uma chamada estruturada, o programa executa a ferramenta e regras de aprovação decidem o que roda.
Um modelo de chat produz texto. Um agente é um sistema que faz o modelo ir além disso: ele lê um pedido, percebe que precisa de uma informação ou de uma ação fora da conversa, pede a um programa hospedeiro que faça o trabalho e segue adiante com o resultado. Quase todo agente se apoia no mesmo mecanismo, o uso de ferramentas, também chamado de chamada de função.
Este artigo explica esse mecanismo passo a passo, com os formatos de requisição e resposta descritos na documentação pública de APIs. Você vai ver por que o programa hospedeiro, e não o modelo, decide o que realmente roda, por que ações com efeito colateral precisam de uma decisão humana e o que a lista da OWASP chama de excesso de agência.
Não é preciso laboratório. Uma pequena simulação local, no fim do texto, roda o ciclo com um modelo de mentira, para que você veja o fluxo de controle sem enviar nada a uma API.
Um agente tem três partes.
O modelo recebe texto e devolve texto. No uso de ferramentas, ele também pode produzir um pedido estruturado que diz "chame esta função com estes argumentos".
O hospedeiro é o programa que você escreve ou executa. Ele envia as requisições ao modelo, mantém a conversa, lê a saída e decide o que fazer com ela. É no hospedeiro que fica o código real de cada ferramenta.
As ferramentas são funções que o hospedeiro aceita rodar em nome do modelo: consultar um arquivo, ler um banco de dados, buscar uma página, enviar uma mensagem. Cada ferramenta tem um nome, uma descrição e um esquema para os argumentos.
O modelo propõe e o hospedeiro decide. Boa parte das questões de segurança deste artigo sai dessa divisão.
Um modelo sem ferramentas só responde com o que tem no contexto. Pergunte a ele a cotação de hoje e ele vai chutar ou dizer que não sabe. Com uma ferramenta de clima, ele pede ao hospedeiro o valor, recebe a resposta e escreve a partir dela.
O ciclo também permite vários passos. O modelo pode pesquisar, ler um resultado, achar que não basta, abrir um link que encontrou e então resumir. Cada passo é uma ida e volta separada, e o hospedeiro controla todas: quantas são permitidas, quais ferramentas são oferecidas e o que acontece quando uma chamada falha.
Toda troca com ferramentas segue a mesma sequência, qualquer que seja a API.
O ciclo termina quando o modelo devolve texto simples, quando o hospedeiro atinge um limite de turnos ou quando o hospedeiro o interrompe.
Uma definição de ferramenta tem nome, descrição escrita para o modelo e um esquema de entrada. O esquema usa JSON Schema para declarar o tipo de cada argumento e quais são obrigatórios. O modelo escolhe ferramentas lendo as descrições, então uma descrição vaga gera chamadas erradas.
O modelo não executa nada. Ele devolve um dado que informa o nome da função e os argumentos em um objeto JSON. Como a saída é estruturada, o hospedeiro não precisa adivinhar o nome da função a partir de texto livre.
Estruturado não quer dizer correto. O modelo pode citar uma ferramenta que não existe, ou passar um valor que respeita o esquema mas não faz sentido naquela situação.
Essa etapa é só do hospedeiro. Ele procura o nome no próprio registro. Se o nome não existe, responde com erro. Se existe, ainda precisa decidir se aquela chamada, com aqueles argumentos, para aquele usuário, pode rodar. Só então executa o código.
O resultado vai como uma mensagem que carrega o ID da chamada original. O modelo lê o resultado como dado e decide o próximo passo. Trate esse conteúdo como dado, não como instrução. Uma página web buscada pode conter frases que parecem comandos. Esse é o cerne da injeção de prompt, e o hospedeiro não pode deixar que um conteúdo devolvido altere as regras dele.
Os formatos abaixo seguem duas referências públicas sobre uso de ferramentas, consultadas em 08/11/2026. Os identificadores de modelo são marcadores. Os nomes dos campos são exatos.
{
"model": "<model-id>",
"max_tokens": 1024,
"tools": [
{
"name": "get_weather",
"description": "Get the current weather for a given location.",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}
}
],
"messages": [
{ "role": "user", "content": "What's the weather in San Francisco?" }
]
}tools é a lista completa de funções que o modelo pode chamar. O que não está nela não existe para o modelo.name é o que o modelo devolve, e é com ele que o hospedeiro escolhe a função.description é lida pelo modelo, então deve dizer o que a ferramenta faz e quando usá-la.input_schema é JSON Schema. required informa quais argumentos o modelo precisa fornecer.{
"stop_reason": "tool_use",
"content": [
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "location": "San Francisco, CA" }
}
]
}stop_reason com o valor tool_use indica que o modelo espera um resultado. Um hospedeiro que só lê texto deixa essa chamada passar despercebida.type igual a tool_use é a chamada estruturada. O id precisa voltar na resposta.input deve respeitar o esquema. Valide mesmo assim, porque o modelo não é um motor de esquemas.{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "15 degrees Celsius, partly cloudy"
}
]
}tool_use_id precisa ser igual ao id da chamada. Se não for, o pareamento quebra.content curto. Despejos grandes gastam contexto e podem carregar texto injetado.Uma segunda referência usa outros nomes para a mesma ideia. A ferramenta tem type igual a function, e o modelo devolve um item function_call cujo campo arguments é uma string JSON que o hospedeiro precisa interpretar.
{
"type": "function",
"name": "get_weather",
"description": "Retrieves current weather for the given location.",
"parameters": {
"type": "object",
"properties": {
"location": { "type": "string" }
},
"required": ["location"],
"additionalProperties": false
},
"strict": true
}{
"type": "function_call",
"id": "fc_12345xyz",
"call_id": "call_12345xyz",
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}{
"type": "function_call_output",
"call_id": "call_12345xyz",
"output": "{\"temperature\":\"25\",\"unit\":\"C\"}"
}Essa referência afirma a regra com clareza: quando o modelo chama uma função, a sua aplicação precisa executá-la e devolver o resultado. Os nomes dos campos mudam entre endpoints e versões, então confira antes de copiar um formato.
É tentador imaginar o modelo "usando" uma ferramenta como uma pessoa usa uma calculadora. Essa imagem esconde o fato principal. O modelo escreve um texto que descreve uma chamada. É o hospedeiro que decide se esse texto vira ação.
Isso dá ao hospedeiro três funções. Ele é o único lugar onde o código executa, então uma função de apagar só roda quando o hospedeiro a executa. É também o único lugar onde a política pode ser aplicada: uma frase no prompt dizendo "não escreva arquivos" é um pedido, enquanto a ausência de uma ferramenta de escrita ou um papel de banco somente leitura é um controle. E ele controla o escopo, porque escolhe quais ferramentas são oferecidas a cada turno. Oferecer menos ferramentas é o controle mais barato que você tem.
A especificação do MCP descreve a mesma divisão em termos de protocolo. O host é a aplicação com LLM, os clientes são conectores dentro dela, e os servidores expõem ferramentas. A especificação diz que hosts devem obter consentimento explícito do usuário antes de invocar qualquer ferramenta, e que descrições de comportamento de ferramentas, como as anotações, devem ser tratadas como não confiáveis, salvo se vierem de um servidor confiável. A página da especificação que eu verifiquei em 08/11/2026 aponta para uma revisão datada de 28/07/2026. Leia a revisão atual antes de construir sobre ela.
Algumas chamadas só leem. Uma busca ou uma consulta não muda nada fora do hospedeiro. Outras mudam algo: enviam uma mensagem, alteram um registro, gastam dinheiro ou apagam dados.
Uma leitura errada gera uma resposta errada que alguém pode conferir. Uma escrita errada pode ser permanente, pode atingir outras pessoas e pode passar despercebida por um tempo. Por isso um padrão sensato é separar as ferramentas pelo efeito e exigir uma decisão explícita de uma pessoa antes de qualquer chamada que cause efeito colateral. O hospedeiro mostra a ação em linguagem simples, com os argumentos reais, e espera um sim.
Aprove a chamada específica, não a sessão. Um sim dado uma vez para "enviar e-mail" não deve cobrir em silêncio as próximas cem mensagens com destinatários diferentes. A aprovação também falha quando quem revisa não olha com atenção, então combine com argumentos visíveis, limites para o que uma chamada pode fazer e registros.
A OWASP Top 10 para aplicações com LLM lista o excesso de agência como LLM06:2025. A entrada descreve uma vulnerabilidade que permite ações danosas em resposta a saídas inesperadas, ambíguas ou manipuladas do modelo, qualquer que seja a causa. Alucinação e injeção de prompt, direta ou indireta, são gatilhos comuns.
Ela aponta três causas raiz: funcionalidade excessiva (ferramentas que a tarefa não precisa), permissões excessivas (ferramentas que podem mais do que a tarefa exige) e autonomia excessiva (ações de alto impacto sem nenhuma verificação). As mitigações decorrem delas. Ofereça só as ferramentas necessárias. Prefira ferramentas estreitas a shells gerais ou leitores de URL arbitrárias. Imponha o acesso mínimo no sistema de destino, por exemplo com papéis de banco ou escopos OAuth restritos. Execute ações sob a autorização do próprio usuário. Exija aprovação humana para ações de alto impacto. Aplique a autorização no sistema de destino, em vez de confiar que o modelo decida.
Registro, monitoramento e limite de taxa aparecem como medidas para limitar danos. Eles não impedem o problema.
Esta simulação não tem provedor, chamada de rede nem segredo. O "modelo" é um stub fixo. Ele devolve uma chamada de ferramenta no primeiro turno e texto depois que o resultado chega. O papel dele é mostrar onde fica o hospedeiro. Troque o stub por uma chamada real de API no seu ambiente e mantenha a lógica do hospedeiro.
Salve como loop_demo.py:
# Local sketch: no provider, no network. The "model" is a fixed stub that
# returns the same shape a provider returns for a tool call.
import json
def get_weather(location: str) -> str:
return json.dumps({"location": location, "temp_c": 15, "sky": "partly cloudy"})
def delete_file(path: str) -> str:
return json.dumps({"deleted": path}) # never reached in this demo
# The host decides what may run. The model only names a tool.
REGISTRY = {"get_weather": get_weather} # delete_file is NOT registered
REQUIRES_APPROVAL = {"delete_file"}
def fake_model(messages):
# Stub: first turn asks for weather, second turn answers with text.
last = messages[-1]
if last["role"] == "user" and isinstance(last["content"], str):
return {"stop_reason": "tool_use",
"content": [{"type": "tool_use", "id": "toolu_demo_1",
"name": "get_weather", "input": {"location": "Lisboa, PT"}}]}
return {"stop_reason": "end_turn", "content": [{"type": "text", "text": "done"}]}
messages = [{"role": "user", "content": "Weather in Lisboa?"}]
for step in range(5): # hard cap on turns
reply = fake_model(messages)
messages.append({"role": "assistant", "content": reply["content"]})
if reply["stop_reason"] != "tool_use":
print("final:", reply["content"][0]["text"])
break
results = []
for block in reply["content"]:
if block["type"] != "tool_use":
continue
name, args = block["name"], block["input"]
if name not in REGISTRY:
out, err = f"unknown tool {name}", True
elif name in REQUIRES_APPROVAL:
out, err = "approval required", True
else:
out, err = REGISTRY[name](**args), False
print(f"host ran {name}({args}) -> {out}")
results.append({"type": "tool_result", "tool_use_id": block["id"],
"content": out, "is_error": err})
messages.append({"role": "user", "content": results})Rode a partir de uma pasta que contenha só este arquivo:
python -I loop_demo.pyA flag -I inicia o Python em modo isolado. Ela ignora variáveis de ambiente e tira a pasta atual do caminho de importação, um hábito útil para código que você não escreveu.
Em uma máquina Windows 11 com Git Bash e Python 3.12.10, a execução imprimiu:
host ran get_weather({'location': 'Lisboa, PT'}) -> {"location": "Lisboa, PT", "temp_c": 15, "sky": "partly cloudy"}
final: doneO stub devolveu um stop reason tool_use e uma chamada a get_weather. O hospedeiro achou o nome no registro, executou a função e gravou um tool_result na conversa. Depois o stub respondeu com texto e o ciclo terminou. delete_file nunca rodou porque o hospedeiro não o registrou. Para ver o ramo de aprovação, faça o stub pedir delete_file e observe o hospedeiro recusar.
Oferecer todas as ferramentas do código. O modelo usa o que vê, e também qualquer texto que ele lê. Comece com uma lista curta e adicione ferramentas uma de cada vez.
Escrever descrições vagas. "Faz coisas com arquivos" convida a chamadas erradas. Diga o que a ferramenta lê ou escreve e quando não usá-la.
Confiar nos argumentos só porque passaram no esquema. A validação de esquema confere a forma, não o sentido. Um caminho bem formado pode apontar para fora da pasta que você queria liberar.
Confiar no conteúdo devolvido. Um resultado de ferramenta é dado. Colá-lo no próximo prompt como se fosse instrução dá a uma página web o poder de conduzir o seu hospedeiro.
Esquecer o limite de turnos. Um ciclo sem teto pode repetir uma chamada que falha até gastar dinheiro de verdade.
Aprovar a sessão inteira. Aprove chamadas específicas, com os argumentos à vista.
Registrar as coisas erradas. Guarde nomes de ferramentas, decisões e resultados com cuidado com dados pessoais. Prompts e respostas completos nos logs podem guardar dados que você não deveria manter.
Monte um ciclo com uma chave de API real e uma ferramenta somente leitura, como uma função que devolve a hora atual. Inspecione os corpos da requisição e da resposta, encontre o bloco tool_use e o tool_result que você devolve.
Depois adicione uma ferramenta que escreve em um arquivo de rascunho dentro de uma pasta sua, com uma etapa de aprovação antes dela. Peça ao modelo um caminho fora dessa pasta e confira se o hospedeiro interrompe a chamada.
Leia as seções da especificação do MCP sobre ferramentas e segurança, e a entrada da OWASP sobre excesso de agência por inteiro. Associe cada mitigação a uma linha do seu código.
Consultadas em 08/11/2026. Páginas de documentação mudam, então confirme a versão atual antes de confiar em um nome de campo.
Agentes de IA para segurança
MCP é o protocolo aberto que liga apps de IA a ferramentas e dados. Veja papéis, primitivas, transportes e segurança com um laboratório isolado.