--- id: KB-PLUGIN-024 title: mcprotocol — Bug duplo no glpi_delete_item (sucesso silencioso + ProjectTask não exposto em v2) domain: plugin-dev tags: - mcp - bug - glpi - rest-api - delete - error-handling status: active severity: high created_at: 2026-05-26 updated_at: 2026-06-22 applies_to: - glpi-11 - mcprotocol-plugin related_records: - KB-PLUGIN-019 - KB-PLUGIN-016 --- # mcprotocol — Bug duplo no glpi_delete_item (sucesso silencioso + ProjectTask não exposto em v2) ## Resumo Ao tentar deletar uma `ProjectTask` via `glpi_delete_item`, a tool retorna sucesso ao cliente mas o item **continua existindo no banco**. Investigação revelou dois bugs sobrepostos. ## Evidência Comando que reproduz: ```json {"method":"tools/call","params":{ "name":"glpi_delete_item", "arguments":{"itemtype":"ProjectTask","id":30,"force_purge":true} }} ``` Resposta MCP: aparenta sucesso (sem JSON-RPC error). Banco: registro ID 30 segue em `glpi_projecttasks` com `is_deleted=0`. ## Causa raiz #1 — REST API v2 do GLPI não expõe ProjectTask Verificado por probes: | Endpoint | HTTP | |---|---| | `GET /api.php/v2/Project` | 200 ✅ | | `GET /api.php/v2/ProjectTask` | 404 ❌ | | `DELETE /api.php/v2/Project/4/ProjectTask/30` | 404 ❌ | | `DELETE /api.php/v2/Project/ProjectTask/30` | 404 ❌ | | `DELETE /api.php/v2/Assistance/ProjectTask/30` | 404 ❌ | `ProjectTask` simplesmente **não está no roteamento público da v2** desta build do GLPI 11. Não há caminho documentado. Pode ser limitação da própria release (não confirmado se evolui em versões futuras). ## Causa raiz #2 — `makeRequest` não valida HTTP status Em `src/Server.php`, o handler de `glpi_delete_item` faz: ```php $response = $this->makeRequest('DELETE', "/$itemtype/{$args['id']}$purge"); break; ``` O método `makeRequest()` retorna o body da resposta sem checar o código HTTP. Quando a API retorna `{"status":"ERROR_ITEM_NOT_FOUND"}` com HTTP 404, esse JSON vira o `content[0].text` da resposta MCP — que do ponto de vista do JSON-RPC parece sucesso (não tem campo `error` no envelope JSON-RPC). O cliente (Python, LLM, etc.) que checa apenas `if "error" in response` é enganado. ## Impacto - **Alto:** silenciosamente perde operações de delete sem alertar o usuário/LLM - LLM acredita que removeu, segue trabalhando com base nessa premissa falsa - Afeta qualquer itemtype que não esteja na v2 do GLPI (ProjectTask confirmado; outros podem estar afetados — `KnowbaseItem`, `Document_Item`, etc., precisam ser testados) ## Mitigações temporárias 1. **Não usar `glpi_delete_item` para ProjectTask** — usar SQL direto ou criar tool de domínio (`glpi_projecttask_delete`) com ORM `(new ProjectTask())->delete(['id' => $id], true)`. 2. **Cliente checa `applied`** — tools de domínio do plugin retornam `applied`. Para tools genéricas, o cliente deve fazer `GET` após `DELETE` pra confirmar remoção (verificação ativa). ## Correções recomendadas ### Correção #1 (rápida, alta prioridade) — Validar HTTP status no `makeRequest` Em `src/Server.php`, modificar `makeRequest` para propagar 4xx/5xx como exception: ```php private function makeRequest(string $method, string $path, ?array $body = null): array { // ... cURL setup ... $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $decoded = json_decode($response, true) ?? []; if ($httpCode >= 400) { $msg = $decoded['title'] ?? $decoded['status'] ?? 'Unknown error'; $detail = $decoded['detail'] ?? ''; throw new \Exception("GLPI API {$method} {$path} → HTTP {$httpCode}: {$msg}" . ($detail ? " ({$detail})" : '')); } return $decoded; } ``` Assim qualquer 404/422/500 da API vira erro JSON-RPC visível para o cliente. ### Correção #2 (evolução) — Criar tools de domínio para ProjectTask Adicionar em `src/ProjectTaskTools.php`: ```php 'glpi_projecttask_delete' => [ 'description' => 'Remove uma tarefa de projeto. Bypassa REST API v2 (não exposta).', 'inputSchema' => [...], 'handler' => [self::class, 'handleDelete'] ], ``` Handler usa ORM diretamente (não a REST API): ```php public static function handleDelete(array $args) { $task = new \ProjectTask(); if (!$task->can($args['id'], DELETE)) throw new \Exception("Acesso Negado."); if (!$task->delete(['id' => $args['id']], (bool)($args['force_purge'] ?? true))) { throw new \Exception("Erro ao remover tarefa."); } return ['status'=>'success', 'id'=>$args['id'], 'message'=>'Tarefa removida.']; } ``` ### Correção #3 (defensiva) — Probe na lista de tools Documentar em `glpi_delete_item` quais itemtypes são conhecidamente quebrados na v2. Ou no startup do plugin, fazer um `OPTIONS` em itemtypes comuns e marcar quais funcionam. ## Como reproduzir ```bash # 1. criar uma ProjectTask curl -s -X POST .../mcp.php -H "Authorization: Bearer $TOKEN" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"glpi_projecttask_create","arguments":{"projects_id":4,"name":"X"}}}' # 2. tentar deletar (parece sucesso) curl -s -X POST .../mcp.php -H "Authorization: Bearer $TOKEN" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"glpi_delete_item","arguments":{"itemtype":"ProjectTask","id":,"force_purge":true}}}' # 3. confirmar que o registro continua docker exec glpi11-mariadb mariadb -uglpi -pglpi_local_dev glpi \ -e "SELECT id,name,is_deleted FROM glpi_projecttasks WHERE id=;" ``` ## Status - ✅ **RESOLVIDO em 2026-06-22** (plugin v1.2.0, repo dev `origin`). - ✅ Correção #1 (`makeRequest` propaga 4xx/5xx) — commit `09ce92c`. `makeRequest` agora lança exception em HTTP ≥400, capturada no `callTool` e devolvida como erro JSON-RPC visível. Resolve o silent-success de TODAS as tools genéricas REST. - ✅ Correção #2 (`glpi_projecttask_delete` via ORM) — commit `0ca94fc`. Implementada em `src/ProjectTaskTools.php` junto com get/create/update, todas via ORM `\ProjectTask`. - 🔲 Correção #3 (probe/documentação de itemtypes quebrados na v2) — segue no backlog. ## Lições 1. **Tools wrapper REST nunca confiam em corpo da resposta sem checar HTTP status** — sempre validar. 2. **Tools de domínio (ORM) são mais robustas** — não dependem do estado da REST API, usam o GLPI diretamente em PHP. 3. **`applied` em response** ajuda o cliente confiar — mas não substitui checagem ativa pós-delete. 4. **REST API v2 do GLPI tem buracos** — não cobre todos os itemtypes. Validar caso a caso antes de assumir que `glpi_*_item` genérico funciona.