Skip to main content
Glama
leandrosflora

Consignado MCP Local

README.md
# McpServerPython

Servidor MCP local em Python para orquestrar uma jornada de **empréstimo consignado** usando tools expostas via HTTP.

O projeto funciona como um **harness local**: recebe chamadas padronizadas, valida o input, resolve contexto da jornada, executa uma tool registrada e devolve uma resposta com trace.

> Status: **POC / desenvolvimento local**. Não está endurecido para produção.

---

## Objetivo

Este repositório demonstra uma abordagem simples para expor capacidades de negócio como tools reutilizáveis em uma jornada conversacional.

O caso implementado é uma jornada de consignado com etapas como:

1. Buscar cliente por telefone.
2. Validar identidade por CPF.
3. Consultar elegibilidade.
4. Buscar ofertas de empréstimo.
5. Criar contrato.
6. Gerar link de formalização.

---

## Alterações da branch `feature/melhorias`

Esta branch mantém a natureza local/dev da POC, mas melhora alguns pontos de qualidade:

- `server.py` passou a centralizar a montagem de respostas de erro.
- `server.py` registra erro de tool em log estruturado via `log_event("tool.error", ...)`.
- `GET /healthz` agora retorna também contagem de tools registradas e sessões em memória.
- A autenticação é tratada antes da execução da tool, retornando erro padronizado.
- `IdempotencyStore` passou a usar cópia defensiva em `get` e `put`.
- `customers.py` passou a respeitar valores booleanos explícitos vindos da API, inclusive `false`.
- `eligibility.py` passou a respeitar `eligible=false` explicitamente retornado pela API.

---

## Arquitetura geral

```text
Cliente / Agente / Orquestrador
        |
        v
+-----------------------------+
| FastAPI - MCP Local Server  |
| /healthz                    |
| /tools                      |
| /call                       |
+-------------+---------------+
              |
              v
+-----------------------------+
| Registry de Tools           |
| lookup_customer_by_phone    |
| resolve_identity_by_cpf     |
| check_eligibility           |
| get_loan_offers             |
| create_contract             |
| get_formalization_link      |
+-------------+---------------+
              |
              v
+-----------------------------+
| APIs de domínio             |
| Clientes                    |
| Elegibilidade               |
| Simulação                   |
| Formalização                |
+-----------------------------+
```

---

## Stack

Principais tecnologias usadas:

- Python
- FastAPI
- Uvicorn
- Pydantic
- HTTPX
- LangChain / OpenAI, presentes nas dependências
- boto3, presente nas dependências
- SQLAlchemy, presente nas dependências

---

## Estrutura do projeto

```text
.
├── server.py
├── requirements.txt
└── mcp/
    ├── errors.py
    ├── http_clients.py
    ├── models.py
    ├── observability.py
    ├── registry.py
    ├── runtime.py
    ├── policies/
    │   ├── auth.py
    │   └── idempotency.py
    └── tools/
        ├── __init__.py
        ├── customers.py
        ├── eligibility.py
        ├── offers.py
        └── contracts.py
```

### Responsabilidades principais

| Arquivo | Responsabilidade |
|---|---|
| `server.py` | Inicializa o FastAPI, registra tools e expõe `/healthz`, `/tools` e `/call`. |
| `mcp/models.py` | Define os contratos `MCPCall`, `MCPContext`, `ToolResponse`, `ToolError` e `ToolTrace`. |
| `mcp/registry.py` | Mantém o registry global de tools via decorator `@register_tool`. |
| `mcp/runtime.py` | Define sessão, services, idempotência e contexto de execução. |
| `mcp/http_clients.py` | Implementa client HTTP com cache simples de token. |
| `mcp/policies/auth.py` | Implementa autenticação simplificada para ambiente local. |
| `mcp/policies/idempotency.py` | Implementa store de idempotência em memória com cópia defensiva. |
| `mcp/tools/*` | Implementa as tools de negócio. |

---

## Endpoints

### `GET /healthz`

Health check simples com informações úteis para debug local.

Resposta esperada:

```json
{
  "ok": true,
  "tools": 7,
  "sessions": 0
}
```

Campos:

| Campo | Descrição |
|---|---|
| `ok` | Indica que o serviço respondeu. |
| `tools` | Quantidade de tools registradas no runtime. |
| `sessions` | Quantidade de sessões em memória no processo atual. |

---

### `GET /tools`

Lista todas as tools registradas no runtime, incluindo:

- nome;
- descrição;
- schema de input;
- schema de output.

---

### `POST /call`

Executa uma tool registrada.

Formato base da requisição:

```json
{
  "tool": "nome_da_tool",
  "input": {},
  "context": {
    "correlationId": "abc123456",
    "channel": "whatsapp",
    "subject": "cliente-ou-usuario"
  }
}
```

Formato base da resposta com sucesso:

```json
{
  "ok": true,
  "output": {},
  "trace": {
    "correlationId": "abc123456",
    "tool": "nome_da_tool",
    "startedAt": 1710000000.0,
    "durationMs": 10
  }
}
```

Formato base da resposta com erro:

```json
{
  "ok": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "Mensagem do erro",
    "retriable": false,
    "details": {}
  },
  "trace": {
    "correlationId": "abc123456",
    "tool": "nome_da_tool",
    "startedAt": 1710000000.0,
    "durationMs": 10
  }
}
```

---

## Tools disponíveis

### `lookup_customer_by_phone`

Busca cliente a partir do telefone.

Input:

```json
{
  "phone": "+5511999999999"
}
```

Output:

```json
{
  "found": true,
  "customerRef": "customer-123",
  "cpfRequired": true
}
```

Observação: a branch `feature/melhorias` corrige o parsing de `cpfRequired` para respeitar `false` explícito retornado pela API.

---

### `resolve_identity_by_cpf`

Valida a identidade do cliente usando telefone e CPF.

Input:

```json
{
  "phone": "+5511999999999",
  "cpf": "12345678901"
}
```

Output:

```json
{
  "resolved": true,
  "customerRef": "customer-123",
  "cpfToken": "token-cpf"
}
```

Observação: a branch `feature/melhorias` corrige o parsing de `resolved` para respeitar `false` explícito retornado pela API.

---

### `get_identity_context`

Retorna o contexto de identidade salvo na sessão da jornada.

Input:

```json
{}
```

Output:

```json
{
  "hasIdentity": true,
  "customerRef": "customer-123",
  "cpfToken": "token-cpf"
}
```

---

### `check_eligibility`

Consulta elegibilidade do cliente para consignado.

Input:

```json
{
  "customerRef": "customer-123",
  "channel": "whatsapp"
}
```

Output elegível:

```json
{
  "eligible": true,
  "eligibilityId": "elig-123",
  "reasonCode": null
}
```

Output não elegível:

```json
{
  "eligible": false,
  "eligibilityId": null,
  "reasonCode": "NOT_ELIGIBLE"
}
```

Observação: para o canal `whatsapp`, o fluxo exige identidade previamente resolvida. A branch `feature/melhorias` corrige o parsing para respeitar `eligible=false` explicitamente retornado pela API.

---

### `get_loan_offers`

Busca ofertas de empréstimo com base no valor solicitado.

Input:

```json
{
  "requestedAmount": 5000.0
}
```

Output:

```json
{
  "offers": [
    {
      "id": "offer-123",
      "installment": 250.0,
      "termMonths": 24,
      "rateMonthly": 0.018,
      "totalCost": 6000.0
    }
  ]
}
```

---

### `create_contract`

Cria um contrato pendente a partir de uma oferta.

Input:

```json
{
  "offerId": "offer-123",
  "idempotencyKey": "journey-abc123-contract"
}
```

Output:

```json
{
  "contractId": "contract-123"
}
```

A tool usa idempotência em memória para evitar duplicidade no mesmo runtime. Na branch `feature/melhorias`, o store faz cópia defensiva na gravação e leitura.

---

### `get_formalization_link`

Gera ou busca o link de formalização do contrato.

Input:

```json
{
  "contractId": "contract-123"
}
```

Output:

```json
{
  "formalizationUrl": "https://exemplo.com/formalizacao/contract-123",
  "expiresAt": "2026-01-01T12:00:00Z"
}
```

---

## Fluxo sugerido de chamada

```text
1. lookup_customer_by_phone
2. resolve_identity_by_cpf
3. check_eligibility
4. get_loan_offers
5. create_contract
6. get_formalization_link
```

Todas as chamadas devem reutilizar o mesmo `correlationId` para preservar o contexto da jornada.

---

## Variáveis de ambiente

O projeto usa variáveis por prefixo para configurar os serviços externos.

### Clientes

```bash
CLIENTES_BASE_URL=https://localhost:7287
CLIENTES_VERIFY_SSL=false
CLIENTES_TIMEOUT=10
CLIENTES_AUTH_PATH=/api/auth/token
CLIENTES_CLIENT_ID=mcp
CLIENTES_CLIENT_SECRET=mcp-secret
```

### Elegibilidade

```bash
ELEG_BASE_URL=https://localhost:7184
ELEG_VERIFY_SSL=false
ELEG_TIMEOUT=10
ELEG_AUTH_PATH=/api/auth/token
ELEG_CLIENT_ID=mcp
ELEG_CLIENT_SECRET=mcp-secret
```

### Simulação

```bash
SIM_BASE_URL=https://localhost:7173
SIM_VERIFY_SSL=false
SIM_TIMEOUT=10
SIM_AUTH_PATH=/api/auth/token
SIM_CLIENT_ID=mcp
SIM_CLIENT_SECRET=mcp-secret
```

### Formalização

```bash
FORM_BASE_URL=https://localhost:7131
FORM_VERIFY_SSL=false
FORM_TIMEOUT=10
FORM_AUTH_PATH=/api/auth/token
FORM_CLIENT_ID=mcp
FORM_CLIENT_SECRET=mcp-secret
```

---

## Como executar localmente

### 1. Criar ambiente virtual

```bash
python -m venv .venv
```

### 2. Ativar ambiente virtual

Windows:

```bash
.venv\Scripts\activate
```

Linux/macOS:

```bash
source .venv/bin/activate
```

### 3. Instalar dependências

```bash
pip install -r requirements.txt
```

### 4. Subir o servidor

```bash
uvicorn server:app --reload
```

A API ficará disponível em:

```text
http://127.0.0.1:8000
```

Documentação automática do FastAPI:

```text
http://127.0.0.1:8000/docs
```

---

## Exemplos com cURL

### Health check

```bash
curl http://127.0.0.1:8000/healthz
```

### Listar tools

```bash
curl http://127.0.0.1:8000/tools
```

### Chamar `lookup_customer_by_phone`

```bash
curl -X POST http://127.0.0.1:8000/call \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer local-dev-token" \
  -d '{
    "tool": "lookup_customer_by_phone",
    "input": {
      "phone": "+5511999999999"
    },
    "context": {
      "correlationId": "journey-abc123",
      "channel": "whatsapp",
      "subject": "user-123"
    }
  }'
```

---

## Tratamento de erros

As tools usam erros padronizados com os seguintes campos:

```json
{
  "code": "UPSTREAM_UNAVAILABLE",
  "message": "Descrição do erro",
  "retriable": true,
  "details": {}
}
```

Códigos comuns:

| Código | Significado |
|---|---|
| `BAD_REQUEST` | Input inválido ou campo obrigatório ausente. |
| `FORBIDDEN` | Fluxo inválido ou identidade não resolvida. |
| `CONFLICT` | Inconsistência no contexto da jornada. |
| `UPSTREAM_ERROR` | Erro HTTP retornado por serviço externo. |
| `UPSTREAM_UNAVAILABLE` | Serviço externo indisponível ou falha de comunicação. |
| `UNKNOWN_TOOL` | Tool solicitada não foi registrada. |
| `VALIDATION_ERROR` | Validação Pydantic falhou. |
| `INTERNAL_ERROR` | Erro não tratado. |

Na branch `feature/melhorias`, falhas de autenticação e erros de execução das tools também passam pelo envelope padronizado de erro.

---

## Limitações atuais

Este projeto ainda tem características de desenvolvimento local:

- Autenticação simplificada: aceita Bearer token sem validação real.
- Permite execução anônima quando não há header `Authorization`.
- Sessão em memória, indexada por `correlationId`.
- Idempotência em memória.
- Sem persistência externa.
- Sem testes automatizados documentados.
- Sem Dockerfile ou pipeline de CI/CD documentado.
- Defaults locais para as APIs de domínio.

---

## Melhorias recomendadas

Antes de levar para produção, considerar:

- Validar JWT real, por exemplo Azure AD, Cognito ou outro IdP corporativo.
- Persistir sessão em Redis, DynamoDB ou store equivalente.
- Persistir idempotência fora do processo.
- Adicionar testes unitários e de contrato para cada tool.
- Adicionar logs estruturados com correlação ponta a ponta.
- Adicionar métricas de latência, erro e uso por tool.
- Implementar rate limit e políticas por canal.
- Remover defaults inseguros de ambiente.
- Criar Dockerfile e pipeline de build/deploy.
- Documentar contratos das APIs externas.

---

## Natureza do projeto

Este servidor não é uma implementação completa de plataforma MCP corporativa. Ele é uma POC objetiva para demonstrar como encapsular capacidades de negócio como tools executáveis por um agente ou orquestrador conversacional.

O foco está em clareza arquitetural, separação de responsabilidades e simulação de uma jornada bancária real.

Maintenance

ActivityInactive
ResponsivenessNo issues