- 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>
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 |
|
active | medium | 2026-05-26 | 2026-05-26 |
|
|
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→ preenche00:00:00YYYY-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_id→glpi_users(login ou ID)groups_id→glpi_groups(nome ou ID)entities_id→glpi_entities(nome ou ID)itilcategories_id→glpi_itilcategorieslocations_id→glpi_locationstickets_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
src/ProjectTools.php— handlerhandleCreate(referência canônica deste padrão)src/ToolRegistry.php— registro central de tools- Spec MCP — Tool Input Schemas: https://spec.modelcontextprotocol.io/specification/server/tools
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).