# TecJustica MCP Server

MCP Server para consulta de processos judiciais armazenados no Supabase.

## Stack

- **Framework**: FastMCP 2.14+ (Python 3.11+)
- **Database**: Supabase (PostgreSQL) com RLS
- **Auth**: Supabase OAuth 2.1 (SupabaseProvider com DCR)
- **LLM**: OpenRouter (Gemini 2.5 Flash — unico modelo)
- **Deploy**: Railway (`https://mcp.tecjustica.com`)

## Testes MCP em Producao

**SEMPRE testar em PRODUCAO** (`https://mcp.tecjustica.com/mcp`), nunca localhost.

### Fluxo completo de teste via curl

**1. Obter token Supabase** (email/senha do usuario):
```bash
ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBnbGdoeXpuaXJ4a2V3Y211aHZwIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjYzNjk4MTgsImV4cCI6MjA4MTk0NTgxOH0.hac05fv66miyiRqzwBIZYSA8gjA8dcRFOJMs3wSqWpA"

TOKEN=$(curl -s -X POST "https://pglghyznirxkewcmuhvp.supabase.co/auth/v1/token?grant_type=password" \
  -H "apikey: $ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"EMAIL","password":"SENHA"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
```

> **ATENCAO**: O token e do **Supabase Auth**, NAO do PJE/Keycloak. Tokens de outros provedores serao rejeitados com `invalid_token`.

**2. Initialize** (obter `mcp-session-id` do header de resposta):
```bash
SID=$(curl -s -D- -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test-cli","version":"1.0"}}}' \
  | grep -i 'mcp-session-id' | tr -d '\r' | awk '{print $2}')
```

**3. Notification initialized** (obrigatorio antes de chamar tools):
```bash
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'
```

**4. Chamar tools** (todas as chamadas precisam do `mcp-session-id`):
```bash
# tools/list
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

# tools/call (ex: whoami)
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'

# tools/call (ex: listar_processos)
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"listar_processos","arguments":{}}}'

# tools/call (ex: visao_geral_processo)
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"visao_geral_processo","arguments":{"numero_processo":"1234567-89.2024.8.26.0100"}}}'
```

### Erros comuns

| Erro | Causa | Solucao |
|------|-------|---------|
| `invalid_token` | Token nao e do Supabase (ex: PJE/Keycloak) | Usar endpoint Supabase `/auth/v1/token` com ANON_KEY |
| `invalid_credentials` | Email/senha errados no Supabase | Verificar credenciais ou criar usuario |
| `Missing session ID` | Chamou tool sem `mcp-session-id` header | Capturar session ID do initialize e enviar em todas as requests |
| `Bad Request` apos initialize | Faltou `notifications/initialized` | Enviar notification antes de tools/list ou tools/call |

### Checklist de teste

1. OAuth discovery: `GET /.well-known/oauth-authorization-server` (nao precisa auth)
2. `initialize` → capturar `mcp-session-id`
3. `notifications/initialized`
4. `tools/list` → verificar 15 tools
5. `whoami` → verificar usuario autenticado
6. `listar_processos()` → listar processos disponiveis
7. `visao_geral_processo(numero_processo="...")` → metadados, partes, stats, movs recentes

### Teste via API Key (mais simples)

A API key ja funciona como Bearer token direto — nao precisa obter token Supabase.

```bash
TOKEN="tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"

# 1. Initialize → capturar mcp-session-id
SID=$(curl -s -D- -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test-cli","version":"1.0"}}}' \
  | grep -i 'mcp-session-id' | tr -d '\r' | awk '{print $2}')

# 2. Notification initialized (obrigatorio)
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'

# 3. whoami
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'

# 4. listar_processos
curl -s -X POST https://mcp.tecjustica.com/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Authorization: Bearer $TOKEN" \
  -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"listar_processos","arguments":{}}}'
```

> **API Key do Marcos**: `tk_0bd3...e611dd` — usuario `marcosmarf27@gmail.com` (user_id `76ba8190-b43d-4665-84cd-0c30f3bc7b8e`)

## Comandos de Desenvolvimento

```bash
# Setup
uv venv && uv pip install -r requirements.txt -r requirements-dev.txt

# Lint + Format + Type check (OBRIGATORIO antes de commit)
uv run ruff check --fix . && uv run ruff format . && uv run mypy .

# Testes
uv run pytest --cov=. --cov-report=html

# Servidor local
uv run python server.py
```

## Arquitetura

```
server.py                  # FastMCP v4.0.0, resources (2), prompts (6), main
tools/
  __init__.py              # Exporta 7 register_*_tools()
  navegacao.py             # 5 tools: listar_processos, visao_geral_processo, ls_documentos, ls_movimentacoes, read_documento
  busca.py                 # 4 tools: grep_documentos, grep_movimentacoes, glob_documentos, localizar_no_documento
  estatisticas.py          # 1 tool: stats_documentos
  analise.py               # 1 tool: analisar (map-reduce com Gemini Flash)
  precedentes.py           # 1 tool: buscar_precedentes (BNP/CNJ)
  utilidades.py            # 2 tools: calculadora, data_hora_atual
  user.py                  # 1 tool: whoami
  _helpers.py              # Constantes, eviction inteligente, map-reduce, paginacao
  _legacy/                 # Tools antigos preservados para referencia
services/
  supabase_client.py       # Cliente Supabase com RLS, deteccao civel/criminal
  llm_service.py           # OpenRouter integration (Gemini Flash unico, retry + timeout)
  usage_logger.py          # Logging fire-and-forget (LLM + tool calls)
docs/                      # OAuth flow, troubleshooting, integracao frontend
```

### Padrao Deep Agent (Metafora Filesystem)

O agent consumidor segue o padrao: **descobrir → navegar → buscar → analisar**.

1. `listar_processos()` — Descobrir processos, buscar por parte
2. `visao_geral_processo(cnj)` — Metadados + partes + stats + movs recentes (com `fonte` por mov)
3. `ls_documentos(cnj, fonte?)` / `ls_movimentacoes(cnj, fonte?)` — Listar sem conteudo, filtro por fonte
4. `read_documento(id, offset, max_chars)` — Ler conteudo controlado (retorna `fonte` e `confianca`)
5. `grep_documentos(padrao, fonte?)` / `grep_movimentacoes(padrao, fonte?)` — Full-text search, filtro por fonte
6. `glob_documentos(cnj, padrao_tipo, fonte?)` — Filtrar por nome/tipo/fonte
7. `localizar_no_documento(id, termo)` — Bridge grep→read (posicoes exatas, retorna `fonte` e `confianca`)
8. `stats_documentos(cnj, fonte?)` — Contagens agregadas, zero texto, breakdown `por_fonte`
9. `analisar(cnj, pergunta, perspectiva)` — Analise com Gemini Flash (map-reduce para processos grandes)
10. `buscar_precedentes(busca, orgaos?, tipos?)` — Sumulas, temas repetitivos, IRDR (BNP/CNJ)
11. `calculadora(modo, expressao?, data_intimacao?, dias?)` — Calculos matematicos e prazos processuais (CPC/CPP)
12. `data_hora_atual(timezone?)` — Data/hora atual com dia da semana
13. `whoami()` — Identidade do usuario

### Fluxo de uma request

```
Claude -> FastMCP (server.py) -> @mcp.tool -> services/supabase_client.py -> Supabase
                                           -> services/llm_service.py -> OpenRouter
```

### Como adicionar uma nova tool

Preferir estender tools existentes (dispatch implicito) a criar tools novas.

1. Criar funcao em `tools/<modulo>.py` dentro de `register_<modulo>_tools(mcp)`:
```python
def register_exemplo_tools(mcp: FastMCP) -> None:
    @mcp.tool
    async def minha_tool(param: str, ctx: Context) -> dict[str, Any]:
        """Descricao da tool."""
        client = get_supabase_client()
        # ... logica
        return {"resultado": ...}
```
2. Usar helpers de `_helpers.py`: `paginar_resultado()`, `_smart_response()`, `erro_instrutivo()`
3. Exportar em `tools/__init__.py`
4. Registrar em `server.py`: `register_exemplo_tools(mcp)`

### Auth

O servidor usa `SupabaseProvider` com DCR (Dynamic Client Registration). Nao precisa de `client_id`, `client_secret`, nem `JWT_SIGNING_KEY`. Ver `docs/` para detalhes do fluxo OAuth e troubleshooting.

Variaveis de ambiente: `SUPABASE_URL`, `SUPABASE_SERVICE_KEY`, `AUTH_ENABLED`, `MCP_BASE_URL`, `OPENROUTER_API_KEY`, `API_KEY_HASHES`

Auth dual: `DualTokenVerifier` em `server.py` valida tokens `tk_` via hash SHA-256 (env var `API_KEY_HASHES`, formato `hash:user_id:email`) ou delega JWTs ao `JWTVerifier` do Supabase.

### Railway

- **Projeto**: `TecJustica MCP` | **Service**: `web` | **Workspace**: `Marcos Fonseca's Projects`
- **Project ID**: `e034306d-50a9-40ab-87d3-91daf3c7e849`
- **Link**: `railway link -p "TecJustica MCP" -s "web"`
- **Env vars**: `railway variables --json` (precisa estar linkado)
- **Deploy**: automatico via `git push origin master`
- **Linters via uv**: `uv run ruff check --fix . && uv run ruff format . && uv run mypy .`

## Modelo LLM

Unico modelo: **Gemini 2.5 Flash** (`google/gemini-2.5-flash`) via OpenRouter.
- Contexto: 1M tokens (~3.2M chars com margem)
- Custo: $0.15/1M input, $0.60/1M output
- **Retry**: exponential backoff (tenacity) — 4 tentativas, 2-60s, para erros 429/5xx do OpenRouter
- **Timeout**: 120s por chamada LLM via `asyncio.wait_for`
- Processos grandes: estrategia map-reduce em `analisar_map_reduce()` (`_helpers.py`)
  - Divide docs em lotes de ~800K chars (~200K tokens)
  - MAP: resume cada lote em paralelo (tolerante a falhas parciais)
  - REDUCE: consolida resumos e responde a pergunta
  - Se lotes falham parcialmente, prossegue com os disponiveis + nota no contexto

## Eviction Inteligente

`_smart_response()` em `_helpers.py` resume conteudo > 10k chars com Gemini em vez de truncar.
Instrucoes adaptadas por tipo: documento, analise, lista. Retorna metadata: `foi_resumido`, `tamanho_original`, `tamanho_final`.

## Deteccao Civel vs Criminal

`detectar_tipo_processo()` em `services/supabase_client.py` analisa a classe processual. Isso adapta automaticamente o contexto enviado ao LLM.

Perspectivas para `analisar(perspectiva=...)`: `autor`, `reu`, `terceiro` (civel) | `acusado`, `ministerio_publico`, `defensor`, `vitima` (criminal) | `juiz`, `assessor` (neutro)

## Banco de Dados

**Tabelas**: `jobs`, `processos`, `documents`, `movimentacoes`, `admin_users`, `llm_usage_logs`, `tool_call_logs`

**Dual-source**: `documents` e `movimentacoes` possuem coluna `source` (varchar, NOT NULL, default `'api'`). Valores: `'api'` (dados do PJE) e `'segmentation'` (dados de segmentacao PDF). `documents` tambem tem `confianca` (float, nullable) para score de confianca da segmentacao.

**Views**: `vw_processo_completo`, `vw_documentos_preview`, `vw_documentos_relevantes`, `vw_stats_processo`, `vw_timeline_processo`, `vw_movimentacoes_recentes` — todas incluem `source` (e `confianca` onde aplicavel)

**RPCs**: `buscar_documentos(p_source?)`, `buscar_movimentacoes(p_source?)`, `localizar_no_documento` (retorna `fonte` e `confianca`), `stats_documentos(p_source?)` (retorna `por_fonte` breakdown), `buscar_processos_por_parte`

## Convencoes

- **Tipagem**: SEMPRE usar type hints. `str | None`, `list[str]`
- **`from __future__ import annotations`**: OK em `_helpers.py` e `services/`, mas NAO em arquivos que registram tools (conflita com Pydantic em `@mcp.tool`)
- **Formato CNJ**: `NNNNNNN-DD.AAAA.J.TR.OOOO`
- **Valores**: BRL. **Datas**: ISO 8601. **Idioma**: pt-BR
- **Antes de commit**: SEMPRE rodar `uv run ruff check --fix . && uv run ruff format . && uv run mypy .`

## MCPs para Documentacao (OBRIGATORIO)

**SEMPRE** usar Ref e Exa para buscar documentacao antes de implementar, refatorar ou integrar com bibliotecas/APIs.

- `ref_search_documentation(query)` — docs oficiais (FastMCP, Supabase, etc.)
- `ref_read_url(url)` — ler URL retornada pelo Ref
- `get_code_context_exa(query)` — exemplos de codigo
- `web_search_exa(query)` — informacoes atualizadas

## Agente Especializado

Para criar/modificar o MCP server, usar o agente `mcp-server-architect` (tools, resources, prompts, integracao Supabase, background tasks).
