Skip to main content
Agentgateway uses CEL (Common Expression Language) throughout the request processing pipeline. CEL expressions give you fine-grained, programmable control over authorization, header transformations, rate limiting selectors, and log/trace fields — all evaluated at runtime without restarting the proxy.

What is CEL?

CEL is a fast, safe expression language designed for evaluating user-defined conditions at runtime. Unlike Lua or WASM, CEL is sandboxed and cannot perform I/O or side effects — this makes it predictable, safe to use in a high-performance proxy, and straightforward to reason about. A simple MCP authorization expression:
jwt.sub == "test-user" && mcp.tool.name == "add"

Where CEL is used

Use caseExample
Authorization policiesjwt.sub == "alice" && mcp.tool.name != "delete"
Log and trace field extractionrequest.headers["user-agent"]
HTTP header and body transformations"Bearer " + jwt.sub
Rate limit selectorssource.address
Tracing sampling0.1 (10% random sampling)

How expressions are evaluated

Expressions are compiled at configuration load time (not per request). During compilation, Agentgateway extracts which context variables the expression references. At request time, only the referenced data is collected — you only pay for what you use.
Config load → CEL expression parsed → referenced variables extracted


Request arrives → ContextBuilder collects required fields → CEL evaluates expression
This is especially important for expensive fields:
  • request.body — causes the body to be buffered in memory (expensive)
  • request.headers — header map allocation (moderate cost)
  • llm.prompt / llm.completion — large string copies for LLM traffic
Accessing request.body or response.body in a CEL expression causes the full body to be buffered in memory before the expression is evaluated. Avoid this for large payloads unless necessary.
Variable extraction uses static analysis. It handles request.body correctly, but not dynamic access patterns like request["body"]. Stick to dot notation for reliable optimization.

Context reference

The following variables are available in CEL expressions. Availability depends on where in the pipeline the expression runs — for example, response is not available in request-phase transformations.

request — incoming HTTP request

The request object contains attributes about the incoming HTTP request.
FieldTypeDescription
request.methodstringHTTP method, e.g. GET
request.uristringComplete URI, e.g. http://example.com/path
request.hoststringHostname, e.g. example.com
request.schemestringScheme, e.g. https
request.pathstringPath component, e.g. /path
request.pathAndQuerystringPath and query, e.g. /path?foo=bar
request.versionstringHTTP version, e.g. HTTP/1.1
request.headersmapRequest headers (access by name)
request.bodystringRequest body (buffers entire body)
request.startTimetimestampTime the request started
request.endTimetimestampTime the request completed
Examples:
# Allow only GET and POST
request.method in ["GET", "POST"]

# Match a specific path prefix
request.path.startsWith("/api/v2")

# Check a custom header
request.headers["x-tenant-id"] == "acme"

# Use in a log field
request.headers["user-agent"]
The response object is available in response-phase expressions (e.g., response transformations, access logs).
FieldTypeDescription
response.codeintHTTP status code
response.headersmapResponse headers
response.bodystringResponse body (buffers entire body)
Examples:
# Log only error responses
response.code >= 400

# Check a response header
response.headers["content-type"].contains("application/json")
The jwt object contains claims from a verified JWT token. It is only present when the JWT authentication policy is enabled and a valid token is provided.
FieldTypeDescription
jwt.substringSubject claim
jwt.issstringIssuer claim
jwt.audlistAudience claim
jwt.<claim>anyAny custom claim from the token
Examples:
# Allow a specific user
jwt.sub == "alice@example.com"

# Check a custom role claim
jwt.roles.contains("admin")

# Require a specific issuer
jwt.iss == "https://auth.example.com"

# Combine user and tool checks (MCP)
jwt.sub == "test-user" && mcp.tool.name == "add"
jwt is only available after the JWT policy has verified the token. If authentication fails, the request is rejected before authorization expressions run.
The mcp object contains attributes about an MCP request. It is available when the route handles MCP traffic.
FieldTypeDescription
mcp.tool.namestringName of the MCP tool being called
mcp.tool.targetstringTarget server for the tool call
mcp.resource.namestringName of the MCP resource being accessed
mcp.resource.targetstringTarget server for the resource
mcp.prompt.namestringName of the MCP prompt being requested
mcp.prompt.targetstringTarget server for the prompt
Examples:
# Allow access to a specific tool only
mcp.tool.name == "get_weather"

# Block a dangerous tool
mcp.tool.name != "delete_all"

# Allow only reads (tools starting with "get" or "list")
mcp.tool.name.startsWith("get") || mcp.tool.name.startsWith("list")

# Restrict a user to specific tools
jwt.sub == "readonly-bot" && mcp.tool.name in ["search", "get_item"]

# Check which backend is serving the request
mcp.tool.target == "production-server"
The llm object contains attributes about an LLM request or response. It is only present when using an ai backend.
FieldTypeDescription
llm.streamingboolWhether the response is streamed
llm.requestModelstringModel requested by the client
llm.responseModelstringModel that served the response
llm.providerstringLLM provider name
llm.inputTokensintInput/prompt token count
llm.outputTokensintOutput/completion token count
llm.totalTokensintTotal token count
llm.cachedInputTokensintTokens read from cache (savings)
llm.reasoningTokensintReasoning tokens in output
llm.promptlistPrompt messages (has performance impact)
llm.completionstringCompletion text (has performance impact)
llm.params.temperaturefloatTemperature parameter
llm.params.max_tokensintMax tokens parameter
Examples:
# Log total token usage
llm.totalTokens

# Alert on large requests
llm.inputTokens > 10000

# Rate limit by model
llm.requestModel == "gpt-4"

# Block non-streaming requests to a specific model
llm.requestModel == "claude-3" && !llm.streaming
The source object contains attributes about the downstream connection.
FieldTypeDescription
source.addressstringIP address of the downstream connection
source.portintPort of the downstream connection
source.identityobjectSPIFFE identity (if mTLS is enabled)
source.identity.trustDomainstringSPIFFE trust domain
source.identity.namespacestringKubernetes namespace
source.identity.serviceAccountstringKubernetes service account
source.subjectAltNameslistSANs from the downstream certificate
source.issuerstringIssuer from the downstream certificate
source.subjectstringSubject from the downstream certificate
Examples:
# Allow only from a specific IP range (use for rate limiting key)
source.address

# Require a specific Kubernetes service account (mTLS)
source.identity.serviceAccount == "my-agent"

# Restrict to a specific namespace
source.identity.namespace == "production"
The backend object contains information about the backend selected for the request.
FieldTypeDescription
backend.namestringBackend name, e.g. my-service
backend.typestringBackend type: ai, mcp, static, dynamic, service
backend.protocolstringBackend protocol: http, tcp, a2a, mcp, llm
Examples:
# Log the backend name (useful in access logs)
backend.name

# Different behavior based on backend type
backend.type == "mcp"
These objects are present when the corresponding authentication policy is enabled and credentials have been verified.
FieldTypeDescription
apiKey.keystringThe verified API key value
basicAuth.usernamestringThe verified username
Examples:
# Restrict to a specific API key holder
apiKey.key == "internal-service-key"

# Use the username in an authorization rule
basicAuth.username == "admin"
The env object exposes a curated subset of well-known environment attributes. It does not expose raw process environment variables.
FieldTypeDescription
env.podNamestringKubernetes pod name
env.namespacestringKubernetes namespace
env.gatewaystringGateway name (Kubernetes)
Examples:
# Include pod name in logs
env.podName

# Namespace-aware routing logic
env.namespace == "staging"

Authorization policy examples

CEL expressions are most commonly used in authorization (authz) policies. Here are practical examples:
routes:
- policies:
    auth:
      jwt:
        issuer: https://auth.example.com
    authz:
      rules:
      # Only allow verified users to call the "add" tool
      - expression: 'jwt.sub == "test-user" && mcp.tool.name == "add"'
  backends:
  - mcp:
      targets:
      - name: calculator
        stdio:
          cmd: npx
          args: ["mcp-server-calculator"]

Authorization guide

Complete guide to setting up CEL-based authorization policies

MCP proxy guide

Proxy MCP servers with tool-level access control