How it works
From request to decision.
An agent authenticates once. After that, every request it makes goes through the same checks before anything is forwarded, and every check result is logged.
The pipeline
Seven steps, in a fixed order.
Model calls and MCP calls are checked by the same code. Only the target differs.
-
1
Authenticate the caller
The bearer token is an OIDC JWT or a personal access token (
mcp_pat_…). Tokens from unknown or disabled agents are refused, and the attempt is logged. The refusal looks the same whether or not the requested path exists. -
2
Resolve the target
The requested provider endpoint or MCP server is looked up. Unknown names get a plain 404, and so do disabled ones.
-
3
Run the access checks
Approval, assignment, policy rules, and agent grants, described below. All of them must pass; if any fails, the request is denied with a stated reason and nothing is forwarded.
-
4
Check rate limits
Requests-per-minute limits and daily budgets are counted in shared PostgreSQL tables, so they hold across replicas. A per-minute denial does not use up the daily budget.
-
5
Redact sensitive data
DLP rules redact sensitive data in arguments and prompt inputs before anything is forwarded, and the same rules apply to what the audit log stores. If the rules cannot be loaded, the request fails rather than going out unredacted.
-
6
Forward the request
The proxy authenticates to the upstream with its own credentials, so the agent's token is never forwarded. Circuit breakers protect against failing upstreams.
-
7
Record the decision
Every request, allowed or denied, is written to the audit log with a reason, the redacted payload, and the response latency.
The two endpoints
What agents connect to.
/llm/{slug}/v1
Speaks the OpenAI and Anthropic API formats as-is, so agents keep their existing SDKs. Model names are resolved through aliases and checked against an allowlist, and responses are streamed back while a copy is written to the audit log.
/mcp/{vserver}
Built on the official Go MCP SDK, served over streamable HTTP. One endpoint exposes selected tools, prompts, and resources from any number of upstream MCP servers. With strict hash pinning on, the proxy re-checks on every call that the definition still matches what was approved.
Access checks
Five checks, all of them required.
A request only goes through when it passes all five checks below, in this order. If a check cannot be evaluated, the request is denied. Rate limiting is the one exception: if the counter database is unreachable, calls proceed, because a rate limit is a capacity cap, not a security control.
The scopes a request can use are the token's scopes intersected with the agent's
allowed_scopes. Model calls run through the same policy engine, with model aliases,
allowlists, and budgets on top.
-
1
The caller is a known agent
The token belongs to a registered, enabled agent. Anything else is refused, and the attempt is logged.
-
2
The tool, prompt, or resource is approved
Approved means enabled, signed off, and unchanged: if the definition changed since approval, it is no longer served.
-
3
It is assigned to the server being called
The definition must be assigned to the MCP server the agent connected to. Unassigned definitions are not served.
-
4
A policy rule allows it
A policy rule must allow the call. Deny rules always win, allow rules can require specific scopes, and without a matching allow rule the request is denied.
-
5
The agent's own restrictions don't block it
An agent can carry per-tool or per-name deny entries. They can only restrict further; they never override the policy rules.
Rate limits & budgets
Limits that hold across replicas.
A rule combines a requests-per-minute limit with an optional daily budget, and applies either to all agents, to each agent separately, or to one specific agent. Token budgets count tokens per UTC day, using the database clock so differently-set server clocks cannot split a quota around midnight.
Counters live in PostgreSQL and are updated with single-statement upserts, so several replicas can share them
without losing or double-counting. If the database is unreachable, calls go through anyway and the failure is
logged and counted, because a rate limit should not take the service down. The agent gets a clear error
either way: a 429 with Retry-After on the LLM endpoints, the name of the exhausted
rule on MCP.
Denial reasons
Denials come with a reason.
Every decision has a fixed class: allow, policy_denied,
grant_denied, unassigned, unregistered_agent,
rate_limited, hash_mismatch, dlp_unavailable. The same class shows up
in the metrics, in the audit log, and in the error the agent receives. Rules are evaluated
order-independently and deny wins, so any decision can be reconstructed from the rules alone. The dry-run mode
runs the same code as the proxy, so a test prediction matches real behavior.