biatoolkit 1.3.1
Biblioteca para desenvolvedores que utilizam o BiaAgentBuilder
Requires Python: >=3.10

# Bia Toolkit (biatoolkit)

Toolkit Python para facilitar o desenvolvimento e teste de **MCP Servers (Model Context Protocol)** integrados ao **Bia Agent Builder** (AWS Bedrock AgentCore).

Este repositório entrega dois grandes blocos:

1) **SDK para MCP**
- `BiaClient`: cliente para chamar um MCP Server (ListTools / CallTool)
- `BiaUtil`: utilitário para MCP Server ler headers do runtime e parâmetros/segredos

2) **DevTools (biatoolkit.devtools)**
- `cli_validate`: validação estática do bundle (estrutura, Dockerfile, entrypoint etc.)
- `cli_smoke`: validação runtime (docker build/run + ListTools MCP)

---

## Sumário

- [Instalação](#instalação)
- [Conceitos rápidos](#conceitos-rápidos)
- [BiaClient (consumir um MCP Server)](#biaclient-consumir-um-mcp-server)
- [BiaUtil (usar dentro do MCP Server)](#biautil-usar-dentro-do-mcp-server)
- [Headers do Runtime (AgentCore)](#headers-do-runtime-agentcore)
- [Parâmetros e Segredos (env e SSM)](#parâmetros-e-segredos-env-e-ssm)
- [Integração Sankhya](#integração-sankhya)
  - [load_view (recomendado)](#load_view-recomendado)
  - [Sankhya.Call (compatibilidade / uso genérico)](#sankhyacall-compatibilidade--uso-genérico)
  - [Configurações via env](#configurações-via-env-sankhya)
- [Configurações do Toolkit (env)](#configurações-do-toolkit-env)
- [DevTools CLI](#devtools-cli)
  - [Validação estática: cli_validate](#validação-estática-cli_validate)
  - [Runtime smoke: cli_smoke](#runtime-smoke-cli_smoke)
- [Troubleshooting](#troubleshooting)
- [Licença](#licença)

---

## Instalação

```bash
pip install mcp biatoolkit
```

---

## Conceitos rápidos

### O que é um MCP Server?
Um servidor MCP expõe **tools** (funções) que podem ser listadas e executadas via protocolo MCP.

### Onde roda?
- Local: `FastMCP` + `transport="streamable-http"`
- Produção: Bia Agent Builder (AWS Bedrock AgentCore)

---

## BiaClient (consumir um MCP Server)

O `BiaClient` é um cliente assíncrono que abstrai:
- conexão streamable-http
- criação/initialize de sessão MCP
- chamadas `list_tools()` e `call_tool()`

### Criando um cliente e listando tools

```python
import asyncio
from biatoolkit.basic_client import BiaClient

async def main():
    client = BiaClient("http://127.0.0.1:8000/mcp")
    tools = await client.list_tools()

    # o retorno é o objeto retornado pelo mcp.ClientSession (list_tools)
    # normalmente contém tools com name/description/schema dependendo do server
    for t in tools.tools:
        print(t.name, "-", t.description)

asyncio.run(main())
```

### Executando uma tool (CallTool)

```python
import asyncio
from biatoolkit.basic_client import BiaClient

async def main():
    client = BiaClient("http://127.0.0.1:8000/mcp")
    result = await client.call_tool("minha_tool", {"x": 1})

    # o shape do result depende do MCP server
    print(result)

asyncio.run(main())
```

### Passando headers (simular runtime do AgentCore)

```python
from biatoolkit.basic_client import BiaClient

headers = {
  "X-Amzn-Bedrock-AgentCore-Runtime-Custom-current-host": "https://meu.erp.sankhya.com.br",
  "X-Amzn-Bedrock-AgentCore-Runtime-Custom-user-email": "user@empresa.com",
  "X-Amzn-Bedrock-AgentCore-Runtime-Custom-jsessionid": "JSESSIONID-ABC",
  "X-Amzn-Bedrock-AgentCore-Runtime-Custom-organization-id": "123",
  "Content-Type": "application/json",
}

client = BiaClient("http://127.0.0.1:8000/mcp", headers=headers)
```

> Em produção, o runtime AgentCore controla quais headers são repassados.

---

## BiaUtil (usar dentro do MCP Server)

O `BiaUtil` é usado **dentro** do MCP Server para:
- ler headers do runtime do AgentCore
- recuperar parâmetros/segredos de forma segura (`env` -> `SSM fallback`)

### Exemplo (ler header)

```python
from mcp.server.fastmcp import FastMCP
from biatoolkit.util import BiaUtil

mcp = FastMCP(host="0.0.0.0", stateless_http=True)

@mcp.tool()
def whoami() -> str:
    util = BiaUtil(mcp)
    h = util.get_header()
    return f"user_email={h.user_email} org={h.organization_id} host={h.current_host}"

if __name__ == "__main__":
    mcp.run(transport="streamable-http")
```

---

## Headers do Runtime (AgentCore)

O `BiaUtil.get_header()` retorna um objeto `Header` com os campos:

| Campo | Tipo | Origem |
|------|------|--------|
| `current_host` | str\|None | header `...-current-host` |
| `user_email` | str\|None | header `...-user-email` |
| `jwt_token` | str\|None | header `...-jwt-token` |
| `jsessionid` | str\|None | header `...-jsessionid` |
| `organization_id` | int | header `...-organization-id` (fallback 0) |
| `codparc` | int | header `...-codparc` (fallback 0) |
| `iam_user_id` | int | header `...-iam-user-id` (fallback 0) |
| `gateway_token` | str\|None | header `...-gateway-token` |

### Header prefix
O toolkit trabalha com um prefixo base (default):
- `x-amzn-bedrock-agentcore-runtime-custom`

Exemplo de header efetivo (case-insensitive em HTTP):
- `X-Amzn-Bedrock-AgentCore-Runtime-Custom-user-email`

---

## Parâmetros e Segredos (env e SSM)

O `BiaUtil.get_parameter("NOME")` resolve na ordem:

1) **Variável de ambiente**
2) **AWS SSM Parameter Store** (fallback)

### Como o SSM é resolvido
Para buscar no SSM, o toolkit usa um **prefixo** vindo do header:

- `{HEADER_PREFIX}-prefix`

Exemplo (nome efetivo do header):
- `X-Amzn-Bedrock-AgentCore-Runtime-Custom-prefix: /bia/agentbuilder/segredos`

Então o toolkit busca no SSM:
- `Name = "{prefix}/{parameter_name}"` com `WithDecryption=True`

Se o header `...-prefix` não existir, o toolkit **não consulta** SSM e retorna `None`.

---

## Integração Sankhya

A integração Sankhya vive em `biatoolkit.sankhya_call` e tem como foco:
- chamar `mge/service.sbr`
- usar `JSESSIONID` do header do runtime (quando disponível)
- suportar retries/timeouts via variáveis de ambiente

> Caminho do serviço é fixo no toolkit: `/mge/service.sbr`

### load_view (recomendado)

`load_view` é um helper para:
- `CRUDServiceProvider.loadView`

Ele monta o payload e querystring automaticamente.

```python
from mcp.server.fastmcp import FastMCP
from biatoolkit.sankhya_call import Sankhya

mcp = FastMCP(host="0.0.0.0", stateless_http=True)

@mcp.tool()
def recomendacoes() -> dict:
    sk = Sankhya(mcp=mcp)

    # base_url pode ser omitido se o header current_host existir no runtime
    return sk.load_view(
        view_name="BIA_VW_MB_RULES",
        where_sql="CODPROD_A = 123",
        fields="*",
    )
```

Parâmetros principais:
- `view_name` (obrigatório)
- `where_sql` (obrigatório)
- `fields` (opcional, default `"*"`)
- `jsessionid` (opcional; se None tenta extrair do header do runtime)
- `base_url` (opcional; se None tenta usar `current_host` do header)
- `url` (opcional override total)
- `extra_headers` (opcional)

### Sankhya.Call (compatibilidade / uso genérico)

Existe um método estático `Sankhya.Call(...)` para compatibilidade com scaffolds legados e uso genérico.

```python
from biatoolkit.sankhya_call import Sankhya

out = Sankhya.Call(
  jsessionID="...",
  payload={"serviceName": "...", "requestBody": {...}},
  base_url="https://meu.erp.sankhya.com.br",
  query="serviceName=...&outputType=json",
)
```

> Se estiver rodando dentro de MCP Server, você pode omitir `jsessionID` e passar `mcp=...` para extrair do header.

---

## Configurações via env (Sankhya)

O toolkit lê as seguintes variáveis (com defaults no código):

| Variável | Default | Descrição |
|---------|---------|----------|
| `SANKHYA_TIMEOUT_CONNECT` | `3.05` | timeout de conexão |
| `SANKHYA_TIMEOUT_READ` | `12.0` | timeout de leitura |
| `SANKHYA_RETRIES_TOTAL` | `3` | tentativas em falha |
| `SANKHYA_RETRY_BACKOFF` | `0.5` | backoff entre tentativas |
| `SANKHYA_VERIFY_SSL` | `1` | valida SSL (`1/true/yes/on`) |

---

## Configurações do Toolkit (env)

O toolkit expõe `BiaToolkitSettings.from_env()` com:

| Variável | Default | Descrição |
|---------|---------|----------|
| `BIATOOLKIT_HEADER_PREFIX` | `x-amzn-bedrock-agentcore-runtime-custom` | prefixo base de headers |
| `BIATOOLKIT_AWS_REGION` | `sa-east-1` | região para AWS SSM |
| `BIATOOLKIT_CLIENT_TIMEOUT_SECONDS` | `120` | timeout do cliente MCP |

---

## DevTools CLI

Os DevTools validam bundles de MCP Server antes do deploy no AgentCore.

Eles ficam em:
- `biatoolkit.devtools`

### Validação estática: cli_validate

Valida estrutura e consistência do bundle (fase 1).

```bash
python -m biatoolkit.devtools.cli_validate --path ./meu_bundle
```

Parâmetros:

| Flag | Obrigatório | Descrição |
|------|-------------|----------|
| `--path` | ✅ | pasta do bundle |
| `--entry-file` | ❌ | arquivo Python que inicia o server (ex: `app.py`) |
| `--entry-module` | ❌ | módulo para `python -m` (ex: `sales_agent_murilo`) |
| `--require-entry` | ❌ | exige informar `--entry-file` ou `--entry-module` |
| `--allow-hyphen-module` | ❌ | transforma nome inválido (com hífen) de ERROR para WARN |
| `--verbose` | ❌ | logs detalhados |

### Runtime smoke: cli_smoke

Executa validação runtime (fase 2):
- docker build
- docker run detached
- valida container vivo por X segundos
- healthcheck HTTP (opcional)
- MCP `ListTools` (opcional, mas recomendado)

```bash
python -m biatoolkit.devtools.cli_smoke \
  --path ./meu_bundle \
  --port 8000 \
  --mcp-path /mcp
```

Parâmetros:

| Flag | Obrigatório | Descrição |
|------|-------------|----------|
| `--path` | ✅ | pasta do bundle |
| `--port` | ❌ | porta mapeada host:container (default `8000`) |
| `--mcp-path` | ❌ | path MCP (default `/mcp`) |
| `--mcp-url` | ❌ | URL completa do MCP (override; ignora `--port/--mcp-path`) |
| `--skip-mcp-list-tools` | ❌ | pula o ListTools (debug) |
| `--health-path` | ❌ | path HTTP para healthcheck (ex: `/health`) |
| `--run-seconds` | ❌ | tempo que o container deve ficar vivo (default `8`) |
| `--build-timeout-sec` | ❌ | timeout do build (default `600`) |
| `--start-timeout-sec` | ❌ | timeout do start (default `60`) |
| `--env KEY=VALUE` | ❌ | injeta env no container (pode repetir) |
| `--tag` | ❌ | tag da imagem docker |
| `--skip-static` | ❌ | pula validação estática |
| `--keep-image` | ❌ | não remove imagem |
| `--keep-container` | ❌ | não remove container |
| `--verbose` | ❌ | logs de progresso |
| `--show-logs` | ❌ | tail de logs no sucesso |

> Próxima evolução planejada do `cli_smoke`: CallTool automático (Item 4).

---

## Troubleshooting

### 1) Docker daemon indisponível
- Garanta que Docker está instalado e rodando.
- O smoke test detecta e reporta esse cenário.

### 2) Container sobe e morre rápido
- Verifique entrypoint no Dockerfile.
- Rode com `--keep-container` e `--show-logs` para diagnóstico.

### 3) MCP ListTools falha (406 / handshake)
- Use o `BiaClient` (já é o padrão do smoke).
- Confirme o endpoint e o path (`--mcp-url` / `--mcp-path`).

### 4) SSM não retorna segredo
- Confirme se o header `...-prefix` existe no runtime.
- Confirme a região AWS (`BIATOOLKIT_AWS_REGION`).
- Confirme permissão IAM para `ssm:GetParameter`.

---

## Licença

Defina aqui o modelo de licença (ex: MIT / Apache-2.0 / Proprietária).