11 KiB
MCP Multi-item — Guia do Desenvolvedor
Objetivo
Use este guia quando uma única intenção do usuário precisa operar sobre mais de um item do mesmo tipo em uma chamada MCP, por exemplo:
- cancelar vários VAS/produtos;
- devolver ou consultar vários pedidos;
- alterar vários ativos;
- processar uma lista de faturas, linhas, contratos ou recursos.
O framework já possui contratos genéricos para coleta de listas, pré-validação/canonicalização, confirmação transacional, execução MCP e normalização do resultado por item. O desenvolvedor não deve criar um segundo motor de multi-item dentro do agente.
Regra principal
Não faça loop de negócio no agente. O agente declara a tool e seus parâmetros; o runtime mantém estado, confirmação e evidência. A operação em lote deve ser exposta como uma tool MCP que aceite uma coleção, ou como uma tool transacional apoiada por um workflow/MCP que processe a coleção.
Fluxo recomendado:
Usuário cita N itens
-> Router/Agent seleciona a tool
-> Runtime coleta/reconcilia parâmetro array/list
-> (opcional) pre-validation canonicaliza itens
-> Runtime pede UMA confirmação coerente para o conjunto
-> MCP recebe a coleção
-> MCP/workflow processa item a item
-> MCP devolve results[] com success/ok por item
-> Framework normaliza SUCCESS / PARTIAL_SUCCESS / FAILED
-> Composição final relata sucessos e falhas individualmente
1. Declarar um parâmetro de lista em tools.yaml
O reconciliador transacional do framework aceita type: array ou type: list.
tools:
cancelar_produtos:
description: Cancela um ou mais produtos do cliente.
mcp_server: contas
enabled: true
tool_type: action
confirmation_required: true
requires: [items]
args_schema:
items:
type: array
label: os produtos
description: Lista de produtos que o cliente deseja cancelar.
user_prompt: Informe quais produtos deseja cancelar.
O runtime pode reconciliar uma frase como:
quero cancelar TIM Fashion, Aya Audiobooks e Neymar Jr
para um argumento lógico semelhante a:
{
"items": ["TIM Fashion", "Aya Audiobooks", "Neymar Jr"]
}
A semântica de domínio e a validação dos nomes continuam pertencendo ao MCP/pre-validator, não ao core.
2. Quando usar pre-validation
Use tool_policies.yaml quando a lista precisa ser resolvida/canonicalizada antes da confirmação.
tool_policies:
cancelar_produtos:
operation_type: transactional
require_confirmation: true
requires: [items]
pre_validation:
enabled: true
tool: validar_produtos_cancelamento
fail_open: false
O pre-validator pode retornar eligible: true e substituir argumentos por valores canônicos em transaction_decision.resolved_arguments:
{
"eligible": true,
"transaction_decision": {
"resolved_arguments": {
"items": [
{"name": "TIM Fashion Mensal", "id": "1001"},
{"name": "Aya Audiobooks Premium", "id": "1002"},
{"name": "Neymar Jr", "id": "1003"}
]
},
"target_tool": "cancelar_produtos",
"confirmation_message": "Você confirma o cancelamento de TIM Fashion Mensal, Aya Audiobooks Premium e Neymar Jr?"
}
}
Importante para validação parcial
O contrato eligible da pre-validation é global. Portanto, não retorne eligible: false apenas porque um item da lista falhou, caso os demais ainda possam ser processados. Isso encerraria toda a transação.
Para permitir resultado parcial, a estratégia recomendada é:
- retornar
eligible: truequando existe pelo menos um item acionável; - preservar/canonicalizar a coleção em
resolved_arguments; - deixar a tool primária devolver o resultado terminal de cada item em
results[].
Se nenhum item puder ser processado, então eligible: false é apropriado.
3. Contrato de entrada do MCP
Prefira uma única chamada MCP com uma coleção explícita:
{
"tool_name": "cancelar_produtos",
"arguments": {
"customer_id": "123",
"items": [
{"name": "TIM Fashion Mensal", "id": "1001"},
{"name": "Aya Audiobooks Premium", "id": "1002"},
{"name": "Neymar Jr", "id": "1003"}
]
}
}
O framework não deve ser usado como um loop improvisado que chama N vezes uma tool escalar a partir do agente. Se o backend só possui API unitária, o fan-out pode ficar encapsulado no MCP Server ou em um workflow de domínio, mantendo uma única tool lógica para o agente.
4. Contrato obrigatório de resultado por item
Para o framework reconhecer multi-item automaticamente, a tool primária solicitada deve expor uma lista results com dois ou mais itens, e cada item deve possuir um booleano success ou ok.
Exemplo:
{
"ok": true,
"result": {
"results": [
{"id": "1001", "subject": "TIM Fashion Mensal", "success": true},
{"id": "1002", "subject": "Aya Audiobooks Premium", "success": false, "reason": "not_found"},
{"id": "1003", "subject": "Neymar Jr", "success": true}
]
}
}
Campos como id, subject, name, reason, error, protocol e outros são de domínio e opcionais. O sinal estrutural exigido pelo normalizador é success: true|false ou ok: true|false por item.
Workflow-backed tool
Se a tool é executada por workflow, o caminho preferencial e autoritativo é:
workflow.output[tool_name].results
Exemplo:
{
"ok": false,
"result": {
"status": "COMPLETED",
"output": {
"cancelar_produtos": {
"results": [
{"subject": "A", "success": true},
{"subject": "B", "success": false, "reason": "not_found"},
{"subject": "C", "success": true}
]
},
"etapa_auxiliar": {
"success": false,
"error": "falha em pós-processamento"
}
}
},
"error": "falha em pós-processamento"
}
O runtime preserva o resultado da operação primária e não permite que uma falha auxiliar apague itens já concluídos.
5. O que o framework produz automaticamente
Com results[] válido, o runtime adiciona:
{
"multi_item_status": "PARTIAL_SUCCESS",
"partial_success": true,
"metadata": {
"multi_item": {
"status": "PARTIAL_SUCCESS",
"items_count": 3,
"items_succeeded_count": 2,
"items_failed_count": 1,
"primary_tool": "cancelar_produtos"
}
}
}
Estados possíveis:
| Situação | multi_item_status |
|---|---|
| todos os itens concluíram | SUCCESS |
| parte concluiu e parte falhou | PARTIAL_SUCCESS |
| todos falharam | FAILED |
Se uma etapa posterior fizer o wrapper retornar ok=false, mas pelo menos um item primário tiver sucesso, o framework preserva para auditoria:
{
"ok": true,
"original_ok": false,
"secondary_error": "erro da etapa posterior"
}
Isso evita transformar sucesso parcial/real em falha total na resposta ao usuário.
6. O que NÃO fazer
Evite estes padrões:
# NÃO: motor multi-item paralelo dentro do agente
for product in products:
await tool_router.call("cancelar_produto", {"product": product})
// NÃO: apenas um approved global sem evidência por item
{
"approved": false,
"error": "um item falhou"
}
// NÃO: um único success agregado quando houve resultados mistos
{
"success": false,
"message": "operação bloqueada"
}
Esses formatos perdem a evidência terminal individual e podem fazer itens concluídos desaparecerem da composição final.
7. Exemplo completo: vários pedidos
tools.yaml
tools:
cancelar_pedidos:
description: Cancela um ou mais pedidos.
mcp_server: retail
enabled: true
tool_type: action
confirmation_required: true
requires: [order_ids]
args_schema:
order_ids:
type: array
label: os pedidos
description: Lista de identificadores de pedidos a cancelar.
user_prompt: Quais pedidos deseja cancelar?
Retorno MCP
{
"ok": true,
"result": {
"results": [
{"order_id": "P-100", "success": true, "protocol": "ABC1"},
{"order_id": "P-101", "success": false, "reason": "already_shipped"},
{"order_id": "P-102", "success": true, "protocol": "ABC3"}
]
}
}
O framework classificará automaticamente como PARTIAL_SUCCESS e fornecerá ao LLM a instrução de relatar os resultados item a item.
8. Read-only multi-item
A mesma estrutura serve para consultas em lote. A diferença é que normalmente não existe confirmação:
tool_policies:
consultar_pedidos:
operation_type: read_only
require_confirmation: false
A tool ainda pode retornar results[] com success/ok por item para que uma consulta parcialmente indisponível não esconda as consultas bem-sucedidas.
9. Idempotência e protocolos
Em operações com efeito colateral:
- use uma chave de idempotência de requisição e, quando necessário, uma chave por item;
- devolva protocolo/operation id no próprio item;
- não dependa apenas de uma mensagem textual agregada;
- em retry, preserve os itens já concluídos e execute apenas o que a regra de domínio permitir.
O framework preserva evidência; a política de idempotência do sistema de destino continua pertencendo ao MCP/workflow de domínio.
10. Checklist para o desenvolvedor
- A tool aceita
array/listquando o caso de uso é multi-item. - Não existe loop MCP de negócio dentro da classe do agente.
- Se houver canonicalização, ela está em pre-validation/MCP, não hardcoded no agente.
- A confirmação mostra o conjunto efetivo de itens.
- A tool primária retorna
results[]comsuccessouokpor item. - Resultado parcial não é reduzido a um único
approved=falseousuccess=falseglobal. - Workflow usa
output[tool_name].resultspara a evidência terminal da tool primária. - Testes cobrem todos-sucesso, parcial, todos-falha e falha auxiliar posterior.
- A resposta final menciona sucessos e falhas quando houver
PARTIAL_SUCCESS.
11. Onde o comportamento existe no framework
Referências atuais:
libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py
- coerção de type=array/list
libs/agent_framework/src/agent_framework/runtime/agent_runtime.py
- pre-validation e resolved_arguments
- _primary_multi_item_outcomes()
- _normalize_multi_item_tool_result()
- instrução de composição multi-item para o LLM
tests/test_multi_item_result_normalization.py
- regressões de SUCCESS/PARTIAL_SUCCESS e proteção contra falha auxiliar
12. Resumo de responsabilidade
| Camada | Responsabilidade |
|---|---|
| Agente | declarar intenção/tool e usar o runtime; não implementar motor multi-item |
tools.yaml |
declarar parâmetro array/list e schema |
tool_policies.yaml |
confirmação e pre-validation |
| Pre-validator MCP | resolver/canonicalizar entidades e argumentos |
| Tool MCP/workflow | executar a coleção e produzir resultado por item |
| Framework runtime | manter estado, confirmação, normalizar resultado multi-item e preservar evidência |
| LLM/presentation | transformar evidências em resposta, sem apagar sucesso/falha individual |