mirror of
https://github.com/mcloh/Enterprise-MCP-Service-Bus.git
synced 2026-11-05 22:14:11 +00:00
207 lines
8.7 KiB
Markdown
207 lines
8.7 KiB
Markdown
# How-To — rodando a RI
|
|
|
|
Guia prático orientado a tarefas. Para entender *o que* cada componente faz e *por que*, ver
|
|
[`AS-BUILT.md`](AS-BUILT.md). Público-alvo: quem vai **rodar** esta RI.
|
|
|
|
## Pré-requisitos
|
|
|
|
- Python 3.12+ e `pip`/`uv`.
|
|
- Opcional: binário `opa` no PATH (ou `OPA_BINARY` apontando para ele) — sem ele, os testes que
|
|
exercitam o PDP real são pulados (`pytest.skip`), não falham.
|
|
- Opcional: `docker` no PATH — sem ele, `tests/e2e/test_real_keycloak.py` e
|
|
`tests/e2e/test_security_acceptance.py` são pulados pela mesma razão.
|
|
- Opcional: credenciais de um LLM OpenAI-compatible (`LLM_BASE_URL`/`LLM_API_KEY`/`LLM_MODEL`,
|
|
ver `.env.example`) — sem elas, `tests/e2e/test_example_agent.py` e
|
|
`tests/e2e/test_multi_agent_mode_a.py` são pulados.
|
|
|
|
## Instalação
|
|
|
|
```bash
|
|
make install # cria .venv e instala o pacote em modo editável (dev extras inclusos)
|
|
```
|
|
|
|
### Instalar o OPA (sem Docker)
|
|
|
|
OPA é um binário Go estático — não precisa de Docker nem de build:
|
|
|
|
```bash
|
|
curl -sL -o /tmp/opa "https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static"
|
|
chmod +x /tmp/opa
|
|
cp /tmp/opa .venv/bin/opa
|
|
opa version
|
|
```
|
|
|
|
Versão de referência usada nesta RI: OPA v1.20.2 (Rego v1 é o padrão — `config/policies/*.rego`
|
|
usa `import rego.v1`).
|
|
|
|
## Lint, typecheck e validação de schema
|
|
|
|
```bash
|
|
make lint # ruff check + ruff format --check
|
|
make typecheck # mypy --strict
|
|
make validate-schemas # valida todo YAML de config/ contra seu modelo Pydantic
|
|
make export-schemas # regenera schemas/*.schema.json a partir dos modelos
|
|
```
|
|
|
|
## Rodando os testes
|
|
|
|
```bash
|
|
make test # == pytest --forked
|
|
```
|
|
|
|
`--forked` (pytest-forked) isola cada função de teste em seu próprio processo — necessário
|
|
porque vários pares de servidor ASGI em processo, compartilhando um único event loop entre
|
|
muitos testes sequenciais na mesma sessão pytest, produziam falhas intermitentes de limpeza
|
|
assíncrona. Com `--forked`, a suíte é 100% determinística em execuções repetidas. O custo é só
|
|
tempo de execução (cerca de +1min nesta máquina de referência). `make test`/CI usam `--forked`
|
|
por padrão.
|
|
|
|
Por suíte, o que cada uma prova:
|
|
|
|
- `pytest tests/unit --forked` — comportamento isolado de cada componente (schemas, PDP,
|
|
entitlement, authn, registry, downstream identity, approval, fabric router, audit, canonical
|
|
request, rest adapter, bypass detection, gateway cache, shared client lint, profile
|
|
intelligence, offering filter + invariantes de propriedade, orchestrator, agent runtime, PII
|
|
redaction, prompt store).
|
|
- `pytest tests/e2e --forked` — fluxos ponta a ponta contra um Gateway real em processo
|
|
(`test_walking_skeleton.py`, `test_governed_gateway.py`), Keycloak/Docker real
|
|
(`test_real_keycloak.py`), LLM real (`test_example_agent.py`, `test_multi_agent_mode_a.py`),
|
|
política de falha (`test_fail_closed.py`).
|
|
- `pytest tests/contract --forked` — o contrato input/output de cada tool, conectando direto a
|
|
cada domain server (nunca pelo Gateway) — o contrato de uma tool é propriedade do domain
|
|
server, independente de quem está ou não entitled a chamá-la.
|
|
- `pytest tests/adversarial --forked` — cenários adversariais de prompt/tool injection
|
|
(`test_prompt_injection.py`, `test_tool_poisoning.py`, `test_payload_identity_spoofing.py`) e
|
|
de protocolo/identidade (`test_protocol_identity.py`).
|
|
- `pytest tests/e2e/test_security_acceptance.py -v` — a suíte de aceite de segurança formal,
|
|
contra o `docker-compose.yml` real (`--profile core --profile identity`, Keycloak incluído).
|
|
**Não** roda com `--forked`/`make test` — exige Docker e paga o custo de build+Keycloak por
|
|
execução, por isso é rodada separadamente:
|
|
```bash
|
|
pytest tests/e2e/test_security_acceptance.py -v
|
|
```
|
|
|
|
## Subindo a stack local
|
|
|
|
### Sem Docker
|
|
|
|
```bash
|
|
source .venv/bin/activate
|
|
opa run --server --addr 127.0.0.1:8181 config/policies/ &
|
|
export SALES_DOMAIN_SERVICE_TOKEN=dev-sales-domain-service-token
|
|
export FINANCE_DOMAIN_SERVICE_TOKEN=dev-finance-domain-service-token
|
|
python3 -m example_mcp_servers.sales_domain.server &
|
|
python3 -m example_mcp_servers.finance_domain.server &
|
|
python3 -m emcp_bus.gateway.server
|
|
```
|
|
|
|
Sem `OIDC_JWKS_URL`/`OIDC_ISSUER` definidos, o Gateway roda em modo "sem IdP configurado": nega
|
|
tudo por padrão (`tools/list` vazio, `tools/call` sempre `UNAUTHENTICATED`) — ver
|
|
[`interfaces/mcp-gateway.md`](interfaces/mcp-gateway.md#dois-modos-de-deployment) para os dois
|
|
modos.
|
|
|
|
### Com Docker Compose
|
|
|
|
```bash
|
|
make up # == docker compose --profile core up --build (sem IdP)
|
|
make down
|
|
make logs
|
|
```
|
|
|
|
Perfis disponíveis (`docker-compose.yml`):
|
|
|
|
| Perfil | Serviços | O que adiciona |
|
|
|---|---|---|
|
|
| `core` | `opa`, `sales-domain`, `finance-domain`, `gateway` | PEP/PDP + os dois domínios de exemplo — mínimo funcional |
|
|
| `identity` | + `keycloak` | autenticação OIDC real |
|
|
| `observability` | + `otel-collector`, `jaeger` | tracing OTLP (UI Jaeger em `http://localhost:16686`) |
|
|
|
|
Com Keycloak real:
|
|
|
|
```bash
|
|
cp .env.example .env # descomente as 3 linhas OIDC_*
|
|
docker compose --profile core --profile identity up --build
|
|
```
|
|
|
|
Todos os perfis simultaneamente:
|
|
|
|
```bash
|
|
docker compose --profile core --profile identity --profile observability up -d --build
|
|
```
|
|
|
|
## Rodando o agente de exemplo e os modos multiagente
|
|
|
|
Requer `LLM_BASE_URL`/`LLM_API_KEY`/`LLM_MODEL` em `.env` (qualquer endpoint OpenAI-compatible;
|
|
validado contra `meta.llama-3.3-70b-instruct` via OCI Generative AI).
|
|
|
|
```bash
|
|
pytest tests/e2e/test_example_agent.py -v # agente único, tool-calling real
|
|
pytest tests/e2e/test_multi_agent_mode_a.py -v # Modo A (Global Supervisor externo, ADR-024)
|
|
pytest tests/e2e/test_real_keycloak.py -k mode_b -v # Modo B (Orchestrator como único cérebro)
|
|
```
|
|
|
|
Modo B exige Docker (sobe seu próprio Keycloak real).
|
|
|
|
## Publicando uma nova capability
|
|
|
|
1. Escreva o manifesto YAML em `config/capabilities/<domain>/<capability>.yaml`, seguindo o
|
|
schema `CapabilityManifest` (ver [`interfaces/registry-capability-manifest.md`](interfaces/registry-capability-manifest.md)
|
|
para o schema completo, e qualquer arquivo em `config/capabilities/*/` como exemplo real —
|
|
nome canônico `<domain>.<capability>`, `risk_tier`, `owner`, `entitlements`, `backend.service`
|
|
apontando para um backend já registrado em `config/backends/`).
|
|
2. Se a capability precisar de um backend novo, registre-o em `config/backends/<backend>.yaml`
|
|
(ver [`interfaces/downstream-identity.md`](interfaces/downstream-identity.md)).
|
|
3. Associe a capability a um `ClientProfile` existente ou novo em
|
|
`config/clients/profiles/<profile>.yaml`, adicionando o nome canônico a `allowed_tools`.
|
|
4. Rode `make validate-schemas` — valida o novo YAML contra seu modelo Pydantic antes de
|
|
qualquer coisa.
|
|
5. Rode `make export-schemas` se o modelo Pydantic em si mudou (não necessário para apenas
|
|
adicionar um YAML de exemplo a um schema já existente).
|
|
6. Reinicie o Gateway (ou a stack Docker) — o Registry é semeado a partir de
|
|
`config/capabilities/` no boot (`registry/seed.py`); não há hot-reload nesta RI.
|
|
|
|
## Registrando um novo MCP Client
|
|
|
|
1. Registre o client no realm Keycloak (ou equivalente OIDC) com grant `client_credentials`
|
|
habilitado e um mapper de audience para o `OIDC_AUDIENCE` do Gateway (`emcp-gateway` por
|
|
padrão) — ver `deploy/keycloak/realm-export.json` para os 3 clients de exemplo já
|
|
configurados dessa forma.
|
|
2. Crie `config/clients/<client-id>.yaml` (`ClientRegistration`: `client_id`, `profile`,
|
|
`environment`) apontando para um `ClientProfile` existente ou novo em
|
|
`config/clients/profiles/`.
|
|
3. Rode `make validate-schemas`.
|
|
4. Reinicie o Gateway — registros de client também são carregados no boot
|
|
(`load_yaml_models(config.clients_dir, ClientRegistration)`).
|
|
|
|
## Observabilidade
|
|
|
|
### Jaeger (tracing)
|
|
|
|
```bash
|
|
docker compose --profile core --profile observability up -d --build
|
|
# UI: http://localhost:16686
|
|
```
|
|
|
|
### Langfuse self-hosted (observabilidade de LLM)
|
|
|
|
```bash
|
|
docker compose -f docker-compose.yml -f deploy/langfuse/docker-compose.override.yml \
|
|
--profile llm-observability up -d
|
|
# Aguardar:
|
|
curl http://localhost:3000/api/public/health
|
|
# Criar um projeto/API key na UI (http://localhost:3000) ou pré-semear via
|
|
# LANGFUSE_INIT_PROJECT_PUBLIC_KEY/LANGFUSE_INIT_PROJECT_SECRET_KEY
|
|
# (ver deploy/langfuse/docker-compose.override.yml).
|
|
```
|
|
|
|
Depois, aponte as variáveis padrão do OTel SDK para o endpoint OTLP nativo do Langfuse (nenhuma
|
|
mudança de código):
|
|
|
|
```bash
|
|
export OTEL_EXPORTER_OTLP_ENDPOINT=http://langfuse-web:3000/api/public/otel
|
|
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <base64(public_key:secret_key)>"
|
|
```
|
|
|
|
Este perfil é pesado (6 containers: web/worker/postgres/clickhouse/redis/minio) e opt-in,
|
|
desacoplado dos perfis `core`/`identity`/`observability`.
|