KB-024 (silent-success do delete): ambas correções implementadas — makeRequest propaga 4xx/5xx (commit 09ce92c) e glpi_projecttask_delete via ORM (0ca94fc). KB-023 (InputValidation): arquivos agora realmente implementados; corrige itens do rascunho que não foram feitos (buildInputFromArgs, project_update estendido). KB-026 (snapshot): atualizado para v1.2.0 — 19 tools, ProjectTask CRUD, métricas. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.5 KiB
| id | title | domain | tags | status | severity | created_at | updated_at | applies_to | related_records | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| KB-PLUGIN-024 | mcprotocol — Bug duplo no glpi_delete_item (sucesso silencioso + ProjectTask não exposto em v2) | plugin-dev |
|
active | high | 2026-05-26 | 2026-06-22 |
|
|
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:
{"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:
$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
- Não usar
glpi_delete_itempara ProjectTask — usar SQL direto ou criar tool de domínio (glpi_projecttask_delete) com ORM(new ProjectTask())->delete(['id' => $id], true). - Cliente checa
applied— tools de domínio do plugin retornamapplied. Para tools genéricas, o cliente deve fazerGETapósDELETEpra 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:
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:
'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):
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
# 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":<NEW_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=<NEW_ID>;"
Status
- ✅ RESOLVIDO em 2026-06-22 (plugin v1.2.0, repo dev
origin). - ✅ Correção #1 (
makeRequestpropaga 4xx/5xx) — commit09ce92c.makeRequestagora lança exception em HTTP ≥400, capturada nocallToole devolvida como erro JSON-RPC visível. Resolve o silent-success de TODAS as tools genéricas REST. - ✅ Correção #2 (
glpi_projecttask_deletevia ORM) — commit0ca94fc. Implementada emsrc/ProjectTaskTools.phpjunto com get/create/update, todas via ORM\ProjectTask. - 🔲 Correção #3 (probe/documentação de itemtypes quebrados na v2) — segue no backlog.
Lições
- Tools wrapper REST nunca confiam em corpo da resposta sem checar HTTP status — sempre validar.
- Tools de domínio (ORM) são mais robustas — não dependem do estado da REST API, usam o GLPI diretamente em PHP.
appliedem response ajuda o cliente confiar — mas não substitui checagem ativa pós-delete.- REST API v2 do GLPI tem buracos — não cobre todos os itemtypes. Validar caso a caso antes de assumir que
glpi_*_itemgenérico funciona.