--- id: KB-PLUGIN-022 title: mcprotocol — Padrões de validação e ergonomia em tool inputs domain: plugin-dev tags: - mcp - tools - validation - ergonomics - llm - patterns status: active severity: medium created_at: 2026-05-26 updated_at: 2026-05-26 applies_to: - glpi-11 - mcprotocol-plugin related_records: - 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). ```php 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: ```php 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: ```php $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: ```php 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`: ```php '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_itilcategories` - `locations_id` → `glpi_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 - `src/ProjectTools.php` — handler `handleCreate` (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`).