knowledge-base/records/plugin-dev/KB-PLUGIN-022-mcprotocol-tool-input-patterns.md
Rodolpho Lopes 200cd6c2ce kb: KB-PLUGIN-031 runbook de workflow dev/deploy de plugins + commit dos registros 021-030 pendentes
- Novo runbook KB-PLUGIN-031: nascimento do plugin ate validacao E2E no
  GLPI dev (scaffold, Forgejo local, deploy CT100, console, bootstrap
  Kernel para testes CLI). Validado de ponta a ponta com o assetinherit.
- Registros KB-PLUGIN-021..030 existiam apenas no disco (drift) e foram
  incluidos no versionamento; index.json sincronizado via kb-fix.
- Ignora lixo AppleDouble/.DS_Store do macOS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 17:59:47 +00:00

5.7 KiB

id title domain tags status severity created_at updated_at applies_to related_records
KB-PLUGIN-022 mcprotocol — Padrões de validação e ergonomia em tool inputs plugin-dev
mcp
tools
validation
ergonomics
llm
patterns
active medium 2026-05-26 2026-05-26
glpi-11
mcprotocol-plugin
KB-PLUGIN-019
KB-PLUGIN-020

mcprotocol — Padrões de validação e ergonomia em tool inputs

Contexto

Tools do MCP são chamadas por LLMs que não conhecem IDs internos do GLPI nem o formato exato esperado pelos campos. Sem padrões de entrada bem desenhados, o LLM precisa fazer várias chamadas exploratórias (listar estados → encontrar ID → criar projeto), gastando turns e tokens. Pior: quando erra, recebe mensagens de erro do ORM crípticas (SQL error, boolean false) e não consegue se autocorrigir.

Este registro documenta os padrões implementados no ProjectTools::handleCreate que resolvem esses problemas, aplicáveis a qualquer tool de criação/atualização no plugin.

Padrões implementados

1. resolveReference — Aceita ID ou nome

Problema: LLM não sabe que projectstates_id=2 é "Processing".

Solução: helper que aceita int (validando existência) ou string (resolvendo por nome).

private static function resolveReference(
    string $table,     // ex: 'glpi_projectstates'
    $value,            // int OU string
    string $field,     // nome do campo (pra mensagem de erro)
    array &$errors     // acumulador de erros
): ?int

Caso int / string numérica:

  • Valida existência via SELECT id FROM {$table} WHERE id = ?
  • Se não existe: "{$field}: ID {$id} não existe em {$table}."

Caso string (nome):

  • Busca via SELECT id FROM {$table} WHERE name = ?
  • Se não encontra: lista até 20 nomes disponíveis na mensagem de erro

Exemplo de erro útil pra LLM:

projectstates_id: nome 'AbacaxiDoido' não encontrado em glpi_projectstates.
Disponíveis: New, Processing, Closed.

A LLM lê isso e se autocorrige na próxima chamada — sem precisar de uma tool separada glpi_projectstate_list.

2. normalizeDate — Normaliza formato

Problema: Datas vêm em formatos variados (2026-06-01, 2026-06-01 00:00:00, ISO 8601, etc.). GLPI espera Y-m-d H:i:s.

Solução: helper aceita os 2 formatos mais comuns e normaliza:

private static function normalizeDate(
    string $value,
    string $field,
    array &$errors
): ?string
  • YYYY-MM-DD → preenche 00:00:00
  • YYYY-MM-DD HH:MM:SS → mantém
  • Qualquer outro: erro claro com formato esperado

3. Validação prévia agregada (fail-loud-once)

Problema: Validar campo por campo lançando exception faz a LLM corrigir 1 erro por chamada — N chamadas pra N erros.

Solução: acumula erros num array e lança UMA exception no final:

$errors = [];
// ... várias validações que appendam em $errors
if (!empty($errors)) {
    throw new \Exception("Falha na validação: " . implode(" | ", $errors));
}

A LLM recebe todos os problemas de uma vez, corrige tudo e tenta de novo.

4. Response inclui applied (debug & confiança)

Problema: depois de resolver nomes → IDs, normalizar datas, etc., a LLM não sabe o que efetivamente foi gravado.

Solução: resposta de sucesso inclui o input final aplicado no ORM:

return [
    'status'  => 'success',
    'id'      => $newId,
    'message' => 'Projeto criado com sucesso.',
    'applied' => $input  // mostra o que realmente entrou no DB
];

A LLM confirma projectstates_id: 2 (resolvido de "Processing"), plan_start_date: 2026-06-01 00:00:00 (normalizado), e segue confiante.

5. Schema usa oneOf pra polimorfismo

Campos que aceitam int ou string declaram isso explicitamente no inputSchema:

'projectstates_id' => [
    'description' => 'Estado. Aceita ID (int) ou nome (ex: "New", "Processing", "Closed").',
    'oneOf' => [
        ['type' => 'integer'],
        ['type' => 'string']
    ]
]

A descrição já lista exemplos comuns — a LLM começa certo na primeira tentativa.

Quando aplicar

Sempre que uma tool tem campo que referencia outra tabela GLPI:

  • users_idglpi_users (login ou ID)
  • groups_idglpi_groups (nome ou ID)
  • entities_idglpi_entities (nome ou ID)
  • itilcategories_idglpi_itilcategories
  • locations_idglpi_locations
  • tickets_states, priority, urgency, impact (esses já são enums numéricos GLPI — pode aceitar string como atalho: "Alta"=4)

Sempre que aceitar data: usar normalizeDate.

Sempre que houver múltiplas validações: agregar erros.

Sempre na resposta de sucesso: retornar applied.

Anti-padrões a evitar

Anti-padrão Por quê é ruim
Aceitar só ID int LLM precisa fazer N chamadas exploratórias
Mensagem de erro do tipo "Erro ao criar projeto" LLM não sabe o que corrigir
Lançar exception no primeiro erro LLM corrige 1 problema por vez
Não validar antes do ORM Erros de SQL/ORM são incompreensíveis pra LLM
Não retornar input aplicado LLM perde rastreabilidade do que foi gravado

Referências de implementação

Próxima evolução

Extrair os helpers (resolveReference, normalizeDate) para uma classe utilitária src/InputValidation.php quando o segundo handler precisar do mesmo padrão (provavelmente glpi_project_update ou glpi_ticket_create com users_id/itilcategories_id).