Centralized Auth
Every upstream tool provider an AI agent talks to, whether an internal MCP server, a partner API, or a SaaS integration, tends to bring its own auth scheme: a bearer token here, an API key there, a client credentials exchange somewhere else. Without a gateway, that logic gets duplicated into every place a tool call could originate, or worse, left inconsistent between them.
The Problem
Auth enforced independently per upstream doesn't scale as a security posture. Every new MCP server or API added to an agent's toolset is a new place where a token could be checked incorrectly, skipped, or checked against the wrong issuer. Auditing "who can call what" means reading through N separate implementations instead of one policy. Rotating a credential or tightening a scope means touching every place that credential was checked.
How Zilla Solves It
Zilla enforces auth once, at the gateway, before any tool call is routed to an upstream server or reaches the agent. The policy lives in one place regardless of how many mcp · client or mcp-http · proxy exits sit behind the mcp · proxy router. An upstream never sees a request that hasn't already been authorized.
The agent's session is authenticated once on the mcp · server binding, through its own options.authorization field, using the open-source jwt guard's JWT Bearer flow. The credential check is one guard configuration, not logic duplicated into every tool call.
Architecture
The guard sits in front of the binding pipeline. A request that fails authorization never reaches mcp · proxy, so routing logic and upstream servers stay unaware of auth entirely.
One Policy, Every Upstream
Adding a tenth mcp · client or mcp-http · proxy exit doesn't mean writing a tenth auth check. The same guard configuration protects every route out of mcp · server, whether the upstream is a real MCP server or a plain REST API fronted by mcp-http · proxy. Changing the policy, for example rotating the JWKS endpoint or tightening the required audience, is a single config change that applies to every upstream at once.
Toolkit Access Is Not Tool Access
routes[].guarded can gate an entire toolkit, a specific tool within it, or both at once, layered. A toolkit-level route requires the roles that let a caller reach the toolkit at all; a second, tool-specific route layered underneath it can require additional roles for one sensitive tool, such as a write operation, while every other tool in the toolkit only needs the toolkit-level roles:
routes:
- exit: github_http_proxy
when:
- toolkit: github
guarded:
authn_jwt:
- github:tools
- exit: github_http_proxy
when:
- toolkit: github
tools:
- create_pr
guarded:
authn_jwt:
- github:tools
- github:pr:writeA caller authorized only for github:tools sees every other github tool but not create_pr; a caller with both roles sees all of them. mcp-openapi · client reaches the same outcome through the OpenAPI document's own security requirement instead of a second guarded: route, mapped to a guard via options.specs.<name>.security; see OpenAPI Specs as Tools.
Either way, the observable behavior is the same: a tool or toolkit a caller isn't authorized for is absent from tools/list, not listed and then rejected on call. There's no "tool present but access denied" state.
Centralized Auth State
Auth state, JWKS keys, is held in a store, in-memory for a single AI Gateway instance. Because that state lives in the store rather than in each request handler, the policy behaves identically for every request the instance handles.
Try It
Full guard configuration is covered in Bearer Auth and JWT Bearer.
Try the example
Walk through Centralized Auth for the runnable steps: one jwt guard protecting two independent toolkits, with no per-toolkit auth code.

