- 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>
6.2 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-05-26 |
|
|
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
- Bug identificado e documentado
- Correção #1 (
makeRequest) priorizada — afeta TODAS as tools genéricas REST - Correção #2 (
glpi_projecttask_delete) pode ser implementada junto da próxima rodada do roadmap - Correção #3 (probe/documentação) — 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.