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

Dry-run panel listing each governance layer with its verdict: enablement, assignment, policy, and grants, plus the matching rate-limit rule
The dry run on the Scopes & Policies page: the same checks the proxy runs, previewed layer by layer for a hypothetical request. See it in the console tour →

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.

Try it locally in a few minutes.