# Guia de Desenvolvimento de Agentes - TecJustica MCP

Guia completo para desenvolvedores (front-end e back-end) que vao construir agentes usando o MCP Server TecJustica.

**URL de Producao:** `https://mcp.tecjustica.com/mcp`

---

## Indice

1. [Visao Geral](#1-visao-geral)
2. [Autenticacao](#2-autenticacao)
3. [Ferramentas (15)](#3-ferramentas-15)
4. [Resources (2)](#4-resources-2)
5. [Prompts (6)](#5-prompts-6)
6. [Banco de Dados Compartilhado](#6-banco-de-dados-compartilhado)
7. [Padrao Deep Agent](#7-padrao-deep-agent)
8. [Selecao de Modelo LLM](#8-selecao-de-modelo-llm)
9. [Tratamento de Erros](#9-tratamento-de-erros)
10. [Exemplos Praticos](#10-exemplos-praticos)
11. [Construindo Agentes com LangGraph](#11-construindo-agentes-com-langgraph)

---

## 1. Visao Geral

O TecJustica MCP Server expoe processos judiciais brasileiros (armazenados no Supabase) para agentes de IA via [Model Context Protocol (MCP)](https://modelcontextprotocol.io).

### Stack

| Camada | Tecnologia |
|--------|-----------|
| Framework | [FastMCP](https://gofastmcp.com) 2.14+ (Python 3.11) |
| Database | Supabase (PostgreSQL) com RLS |
| Auth | Supabase OAuth 2.1 (DCR) + API Keys (`tk_`) |
| LLM | OpenRouter (Gemini 2.5 Flash — modelo unico) |
| Deploy | Railway (auto-deploy via `git push`) |
| Transport | `streamable-http` |

### O que o MCP oferece

| Tipo | Qtd | Descricao |
|------|-----|-----------|
| **Tools** | 15 | Ferramentas com padrao Deep Agent (ls, read, grep, glob, analise, utilidades) |
| **Resources** | 2 | Acesso direto a processos e documentos via URI |
| **Prompts** | 6 | Templates profissionais para analises juridicas |

### Dual-source

Documentos e movimentacoes possuem duas origens de dados:
- `api` — dados vindos diretamente do PJE (tribunal)
- `segmentation` — dados extraidos por segmentacao de PDF

Todas as tools que retornam documentos/movimentacoes incluem o campo `fonte` indicando a origem. O parametro `fonte` (opcional) permite filtrar por origem.

---

## 2. Autenticacao

O servidor aceita dois tipos de autenticacao via header `Authorization: Bearer <token>`:

### API Key (recomendado para desenvolvimento)

Tokens no formato `tk_<hash>`. Funcionam como Bearer token direto — nao precisa de fluxo OAuth.

```bash
TOKEN="tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"
```

### JWT Supabase

Token obtido via endpoint de autenticacao do Supabase Auth:

```bash
ANON_KEY="eyJhbG..."  # Anon key do projeto Supabase

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":"seu@email.com","password":"sua_senha"}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
```

> **Importante**: O token deve ser do Supabase Auth. Tokens de outros provedores (PJE/Keycloak) serao rejeitados com `invalid_token`.

### Protocolo MCP via HTTP

Toda sessao MCP segue 4 passos obrigatorios:

**1. 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":"meu-agente","version":"1.0"}}}' \
  | grep -i 'mcp-session-id' | tr -d '\r' | awk '{print $2}')
```

**2. Notification initialized** (obrigatorio antes de qualquer tool call):
```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":{}}'
```

**3. tools/list** — descobrir ferramentas disponiveis:
```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","id":2,"method":"tools/list","params":{}}'
```

**4. tools/call** — chamar uma ferramenta:
```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","id":3,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'
```

### OAuth Discovery

Metadata do servidor OAuth disponivel sem autenticacao:

```
GET https://mcp.tecjustica.com/.well-known/oauth-authorization-server
```

---

## 3. Ferramentas (15)

As 15 tools seguem o padrao Deep Agent (metafora filesystem): **descobrir -> navegar -> buscar -> analisar**.

### Navegacao (5 tools)

#### `listar_processos`

Lista processos disponiveis ou busca por nome de parte.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `busca` | `str \| None` | `None` | Nome da parte para buscar. Se omitido, lista todos |
| `limite` | `int` | `20` | Maximo de resultados (max 50) |
| `offset` | `int` | `0` | Pular N resultados para paginacao |

**Retorno:** Lista paginada com `numero_processo`, `classe_descricao`, `sigla_tribunal`, `data_ajuizamento`, `valor_causa`, `partes` (resumidas).

**Quando usar:** Primeiro passo. Descobrir processos ou buscar por parte.

**Exemplo:**
```json
{"name": "listar_processos", "arguments": {"busca": "Silva"}}
```

---

#### `visao_geral_processo`

Visao geral consolidada: metadados, partes, stats de documentos e movimentacoes recentes.

| Parametro | Tipo | Obrigatorio | Descricao |
|-----------|------|-------------|-----------|
| `numero_processo` | `str` | Sim | Numero CNJ (formato `NNNNNNN-DD.AAAA.J.TR.OOOO`) |

**Retorno:**
- `numero_processo`, `classe_descricao`, `sigla_tribunal`, `instancia`, `natureza`
- `valor_causa`, `data_ajuizamento`, `justica_gratuita`
- `partes` (lista completa com nomes e tipos)
- `assuntos`
- `stats_documentos` (contagens por tipo, tamanhos, periodo, breakdown por fonte)
- `documentos` (catalogo com IDs, ate 200): `[{id, nome, tipo_nome, data_juntada, tamanho_texto, fonte, confianca}]`
- `ultimas_movimentacoes` (10 mais recentes, com `fonte`)

**Quando usar:** Ponto de entrada ao investigar um processo especifico. Use apos `listar_processos`. O catalogo de documentos com IDs permite chamar `analisar(document_ids='id1,id2')` diretamente sem precisar de `ls_documentos` separado.

**Exemplo:**
```json
{"name": "visao_geral_processo", "arguments": {"numero_processo": "1234567-89.2024.8.26.0100"}}
```

---

#### `ls_documentos`

Lista documentos de um processo SEM conteudo (apenas metadados).

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `numero_processo` | `str` | - | Numero CNJ do processo |
| `limite` | `int` | `30` | Maximo por pagina (max 100) |
| `offset` | `int` | `0` | Pular N documentos |
| `ordem` | `"recentes" \| "antigos"` | `"recentes"` | Ordenacao |
| `fonte` | `str \| None` | `None` | Filtrar: `"api"` ou `"segmentation"` |

**Retorno:** Lista paginada com `id`, `tipo_nome`, `nome`, `sequencia`, `data_juntada`, `tamanho_texto`, `tamanho_bytes`, `nivel_sigilo`, `metodo_extracao`, `signatarios`, `tipo_arquivo`, `source`, `confianca`.

**Quando usar:** Obter catalogo de documentos e IDs para `read_documento` ou `localizar_no_documento`.

---

#### `ls_movimentacoes`

Lista movimentacoes (timeline) de um processo.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `numero_processo` | `str` | - | Numero CNJ do processo |
| `limite` | `int` | `30` | Maximo por pagina (max 100) |
| `offset` | `int` | `0` | Pular N movimentacoes |
| `ordem` | `"recentes" \| "antigos"` | `"recentes"` | Ordenacao |
| `fonte` | `str \| None` | `None` | Filtrar: `"api"` ou `"segmentation"` |

**Retorno:** Lista paginada com `id`, `sequencia`, `data_hora`, `tipo_nome`, `descricao` (truncada em 300 chars), `magistrado_nome`, `orgao_julgador_nome`, `documento_id`, `fonte`.

**Quando usar:** Ver historico cronologico. Para buscar termos especificos, use `grep_movimentacoes`.

---

#### `read_documento`

Le o conteudo de um documento com controle de tamanho.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `document_id` | `str` | - | UUID do documento |
| `max_chars` | `int` | `10000` | Maximo de caracteres a retornar |
| `offset` | `int` | `0` | Posicao inicial no texto |

**Retorno:**
- Metadados: `id`, `tipo_nome`, `nome`, `data_juntada`, `nivel_sigilo`, `tamanho_texto`, `metodo_extracao`, `signatarios`, `fonte`, `confianca`
- `conteudo` — trecho do texto
- `leitura` — controle de paginacao:
  - `offset` — posicao atual
  - `chars_retornados` — quantos chars vieram
  - `tamanho_total` — tamanho total do documento
  - `tem_mais` — `true` se ha mais conteudo
  - `proximo_offset` — valor para a proxima chamada

**Quando usar:** Ler conteudo textual. Use `offset` para navegar documentos longos.

**Dica:** Apos `grep_documentos` encontrar um match, use `localizar_no_documento` para obter a posicao exata e depois `read_documento(id, offset=posicao)`.

---

### Busca (4 tools)

#### `grep_documentos`

Busca conteudo nos documentos usando full-text search, regex ou ilike. Retorna snippets com highlight.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `padrao` | `str` | - | Termo de busca |
| `modo` | `"fulltext" \| "regex" \| "ilike"` | `"fulltext"` | Modo de busca |
| `numero_processo` | `str \| None` | `None` | Filtrar por processo |
| `tipo_nome` | `str \| None` | `None` | Filtrar por tipo (parcial). Ex: `"Sentenca"` |
| `fonte` | `str \| None` | `None` | Filtrar: `"api"` ou `"segmentation"` |
| `limite` | `int` | `20` | Maximo de resultados |
| `offset` | `int` | `0` | Pular N resultados |

**Modos de busca:**
- `fulltext` (default) — usa indice FTS PostgreSQL. Aceita linguagem natural: `"dano moral indenizacao"`. Rapido.
- `regex` — expressao regular: `"sentenc[aç]a"`. Mais lento.
- `ilike` — case-insensitive simples.

**Retorno:** Lista paginada com `id`, `numero_processo`, `tipo_nome`, `nome`, `relevancia`, `snippet` (com `>>>highlight<<<`), `fonte`, `confianca`.

**Quando usar:** Encontrar documentos que mencionam um termo.

---

#### `grep_movimentacoes`

Busca full-text nas movimentacoes. Retorna snippets com highlight.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `padrao` | `str` | - | Termo de busca (linguagem natural, FTS) |
| `numero_processo` | `str \| None` | `None` | Filtrar por processo |
| `tipo_nome` | `str \| None` | `None` | Filtrar por tipo (parcial) |
| `fonte` | `str \| None` | `None` | Filtrar: `"api"` ou `"segmentation"` |
| `limite` | `int` | `30` | Maximo de resultados |
| `offset` | `int` | `0` | Pular N resultados |

**Retorno:** Lista paginada com `id`, `numero_processo`, `data_hora`, `tipo_nome`, `descricao`, `magistrado_nome`, `orgao_julgador_nome`, `snippet`, `fonte`.

**Quando usar:** Encontrar movimentacoes especificas (audiencias, sentencas, citacoes).

---

#### `glob_documentos`

Filtra documentos por nome, tipo ou sigilo SEM buscar no conteudo.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `numero_processo` | `str` | - | Numero CNJ do processo |
| `padrao_nome` | `str \| None` | `None` | Nome (case-insensitive, parcial). Ex: `"peticao"` |
| `padrao_tipo` | `str \| None` | `None` | Tipo (case-insensitive, parcial). Ex: `"Sentenca"` |
| `nivel_sigilo` | `str \| None` | `None` | Sigilo exato: `"PUBLICO"`, `"SEGREDO_DE_JUSTICA"` |
| `fonte` | `str \| None` | `None` | Filtrar: `"api"` ou `"segmentation"` |
| `limite` | `int` | `50` | Maximo de resultados (max 100) |
| `offset` | `int` | `0` | Pular N resultados |

**Retorno:** Lista paginada de documentos com metadados (sem conteudo), `source`, `confianca`.

**Quando usar:** Encontrar documentos por tipo/nome sem buscar no conteudo. Mais rapido que `grep_documentos`.

**Exemplos:**
```json
{"name": "glob_documentos", "arguments": {"numero_processo": "...", "padrao_tipo": "Sentenca"}}
{"name": "glob_documentos", "arguments": {"numero_processo": "...", "padrao_tipo": "Decisao", "nivel_sigilo": "PUBLICO"}}
{"name": "glob_documentos", "arguments": {"numero_processo": "...", "fonte": "segmentation"}}
```

---

#### `localizar_no_documento`

Localiza todas as ocorrencias de um termo em um documento, com posicoes e snippets.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `document_id` | `str` | - | UUID do documento |
| `termo` | `str` | - | Termo a localizar (case-insensitive) |
| `contexto_chars` | `int` | `100` | Caracteres de contexto ao redor |
| `max_ocorrencias` | `int` | `30` | Maximo de ocorrencias |

**Retorno:** `document_id`, `termo`, `fonte`, `confianca`, `total_ocorrencias`, `tamanho_documento`, `ocorrencias` (lista com `posicao` e `snippet`).

**Quando usar:** Ponte entre `grep_documentos` (qual doc?) e `read_documento` (onde ler?). Encontre a posicao exata e use `read_documento(id, offset=posicao)`.

---

### Estatisticas (1 tool)

#### `stats_documentos`

Estatisticas agregadas dos documentos de um processo. Zero texto, apenas numeros.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `numero_processo` | `str` | - | Numero CNJ do processo |
| `fonte` | `str \| None` | `None` | Filtrar: `"api"` ou `"segmentation"` |

**Retorno:** `total_documentos`, contagens `por_tipo`, `por_sigilo`, `por_fonte` (breakdown api/segmentation), tamanhos totais, periodo dos documentos.

**Quando usar:** Panorama quantitativo antes de mergulhar nos documentos. Planejar estrategia de leitura.

---

### Analise (1 tool)

#### `analisar`

Analisa processo ou documentos especificos com LLM. Tool unica que substitui todas as analises.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `numero_processo` | `str` | - | Numero CNJ do processo |
| `pergunta` | `str` | `"Faca uma analise completa deste processo"` | Pergunta ou instrucao |
| `document_ids` | `str \| None` | `None` | IDs separados por virgula para docs especificos |
| `perspectiva` | `str \| None` | `None` | Perspectiva de analise (ver tabela) |
**Modelo:** Gemini 2.5 Flash (unico). Processos grandes usam map-reduce automatico.

**Obter IDs de documentos:** Use `visao_geral_processo()` que retorna catalogo com IDs, ou `ls_documentos()` para listagem paginada.

**Perspectivas disponiveis:**

| Perspectiva | Tipo | Uso |
|-------------|------|-----|
| `autor` | Civel | Polo ativo |
| `reu` | Civel | Polo passivo |
| `terceiro` | Civel | Terceiro interessado |
| `acusado` | Criminal | Reu criminal |
| `ministerio_publico` | Criminal | MP como acusacao |
| `defensor` | Criminal | Advogado de defesa |
| `vitima` | Criminal | Vitima ou assistente |
| `juiz` | Neutro | Perspectiva do magistrado |
| `assessor` | Neutro | Assessor juridico |

**Estrategias de contexto (automaticas):**
- `contexto_unico` — quando todos os documentos cabem em uma unica chamada
- `map_reduce` — divide em lotes de ~800K chars, resume cada lote, consolida resposta final
- `documentos_especificos` — quando `document_ids` e informado

**Retorno:** `sucesso`, `numero_processo`, `tipo_processo` (civil/criminal), `modelo`, `estrategia`, `resposta` (texto da analise).

**Exemplos de perguntas:**
```
"Qual a situacao atual? Fase, pendencias, alertas."
"Extraia todos os pedidos com tipo, valor e status."
"Analise as decisoes: fundamentacao, ratio decidendi."
"Analise de risco completa com cenarios."
"Identifique prazos proximos e urgencias."
"Elabore parecer juridico sobre viabilidade recursal."
```

**Formatos de uso:**
```json
// Analise completa
{"name": "analisar", "arguments": {"numero_processo": "..."}}

// Pergunta especifica
{"name": "analisar", "arguments": {"numero_processo": "...", "pergunta": "Qual a situacao atual?"}}

// Documentos especificos
{"name": "analisar", "arguments": {"numero_processo": "...", "document_ids": "uuid1,uuid2"}}

// Com perspectiva
{"name": "analisar", "arguments": {"numero_processo": "...", "pergunta": "Analise de risco", "perspectiva": "autor"}}

```

---

### Precedentes (1 tool)

#### `buscar_precedentes`

Busca precedentes no Banco Nacional de Precedentes (BNP/CNJ/PDPJ).

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `busca` | `str` | - | Termo de busca em linguagem natural |
| `orgaos` | `list[str] \| None` | `None` | Filtrar por orgaos (todos se omitido) |
| `tipos` | `list[str] \| None` | `None` | Filtrar por tipos de precedente (todos se omitido) |
| `pagina` | `int` | `1` | Pagina de resultados |
| `tamanho_pagina` | `int` | `20` | Resultados por pagina (max 100) |

**Orgaos disponiveis:** `STF`, `STJ`, `TST`, `TSE`, `STM`, `CSJT`, `CJF`, `TNU`, `TJAC`-`TJTO` (27 TJs), `TRF01`-`TRF06`, `TRT01`-`TRT24`.

**Tipos de precedente:**

| Codigo | Descricao |
|--------|-----------|
| `SUM` | Sumula |
| `SV` | Sumula Vinculante |
| `RG` | Repercussao Geral |
| `RR` | Recursos Repetitivos |
| `IRDR` | Incidente de Resolucao de Demandas Repetitivas |
| `IAC` | Incidente de Assuncao de Competencia |
| `CT` | Controversia Trabalhista |
| `ADI` | Acao Direta de Inconstitucionalidade |
| `ADPF` | Arguicao de Descumprimento de Preceito Fundamental |
| `OJE` | Orientacao Jurisprudencial - SDI Especial |
| `OJSDI1` | Orientacao Jurisprudencial - SDI 1 |
| `OJSDI2` | Orientacao Jurisprudencial - SDI 2 |
| `OJSDI1T` | Orientacao Jurisprudencial - SDI 1 Transitoria |

**Retorno:** Lista paginada com `id`, `orgao`, `tipo`, `nr`, `situacao`, `tese`, `ultima_atualizacao`, `processos_paradigma`, mais `agregacoes` (por tipo e orgao).

**Exemplos:**
```json
// Busca geral
{"name": "buscar_precedentes", "arguments": {"busca": "dano moral"}}

// Filtrar por tribunal e tipo
{"name": "buscar_precedentes", "arguments": {"busca": "honorarios", "orgaos": ["STJ"], "tipos": ["SUM"]}}
```

---

### Utilidades (2 tools)

#### `calculadora`

Calculos matematicos e prazos processuais (CPC/CPP).

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `modo` | `"expressao" \| "prazo"` | - | Tipo de calculo |
| `expressao` | `str \| None` | `None` | Expressao matematica (modo "expressao") |
| `data_intimacao` | `str \| None` | `None` | Data ISO 8601 (modo "prazo") |
| `dias` | `int \| None` | `None` | Dias do prazo (modo "prazo") |

**Retorno:** Resultado do calculo ou data final do prazo processual.

**Exemplos:**
```json
{"name": "calculadora", "arguments": {"modo": "expressao", "expressao": "150000 * 0.1 + 5000"}}
{"name": "calculadora", "arguments": {"modo": "prazo", "data_intimacao": "2024-03-15", "dias": 15}}
```

---

#### `data_hora_atual`

Data e hora atual com dia da semana.

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `timezone` | `str \| None` | `"America/Sao_Paulo"` | Timezone |

**Retorno:** Data/hora formatada com dia da semana.

**Quando usar:** Para calcular prazos ou contextualizar datas.

---

### Identidade (1 tool)

#### `whoami`

Retorna informacoes do usuario autenticado.

**Parametros:** Nenhum.

**Retorno:** `autenticado`, `user_id`, `email`, `role`, `scopes`, `client_id`, `token_emitido_em`, `token_expira_em`.

**Quando usar:** Verificar se a autenticacao esta funcionando e quem e o usuario.

---

## 4. Resources (2)

Resources permitem acesso direto a dados via URI, sem precisar chamar tools.

### `processo://{numero_processo}`

Retorna dados formatados de um processo.

```
URI: processo://1234567-89.2024.8.26.0100
```

### `documento://{document_id}`

Retorna conteudo formatado de um documento.

```
URI: documento://a1b2c3d4-e5f6-7890-abcd-ef1234567890
```

---

## 5. Prompts (6)

Prompts sao templates guiados que instruem o agente a executar analises complexas em multiplas etapas.

### `analise_completa`

Analise juridica profissional completa em 4 fases: exploracao, coleta seletiva, analise e avaliacao estrategica.

| Parametro | Tipo | Descricao |
|-----------|------|-----------|
| `numero_processo` | `str` | Numero do processo |

### `parecer_tecnico`

Parecer juridico tecnico formal com ementa, relatorio, fundamentacao, conclusao e recomendacoes.

| Parametro | Tipo | Descricao |
|-----------|------|-----------|
| `numero_processo` | `str` | Numero do processo |
| `questao` | `str` | Questao juridica especifica |

### `due_diligence`

Due diligence processual para tomada de decisao (acordo, M&A). Gera ficha tecnica, analise de contingencia, cenarios e recomendacao.

| Parametro | Tipo | Descricao |
|-----------|------|-----------|
| `numero_processo` | `str` | Numero do processo |

### `preparar_audiencia`

Briefing para preparacao de audiencia (conciliacao, instrucao ou julgamento).

| Parametro | Tipo | Default | Descricao |
|-----------|------|---------|-----------|
| `numero_processo` | `str` | - | Numero do processo |
| `tipo_audiencia` | `str` | `"instrucao"` | `"conciliacao"`, `"instrucao"`, `"julgamento"` |

### `estrategia_recursal`

Analise de viabilidade e estrategia recursal com recursos cabiveis, teses sugeridas e custo-beneficio.

| Parametro | Tipo | Descricao |
|-----------|------|-----------|
| `numero_processo` | `str` | Numero do processo |

### `guia_ferramentas`

Guia rapido de todas as ferramentas disponiveis (sem parametros).

---

## 6. Banco de Dados Compartilhado

O MCP, o frontend e o backend compartilham o mesmo banco Supabase. Todas as tabelas usam RLS (Row Level Security).

### Tabelas principais

#### `processos`
Metadados de processos judiciais.
- `numero_processo` (PK, formato CNJ)
- `classe_descricao`, `sigla_tribunal`, `instancia`, `natureza`
- `valor_causa` (BRL), `data_ajuizamento`
- `justica_gratuita` (bool)
- `partes` (JSONB — lista de {nome, tipo, advogados})
- `assuntos` (JSONB)

#### `documents`
Documentos dos processos.
- `id` (UUID, PK)
- `numero_processo` (FK)
- `tipo_nome`, `nome`, `sequencia`, `data_juntada`
- `conteudo` (text — conteudo textual extraido)
- `tamanho_texto`, `tamanho_bytes`
- `nivel_sigilo` (`PUBLICO`, `SEGREDO_DE_JUSTICA`, etc.)
- `metodo_extracao`, `signatarios`, `tipo_arquivo`
- `source` (varchar, NOT NULL, default `'api'`) — `'api'` ou `'segmentation'`
- `confianca` (float, nullable) — score de confianca da segmentacao

#### `movimentacoes`
Timeline de movimentacoes processuais.
- `id` (UUID, PK)
- `numero_processo` (FK)
- `sequencia`, `data_hora`
- `tipo_nome`, `descricao`
- `magistrado_nome`, `orgao_julgador_nome`
- `documento_id` (FK nullable para `documents`)
- `source` (varchar, NOT NULL, default `'api'`) — `'api'` ou `'segmentation'`

#### `jobs`
Controle de sincronizacao de processos.
- `id`, `numero_processo`, `status` (`pending`, `processing`, `completed`, `failed`)
- `created_at`, `updated_at`
- `markdown` (text — markdown completo do processo)

#### `admin_users`
Usuarios administradores do dashboard.
- `user_id` (FK para Supabase Auth)
- `email`, `created_at`

#### `llm_usage_logs` / `tool_call_logs`
Logs de uso do LLM e chamadas de tools (observabilidade).

### Views

| View | Descricao |
|------|-----------|
| `vw_processo_completo` | Processo com todos os campos (inclui `source`) |
| `vw_documentos_preview` | Documentos com preview de conteudo |
| `vw_documentos_relevantes` | Documentos prioritarios |
| `vw_stats_processo` | Estatisticas do processo |
| `vw_timeline_processo` | Timeline formatada |
| `vw_movimentacoes_recentes` | Movimentacoes recentes |

### RPCs (funcoes PostgreSQL)

| RPC | Descricao |
|-----|-----------|
| `buscar_documentos(p_termo, p_modo, p_numero_processo, p_tipo_nome, p_source, p_limite, p_offset)` | Full-text/regex/ilike search em documentos |
| `buscar_movimentacoes(p_termo, p_numero_processo, p_tipo_nome, p_source, p_limite, p_offset)` | Full-text search em movimentacoes |
| `localizar_no_documento(p_document_id, p_termo, p_contexto_chars, p_max_ocorrencias)` | Posicoes de um termo em um doc |
| `stats_documentos(p_numero_processo, p_source)` | Estatisticas agregadas com breakdown `por_fonte` |
| `buscar_processos_por_parte(p_nome)` | Busca processos por nome de parte |

---

## 7. Padrao Deep Agent

O padrao de uso recomendado para agentes segue a metafora de navegacao em filesystem:

```
descobrir -> navegar -> buscar -> analisar
```

### Diagrama de fluxo

```
listar_processos()
        |
        v
visao_geral_processo(cnj)
        |
   +---------+---------+
   |         |         |
   v         v         v
ls_docs   ls_movs   stats_docs
   |         |         |
   v         v         |
grep_docs grep_movs   |
   |                   |
   v                   |
localizar_no_doc       |
   |                   |
   v                   |
read_documento         |
   |                   |
   +-------------------+
   |
   v
analisar(cnj, pergunta, perspectiva)
   |
   v
buscar_precedentes(tema)  [complementar]
```

### Fluxo tipico passo a passo

1. **`listar_processos()`** — Descobrir processos disponiveis
2. **`visao_geral_processo("CNJ")`** — Metadados + partes + stats + **catalogo de docs com IDs** + movs recentes
3. **`analisar("CNJ", "Qual a situacao atual?")`** — Analise geral com LLM
4. **`analisar("CNJ", "Analise a sentenca", document_ids="id1")`** — Analise cirurgica de docs especificos (IDs obtidos no passo 2)
5. **`grep_documentos("dano moral", numero_processo="CNJ")`** — Buscar termos no conteudo
6. **`read_documento(id, offset=posicao)`** — Ler trecho relevante
7. **`buscar_precedentes("dano moral")`** — Complementar com jurisprudencia

### Dicas de uso

- **Comece sempre por `visao_geral_processo`** — ele retorna stats, catalogo de docs com IDs e movs recentes, dando contexto suficiente para decidir os proximos passos.
- **Use IDs do catalogo para analise cirurgica** — `visao_geral_processo` retorna IDs de documentos; passe-os para `analisar(document_ids='id1,id2')`.
- **Use `grep_documentos` antes de `read_documento`** — nao leia documentos as cegas. Busque primeiro.
- **Use `localizar_no_documento` como ponte** — apos o grep encontrar um doc, localize as posicoes exatas para ler com offset preciso.
- **Paginacao**: todas as tools de listagem suportam `limite` + `offset`. Verifique `has_more` no retorno.
- **Filtro por fonte**: se precisa apenas de dados oficiais (PJE), passe `fonte="api"`.

---

## 8. Modelo LLM

A tool `analisar` usa modelo unico: **Gemini 2.5 Flash** (`google/gemini-2.5-flash`) via OpenRouter.

| OpenRouter ID | Contexto | Custo |
|---------------|----------|-------|
| `google/gemini-2.5-flash` | ~1M tokens (~3.2M chars) | $0.15/1M input, $0.60/1M output |

### Processos grandes (map-reduce)

Quando o conteudo excede ~800K chars (~200K tokens), a estrategia **map-reduce** e usada automaticamente:
1. Divide documentos em lotes de ~800K chars
2. MAP: resume cada lote em paralelo
3. REDUCE: consolida resumos e responde a pergunta

### Eviction inteligente

Quando uma resposta de tool excede 10.000 caracteres, o sistema resume o conteudo com Gemini Flash em vez de truncar. A resposta inclui:
- `foi_resumido: true`
- `tamanho_original` e `tamanho_final`
- `nota` explicando o resumo

---

## 9. Tratamento de Erros

### Formato padrao de erro

Todas as tools retornam erros no formato `erro_instrutivo`:

```json
{
  "sucesso": false,
  "erro": "Processo nao encontrado. Verifique o numero...",
  "dica": "Use listar_processos() para ver processos disponiveis."
}
```

O campo `dica` orienta o agente sobre o proximo passo.

### Erros comuns

| Erro | Causa | Solucao |
|------|-------|---------|
| `invalid_token` | Token nao e do Supabase | Usar endpoint Supabase `/auth/v1/token` ou API key `tk_` |
| `invalid_credentials` | Email/senha incorretos | Verificar credenciais |
| `Missing session ID` | Chamou tool sem `mcp-session-id` | Capturar session ID do `initialize` |
| `Bad Request` apos initialize | Faltou `notifications/initialized` | Enviar notification antes de tools |
| Processo nao encontrado | Processo nao sincronizado | Sincronizar em `https://tecjusticamcp3.vercel.app/login` |
| Documento nao encontrado | UUID invalido | Usar `ls_documentos()` para obter IDs |
| Timeout BNP | API BNP lenta | Busca mais especifica ou reduzir `tamanho_pagina` |

### Formato de resposta paginada

Todas as tools de listagem retornam:

```json
{
  "total": 47,
  "limite": 30,
  "offset": 0,
  "has_more": true,
  "resultados": [...],
  "dica": "..."
}
```

---

## 10. Exemplos Praticos

### Cenario 1: Encontrar a sentenca de um processo

```
1. visao_geral_processo("1234567-89.2024.8.26.0100")
   -> Ver stats: "por_tipo": {"Sentenca": 2, ...}

2. glob_documentos("1234567-89.2024.8.26.0100", padrao_tipo="Sentenca")
   -> Retorna: [{id: "abc-123", tipo_nome: "Sentenca", ...}]

3. read_documento("abc-123")
   -> Le o conteudo da sentenca (primeiros 10.000 chars)

4. read_documento("abc-123", offset=10000)
   -> Continua leitura se tem_mais=true
```

### Cenario 2: Analise de risco para o autor

```
1. visao_geral_processo("1234567-89.2024.8.26.0100")
   -> Metadados, partes, stats

2. analisar("1234567-89.2024.8.26.0100",
           "Analise de risco completa com cenarios e probabilidades.",
           perspectiva="autor")
   -> Analise detalhada com cenarios melhor/provavel/pior
```

### Cenario 3: Buscar precedentes sobre um tema do processo

```
1. grep_documentos("dano moral", numero_processo="1234567-89.2024.8.26.0100")
   -> Encontra documentos que mencionam dano moral

2. buscar_precedentes("dano moral consumidor", orgaos=["STJ"], tipos=["SUM"])
   -> Sumulas do STJ sobre dano moral em relacoes de consumo
```

### Cenario 4: Comparar documentos (peticao inicial vs contestacao)

```
1. glob_documentos("1234567-89.2024.8.26.0100", padrao_tipo="Peticao")
   -> IDs das peticoes

2. analisar("1234567-89.2024.8.26.0100",
           "Compare os argumentos da inicial com a contestacao.",
           document_ids="uuid-peticao,uuid-contestacao")
   -> Comparacao lado a lado dos argumentos
```

### Cenario 5: Workflow completo via curl

```bash
TOKEN="tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"

# 1. Initialize
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","version":"1.0"}}}' \
  | grep -i 'mcp-session-id' | tr -d '\r' | awk '{print $2}')

# 2. Notification
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. 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":2,"method":"tools/call","params":{"name":"listar_processos","arguments":{}}}'

# 4. Visao geral
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":"visao_geral_processo","arguments":{"numero_processo":"1234567-89.2024.8.26.0100"}}}'

# 5. Buscar documentos
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":"grep_documentos","arguments":{"padrao":"sentenca","numero_processo":"1234567-89.2024.8.26.0100"}}}'
```

---

## 11. Construindo Agentes com LangGraph

Esta secao mostra como construir agentes que consomem as tools do TecJustica MCP — do simples ao avancado.

### 11.1 Setup — Conectando ao MCP

**Instalacao:**

```bash
pip install langchain-mcp-adapters langchain-anthropic langgraph
```

**Conexao minima:**

```python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent

async def main():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()
    agent = create_agent("anthropic:claude-sonnet-4-20250514", tools)
    result = await agent.ainvoke(
        {"messages": [{"role": "user", "content": "Liste os processos disponíveis"}]}
    )
    for msg in result["messages"]:
        print(msg.content)

asyncio.run(main())
```

> **Nota**: `transport: "http"` usa Streamable HTTP (MCP 2025-03-26). O `MultiServerMCPClient` gerencia sessoes automaticamente.

---

### 11.2 Agente ReAct Simples

O `create_agent` (LangChain v1) cria um agente ReAct que decide quais tools chamar e em que ordem.

```python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent

SYSTEM_PROMPT = """Você é um assistente jurídico especializado.

Siga o padrão Deep Agent ao consultar processos:
1. listar_processos() — descobrir processos
2. visao_geral_processo(cnj) — metadados, partes, stats
3. grep_documentos/ls_documentos — buscar/listar documentos
4. read_documento(id, offset) — ler conteúdo
5. analisar(cnj, pergunta, perspectiva) — análise com LLM
6. buscar_precedentes(tema) — jurisprudência complementar

Sempre comece por visao_geral_processo antes de ler documentos.
Use grep_documentos antes de read_documento — não leia às cegas."""

async def main():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()

    agent = create_agent(
        "anthropic:claude-sonnet-4-20250514",
        tools,
        system_prompt=SYSTEM_PROMPT,
    )

    result = await agent.ainvoke(
        {"messages": [{"role": "user", "content": "Analise o processo 1234567-89.2024.8.26.0100"}]}
    )
    # Ultima mensagem contem a resposta final
    print(result["messages"][-1].content)

asyncio.run(main())
```

> **`create_react_agent` vs `create_agent`**: O `create_react_agent` de `langgraph.prebuilt` ainda funciona mas e considerado legacy. O `create_agent` de `langchain.agents` (LangChain v1) e o padrao atual — suporta middleware, structured output e context injection nativamente.

---

### 11.3 Agente LangGraph Customizado (StateGraph)

Para controle total sobre o fluxo, use `StateGraph` diretamente.

```python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_anthropic import ChatAnthropic
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition

async def main():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()

    model = ChatAnthropic(model="claude-sonnet-4-20250514")
    model_with_tools = model.bind_tools(tools)

    # --- Nodes ---
    def classificar(state: MessagesState):
        """Classifica a consulta e decide o proximo passo."""
        system = (
            "Você é um assistente jurídico. Classifique a consulta do usuário "
            "e use as tools disponíveis seguindo o padrão Deep Agent."
        )
        messages = [{"role": "system", "content": system}] + state["messages"]
        return {"messages": [model_with_tools.invoke(messages)]}

    def sintetizar(state: MessagesState):
        """Sintetiza os resultados das tools em uma resposta final."""
        system = (
            "Sintetize os resultados das consultas anteriores em uma resposta "
            "clara e estruturada para o usuário. Use formato markdown."
        )
        messages = [{"role": "system", "content": system}] + state["messages"]
        return {"messages": [model.invoke(messages)]}

    def should_continue(state: MessagesState):
        last = state["messages"][-1]
        if hasattr(last, "tool_calls") and last.tool_calls:
            return "tools"
        return "sintetizar"

    # --- Grafo ---
    builder = StateGraph(MessagesState)
    builder.add_node("classificar", classificar)
    builder.add_node("tools", ToolNode(tools))
    builder.add_node("sintetizar", sintetizar)

    builder.add_edge(START, "classificar")
    builder.add_conditional_edges("classificar", should_continue, ["tools", "sintetizar"])
    builder.add_edge("tools", "classificar")
    builder.add_edge("sintetizar", END)

    graph = builder.compile()

    result = await graph.ainvoke(
        {"messages": [{"role": "user", "content": "Busque a sentença do processo 1234567-89.2024.8.26.0100"}]}
    )
    print(result["messages"][-1].content)

asyncio.run(main())
```

**Diagrama do grafo:**

```
        +-------------------+
        |      START        |
        +--------+----------+
                 |
                 v
        +--------+----------+
        |   classificar     |<--------+
        +--------+----------+         |
                 |                    |
          tool_calls?                 |
           /       \                  |
          v         v                 |
     +-------+  +----------+         |
     | tools |  |sintetizar|         |
     +---+---+  +----+-----+         |
         |            |               |
         +------------+               |
         |            |
         |            v
         |          END
         +-->(volta a classificar)
```

---

### 11.4 Memoria e Persistencia

Adicione `InMemorySaver` para conversas multi-turno — o agente lembra o contexto entre perguntas.

```python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

async def main():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()

    checkpointer = InMemorySaver()
    agent = create_agent(
        "anthropic:claude-sonnet-4-20250514",
        tools,
        system_prompt="Você é um assistente jurídico. Siga o padrão Deep Agent.",
    )
    # Compilar com checkpointer
    graph = agent.compile(checkpointer=checkpointer) if hasattr(agent, 'compile') else agent

    config = {"configurable": {"thread_id": "sessao-1"}}

    # Turno 1 — agente consulta o processo
    r1 = await graph.ainvoke(
        {"messages": [{"role": "user", "content": "Mostre a visão geral do processo 1234567-89.2024.8.26.0100"}]},
        config,
    )
    print(r1["messages"][-1].content)

    # Turno 2 — agente lembra o contexto do processo
    r2 = await graph.ainvoke(
        {"messages": [{"role": "user", "content": "Quais são as partes desse processo?"}]},
        config,
    )
    print(r2["messages"][-1].content)

    # Turno 3 — busca complementar no mesmo contexto
    r3 = await graph.ainvoke(
        {"messages": [{"role": "user", "content": "Busque precedentes sobre o tema principal"}]},
        config,
    )
    print(r3["messages"][-1].content)

asyncio.run(main())
```

> **Producao**: Use `PostgresSaver` ou `SqliteSaver` em vez de `InMemorySaver` para persistir entre restarts. Veja [LangGraph Persistence](https://langchain-ai.github.io/langgraph/how-tos/memory/add-memory/).

---

### 11.5 Human-in-the-Loop

Use `interrupt()` para pausar o agente antes de operacoes custosas (ex: `analisar` que chama LLM via OpenRouter).

```python
import asyncio
import uuid
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt, Command
from langchain_anthropic import ChatAnthropic
from langchain_mcp_adapters.client import MultiServerMCPClient

async def main():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()
    model = ChatAnthropic(model="claude-sonnet-4-20250514").bind_tools(tools)

    def call_model(state: MessagesState):
        return {"messages": [model.invoke(state["messages"])]}

    def review_tool_calls(state: MessagesState):
        """Pausa para revisao humana antes de executar tools."""
        last = state["messages"][-1]
        if hasattr(last, "tool_calls") and last.tool_calls:
            tool_names = [tc["name"] for tc in last.tool_calls]
            # Interrompe se vai chamar 'analisar' (operacao custosa)
            if "analisar" in tool_names:
                decision = interrupt({
                    "message": f"O agente quer chamar: {tool_names}",
                    "question": "Aprovar execução?",
                })
                if decision != "aprovar":
                    return {"messages": [{"role": "user", "content": "Operação cancelada pelo usuário."}]}
        return state

    builder = StateGraph(MessagesState)
    builder.add_node("model", call_model)
    builder.add_node("review", review_tool_calls)
    builder.add_node("tools", ToolNode(tools))

    builder.add_edge(START, "model")
    builder.add_conditional_edges("model", tools_condition, ["review", END])
    builder.add_edge("review", "tools")
    builder.add_edge("tools", "model")

    checkpointer = InMemorySaver()
    graph = builder.compile(checkpointer=checkpointer)

    config = {"configurable": {"thread_id": str(uuid.uuid4())}}

    # Invoke — vai pausar quando quiser chamar 'analisar'
    result = await graph.ainvoke(
        {"messages": [{"role": "user", "content": "Faça uma análise de risco do processo 1234567-89.2024.8.26.0100"}]},
        config,
    )

    # Aprovar e continuar
    result = await graph.ainvoke(Command(resume="aprovar"), config)
    print(result["messages"][-1].content)

asyncio.run(main())
```

---

### 11.6 Streaming

Stream tokens em tempo real para UIs responsivas.

```python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent

async def main():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()
    agent = create_agent("anthropic:claude-sonnet-4-20250514", tools)

    # --- Modo "updates": ver cada passo do agente ---
    async for event in agent.astream(
        {"messages": [{"role": "user", "content": "Quem são as partes do processo 1234567-89.2024.8.26.0100?"}]},
        stream_mode="updates",
    ):
        for node_name, update in event.items():
            print(f"[{node_name}]", update)

    print("---")

    # --- Modo "messages": tokens do LLM em tempo real ---
    async for msg, metadata in agent.astream(
        {"messages": [{"role": "user", "content": "Resuma o processo 1234567-89.2024.8.26.0100"}]},
        stream_mode="messages",
    ):
        # msg.content contem o token incremental
        if msg.content:
            print(msg.content, end="", flush=True)
    print()

asyncio.run(main())
```

**Modos de streaming disponiveis:**

| Modo | Descricao | Uso |
|------|-----------|-----|
| `values` | Estado completo apos cada super-step | Debugging |
| `updates` | Atualizacoes incrementais por node | Mostrar progresso |
| `messages` | Tokens do LLM em tempo real | Chat UI |
| `debug` | Maximo de informacao | Desenvolvimento |
| `custom` | Dados customizados emitidos pelo grafo | Metricas |

---

### 11.7 Multi-Agentes (Paralelo)

Use o padrao fan-out com `Send` para pesquisas paralelas — multiplos pesquisadores executam simultaneamente e um sintetizador combina os resultados.

```python
import asyncio
import operator
from typing import Annotated
from typing_extensions import TypedDict

from langgraph.graph import StateGraph, START, END
from langgraph.types import Send
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_anthropic import ChatAnthropic

class ResearchState(TypedDict):
    numero_processo: str
    pergunta: str
    resultados: Annotated[list[str], operator.add]
    resposta_final: str

async def main():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()
    tools_by_name = {t.name: t for t in tools}
    model = ChatAnthropic(model="claude-sonnet-4-20250514")

    # --- Pesquisadores especializados ---
    async def pesquisar_documentos(state: ResearchState):
        cnj = state["numero_processo"]
        result = await tools_by_name["grep_documentos"].ainvoke(
            {"padrao": state["pergunta"], "numero_processo": cnj}
        )
        return {"resultados": [f"[DOCUMENTOS] {result}"]}

    async def pesquisar_movimentacoes(state: ResearchState):
        cnj = state["numero_processo"]
        result = await tools_by_name["grep_movimentacoes"].ainvoke(
            {"padrao": state["pergunta"], "numero_processo": cnj}
        )
        return {"resultados": [f"[MOVIMENTAÇÕES] {result}"]}

    async def pesquisar_precedentes(state: ResearchState):
        result = await tools_by_name["buscar_precedentes"].ainvoke(
            {"busca": state["pergunta"]}
        )
        return {"resultados": [f"[PRECEDENTES] {result}"]}

    # --- Dispatcher: fan-out para pesquisadores ---
    def dispatch_research(state: ResearchState):
        return [
            Send("pesquisar_documentos", state),
            Send("pesquisar_movimentacoes", state),
            Send("pesquisar_precedentes", state),
        ]

    # --- Sintetizador: combina resultados ---
    async def sintetizar(state: ResearchState):
        prompt = (
            f"Pergunta: {state['pergunta']}\n\n"
            f"Resultados da pesquisa:\n" +
            "\n---\n".join(state["resultados"]) +
            "\n\nSintetize uma resposta completa e estruturada."
        )
        response = await model.ainvoke([{"role": "user", "content": prompt}])
        return {"resposta_final": response.content}

    # --- Grafo ---
    builder = StateGraph(ResearchState)
    builder.add_node("pesquisar_documentos", pesquisar_documentos)
    builder.add_node("pesquisar_movimentacoes", pesquisar_movimentacoes)
    builder.add_node("pesquisar_precedentes", pesquisar_precedentes)
    builder.add_node("sintetizar", sintetizar)

    builder.add_conditional_edges(START, dispatch_research,
        ["pesquisar_documentos", "pesquisar_movimentacoes", "pesquisar_precedentes"])
    builder.add_edge("pesquisar_documentos", "sintetizar")
    builder.add_edge("pesquisar_movimentacoes", "sintetizar")
    builder.add_edge("pesquisar_precedentes", "sintetizar")
    builder.add_edge("sintetizar", END)

    graph = builder.compile()

    result = await graph.ainvoke({
        "numero_processo": "1234567-89.2024.8.26.0100",
        "pergunta": "dano moral",
        "resultados": [],
        "resposta_final": "",
    })
    print(result["resposta_final"])

asyncio.run(main())
```

**Diagrama:**

```
             START
               |
     +---------+---------+
     |         |         |
     v         v         v
  pesq_docs pesq_movs pesq_prec    (paralelo)
     |         |         |
     +---------+---------+
               |
               v
          sintetizar
               |
               v
              END
```

---

### 11.8 Structured Output

Use Pydantic para respostas tipadas e validadas automaticamente.

```python
import asyncio
from pydantic import BaseModel, Field
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy

class AnaliseProcessual(BaseModel):
    """Análise estruturada de um processo judicial."""
    resumo: str = Field(description="Resumo do processo em 2-3 frases")
    fase_atual: str = Field(description="Fase processual atual")
    risco: str = Field(description="Nível de risco: baixo, medio, alto")
    valor_causa: str = Field(description="Valor da causa formatado em BRL")
    proximos_passos: list[str] = Field(description="Lista de próximos passos recomendados")
    alertas: list[str] = Field(description="Alertas e pontos de atenção", default_factory=list)

async def main():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()

    agent = create_agent(
        "anthropic:claude-sonnet-4-20250514",
        tools,
        system_prompt="Analise processos judiciais e retorne resultados estruturados.",
        response_format=ToolStrategy(AnaliseProcessual),
    )

    result = await agent.ainvoke(
        {"messages": [{"role": "user", "content": "Analise o processo 1234567-89.2024.8.26.0100"}]}
    )

    # Resposta ja vem como AnaliseProcessual
    analise = result["structured_output"]
    print(f"Fase: {analise.fase_atual}")
    print(f"Risco: {analise.risco}")
    print(f"Próximos passos: {analise.proximos_passos}")

asyncio.run(main())
```

---

### 11.9 Middleware (LangChain v1)

O `create_agent` suporta middleware para customizar o comportamento em cada etapa.

```python
from langchain.agents import create_agent
from langchain.agents.middleware import (
    SummarizationMiddleware,
    HumanInTheLoopMiddleware,
    wrap_tool_call,
)
from langchain.messages import ToolMessage

# Middleware 1: Resumir historico longo
summarization = SummarizationMiddleware(
    model="anthropic:claude-sonnet-4-20250514",
    trigger={"tokens": 50000},
)

# Middleware 2: Aprovacao humana para tools custosas
hitl = HumanInTheLoopMiddleware(
    interrupt_on={
        "analisar": {
            "description": "Análise com LLM (operação custosa). Aprovar?",
            "allowed_decisions": ["approve", "reject"],
        }
    }
)

# Middleware 3: Tratamento de erros em tools
@wrap_tool_call
def handle_errors(request, handler):
    try:
        return handler(request)
    except Exception as e:
        return ToolMessage(
            content=f"Erro na tool: {e}. Tente com parâmetros diferentes.",
            tool_call_id=request.tool_call["id"],
        )

agent = create_agent(
    "anthropic:claude-sonnet-4-20250514",
    tools,
    system_prompt="Assistente jurídico com padrão Deep Agent.",
    middleware=[summarization, hitl, handle_errors],
)
```

---

### 11.10 Receitas Prontas

#### Receita A — Assistente Juridico Conversacional

ReAct + memoria + system prompt juridico. Copie e use.

```python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver

SYSTEM_PROMPT = """Você é um assistente jurídico especializado em processos brasileiros.

Regras:
1. Sempre comece com visao_geral_processo para entender o contexto
2. Use grep_documentos antes de read_documento
3. Use buscar_precedentes para fundamentar análises
4. Responda em português, com linguagem técnica mas acessível
5. Cite documentos e movimentações específicas quando relevante"""

async def assistente():
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": "Bearer tk_0bd3797d2b16d47471b4454418f7f8e378ac79635b8316e31eb47c910fe611dd"},
            }
        }
    )
    tools = await client.get_tools()

    agent = create_agent(
        "anthropic:claude-sonnet-4-20250514",
        tools,
        system_prompt=SYSTEM_PROMPT,
    )
    checkpointer = InMemorySaver()
    graph = agent.compile(checkpointer=checkpointer) if hasattr(agent, 'compile') else agent
    config = {"configurable": {"thread_id": "chat-1"}}

    # Loop conversacional
    while True:
        pergunta = input("\nVocê: ")
        if pergunta.lower() in ("sair", "exit", "quit"):
            break
        result = await graph.ainvoke(
            {"messages": [{"role": "user", "content": pergunta}]},
            config,
        )
        print(f"\nAssistente: {result['messages'][-1].content}")

asyncio.run(assistente())
```

#### Receita B — Pipeline de Due Diligence

LangGraph customizado com pesquisa paralela + sintese estruturada.

```python
import asyncio
import operator
from typing import Annotated
from typing_extensions import TypedDict
from pydantic import BaseModel, Field

from langgraph.graph import StateGraph, START, END
from langgraph.types import Send
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_anthropic import ChatAnthropic

class DueDiligenceState(TypedDict):
    numero_processo: str
    visao_geral: str
    pesquisas: Annotated[list[str], operator.add]
    relatorio: str

class Relatorio(BaseModel):
    ficha_tecnica: str = Field(description="Dados do processo")
    risco: str = Field(description="baixo/medio/alto/critico")
    contingencia_estimada: str = Field(description="Valor estimado em BRL")
    pontos_atencao: list[str]
    recomendacao: str = Field(description="Acordo, contestar, ou recorrer")

async def due_diligence(cnj: str, api_key: str):
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": f"Bearer {api_key}"},
            }
        }
    )
    tools = await client.get_tools()
    tools_map = {t.name: t for t in tools}
    model = ChatAnthropic(model="claude-sonnet-4-20250514")

    async def coletar_visao_geral(state: DueDiligenceState):
        result = await tools_map["visao_geral_processo"].ainvoke(
            {"numero_processo": state["numero_processo"]}
        )
        return {"visao_geral": str(result)}

    async def pesq_sentencas(state: DueDiligenceState):
        r = await tools_map["glob_documentos"].ainvoke(
            {"numero_processo": state["numero_processo"], "padrao_tipo": "Sentenca"}
        )
        return {"pesquisas": [f"[SENTENÇAS] {r}"]}

    async def pesq_decisoes(state: DueDiligenceState):
        r = await tools_map["glob_documentos"].ainvoke(
            {"numero_processo": state["numero_processo"], "padrao_tipo": "Decisao"}
        )
        return {"pesquisas": [f"[DECISÕES] {r}"]}

    async def pesq_precedentes(state: DueDiligenceState):
        r = await tools_map["buscar_precedentes"].ainvoke({"busca": "dano moral consumidor"})
        return {"pesquisas": [f"[PRECEDENTES] {r}"]}

    def fan_out(state: DueDiligenceState):
        return [
            Send("pesq_sentencas", state),
            Send("pesq_decisoes", state),
            Send("pesq_precedentes", state),
        ]

    async def gerar_relatorio(state: DueDiligenceState):
        structured = model.with_structured_output(Relatorio)
        prompt = (
            f"Gere um relatório de due diligence para o processo.\n\n"
            f"Visão geral:\n{state['visao_geral']}\n\n"
            f"Pesquisas:\n" + "\n---\n".join(state["pesquisas"])
        )
        relatorio = await structured.ainvoke([{"role": "user", "content": prompt}])
        return {"relatorio": relatorio.model_dump_json(indent=2)}

    builder = StateGraph(DueDiligenceState)
    builder.add_node("coletar_visao_geral", coletar_visao_geral)
    builder.add_node("pesq_sentencas", pesq_sentencas)
    builder.add_node("pesq_decisoes", pesq_decisoes)
    builder.add_node("pesq_precedentes", pesq_precedentes)
    builder.add_node("gerar_relatorio", gerar_relatorio)

    builder.add_edge(START, "coletar_visao_geral")
    builder.add_conditional_edges("coletar_visao_geral", fan_out,
        ["pesq_sentencas", "pesq_decisoes", "pesq_precedentes"])
    builder.add_edge("pesq_sentencas", "gerar_relatorio")
    builder.add_edge("pesq_decisoes", "gerar_relatorio")
    builder.add_edge("pesq_precedentes", "gerar_relatorio")
    builder.add_edge("gerar_relatorio", END)

    graph = builder.compile()
    result = await graph.ainvoke({
        "numero_processo": cnj,
        "visao_geral": "",
        "pesquisas": [],
        "relatorio": "",
    })
    return result["relatorio"]

# Uso:
# print(asyncio.run(due_diligence("1234567-89.2024.8.26.0100", "tk_SUA_KEY")))
```

#### Receita C — Monitor de Movimentacoes

Agente que verifica movimentacoes recentes e alerta sobre eventos importantes.

```python
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient

ALERTAS = ["sentença", "decisão", "audiência", "citação", "intimação", "penhora"]

async def monitorar(cnj: str, api_key: str):
    client = MultiServerMCPClient(
        {
            "tecjustica": {
                "url": "https://mcp.tecjustica.com/mcp",
                "transport": "http",
                "headers": {"Authorization": f"Bearer {api_key}"},
            }
        }
    )
    tools = await client.get_tools()
    tools_map = {t.name: t for t in tools}

    # Buscar movimentacoes recentes
    movs = await tools_map["ls_movimentacoes"].ainvoke(
        {"numero_processo": cnj, "limite": 10, "ordem": "recentes"}
    )

    alertas_encontrados = []
    for mov in movs.get("resultados", []):
        desc = (mov.get("descricao", "") + " " + mov.get("tipo_nome", "")).lower()
        for termo in ALERTAS:
            if termo in desc:
                alertas_encontrados.append({
                    "data": mov.get("data_hora"),
                    "tipo": mov.get("tipo_nome"),
                    "descricao": mov.get("descricao", "")[:200],
                    "alerta": termo,
                })

    if alertas_encontrados:
        print(f"⚠ {len(alertas_encontrados)} alerta(s) no processo {cnj}:")
        for a in alertas_encontrados:
            print(f"  [{a['data']}] {a['tipo']}: {a['descricao'][:100]}...")
    else:
        print(f"Sem alertas relevantes no processo {cnj}")

    return alertas_encontrados

# Uso:
# asyncio.run(monitorar("1234567-89.2024.8.26.0100", "tk_SUA_KEY"))
```

---

## Formato CNJ

Todos os numeros de processo seguem o formato CNJ:

```
NNNNNNN-DD.AAAA.J.TR.OOOO

N = numero sequencial (7 digitos)
D = digito verificador (2 digitos)
A = ano do ajuizamento (4 digitos)
J = segmento da justica (1 digito)
TR = tribunal (2 digitos)
O = vara de origem (4 digitos)

Exemplo: 1234567-89.2024.8.26.0100
```

---

## Sincronizacao de Processos

Se um processo nao for encontrado, ele ainda nao foi sincronizado no sistema.

**URL de sincronizacao:** https://tecjusticamcp3.vercel.app/login

O usuario precisa acessar o dashboard para sincronizar novos processos antes de consulta-los via MCP.
