Gateway#
The MAQPNA gateway (maqpna-gateway) is the component every governed call passes through. For each Model Context Protocol (MCP) tool call, agent-to-agent (A2A) call, model call and browser egress connection, it verifies identity, checks the kill switch, runs data loss prevention (DLP), evaluates policy, applies pins, taint and budgets, asks for approval when a rule says so, forwards the call with its own credential, and writes an audit record. It also serves the admin API that the CLI, console and MAQPNA Desk use.
The gateway's request path uses only the Go standard library (plus pgx for the optional PostgreSQL backend). Sandboxes can reach nothing else: their NetworkPolicy allows egress only to the gateway and DNS.
Code: cmd/maqpna-gateway, with pkg/policy, pkg/dlp, pkg/approval, pkg/audit, pkg/finops, pkg/revocation, pkg/taint, pkg/toolpin and pkg/state.
Internals#
flowchart LR
subgraph In["Callers"]
SB["Sandboxes<br/>(session token)"]
EXT["External MCP clients<br/>(IdP token)"]
ADM["CLI, console, Desk<br/>(admin token or OIDC)"]
OPR["Operator<br/>(usage reports)"]
end
subgraph GW["maqpna-gateway"]
DL["Data listener :8080<br/>/mcp, /llm, /a2a, egress proxy"]
AL["Admin listener (adminListen)<br/>/v1/*, /metrics, pprof"]
PIPE["Pipeline:<br/>auth, revocation, DLP, scope,<br/>guards, pins, policy, budgets,<br/>approvals"]
ST["Stores: approvals, revocations,<br/>taint, pins, outbox, FinOps,<br/>vault, memory"]
LED["Audit ledger + checkpoints"]
CS["Config sync: rendered ConfigMaps"]
end
SB & EXT --> DL --> PIPE
ADM --> AL
OPR --> DL
PIPE --> ST
PIPE --> LED
PIPE -- "residency-checked transport" --> UP["MCP servers, model endpoints,<br/>A2A peers, the web"]
LED --> SINK["SIEM sinks, WORM storage"]
CS --> PIPE
Responsibilities#
| Area | What the gateway does |
|---|---|
| MCP | POST/GET/DELETE /mcp/{server}: governs tools/call, filters tools/list per caller, scans every response, passes other methods through; MCP revisions 2025-03-26, 2025-06-18, 2025-11-25 and 2026-07-28 |
| Models | /llm/{route}/v1/chat/completions, /completions, /embeddings, /models: OpenAI-compatible proxy with token metering, DLP, routing and fallback |
| A2A | /a2a/{ns}/{agent} JSON-RPC and HTTP+JSON bindings, signed agent cards, delegation checks |
| Egress | A forward proxy for browser-profile sandboxes (egress.enabled), with domain policy |
| Built-in tools | maqpna-web (fetch, search), memory, maqpna-exec (code interpreter proxy), evidence and live view |
| Approvals | Holds calls, notifies, collects votes, enforces separation of duties and quorum, CIBA user approval |
| Kill switch | Revocation list from the operator plus break-glass entries |
| Evidence | Hash-chained ledger, HMAC and Ed25519 checkpoints, SIEM streaming, WORM shipping, evidence export, third-party register |
| Admin API | Approvals, audit, sessions and timeline, costs and budgets, policies, servers, revocations, taint, posture, licence |
Inputs and outputs#
| Input | Source |
|---|---|
gateway.json (-config, env MAQPNA_GATEWAY_CONFIG, default /etc/maqpna/gateway.json) |
Helm ConfigMap; decoded strictly (unknown fields rejected) |
policies.json, upstreams.json, models.json, a2a.json, revocations.json, budgets.json, memory.json, tenants.json |
ConfigMaps rendered by the operator, polled every policyReloadSeconds (2 s); with configSync read from the API every second |
| Credential files | Secrets maqpna-upstream-credentials and maqpna-model-credentials |
| Broker public keys | identity.jwksURL (refreshed every 300 s and on an unknown kid) or identity.publicKeyFiles |
| Licence | licenseFile, re-read every 30 s |
| Output | Destination |
|---|---|
| Forwarded calls | Upstreams, with X-Maqpna-Namespace, -Agent, -Session, -User, -Subject, -Groups (and optionally a Txn-Token); the agent's Authorization is never forwarded |
| Audit records | auditPath (JSONL) or the shared audit_chain, SIEM sinks, WORM bucket |
Annotations on AgentSession |
maqpna.com/last-activity, maqpna.com/taints, maqpna.com/result |
MCPServer annotation |
maqpna.com/tool-pins |
AgentRevocation objects |
breakglass-<id> mirrors of break-glass entries |
| Notifications | Webhook, Slack, Microsoft Teams, Matrix |
How a tool call is handled#
- Authenticate the bearer token (broker Ed25519 token or a trusted external IdP token), then the optional X.509-SVID binding and the tenant key binding.
- Resolve
{server}for the caller's namespace and agent (unknown_server,ambiguous_server,cross_tenant_server). - Check the kill switch (
revoked, orrevocation_list_unavailablewhen the list cannot be read). - Check MCP protocol headers and version.
- Run request DLP over the arguments (redact or deny).
- Check the scope
tools:<server>, the injection guards and the tool pin. - Evaluate policy, then the session budget and hierarchical budgets.
- On
require_approval, hold the call (synchronously, or return-32002asynchronously); re-check the kill switch after the wait. - In
auditFailurePolicy: closed, write a durable intent record first. - Forward with the gateway's own upstream credential; scan the response with DLP and the guards.
- Update taint, meter the cost, and append the audit record before releasing the result.
The full pipeline, with every function and reason, is in gateway request pipeline and a governed tool call.
Configuration#
Top-level keys of gateway.json (cmd/maqpna-gateway/config.go), with defaults:
| Key | Default | Purpose |
|---|---|---|
listen, adminListen |
:8080, — |
Data listener; optional separate admin listener (always plain HTTP) |
policyFile, policyReloadSeconds |
—, 2 | Policies and reload interval for every rendered file |
upstreamsFile, upstreamCredentialsDir, tokenExchangeURL |
— | MCP server registry and credentials |
modelsFile, models, modelPolicy |
—, —, scope-only |
Model routes; policy also runs ToolPolicy on model calls |
identity.{jwksURL, publicKeyFiles, issuer, audience, leewaySeconds, trustedIssuers, resourceBaseURL} |
audience maqpna-gateway, leeway 30 s |
Token verification and external MCP clients |
auditPath, auditCheckpointEvery, auditFailurePolicy |
—, 1000, open |
Ledger, HMAC checkpoint cadence, closed mode |
auditCheckpointKey, auditCheckpointIntervalSeconds |
—, 60 | Ed25519 signed checkpoints |
auditSinks[] |
— | SIEM streaming |
approvalTimeoutSeconds, approvalStorePath, approvalQuorum, approvalPreview |
300, —, —, redacted |
Approvals |
adminAuth.{mode, oidc, roles, namespaceRoles} |
token |
Admin API authentication and roles |
notifiers[], notifyOutboxPath, consoleURL |
— | Notifications |
ciba, tokenVault |
off | User approval and per-user tokens |
revocationsFile, revocationsJournalPath, revocationMirror |
— | Kill switch |
budgetsFile, budgetTimezone, pricingFile, sessionBudgetUSD, budgetCurrency |
—, UTC | Budgets and metering |
dlpProfiles, dlp |
— | DLP |
taintStorePath, taintRetentionSeconds, pinStorePath, pinCacheSeconds |
—, 86400, —, 60 | Taint and tool pins |
stateBackend.{type, dsnFile, schema, maxConns, pollMillis, leader, counters} |
file |
Shared state |
maxBodyBytes, upstreamTimeoutSeconds |
4 MiB, 60 | Limits |
a2a, txnTokens, tls |
— | A2A, transaction tokens, SVID binding |
web, exec, egress, liveView, evidence, guards, modelRouting, captureArgs |
off | Sandbox tools and extensions |
otel, sessions, posture, licenseFile, sovereigntyPolicyFile, configSync, activity |
— | Observability and operations |
Environment: MAQPNA_ADMIN_TOKEN (static admin and break-glass token), MAQPNA_AUDIT_HMAC_KEY (HMAC checkpoints), MAQPNA_AUDIT_SINK=worm with MAQPNA_AUDIT_WORM_* (WORM shipping), MAQPNA_JURISDICTION. Helm renders gateway.json from gateway.*, adminAuth.*, approvals.*, audit.*, dlp.*, state.*, budgets.*, delegation.* and sovereignty.*. maqpna config validate checks a file offline.
Failure modes#
| Failure | Behaviour |
|---|---|
| Policies not loaded at start | Every call denied; /readyz 503 policies not loaded |
| No broker verification keys | /readyz 503 no identity verification keys; calls fail authentication |
| Revocation list missing or invalid | Every /mcp and /llm call denied (revocation_list_unavailable); /readyz 503 |
| Invalid rendered file (policies, registry, budgets) | Previous content kept; error in GET /v1/policies or /v1/servers |
| Unknown DLP profile | Calls on that binding denied (dlp:profile_unknown) |
| Upstream unreachable or credential missing | upstream_unavailable (502) or upstream_credential_unavailable; nothing sent |
| Ledger append fails | open: logged and counted in maqpna_gateway_audit_errors_total; closed: call refused or result withheld (audit_unavailable), /readyz 503 |
| PostgreSQL unreachable | /readyz 503 state backend unreachable; approvals fail closed; reads serve the last folded state |
| Injection guard unreachable | failOpen: false denies (guard_unavailable); the default lets the call through |
| Licence missing, invalid or expired | No effect on traffic; only paid features switch off |
Scaling#
The gateway is horizontally scalable. With stateBackend.type: postgres, replicas share approvals, revocations, taint, pins, the outbox, FinOps state, the vault, memory and one audit hash chain, and elect one leader per background job (siem/<sink>, worm/<kind>, tenant-ledgers, memory-sweep, capture-sweep). Still per replica: the policy engine, maxCallsPerMinute windows, guard state and model circuits. With file state, replicas are independent and the chart refuses more than one unless you set guards.allowIndependentGatewayReplicas. See HA and failure handling.
Metrics#
Requests and decisions (maqpna_gateway_requests_total, maqpna_gateway_decisions_total{action,server}, maqpna_gateway_auth_failures_total{reason}, maqpna_gateway_upstream_latency_seconds), approvals (maqpna_gateway_approvals_pending), the kill switch (maqpna_gateway_revoked_total{scope}, maqpna_gateway_revocation_list_ok), DLP, taint, pins, budgets, model routing, audit (maqpna_gateway_audit_head_seq, _audit_errors_total, _audit_commit_seconds, _audit_sink_lag), HA (maqpna_gateway_state_backend_info, maqpna_gateway_leader{job}) and config sync. Full list in observability.
What you see#
/readyz answers 200 when ready, and otherwise 503 with the list of problems:
{"status":"not ready","problems":["revocation list unavailable (failing closed)","state backend unreachable"]}
maqpna doctor evaluates the gateway's posture checks together with cluster checks (format from cmd/internal/cliutil/posture.go; rows illustrative):
STATUS CHECK COMPONENT DETAIL
PASS gateway-ready gateway HTTP 200
WARN audit-failure-policy audit auditFailurePolicy is open
fix: set gateway.auditFailurePolicy: closed
PASS state-backend state postgres
score 92/100: 0 fail, 1 warn, 12 pass, 2 info, 0 skip, 0 accepted
See maqpna doctor, maqpna config validate and maqpna status.