# As reduções de capacidades do MCP enfraquecem os controles dos agentes?

A compatibilidade do protocolo faz parte da superfície de ataque. Quando um cliente MCP negocia uma versão antiga ou deixa de informar uma capacidade, ele deve perder conveniências, não os controles que impedem um agente de agir com a autoridade de outra pessoa.

Já vi essa falha chegar como uma correção inofensiva de compatibilidade: um cliente não anuncia um recurso de notificações, então o código segue um caminho antigo; esse caminho foi escrito antes de existir a aprovação por sessão; uma ação passa porque ninguém tratou esse caminho como uma rota de autorização. O código continua passando nos testes do caminho feliz. Ainda assim, está errado.

As reduções de capacidades do MCP precisam de testes de segurança porque os metadados de inicialização mudam o caminho pelo gateway antes que o agente faça sua primeira solicitação sensível. Teste esse caminho como um adversário faria: declare uma versão antiga do protocolo, omita campos, envie um objeto de capacidades vazio, reinicie o processo, bloqueie o cofre, revogue a sessão e examine o que o diário de auditoria informa depois. Se a resposta mudar de «negar ou perguntar» para «executar», a compatibilidade virou um contorno de credenciais.

## Uma redução muda a rota, não a autoridade

Um caminho de compatibilidade pode mudar o formato das mensagens, as notificações disponíveis, o tratamento do progresso ou a quantidade de contexto recebida pelo cliente. Ele não pode mudar quem tem permissão para usar uma credencial nem se uma pessoa precisa aprovar a chamada.

Essa distinção parece óbvia até o código começar a criar ramificações com base em `protocolVersion` ou `capabilities`. Muitas vezes, a pessoa desenvolvedora escreve a ramificação para evitar o envio de uma mensagem do servidor que não é compatível. Mais tarde, alguém coloca a configuração da sessão, a entrega da aprovação ou a inicialização da auditoria na mesma ramificação porque tudo está próximo. A redução passa então a alterar acidentalmente um limite de segurança.

A especificação do MCP define a inicialização como uma troca em que o cliente declara uma versão do protocolo e suas capacidades, e o servidor responde com sua própria versão e capacidades. Trate esses campos como declarações de uma parte não confiável. Eles podem orientar a interoperabilidade. Não podem conceder poder.

O Sallyport torna essa separação prática: o portão do cofre, a autorização da sessão e a configuração da chave por chamada ficam abaixo da conversa do MCP, para que um agente não receba uma credencial apenas por se descrever como um cliente antigo. Esse é o formato correto, mas ele ainda precisa de testes de regressão no limite do protocolo.

Escreva a invariante de segurança antes de montar a matriz:

> Para cada variante de inicialização do MCP aceita, uma ação protegida é negada enquanto o cofre está bloqueado; um novo processo cliente exige aprovação da sessão; uma credencial marcada para revisão por chamada exige revisão a cada uso; e a ação cria um evento de auditoria quando é bem-sucedida, falha ou é negada.

Essa frase oferece aos revisores algo mais preciso do que «clientes antigos funcionam». Um cliente antigo só funciona se preservar esses resultados.

## A entrada de inicialização merece casos de teste hostis

Um cliente bem-comportado envia uma solicitação de inicialização limpa uma vez, lista as capacidades compatíveis e prossegue depois de receber a resposta. Os testes de segurança devem começar daí e remover cada suposição uma por uma.

Use um fixture de cliente pequeno que consiga enviar mensagens JSON-RPC brutas, em vez de uma biblioteca MCP de alto nível que preencha valores padrão por você. Testes da biblioteca são úteis, mas escondem as condições no protocolo que causam bugs de redução.

Uma solicitação básica pode ser assim:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "CURRENT_TEST_VERSION",
    "capabilities": {
      "roots": { "listChanged": true },
      "sampling": {}
    },
    "clientInfo": { "name": "compat-fixture", "version": "1.0" }
  }
}
```

O fixture deve então produzir variantes com apenas uma diferença relevante:

- a versão de protocolo mais antiga aceita
- a versão atual com `{}` em capabilities
- a versão atual com `capabilities` omitido, se o seu analisador aceitar isso
- um objeto de capacidades sem entradas opcionais conhecidas
- uma versão antiga não compatível que o servidor deve rejeitar

Não combine todas as omissões no primeiro teste. Quando uma solicitação malformada combinada falha, você aprende muito pouco. Casos com uma variável mostram qual valor padrão ou ramificação mudou o resultado.

Para cada caso aceito, confirme o mesmo resultado para uma ação protegida antes da aprovação. A resposta exata do transporte varia conforme a implementação, mas o formato deve ser inequívoco:

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "error": {
    "code": -32001,
    "message": "Session authorization required"
  }
}
```

Não confirme apenas o texto. Confirme uma categoria de erro ou um código de resultado interno estável, a ausência de qualquer solicitação HTTP externa ou invocação SSH e um registro de auditoria para a tentativa negada. Um erro amigável com um efeito colateral real por trás é um teste que falhou.

O caso mais delicado é uma solicitação de inicialização cujos campos podem ser analisados, mas não fazem sentido juntos. Talvez o cliente diga que fala uma versão reconhecida pelo servidor, mas declare nenhuma das capacidades que um cliente normal dessa versão usaria. Seu gateway não precisa dar uma palestra ao cliente. Ele precisa escolher uma rota segura: aceitar a sessão limitada e manter todos os controles locais, ou rejeitar a inicialização. Não pode escolher silenciosamente uma rota sem proteção porque a apresentação normal da aprovação não está disponível.

## Formatos antigos no protocolo não podem criar um modelo de segurança antigo

As equipes costumam dizer que oferecem suporte a uma versão antiga do protocolo quando querem dizer que ainda conseguem analisar suas mensagens. Isso é apenas metade do trabalho. Também é preciso decidir qual comportamento de segurança atual permanece quando o formato antigo não tem um campo para representá-lo.

A resposta normalmente deve ser: todo ele. A autorização da sessão é um estado local. A revisão por chamada é um estado local. A captura de auditoria é um estado local. Nada disso exige que o cliente carregue um campo equivalente em uma solicitação antiga.

É aqui que a negociação de capacidades e a autorização acabam misturadas. Um cliente pode não ter um recurso para receber uma atualização de status estruturada. Isso informa como comunicar o estado da aprovação, não se ela deve ser dispensada. Se o produto não consegue apresentar uma revisão ao usuário para aquela invocação, negue a chamada com um erro claro. Pedir ao cliente que prometa ter exibido uma solicitação não substitui uma decisão local.

Evite o fallback popular de «aprovar a sessão inteira se o cliente não conseguir lidar com solicitações por chamada». Ele é popular porque elimina atrito e faz uma demonstração funcionar. Está errado porque transforma a decisão do proprietário da credencial sobre cada uso em uma decisão única, sem o consentimento dele. A configuração da credencial deve prevalecer.

Mantenha o adaptador de versão restrito. Ele deve traduzir a sintaxe de solicitações e respostas, filtrar mensagens que a outra parte não entende e mapear formatos de erro compatíveis. Ele não deve decidir se uma ação será executada. Coloque a autorização em um único caminho chamado por todos os adaptadores.

Uma pergunta útil na revisão de código é: uma ramificação de compatibilidade pode retornar um identificador de execução, um cabeçalho de autorização injetado ou um resultado SSH sem passar primeiro pela mesma função de autorização usada pelo cliente mais recente? Se a resposta for sim, a ramificação precisa de um teste agora e de uma reformulação em breve.

## A aprovação da sessão deve acompanhar o processo que fez a solicitação

É fácil testar mal a aprovação por sessão. Se o cliente de teste mantém um processo em execução, recebe aprovação e faz dez chamadas, você só comprova que uma sessão que passou pelo caminho feliz continua aprovada. Não comprova que o próximo processo de agente precisará de sua própria decisão.

Use dois processos fixture iniciados de forma independente. Dê a eles o mesmo nome de cliente exibido, a mesma versão do protocolo e a mesma declaração de capacidades. O processo A inicializa e solicita uma ação protegida. Aprove a sessão dele. Em seguida, o processo B inicializa e solicita a mesma ação.

O processo B deve receber uma nova exigência de aprovação. Ele não pode herdar a autorização de A por compartilhar um rótulo de cliente, um diretório de trabalho, um endpoint de transporte ou um detalhe de conexão armazenado em cache. Se a implementação expõe a autoridade de assinatura de código no cartão de aprovação, confirme que o cartão identifica a autoridade do processo que realmente iniciou B. Não faça o teste confirmar as partes decorativas do cartão; confirme o sinal de identidade que uma pessoa usa para decidir.

Depois, teste as situações do ciclo de vida encontradas pelos agentes em produção:

1. Aprove o processo A e deixe-o sair normalmente. Inicie o processo B com metadados idênticos. B deve exigir aprovação.
2. Aprove A, encerre-o sem um desligamento normal e inicie B. B deve exigir aprovação.
3. Aprove A, revogue A no diário de sessões enquanto ele continua em execução e faça outra chamada a partir de A. Essa chamada deve ser interrompida antes da execução.
4. Revogue A enquanto sua primeira ação aguarda uma resposta externa. A ação pendente não pode criar uma segunda ação aprovada depois da revogação.

O terceiro e o quarto casos expõem um erro comum: o sistema verifica a aprovação apenas quando abre uma conexão ou apenas quando cria um objeto de ação. Uma sessão pode mudar enquanto o processo continua em execução. Verifique a autoridade no momento em que o gateway confirma a ação externa.

Não use um ID de sessão fornecido pelo chamador como chave da aprovação. Um teste pode tornar esse defeito evidente: faça B repetir o rótulo de sessão de A e prove que o gateway ainda solicita aprovação. O nome do cliente é um identificador para exibição e diagnóstico, não uma prova de que o mesmo executável está fazendo uma nova solicitação.

## A revisão por chamada pertence ao uso da credencial

Uma configuração de chave com revisão por chamada significa que o gateway solicita consentimento toda vez que a credencial é usada. Não significa «uma vez para cada nome de ferramenta», «uma vez para cada conexão aberta» ou «uma vez, exceto quando o cliente é antigo». O objeto em revisão é a solicitação real que levará o segredo ou usará a chave SSH.

Monte um teste com uma credencial marcada para revisão por chamada e um fixture que envie duas ações equivalentes em uma sessão já aprovada. Para HTTP, use duas chamadas a um endpoint de teste que retorne um marcador inofensivo. Para SSH, use dois comandos inofensivos em um host de teste isolado. O teste deve observar dois eventos de revisão separados e dois registros de ação separados.

Uma sequência mínima de eventos esperada é esta:

```text
session_authorized process=fixture-A
call_review_requested action=41 credential=deploy-token
call_completed action=41 result=success
call_review_requested action=42 credential=deploy-token
call_completed action=42 result=success
```

A asserção importante não é a numeração. É que a ação 42 não possa usar a aprovação concedida para a ação 41. Faça a segunda solicitação chegar depois que a primeira terminar e crie outro caso em que ambas cheguem quase ao mesmo tempo. A concorrência expõe implementações que armazenam na sessão um único sinal temporário de «revisão aprovada», em vez de vinculá-lo a uma ação específica.

Agora repita o teste com uma declaração de protocolo antiga e com a capacidade usada pelo caminho normal de status da aprovação omitida. O mecanismo de revisão pode ser apresentado de outra forma na máquina local, mas o resultado deve permanecer igual. Se o usuário dispensar, cancelar ou deixar expirar a segunda revisão, o endpoint de teste deve receber uma solicitação, não duas.

Mantenha uma asserção rigorosa sobre a exposição de segredos. A resposta MCP deve conter o resultado da ação, um erro ou o estado de revisão necessária. Ela não pode conter uma chave de API, uma chave privada SSH, um marcador mascarado no lugar do segredo ou um parâmetro de ferramenta que permita ao agente reconstruí-lo. Uma redução é um lugar tentador para que um adaptador de compatibilidade serialize contexto extra. Inspecione a transcrição bruta, não apenas o objeto estruturado do teste.

## A captura de auditoria deve ficar abaixo da negociação do protocolo

Um diário de auditoria que registra apenas chamadas bem-sucedidas de clientes modernos é uma falsa sensação de segurança. Os casos necessários depois são as chamadas negadas, as revisões canceladas, as solicitações de inicialização rejeitadas e os caminhos estranhos de compatibilidade que fizeram uma pessoa engenheira dizer «isso nunca deveria acontecer».

Registre os fatos necessários para reconstruir a decisão: a execução ou sessão do agente, a identidade da ação, o canal, o destino solicitado, a referência da credencial ou um identificador seguro, o estado da autorização, o resultado da revisão e o resultado da execução. Evite armazenar o segredo. Para uma inicialização malsucedida, registre contexto suficiente sobre a outra parte e a análise para explicar a rejeição sem transformar o diário em uma cópia de payloads não confiáveis.

O Sallyport projeta seus diários Sessions e Activity a partir de um único registro de auditoria criptografado e encadeado por hash. Esse desenho é importante nos testes de redução porque a capacidade do cliente não deve decidir se existe um segundo diário. O mesmo caminho de gravação deve registrar um cliente atual, um cliente parcial e uma chamada negada.

Use a verificação offline da cadeia como uma asserção depois de cada caso ponta a ponta:

```text
$ sp audit verify
verified: 18 records
chain: intact
```

A redação exata pode variar, mas o teste deve exigir um resultado de verificação bem-sucedido sem abrir o cofre. Esse teste identifica uma gravação encadeada quebrada ou omitida. Ele não comprova que o evento correto foi gravado, portanto combine-o com uma consulta às visualizações de sessão e atividade que confirme a existência dos registros esperados de negação, aprovação, cancelamento ou conclusão.

Teste também a ordem. Se uma revisão for negada, o diário deve mostrar a solicitação e a negação, mas não uma conclusão inventada. Se a ação de transporte falhar depois da aprovação, registre essa falha como falha de execução, não como negação de autorização. São perguntas operacionais diferentes. Misturá-las dificulta a análise de incidentes e permite que um adaptador quebrado pareça uma decisão do usuário.

## Monte a matriz de compatibilidade em torno dos resultados de segurança

Um produto cartesiano completo de todas as versões, capacidades, canais e configurações de credenciais crescerá até ninguém mais executá-lo. Mantenha uma matriz obrigatória pequena que cubra cada decisão de segurança e adicione um caso quando surgir uma nova ramificação de adaptador.

Use as linhas para o formato de inicialização e as colunas para o estado da decisão local. Por exemplo, execute um cliente da versão mais antiga aceita, um cliente com capacidades vazias e um cliente atual normal contra um cofre bloqueado, uma sessão não aprovada, uma credencial com sessão aprovada e uma credencial com revisão por chamada. Execute cada célula relevante por HTTP e SSH se ambos os canais compartilharem o mesmo caminho de autorização, mas usarem auxiliares de execução diferentes.

O resultado esperado deve ser escrito em termos claros antes da execução do teste:

| Condição do cliente | Estado local | Resultado esperado |
| --- | --- | --- |
| Versão mais antiga aceita | Cofre bloqueado | Ação negada e registrada |
| Capacidades vazias | Cofre desbloqueado, processo novo | Aprovação da sessão solicitada e registrada |
| Versão antiga | Processo aprovado, chave com revisão por chamada | Revisão por chamada solicitada para cada ação |
| Versão não compatível | Qualquer estado | Inicialização rejeitada, nenhuma ação externa |
| Versão atual | Processo revogado | Ação negada e registrada |

Essa tabela é útil porque força uma decisão sobre versões não compatíveis. Não permita que o servidor «faça o melhor possível» depois que a negociação falhar. Essa frase muitas vezes significa executar uma solicitação em um caminho do analisador que recebeu menos testes do que o caminho compatível. Rejeite a sessão, registre a rejeição com segurança e exija um cliente compatível.

Para cada linha, colete três observações independentes: a resposta MCP bruta, uma observação no serviço HTTP falso ou no host SSH de teste e o resultado da auditoria. Uma resposta de erro sozinha não comprova que nenhuma ação chegou ao mundo externo. Um endpoint de teste sozinho não comprova que o gateway registrou a negação. Você precisa dos três.

## Uma transcrição de falha deve identificar o limite quebrado

Quando um teste de compatibilidade falhar, não registre o problema como «o cliente MCP antigo tem problemas». Essa descrição garante que outra pessoa desenvolvedora corrigirá a apresentação e deixará a autorização de lado.

Dê um nome à invariante e mostre a linha do tempo. Este é um relatório de falha que merece ação:

```text
Client B initialized with oldest accepted version and no optional capabilities.
Client A had already received session approval and was still running.
Client B sent an HTTP action using the same displayed clientInfo name.
Gateway injected the credential and the test endpoint received the request.
No approval card appeared for B.
Activity journal recorded completion, but Sessions journal contained only A.
```

Essa sequência informa que o defeito está na identidade do processo ou no escopo do cache de autorização, não na análise da versão. Ela também mostra se o registro de auditoria contém um evento que pode induzir uma pessoa investigadora ao erro. Adicione o teste de regressão nesse nível e depois siga para dentro até que a implementação tenha uma única verificação de autorização que B não consiga contornar.

Um segundo padrão de falha é mais sutil:

```text
Client initialized without the capability used for approval status updates.
Credential required per-call review.
First action displayed local review and completed.
Second action completed immediately.
Audit log recorded two completed actions and one review event.
```

Isso aponta para um token de revisão que escapou do escopo da ação. A correção não é proibir o cliente antigo. A correção é vincular a revisão aprovada a um nonce de ação ou registro interno de ação e consumi-la exatamente uma vez.

Essas transcrições também ajudam as equipes de suporte a distinguir uma decisão do usuário de uma falha de software. Se o diário disser que um usuário negou uma revisão, investigue a solicitação. Se disser que nenhuma revisão ocorreu para uma credencial que exige uma, pare de tratar o evento como comportamento normal do agente.

## Bloqueio e revogação são testes separados

O portão do cofre, a aprovação da sessão e a revisão por chamada respondem a perguntas diferentes. Uma suíte de redução deve testá-los separadamente porque um teste de aprovação aprovado pode esconder uma falha no bloqueio do cofre, e vice-versa.

Primeiro, bloqueie o cofre. Inicie cada fixture de compatibilidade, incluindo a versão mais antiga aceita. Cada ação protegida deve falhar antes da injeção da credencial e antes que o serviço externo veja qualquer coisa. Não aceite um resultado que apenas peça ao agente para fornecer as credenciais. O agente nunca deve recebê-las.

Depois, desbloqueie o cofre, mas mantenha a sessão sem aprovação. O fixture deve seguir um único caminho de autorização de sessão durante sua execução. Aprove a sessão, faça uma ação protegida e revogue a sessão enquanto o processo continua em execução. A ação seguinte deve falhar mesmo que o cofre permaneça desbloqueado.

Por fim, use uma credencial com revisão por chamada nessa sessão aprovada. Aprove uma ação, negue a seguinte e examine o diário. Essa sequência comprova que a ordem da cadeia de decisões permanece correta: o cofre bloqueia toda ação quando está bloqueado; a aprovação da sessão estabelece a autoridade daquele processo; a configuração da credencial pode exigir uma nova decisão além disso.

Não reúna esses testes em um único cenário longo sem também manter testes focados para cada limite. Testes ponta a ponta longos são bons para provar que um caminho existe. Eles são ruins para mostrar por que ele quebrou depois de uma refatoração.

## Os critérios de versão devem penalizar o sucesso silencioso

Bugs de compatibilidade devem bloquear uma versão quando permitem que uma ação aconteça silenciosamente. Uma pequena diferença em uma mensagem de status pode ser corrigida no próximo patch. Um cliente reduzido que herda uma aprovação, ignora a revisão por chamada, age enquanto o cofre está bloqueado ou desaparece do registro de auditoria ultrapassou um limite de segurança.

Torne esses testes obrigatórios na integração contínua para toda alteração que toque na inicialização do MCP, nos adaptadores de protocolo, na entrega de aprovações, no rastreamento de processos, na injeção de credenciais ou na projeção da auditoria. Não espere uma mudança de versão do protocolo. O risco vem das ramificações criadas em torno de uma capacidade ausente, e o trabalho comum de desenvolvimento cria essas ramificações o tempo todo.

O teste que costuma se pagar sozinho é o mais desagradável: uma declaração de cliente antiga, um objeto de capacidades vazio, um nome de exibição reutilizado, um segundo processo e um segundo uso da credencial depois que o primeiro foi aprovado. Mantenha-o. É assim que você encontra o código de compatibilidade amigável que transformou silenciosamente uma decisão humana em um acerto de cache.
