TesteMCP
by ericzaim
README.md
# MCPTeste — MCP server + client com Claude Haiku e compressão de contexto
Teste end-to-end de um **MCP server** que expõe tools de leitura de documentos JSON, um
**MCP client** que consome essas tools, e o **Claude Haiku 4.5** decidindo quais chamar
dentro de um loop de tool use. Os outputs das tools passam pelo **Headroom** antes de
entrar no contexto do modelo.
## Setup
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # preencha ANTHROPIC_API_KEY
```
## Uso
```bash
# server sozinho (stdio — fica aguardando o protocolo em stdin)
python -m src.server
# client: sobe o server como subprocesso e conversa com o modelo
python -m src.client "Qual o valor total da invoice 001?"
python -m src.client "Resuma o relatorio do primeiro trimestre de 2026"
# mesma pergunta sem compressão, para comparar input_tokens
python -m src.client "Resuma o relatorio do primeiro trimestre de 2026" --no-compression
```
O client imprime `input_tokens`, `output_tokens` e `tokens_saved_by_compression` ao final.
## Decisões e por quê
**stdio, não HTTP.** O client sobe o server como subprocesso. Tira rede da equação: o que
falhar é o protocolo ou a lógica, nunca porta ocupada ou timeout de socket.
**`documents.py` separado do MCP.** A camada de dados não importa nada de `mcp`, então dá
para testá-la direto no interpretador — inclusive a validação de path, que é a parte com
consequência de segurança. O `server.py` fica sendo só a fachada MCP.
**Loop de tool use manual, não o `tool_runner`.** O SDK da Anthropic tem um tool runner que
esconde o ciclo request → tool_use → tool_result. Aqui o objetivo é justamente ver esse
ciclo, então o loop está escrito à mão em `client.py`. Também evita depender de uma API beta.
**Headroom como biblioteca no client, não como segundo MCP server.** O Headroom expõe
`headroom_compress` como tool MCP, mas nesse formato o modelo precisa **ler o output inteiro
para depois pedir a compressão** — o token já foi gasto. Como biblioteca, a compressão
acontece entre `session.call_tool()` e o `tool_result`, antes de qualquer coisa chegar ao
modelo. O ganho aparece já na primeira iteração.
**Compressão nunca derruba o loop.** Qualquer falha do Headroom (erro, retorno inesperado,
pacote ausente) cai no texto original e segue, apenas logando em stderr. É otimização, não
caminho crítico.
## Compressão — números medidos
O `report-2026-q1.json` tem 420 transações com a mesma forma. O Headroom converte esse array
de objetos em uma linha de schema seguida de linhas CSV, eliminando a repetição das chaves.
**Nenhuma linha é descartada** — as 420 continuam lá.
| Payload | Antes | Depois | Redução |
|---|---|---|---|
| Arquivo indentado (176 KB) | 50.305 tokens | 20.338 tokens | −59,6% |
| Output real da tool `read_document` (JSON compacto) | 38.933 tokens | 26.040 tokens | −33,1% |
Documentos pequenos (invoice, contract, `list_documents`) ficam abaixo do piso de
`MIN_CHARS_TO_COMPRESS` e passam sem alteração — comprimir 700 caracteres não paga o custo.
Dois detalhes de configuração que não são óbvios:
- O Headroom protege mensagens `user` recentes por padrão. Como a tool output é entregue a
ele como uma única mensagem `user`, sem `compress_user_messages=True` e `protect_recent=0`
a chamada vira passthrough silencioso (0% de ganho).
- Os contadores `tokens_before`/`tokens_after` do Headroom são estimativas neste ambiente: o
download do vocabulário do `tiktoken` não completa offline e a lib cai para estimativa. Os
números que valem para custo são os `usage.input_tokens` da própria API, comparáveis via
`--no-compression`.
## Segurança
`document_id` vem do modelo, então é input não confiável. `documents.py` valida contra um
regex restritivo, resolve o caminho com `Path.resolve()` e confirma `is_relative_to()` da
pasta de documentos. Traversal (`../../etc/passwd`, `..%2f..`, caminho absoluto) é rejeitado
antes de qualquer acesso a disco, e a mensagem de erro não expõe caminho do sistema.
Nenhuma credencial no código: `ANTHROPIC_API_KEY` vem de `.env` (via `python-dotenv`) ou do
ambiente. O client verifica a credencial antes de subir o server, para falhar com uma linha
legível em vez de um traceback do SDK.
## Limitações conhecidas
- **CCR (compressão reversível) não está integrado.** O Headroom documenta o
Compress-Cache-Retrieve, mas a referência da API não expõe o método de retrieve em Python
(só cita `ccrHashes` no SDK TypeScript). Como o formato CSV-com-schema preserva todas as
linhas, a perda prática é baixa — o system prompt avisa o modelo sobre o formato. Se o
retrieve em Python aparecer, vira uma tool `retrieve_original` no client.
- **`mcp` 2.0 renomeou a API.** `FastMCP` virou `MCPServer` e os campos do protocolo passaram
a snake_case (`input_schema`, `is_error`, `structured_content`). Código de exemplo escrito
para `mcp` 1.x não roda aqui sem ajuste.
- **Sem testes automatizados.** `documents.py` e `compression.py` são as camadas naturais
para pytest (validação de path e fallback de compressão).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues