Skip to main content
Glama
bernardcaldas

DataClaw MCP Server

README.md
# 🦅 DataClaw MCP Server

**AI-First CSV Analysis Tool** — um servidor MCP para agentes de IA e workflows com LLM.
Feito para datasets grandes (10k–1M linhas) com precisão cirúrgica.

> **🚦 Status: v3.2 — em validação com dados reais.**
> O servidor passou da bateria sintética para datasets públicos de verdade
> (Olist e-commerce, ~1,5 milhão de linhas em 9 arquivos). Essa rodada
> encontrou 4 defeitos que os testes sintéticos não pegavam — **todos já
> corrigidos** e descritos abaixo, com transparência total.
> O time de testes segue validando com novas bases nas próximas semanas.
> **Deploy em produção previsto para as próximas semanas**, condicionado
> aos itens de segurança listados no fim deste documento.

## 🧠 O que é o DataClaw?

DataClaw é um servidor Model Context Protocol (MCP) que dá a agentes de IA a capacidade
de analisar, consultar e auditar arquivos CSV sem escrever uma linha de código.

A ideia central: **o CSV nunca entra no contexto do LLM.** O pandas calcula tudo
localmente e o agente recebe apenas um JSON de ~10 KB com os números já prontos e
rótulos inequívocos. Um arquivo de 1 milhão de linhas e o de 5 mil produzem exatamente
a mesma estrutura de resposta — muda só o conteúdo.

> **Objetivo principal:** servir como servidor MCP para agentes de IA, especificamente
> para uso no **OpenClaw**.

## 🔒 Princípio de confiabilidade

Nenhuma transformação é silenciosa. Tudo que o servidor faz com os dados volta no JSON,
com contagem:

| O que acontece | Onde aparece no JSON |
|---|---|
| Linha descartada pelo parser | `data_quality.rows_dropped_malformed` + `parse_warning` |
| Duplicata encontrada | `data_quality.duplicate_rows_found` + `transformations.deduplication` |
| Duplicata **removida** | `data_quality.duplicate_rows_removed` (só com `deduplicate=True`) |
| Base usada no cálculo | `analysis_basis` + `financial.reporting_basis` |
| Texto convertido em número | `transformations.numeric_coercion` (com o que virou nulo e exemplos) |
| Variantes de texto unificadas | `transformations.text_variants_merged` (canônico + variantes) |
| Data que não pôde ser lida | `dates.invalid_dates_count` + exemplos dos valores |
| Métrica inválida substituída | `WARNING_METRIC` + `metric_requested` |
| Ranking parcial | `total_groups`, `groups_shown`, `all_groups_shown` |
| Lista cortada por tamanho | `<chave>__truncated: {showing, total}` |

Estatísticas de coluna são calculadas sobre o **arquivo inteiro**, nunca sobre amostra.
Se algum dia houver amostragem, o campo `stats_scope` diz explicitamente.

### O servidor não decide o que é duplicata

Duas linhas idênticas podem ser erro de digitação **ou** venda legítima (duas unidades
do mesmo item no mesmo pedido). O dado sozinho não desempata — então o DataClaw
**não remove nada por padrão**. Ele conta, classifica a confiança e recomenda:

```json
"deduplication": {
  "exact_duplicate_rows": 11033,
  "rows_removed_total": 11033,
  "duplicate_confidence": "baixa",
  "confidence_reason": "arquivo transacional (tem data e valor): linhas idênticas
                        costumam ser itens repetidos legítimos, não erro de digitação",
  "duplicate_pct": 9.79,
  "recommended_action": "NÃO remover automaticamente; confirme a regra de negócio antes"
}
```

Para analisar sem as duplicatas, é uma escolha explícita: `analyze_csv(arquivo, deduplicate=True)`.
Os dois totais (`total_WITH_duplicates` e `total_WITHOUT_duplicates`) vêm sempre no JSON.

## 🛠️ Tools expostas

| Tool | O que faz |
|---|---|
| `csv_info` | Estrutura, tipos, nulos e nulos por coluna. Chame primeiro. |
| `analyze_csv` | Análise completa: qualidade, financeiro, tendência mensal, rankings por até 3 dimensões, outliers (IQR + z-score), cancelamentos por dimensão, sazonalidade. |
| `query_csv` | Consulta ad-hoc: agrupa, filtra, ordena. Informa sempre se o ranking é completo. |
| `clean_csv` | Aplica o pipeline de limpeza, salva em `outputs/` e devolve o relatório do que mudou. |

Todas as tools de leitura aceitam `deduplicate: bool = False`.
Em `clean_csv` o padrão é `True` — limpar é o objetivo dela —, mas ela avisa
(`WARNING_DEDUP`) quando a confiança na remoção é baixa.

## 🚀 Instalação e uso

```bash
pip install -r requirements.txt
python server.py            # sobe via stdio
```

Registro em um cliente MCP:

```json
{
  "mcpServers": {
    "dataclaw": {
      "command": "python",
      "args": ["/caminho/absoluto/para/dataclaw-mcp/server.py"]
    }
  }
}
```

## 🧪 Testes

```bash
python tests/test_dataclaw.py                    # 129 checks contra gabarito sintético
python tests/test_mcp_protocol.py                # smoke test do protocolo MCP via stdio
python tests/test_dados_reais.py <arquivo|pasta> # valida QUALQUER CSV contra o pandas
```

`test_dataclaw.py` cobre precisão contra gabarito, conservação, invariância
(5k/50k/150k linhas, metades, ordem embaralhada, `latin-1`, locale en-US, TAB),
determinismo e robustez (arquivo vazio, prosa, só cabeçalho, linha malformada,
quebra de linha dentro de aspas, coluna inexistente, filtro sem match).

**`test_dados_reais.py` é a ferramenta de validação com dados novos.** Aponte para
um CSV ou uma pasta e ele confere o servidor contra o pandas puro, **sem gabarito
pré-calculado** — serve para qualquer base que o time de testes trouxer:

```bash
python tests/test_dados_reais.py ~/dados/vendas_2026.csv
python tests/test_dados_reais.py ~/dados/           # a pasta inteira
```

Ele valida contagem de registros (via `csv.reader`, respeitando aspas e quebra de
linha), soma/média/mediana/máx/mín contra o pandas, as duas leis de conservação,
coerência entre o total reportado e a base declarada, fechamento do ranking completo,
denúncia de métrica inválida, determinismo e a invariância metade + metade.

## 📋 Rodada de validação com dados reais — o que foi encontrado e corrigido

Dataset: **Olist Brazilian E-Commerce** (9 arquivos, ~1,5 milhão de linhas,
123 MB), mais uma planilha de vendas achatada de 112.650 linhas montada por join —
o formato que um usuário de negócio realmente manda para o agente.

| # | Defeito encontrado | Correção |
|---|---|---|
| 1 | **Faturamento subestimado em 6,62%** (R$ 899.980 de R$ 13,6 mi). O servidor removia 11.033 linhas duplicadas que eram vendas legítimas, e o campo se chamava `CORRECT_VALUE_TO_USE` com um `WARNING` mandando sempre usar o número reduzido. O erro cascateava para rankings, tendência mensal e cancelamentos (464 em vez de 542). | Dedup virou **opt-in** (`deduplicate=False` por padrão). `CORRECT_VALUE_TO_USE` deu lugar a `total_reported` + `reporting_basis`, que diz qual base foi usada. Novo campo `ATTENTION` explica a ambiguidade e a confiança. |
| 2 | `metric` inválida caía para `"sum"` **em silêncio** — a única transformação silenciosa do servidor, contra o próprio princípio do projeto. | Agora devolve `WARNING_METRIC` + `metric_requested` com as opções válidas. |
| 3 | `analyze_csv` devolvia **zero rankings** em `order_items`: o teto de 50 valores distintos excluía `seller_id` (3.095 vendedores) — justamente a pergunta de negócio óbvia. | Dimensões de alta cardinalidade viram fallback quando não há nenhuma estreita. Colunas de data e quase-únicas por linha continuam fora (agrupar por timestamp devolveria um grupo por linha). |
| 4 | Em `sellers`, 800 de 3.095 linhas (26%) eram tratadas como duplicata só porque dois vendedores diferentes dividem o mesmo CEP/cidade/UF. Zero duplicatas exatas. Mesmo padrão em `customers` (3.089) e `reviews` (824). | Classificação de confiança (`baixa`/`média`/`alta`) com o motivo explícito, e nada é removido sem pedido. |

**Resultado depois das correções:** 129/129 na suíte sintética, 109/109 na validação
com os 9 arquivos reais, 37/37 na bateria específica dos defeitos acima, e o smoke
test MCP verde. O que já estava correto continuou correto: contagem de registros
exata em todos os arquivos (incluindo os 99.224 registros de `reviews` espalhados em
104.720 linhas físicas por quebra de linha dentro de aspas), soma/média/mediana
idênticas ao pandas, determinismo, filtro insensível a caixa e acento, e 1.000.163
linhas processadas sem erro.

### ⚠️ Mudança de contrato na v3.2

Quem já consumia o JSON precisa saber:

| Antes (v3.1) | Agora (v3.2) |
|---|---|
| `financial.CORRECT_VALUE_TO_USE` | `financial.total_reported` (+ `reporting_basis`) |
| `data_quality.duplicate_rows_removed` = duplicatas achadas | `duplicate_rows_found` = achadas; `duplicate_rows_removed` = removidas de fato (0 por padrão) |
| Métricas calculadas sem duplicatas | Calculadas sobre o arquivo inteiro, salvo `deduplicate=True` |

A invariante de conservação agora é
`rows_parsed == rows_after_dedup + duplicate_rows_found`.

## ⚠️ Limitações atuais

- Variantes de texto só são unificadas quando diferem em caixa, acento, espaço ou
  pontuação. `S. Paulo` e `SP` **não** são unificados a `São Paulo` — casamento fuzzy
  traz risco de fundir categorias distintas. Eles aparecem separados no ranking.
- `analyze_csv` cobre até 3 dimensões categóricas por rodada. O campo
  `columns_detected.categorical_dimensions_analyzed` diz quais foram — use `query_csv`
  para as demais.
- Colunas com mais de 1000 valores distintos não passam pela unificação de variantes.
- A tendência mensal mostra os últimos 12 meses; `monthly_trend_scope` informa o total.
- Transporte apenas `stdio`; `file_path` aceita qualquer caminho do disco local.

## 🔐 Antes do deploy em produção

O deploy hospedado multiusuário depende destes itens, ainda pendentes:

- [ ] **Transporte HTTP** no lugar de `stdio`
- [ ] **Autenticação** por token
- [ ] **Sandbox de arquivos** — hoje o servidor lê qualquer caminho da máquina
- [ ] Limite de tamanho de arquivo e timeout por requisição

Até lá, use apenas localmente ou em ambiente confiável.

## 🗺️ Próximos passos

1. ✅ Fase 1 — arquitetura JSON e suíte sintética
2. ✅ Fase 2 — validação com dados reais (Olist) e correção dos 4 defeitos
3. 🔄 **Fase 3 — validação ampliada pelo time de testes com novas bases reais** ← estamos aqui
4. ⏳ Fase 4 — hardening de segurança (HTTP + auth + sandbox)
5. ⏳ Fase 5 — deploy e publicação para os agentes do **OpenClaw**