Files
Enterprise-MCP-Service-Bus/RI/docs/How-To.md
2026-09-14 14:01:27 -03:00

8.7 KiB

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. 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

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:

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

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

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:
    pytest tests/e2e/test_security_acceptance.py -v
    

Subindo a stack local

Sem Docker

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 para os dois modos.

Com Docker Compose

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:

cp .env.example .env   # descomente as 3 linhas OIDC_*
docker compose --profile core --profile identity up --build

Todos os perfis simultaneamente:

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).

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 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).
  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)

docker compose --profile core --profile observability up -d --build
# UI: http://localhost:16686

Langfuse self-hosted (observabilidade de LLM)

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):

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.