Chamadas duplicadas de ferramentas MCP precisam de uma identidade de execução
Chamadas duplicadas de ferramentas MCP exigem mais que tentativas repetidas. Use impressões digitais de solicitações e registros de atividades para separar reconexões de segundas ações reais.

Uma reconexão é um problema de transporte. Uma segunda ação é um problema de execução. As equipes sofrem quando tratam essas situações como se fossem a mesma coisa e deixam o cliente repetir toda solicitação que não recebeu uma resposta visível.
É fácil não perceber isso no MCP porque uma chamada de ferramenta pode atravessar vários limites: um processo de agente, um transporte MCP, um gateway de ações e uma API HTTP ou um destino SSH. A conexão pode desaparecer depois que o destino aceitou o trabalho, mas antes que o agente receba o resultado. Se o sistema enviar a chamada novamente, o destino verá duas solicitações válidas. Não há motivo para ele deduzir que a segunda foi um acidente.
A solução não é aumentar o orçamento de tentativas. Dê a cada ação solicitada uma identidade de execução, registre seu ciclo de vida e tome decisões sobre novas tentativas com base nesse registro. Uma impressão digital da solicitação mostra o que o chamador tentava fazer. Um registro de atividades mostra se o sistema já iniciou ou concluiu a ação. Você precisa dos dois.
Uma reconexão não autoriza outra execução
Um cliente que se reconecta depois de um fluxo interrompido tem evidências de que a comunicação falhou. Ele não tem evidências de que a chamada de ferramenta original falhou.
Essa distinção parece óbvia até que alguém adicione um middleware genérico de tentativas abaixo de um cliente MCP. O middleware vê um timeout, uma conexão redefinida ou a ausência de uma resposta. Ele não sabe se o POST era uma leitura, uma gravação, um comando remoto ou uma operação irreversível. Envia os bytes novamente porque é isso que o código de tentativas HTTP costuma fazer.
Para uma leitura como GET /repos/acme/api/branches, isso pode ser aceitável. Para POST /payments, DELETE /projects/atlas ou um comando SSH que altera um host de produção, pode criar um segundo efeito colateral. A camada de ferramentas não consegue corrigir isso depois simplesmente retornando um único resultado ao modelo.
A especificação de transporte MCP Streamable HTTP permite explicitamente que os clientes retomem a entrega de eventos do servidor para o cliente com Last-Event-ID quando um fluxo é interrompido. Esse é um mecanismo de recuperação para mensagens em um fluxo. Ele não transforma uma segunda solicitação JSON-RPC tools/call na mesma execução. A documentação do SDK TypeScript também separa os tokens de retomada do caminho da solicitação e permite middleware do cliente em torno de fetch. Essa combinação explica por que as equipes devem declarar a regra de tentativas no código, em vez de presumir que o transporte as salvará.
Use esta regra:
Retome um fluxo de resposta quando o protocolo oferecer suporte a isso. Reenvie uma ação com efeito colateral somente quando a camada da ação puder identificá-la como a mesma execução.
Os casos difíceis não são as falhas limpas. São aqueles em que o servidor inicia o trabalho, a resposta desaparece e o cliente não sabe se deve esperar, retomar, consultar o status ou tentar novamente. Seu projeto precisa tornar essa incerteza visível.
IDs JSON-RPC identificam mensagens, não ações duráveis
Um ID de solicitação JSON-RPC é útil para relacionar uma solicitação à sua resposta. Ele não basta para eliminar duplicatas entre reconexões, reinicializações de processos ou execuções separadas de agentes.
Considere este par de chamadas:
{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"deploy_release","arguments":{"service":"catalog","version":"2026.07.22"}}}
{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"deploy_release","arguments":{"service":"catalog","version":"2026.07.22"}}}
Elas podem ser a mesma solicitação enviada duas vezes depois de uma conexão interrompida. Também podem vir de dois processos de cliente separados que começam a numerar em 1 ou 41. Mesmo dentro de um único processo, um erro de implementação pode reutilizar IDs. O valor diz muito pouco, a menos que esteja vinculado a um chamador autenticado e a uma sessão específica do protocolo.
Agora considere duas chamadas com IDs diferentes:
{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"deploy_release","arguments":{"service":"catalog","version":"2026.07.22"}}}
{"jsonrpc":"2.0","id":42,"method":"tools/call","params":{"name":"deploy_release","arguments":{"service":"catalog","version":"2026.07.22"}}}
Elas podem ser uma tentativa repetida de transporte cujo cliente atribuiu um novo ID. Ou o agente pode ter solicitado deliberadamente uma segunda implantação depois de receber um resultado incerto. IDs de mensagens são evidências, não a decisão.
Não cometa o erro oposto e elimine para sempre toda chamada de ferramenta com correspondência. Implantar a mesma versão duas vezes pode ser inofensivo ou até intencional. Criar o mesmo ticket externo duas vezes pode ser um problema. Rotacionar uma credencial duas vezes pode bloquear um serviço. A classe da ação determina por quanto tempo uma identidade de execução continua significativa.
Um modelo prático mantém três identificadores separados:
- ID de correlação do protocolo: o ID JSON-RPC e, quando aplicável, o contexto da sessão ou do fluxo MCP.
- ID de execução: um identificador criado pelo servidor para uma tentativa aceita de executar uma ação de ferramenta.
- Impressão digital da intenção: um resumo estável do efeito solicitado, usado para encontrar uma execução anterior quando a correlação do protocolo muda.
Depois que você separa esses elementos, os logs deixam de fingir que respondem a uma pergunta que não conseguem responder.
Uma impressão digital útil descreve o efeito
Uma impressão digital de solicitação deve permanecer igual quando a entrega muda e mudar quando o efeito solicitado muda. Não faça o hash dos bytes JSON brutos e chame o resultado de impressão digital. O JSON bruto varia com a ordem das propriedades, os espaços em branco, os valores padrão opcionais, os IDs de solicitação e alterações de formatação sem importância.
Primeiro crie um registro canônico da ação. Para uma ação HTTP, ele pode ter este formato:
{
"actor": "signed-process:com.example.agent",
"tool": "deploy_release",
"channel": "http",
"target": "deploy-api.internal.example/releases",
"credential_ref": "deploy-service",
"method": "POST",
"arguments": {
"service": "catalog",
"version": "2026.07.22",
"region": "us-east-1"
},
"intent_scope": "run:5f8097"
}
Torne a ordem dos campos canônica, omita campos sem significado semântico e normalize equivalentes conhecidos antes de calcular um resumo. Se region tiver como padrão us-east-1, sempre materialize o campo ou sempre o omita quando o destino fornecer esse padrão. Misturar os dois comportamentos cria falsos desencontros.
O campo actor importa. Dois processos de agente autorizados diferentes que enviam argumentos idênticos podem representar ações pretendidas separadas. credential_ref também importa. Uma solicitação feita por uma identidade de serviço não é necessariamente equivalente ao mesmo caminho e corpo feitos por outra.
No SSH, inclua a identidade do host, a conta, o comando, o diretório de trabalho se ele afetar o comportamento e uma representação normalizada do comando quando for possível produzi-la com segurança.
Mantenha os segredos fora do registro canônico. Nunca coloque tokens bearer, chaves privadas ou cabeçalhos de autorização brutos na entrada de uma impressão digital. Se um argumento contiver um segredo, substitua-o por uma referência interna protegida ou calcule a impressão digital usando uma construção com chave, como HMAC. Um hash simples e sem salt de um segredo com baixa entropia transforma seu armazenamento de auditoria em um oráculo para tentativas.
A recomendação conhecida de «basta fazer o hash da solicitação» é popular porque é curta. Para controlar ações, ela está errada. Um hash prova apenas que alguns bytes foram enviados a uma função. Ele não informa se esses bytes representam o mesmo ator, o mesmo efeito no destino ou a mesma janela de tentativa.
O registro de atividades precisa de estados, não de uma única linha de log
Um registro de atividades útil responde onde a ação parou. Se ele registrar apenas sucesso e falha, uma reconexão deixará você tentando adivinhar justamente quando mais precisa de uma resposta clara.
Registre pelo menos estas transições para cada ID de execução:
- Aceita: o gateway validou a solicitação e atribuiu um ID de execução.
- Autorizada: a aprovação necessária ou a autorização da sessão permitiu a ação.
- Enviada: o gateway entregou a ação ao cliente HTTP ou ao auxiliar SSH.
- Resultado observado: chegou a resposta do destino, o status de saída ou uma falha explícita de entrega.
- Resultado entregue: o agente recebeu o resultado da ferramenta, se o transporte puder estabelecer esse fato.
O quarto e o quinto estados precisam permanecer separados. Um destino pode retornar HTTP 201 enquanto a conexão com o cliente MCP é interrompida antes que ele veja a resposta. Marcar essa execução como falha porque a entrega do resultado falhou é uma mentira. Marcar a execução como concluída dá ao código de recuperação algo útil para trabalhar: ele pode retornar ou reconstruir o resultado conhecido sem enviar outra solicitação.
Este é o formato de registro que quero ver durante um incidente:
{
"execution_id": "act_01J4K8J7DX7V",
"fingerprint": "hmac-sha256:4a1e...d90c",
"tool": "deploy_release",
"actor": "signed-process:com.example.agent",
"target": "deploy-api.internal.example/releases",
"state": "completed_result_not_delivered",
"accepted_at": "2026-07-22T14:03:18Z",
"dispatched_at": "2026-07-22T14:03:19Z",
"completed_at": "2026-07-22T14:03:25Z",
"target_status": 201,
"result_reference": "result_01J4K8JFM2"
}
O registro não precisa expor a resposta completa do destino a todos os operadores. Ele precisa ter detalhes protegidos suficientes para que o gateway tome uma decisão de recuperação e detalhes legíveis suficientes para que uma pessoa entenda o que aconteceu.
O diário de atividades do Sallyport registra chamadas individuais, enquanto o diário de sessões registra execuções de agentes. Essa separação é útil nesta investigação: a execução mostra qual processo de agente existia, e o registro da chamada mostra se uma ação específica no mundo externo atravessou o limite de envio. A cadeia de auditoria também pode ser verificada offline com sp audit verify, o que ajuda quando você precisa comprovar que o registro não foi discretamente reescrito depois de um incidente.
Trate resultados desconhecidos como uma categoria separada
A maioria das ações duplicadas começa com um sistema que tem apenas dois resultados: sucesso e falha. Ações em rede precisam de um terceiro: desconhecido.
Desconhecido não significa que o sistema não fez nada. Significa que o sistema não consegue provar se o destino aceitou a ação. Um timeout antes que qualquer byte saia do seu processo costuma ser seguro para uma nova tentativa. Um timeout depois que o corpo de uma solicitação HTTP foi entregue ao sistema operacional não é o mesmo evento. Uma conexão SSH interrompida depois que o shell remoto iniciou um comando é ainda pior, porque o comando remoto pode continuar depois que o processo local termina.
Classifique cada ação de ferramenta antes de decidir como a recuperação funcionará:
| Tipo de ação | Exemplo | Padrão após resultado desconhecido |
|---|---|---|
| Somente leitura | Consultar o status de uma build | Tentar novamente dentro dos limites normais |
| Gravação idempotente | Definir um recurso nomeado para um estado declarado | Tentar novamente usando a mesma identidade de idempotência |
| Gravação condicional | Atualizar somente se a versão corresponder | Consultar o estado e tentar novamente apenas se a condição continuar válida |
| Ação irreversível | Enviar pagamento, revogar acesso, trocar credencial | Parar e solicitar revisão explícita |
| Comando remoto | Executar uma migração por SSH | Consultar um marcador durável ou interromper para revisão |
O verbo HTTP da API não resolve essa tabela. PUT costuma ser descrito como idempotente, mas um endpoint mal projetado pode enviar uma notificação, iniciar uma build ou acrescentar um evento de auditoria sempre que recebe a solicitação. POST pode ser repetido com segurança quando a API respeita uma chave de idempotência. Inspecione o contrato real do destino.
Para comandos remotos de longa duração, adicione um marcador durável antes de executar o trabalho. Um comando de migração pode criar um registro com um ID de execução, atualizá-lo quando o trabalho começar e marcá-lo como concluído somente depois da validação. Na reconexão, consulte esse marcador antes de enviar o comando novamente. Sem um marcador, «provavelmente não foi executado» não é uma estratégia de recuperação.
Relacione tentativas dentro de um escopo de intenção limitado
Uma impressão digital sozinha fará correspondências demais com trabalhos legítimos. Limite-a ao período e ao contexto em que uma nova tentativa pode razoavelmente ocorrer.
O escopo mais simples é uma única execução do agente. Se o mesmo processo assinado enviar a mesma ação duas vezes enquanto o primeiro resultado ainda estiver pendente, trate a segunda solicitação como uma possível tentativa repetida. Se outro processo a enviar horas depois, trate-a como uma nova intenção, a menos que a própria ação forneça uma chave de idempotência durável.
Uma boa regra de correspondência é esta:
if prior.fingerprint == incoming.fingerprint
and prior.actor == incoming.actor
and prior.intent_scope == incoming.intent_scope
and prior.state in {accepted, authorized, dispatched, completed_result_not_delivered}:
recover_or_attach_to(prior.execution_id)
else:
create_new_execution()
recover_or_attach_to não deve retornar sucesso às cegas. Seu comportamento depende do estado anterior.
Se a execução anterior foi aceita, mas ainda não foi enviada, o gateway pode continuar essa execução. Se ela foi enviada e o resultado é desconhecido, o gateway deve consultar o endpoint de status, o mecanismo de idempotência ou o marcador durável do destino. Se ela foi concluída, mas a entrega do resultado falhou, deve retornar a referência ao resultado armazenado. Se foi rejeitada pela autorização, deve retornar essa rejeição em vez de criar um novo fluxo de aprovação a partir da mesma tentativa ambígua.
O escopo precisa corresponder à ação. Uma janela de cinco minutos pode ser razoável para uma solicitação de API que sofre timeout. Não basta para uma implantação de software que dura uma hora. Uma troca de credencial pode exigir uma impressão digital durável até que você verifique qual credencial está ativa. Não use um TTL global apenas porque é fácil de configurar. Use regras de retenção e recuperação específicas para cada ação.
Aprovação é evidência, não um mecanismo de idempotência
Uma aprovação humana pode estabelecer que um processo tinha permissão para tentar uma ação. Ela não prova se uma tentativa anterior já aconteceu.
Isso importa em sistemas que solicitam aprovação para cada chamada sensível. Suponha que um agente peça para trocar uma credencial de produção. Uma pessoa aprova. O gateway envia a solicitação e o cliente se desconecta. O agente se reconecta e gera a mesma chamada de ferramenta. Pedir aprovação à pessoa novamente cria uma escolha enganosa. O operador vê uma solicitação familiar e pode aprová-la, mas a pergunta que precisa ser respondida é se a primeira troca foi concluída.
A aprovação por chamada continua tendo seu lugar. Ela controla a autorização no momento do uso. Mantenha-a separada do tratamento de duplicatas:
- A autorização decide se o chamador atual pode iniciar uma execução.
- A impressão digital decide se uma solicitação recebida corresponde a uma execução existente.
- Os registros de atividades decidem se essa execução existente pode ser retomada, recuperada ou precisa de revisão.
Quando uma tentativa repetida corresponder a uma execução pendente, mostre o registro de atividades original em vez de apresentar uma nova aprovação como se nada tivesse acontecido. O revisor deve ver o destino, o primeiro horário de envio, o resultado conhecido e o motivo pelo qual o gateway não enviou a ação novamente.
O Sallyport usa uma sequência fixa de decisões: um cofre bloqueado nega ações, um novo processo de agente recebe autorização por sessão por padrão e determinadas credenciais podem exigir aprovação a cada uso. Esses controles respondem quem pode agir. O registro de execução ainda precisa responder se a ação já atravessou o limite.
Chaves de idempotência HTTP resolvem apenas parte do problema
Se uma API upstream aceitar chaves de idempotência, use-as. Envie um valor estável durante toda a vida de uma execução, persista a resposta do destino e reutilize esse valor somente ao recuperar a mesma execução.
Por exemplo, o gateway pode criar um ID de execução antes do envio e mapeá-lo para o cabeçalho esperado pela API:
POST /v1/releases HTTP/1.1
Host: deploy-api.internal.example
Idempotency-Key: act_01J4K8J7DX7V
Content-Type: application/json
{"service":"catalog","version":"2026.07.22","region":"us-east-1"}
A API precisa definir o que faz quando esse cabeçalho se repete. O melhor comportamento é retornar o resultado original para a mesma solicitação semântica e rejeitar uma solicitação diferente que tente reutilizar o mesmo valor. Se ela aceitar silenciosamente um corpo alterado com a mesma chave, seu gateway não poderá inferir nada com segurança a partir de uma reprodução.
Não use a própria impressão digital como chave externa de idempotência se ela puder persistir entre ações intencionais. Um ID de execução é exclusivo para uma tentativa aceita. A impressão digital localiza uma tentativa potencialmente relacionada. Eles têm funções diferentes.
A idempotência HTTP também não faz nada pelo SSH por si só. Você precisa de um protocolo remoto. Um padrão seguro é passar um ID de execução gerado a um script que grave um registro de status durável no host ou em um armazenamento compartilhado e depois se recuse a iniciar a mesma operação duas vezes. Se não puder modificar o comando nem consultar um marcador externo, coloque esse comando na categoria irreversível e exija revisão depois de uma desconexão incerta.
Investigue a sequência, não a contagem final
Duas linhas de atividade com argumentos correspondentes não provam uma duplicata. Comece pela sequência de eventos e acompanhe a primeira chamada até seu limite de envio.
Uma investigação real deve responder a estas perguntas nesta ordem:
- Um processo de agente ou dois processos distintos enviaram as chamadas?
- A primeira chamada recebeu autorização e entrou no envio?
- O gateway recebeu uma resposta ou um status de saída do destino?
- A entrega do resultado falhou depois da conclusão no destino?
- A segunda chamada reutilizou o ID de execução original, carregou uma chave de idempotência ou criou uma nova tentativa?
Essa ordem evita uma conclusão comum e equivocada: «Os logs mostram duas chamadas, então o agente agiu duas vezes». Você pode descobrir que o gateway registrou uma execução concluída e uma tentativa do cliente que se vinculou a ela. Ou pode encontrar dois processos autorizados diferentes, cada um com um contexto de planejamento distinto, que emitiram a ação. Esses casos exigem correções diferentes.
Mantenha o armazenamento de atividades somente para acréscimos ou, de outra forma, resistente a adulterações. Investigações de duplicatas costumam acontecer depois de um evento caro, quando alguém quer uma história mais limpa do que o sistema consegue sustentar. Um registro encadeado por hashes não torna correta a decisão original, mas torna mais difícil manipular a reconstrução posterior.
Também não esconda a ambiguidade do agente. Retorne um resultado informando que a execução anterior aguarda verificação ou foi concluída, mas a entrega do resultado foi interrompida. Um modelo que vê uma falha inventada tentará novamente. Um modelo que vê um estado incerto claro pode consultar o status, pedir revisão ou escolher um caminho mais seguro.
Faça do comportamento de reprodução parte do contrato de toda ferramenta
Toda ferramenta com efeito colateral precisa responder explicitamente a uma pergunta: o que acontece quando o chamador perde a resposta depois do envio?
Escreva a resposta ao lado da definição da ferramenta. Informe se a ação é somente leitura, repetível com uma identidade de idempotência, recuperável por meio de uma consulta de status ou bloqueada depois de um resultado desconhecido. Informe o que deve entrar na impressão digital e por quanto tempo uma execução inacabada permanece disponível para vinculação. Se ninguém consegue escrever isso, a ferramenta não está pronta para uso autônomo.
O trabalho de engenharia costuma ser modesto em comparação com a limpeza depois de uma implantação duplicada, uma conta duplicada, um pagamento duplicado ou uma segunda troca de credencial. Adicione o ID de execução antes da chamada ao destino. Persista as transições de estado antes e depois do envio. Preserve uma referência ao resultado. Depois faça o código de reconexão consultar esse registro antes de tocar novamente no mundo externo.
Esse é o padrão a seguir: um transporte interrompido pode interromper uma conversa, mas não pode transformar silenciosamente a incerteza em uma segunda ação.
FAQ
O que conta como uma chamada duplicada de ferramenta MCP?
É um efeito colateral repetido causado por duas execuções que chegam ao destino, não apenas duas mensagens em um registro. Uma solicitação HTTP repetida que apenas lê dados pode ser incômoda. Já uma solicitação repetida que envia dinheiro, exclui uma branch, troca uma credencial ou cria uma conta exige outra resposta. Primeiro identifique o efeito downstream antes de discutir se o cliente «pretendia» tentar novamente.
Uma reconexão MCP pode fazer a mesma chamada de ferramenta rodar duas vezes?
Às vezes. O cliente pode perder a resposta depois que o servidor já concluiu a chamada e, em seguida, reconectar e enviar a mesma solicitação novamente. Mas chamadas visualmente idênticas também podem vir de um agente que reconsiderou seu plano, de um supervisor que reiniciou um worker ou de uma pessoa que deu a instrução duas vezes.
O ID da solicitação JSON-RPC é suficiente para eliminar duplicatas?
Não. Um ID de solicitação JSON-RPC identifica uma mensagem dentro de uma conversa de protocolo, mas não é uma identidade de execução durável entre uma reinicialização do cliente ou uma nova conexão. Trate-o como uma evidência útil e combine-o com uma impressão digital da solicitação e um registro da ação.
O que uma impressão digital de solicitação MCP deve incluir?
Inclua o processo ou principal autorizado, o nome da ferramenta, os argumentos normalizados, a identidade do destino, a identidade da credencial e um escopo de tempo ou intenção limitado. Não inclua ruídos específicos do transporte, como o número de um socket, um ID temporário de evento SSE ou um ID de solicitação gerado aleatoriamente. A impressão digital deve descrever o efeito solicitado, não o caminho usado para entregá-lo.
Devo tentar novamente automaticamente uma chamada de ferramenta que falhou?
Evite reexecutar automaticamente qualquer ação com efeito externo, a menos que o destino aceite uma chave de idempotência ou você consiga provar que a primeira tentativa não começou. Reconectar um fluxo de resposta é diferente de reenviar uma chamada de ferramenta. O caminho seguro costuma ser recuperar o resultado ou consultar primeiro o registro de atividades.
O que um registro de atividades deve mostrar durante uma investigação de tentativas repetidas?
Um registro de atividades deve guardar a impressão digital da solicitação, a tentativa de execução, o contexto de autorização, os horários, o destino e um estado de resultado. Ele precisa distinguir «recebida», «iniciada», «concluída» e «entrega do resultado falhou». Se esses estados forem reduzidos a uma única linha de log, o operador não conseguirá saber se uma segunda ação aconteceu.
Quando um agente deve enviar uma chave de idempotência?
Use uma chave de idempotência quando a API receptora oferecer suporte a ela e preserve-a durante uma tentativa de transporte da mesma ação pretendida. Não a reutilize em uma ação intencional posterior, mesmo que os argumentos sejam iguais. Se a API não tiver um mecanismo de idempotência, seu gateway precisará de registros próprios de execuções pendentes e concluídas.
Como diferenciar uma tentativa repetida de uma segunda ação intencional?
Uma reconexão geralmente altera evidências da conexão: horário de início do processo, sessão de transporte, ID da solicitação ou estado do fluxo. Uma repetição intencional costuma ter um novo contexto de planejamento, uma nova decisão de autorização, argumentos diferentes ou um intervalo significativo após o resultado anterior. Nenhum desses sinais resolve o caso sozinho. Por isso o sistema precisa de uma regra de decisão registrada.
Posso armazenar apenas um hash de cada solicitação de ferramenta?
Não. Um resumo é apenas um índice. Mantenha uma representação canônica protegida ou campos estruturados suficientes para explicar por que duas chamadas coincidiram, limitando os valores sensíveis nas visões dos operadores. Se as impressões digitais abrangerem segredos, derive-as com uma chave secreta para que um leitor do log não possa testar palpites contra o resumo.
O que deve acontecer quando o resultado da primeira chamada é desconhecido?
Trate-a como uma execução ambígua e interrompa a reprodução automática para essa classe de ação. Mostre o registro de atividades anterior, o resultado conhecido e o efeito exato no destino à pessoa responsável pela aprovação. Uma duplicata rápida pode custar menos que uma duplicata irreversível, mas ainda é uma falha de projeto que merece correção.