knowledge-base/records/plugin-dev/KB-PLUGIN-024-mcprotocol-delete-item-silent-success-bug.md
Rodolpho Lopes f072b1cde7 docs: marca bugs do mcprotocol como resolvidos em v1.2.0
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>
2026-06-22 22:16:02 +00:00

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
mcp
bug
glpi
rest-api
delete
error-handling
active high 2026-05-26 2026-06-22
glpi-11
mcprotocol-plugin
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:

{"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

  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:

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 (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.