- 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>
163 lines
6.2 KiB
Markdown
163 lines
6.2 KiB
Markdown
---
|
|
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-05-26
|
|
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":<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
|
|
|
|
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.
|