Nervora
Nervora
Secure MCP Gateway for Enterprise AI Tool Execution Internal R&D Reference Architecture
A production-grade reference implementation showing how enterprise AI agents can call real business tools safely, through a governed FastAPI / MCP-style integration layer. It is deliberately not a chatbot demo — it is a demonstration of the control plane that has to sit between an autonomous agent and your systems of record: authentication, tool-level RBAC, audit logging, PII redaction, dry-run for destructive actions, async execution with idempotency, retry/DLQ handling, Databricks integration, OpenTelemetry tracing, and Azure-deployment readiness.
Status: Internal R&D reference architecture. Mock-first by design (no real production systems are touched). Suitable for public GitHub and as a DACH-facing technical trust signal on inovativi.com.

What this project proves
An AI agent can be given capabilities, not credentials. It calls named, declared tools; it never holds a database connection or a Databricks token.
Every tool call is authenticated, authorised against tool-level RBAC, PII-redacted, and audited — including the calls that are denied.
Destructive actions are deny-by-default: they require an explicit human-approved token and are hard-disabled in demo mode.
Long-running work is decoupled onto a queue with an idempotency key, a worker, retries and a dead-letter path — agents cannot run long jobs synchronously, and duplicate submissions cannot double-execute.
The whole thing is observable (OpenTelemetry spans around every stage, trace ids echoed into responses and audit rows) and portable (one abstraction for the queue, one for Databricks — local mock today, Azure Service Bus / Databricks tomorrow with a config change).
What this project deliberately does not do
It does not ship a model or an agent reasoning loop. The "agent" is a thin client; the value here is the gateway, not the LLM.
It does not connect to real HR/finance/CRM systems. Tool data is synthetic.
The Databricks and Azure Service Bus connectors run as mocks/local backends by default; the real implementations are present as prepared interfaces, not exercised in the demo.
It is not a turnkey production deployment. The IaC under
infra/is reference material — networking, secret management (Key Vault) and identity hardening are called out but not fully implemented.
See docs/judgment-block.md for the explicit list of things agents are not allowed to do.
Architecture
┌──────────────────────────────────────────────┐
AI Agent ──Bearer──▶ │ Nervora MCP Gateway (FastAPI) │
(demo-agent) │ │
│ auth ─▶ RBAC ─▶ args ─▶ route ─▶ PII ─▶ audit │
└───┬───────────────┬───────────────┬───────────┘
│ sync │ async │ writes
▼ ▼ ▼
Databricks (mock) │ Service Bus │ PostgreSQL
SQL + Jobs API │ (local | azure) │ audit_events
│ │ │ tool_calls
│ ▼ │ tool_policies
│ Worker ────────┤ async_jobs
│ idempotency │ approvals
│ retry / DLQ │ idempotency_keys
└──────┬───────────┘
▼
OpenTelemetry ─▶ Collector ─▶ GrafanaFull detail: docs/architecture.md.
Repository layout
apps/
mcp-gateway/ FastAPI gateway: the governed execution pipeline + API
worker/ Async job worker: idempotency, retry, dead-letter
demo-agent/ CLI client that mints dev tokens and runs the scripted demo
admin-web/ Static, read-only operator console (tools, queue, audit)
packages/
common/ Settings + id/hash helpers shared by every service
auth/ OIDC/JWT abstraction (dev HS256 + Entra ID RS256/JWKS)
rbac/ Roles + deny-by-default tool-level policy evaluation
audit/ SQLAlchemy schema + the only sanctioned audit writer
pii/ Field-level + pattern-based redaction
telemetry/ OpenTelemetry tracer setup + span helpers
tool_registry/ ToolSpec metadata + the reference tools (incl. demo-flow tools)
databricks_connector/ Mock + prepared-real SQL / Jobs connector
servicebus/ Queue abstraction: local Postgres backend + Azure Service Bus
infra/
docker-compose.yml Local stack (postgres, gateway, worker, otel, grafana, web)
terraform/ | bicep/ Azure reference deployment
grafana/ Dashboard + provisioning
docs/ Architecture, security model, RBAC matrix, demo script…
tests/ Pytest suite (RBAC, PII, tools, async/idempotency, API)Security flow
Every call to POST /tools/{tool_name}/invoke runs this fixed pipeline
(each stage is its own OpenTelemetry span):
Authenticate — validate the bearer token (dev HS256 locally, Entra ID JWKS/RS256 in production) →
Principal{subject, agent_id, role}.Authorise (RBAC) — deny-by-default; the role must be explicitly listed in the tool's
required_roles. Admin is not a wildcard. Denials are logged.Validate — arguments are validated against the tool's pydantic schema.
Route by classification / execution mode:
destructive → deny unless tool enabled and a valid approval token and an approved approval record exist;
dry-run-required → execute the read-only diff, create a
pendingapproval, return "human approval required";async → reserve idempotency key, create job, publish to the queue (202);
sync read/write → execute.
Redact PII — sensitive fields are masked before the output is model-visible, unless policy + role allow raw access. A regex sweep catches leaks in free-text.
Audit — one
tool_callsrow + oneaudit_eventsrow are written for every outcome, with trace id, input hash, decision, redaction status, error code and latency.
Details: docs/security-model.md.
RBAC matrix
Tool | Class | Mode | HR | Finance | Sales | Admin |
| read (PII) | sync | ✅ | – | – | ✅ |
| read | sync | ✅ | – | – | ✅ |
| read | sync | – | ✅ | – | ✅ |
| read | sync | – | ✅ | – | ✅ |
| write | async | – | ✅ | – | ✅ |
| write (dry-run) | sync | – | – | ✅ | ✅ |
| destructive | sync | – | – | – | ✅* |
| read (PII) | sync | – | – | ✅ | ✅ |
| write (approval) | sync | – | ✅ | – | ✅ |
* disabled in demo mode and gated by an approval token even when enabled.
Full matrix incl. PII class and dry-run flags: docs/rbac-matrix.md.
Local setup
Requirements: Docker + Docker Compose. (For running tests/agents directly:
Python 3.12 recommended — make install then make test. If your default
python3 is a different version, point the Makefile at a 3.12 interpreter, e.g.
make test PY=.venv/bin/python.)
cp .env.example .env
# Full stack: gateway, worker, postgres, otel-collector, grafana, admin-web
make up
# or: docker compose -f infra/docker-compose.yml up --buildThen:
API docs (Swagger): http://localhost:8000/docs
Health: http://localhost:8000/health · AI readiness: http://localhost:8000/health/ai
Admin console: http://localhost:8080
Grafana: http://localhost:3000 (anonymous admin)
Run the scripted demo against the running stack:
make demo # or: GATEWAY_URL=http://localhost:8000 python apps/demo-agent/agent.py demo
make token ROLE=finance_agent # mint a dev bearer token for manual curl/SwaggerRun the tests (SQLite-backed, no Docker needed):
make install # editable install incl. dev deps
make test # RBAC, PII, tools, async/idempotency, HTTP API, demo flow
make lintNervora demo flow
For a buyer-facing, 5-minute technical walkthrough (with example requests, denied calls, the approval gate, audit output and a Mermaid diagram), see docs/demo-walkthrough.md.
The shortest path that shows the control plane doing its job, using the two
Phase 1 demo tools. Mint tokens with make token ROLE=..., then:
AI asks for a customer →
POST /tools/crm.lookup_customer/invokeassales_agent→ RBAC allows it → executes → contact PII (email,phone) is redacted in the result → an audit row is written.Wrong role is blocked → the same call as
finance_agent→ RBAC denies it (403,error_code: role_not_permitted) → the denial is also audited.AI drafts an invoice →
POST /tools/billing.create_invoice_draft/invokeasfinance_agent→ the approval gate engages: nothing is written, the response isdecision: dry_runwithhuman_approval_required: trueand a pendingapproval_id.A human approves →
POST /approvals/{approval_id}/approveasadmin_agent(only admin may approve) → the approval flips toapproved.Status is visible →
GET /health/aishows backing-service modes and the full tool surface: which tools are registered, enabled, and approval-gated.
Every step returns a deterministic JSON envelope (decision, result,
approval_id, error_code, trace_id) and is recorded in the audit trail —
including the denied call. Covered end-to-end by tests/test_demo_flow.py.
Demo script (5–7 min)
A finance agent runs an allowed report, triggers an async Databricks workflow (queued → worker → succeeded), a duplicate submission is de-duplicated by idempotency key, a sales agent is denied HR access and blocked from a destructive write, and the full audit trail is shown. Step-by-step narration in docs/demo-script.md.
Azure deployment notes
Production swaps three things via configuration, no code changes:
AUTH_MODE=entra→ tokens validated against Entra ID tenant JWKS (RS256).QUEUE_BACKEND=azure→ Azure Service Bus (native peek-lock + DLQ).DATABRICKS_MODE=real→ real SQL Statement Execution + Jobs APIs.
Container Apps + PostgreSQL Flexible Server + Service Bus + Application Insights
are provisioned by infra/terraform or infra/bicep. See
docs/azure-deployment.md.
Observability
OpenTelemetry spans wrap auth, RBAC, PII redaction, tool execution, queue
publish, worker execution, the Databricks call and every audit write. The trace
id is returned in the X-Trace-Id response header and stored on every audit
row, so a single id ties an API response to its spans and its audit records.
Grafana dashboard JSON and setup notes: docs/observability.md.
Screenshots checklist
What to capture for the portfolio / inovativi.com write-up: docs/screenshots.md. Website-ready case-study copy: docs/inovativi-case-study.md.
Honest limitations
Mock-first: Databricks and Service Bus run as local/mock backends by default; the real connectors are prepared interfaces, not battle-tested.
The local queue is a faithful but simple Postgres-backed stand-in, not a high-throughput broker.
Dev tokens use HS256 with a shared secret for local convenience; this path is never used in production (Entra ID JWKS is).
PII redaction is policy- + regex-based; it is defence-in-depth, not a guarantee against every possible leak vector.
IaC is reference-grade; production networking/secret/identity hardening is documented but not fully implemented.
Roadmap
Phase 2 plan (builds on the Phase 1 controlled execution layer): docs/phase-2.md. Highlights:
Real Databricks connector implementation + integration tests behind a flag.
KEDA queue-length autoscaling for the worker on Container Apps.
Native MCP transport (stdio / streamable HTTP) in front of the tool registry.
Policy-as-code (OPA/Rego) alternative to the in-code RBAC matrix.
Approval workflow UI + signed, expiring approval tokens.
Tempo/Jaeger trace backend wired into the Grafana dashboard out of the box.
License
Apache-2.0. Reference/educational use. Synthetic data only.