Skip to content

examples: agentgateway in front of the mesh, with a script that checks each case - #802

Merged
aojea merged 7 commits into
google:mainfrom
HosniBelfeki:examples/agentgateway
Oct 22, 2026
Merged

aojea merged 7 commits into
google:mainfrom
HosniBelfeki:examples/agentgateway

Conversation

@HosniBelfeki

Copy link
Copy Markdown
Contributor

Closes #801. Part of #734, the agentgateway checkbox.

What

development/examples/agentgateway/ puts agentgateway v1.6.0 (standalone) in front of a shared agentmesh-node, in Docker Compose, with Keycloak 26.4.0 as the identity provider. agentgateway validates each caller's access token (jwtAuth, strict), exchanges it at the node's POST /oauth/token with its built-in RFC 8693 oauthTokenExchange, and calls the node's /mesh/ proxy with the exchanged credential in X-Mesh-Authentication. run.sh brings it up in order, runs each case of #801 and exits non-zero if any result differs:

== Calling mcp://calc through agentgateway
  PASS  alice (calc-user through group:devs)                               200
  PASS  bob (no calc-user), refused by the provider                        403

== Refused at agentgateway
  PASS  no token                                                           401
  PASS  not a token                                                        401
  PASS  alice, token for client other-app (audience)                       401
  PASS  alice, token from realm elsewhere (issuer)                         401

== What a backend behind agentgateway receives
  PASS  the exchanged credential in x-mesh-authentication                  present
  PASS  the caller's token in authorization                                absent

== A token through its expiry (60s lifetime in this realm)
  PASS  alice, before exp                                                  200
  PASS  alice, 20s after exp: accepted by agentgateway, refused at the exchange 400
  PASS  alice, 80s after exp: refused by agentgateway                      401
  PASS  alice, a new token                                                 200

agentgateway holds no mesh credential and no client secret: the node exchanges a JWT subject token without client authentication, and the control plane verifies the token's issuer and audience. jwtAuth removes the caller's token from the request, and a header echo behind the same policies shows that a backend receives only the exchanged credential.

A limitation, stated in the guide

agentgateway accepts a token for 60 seconds after its exp (the default leeway of the jsonwebtoken library; v1.6.0 has no setting for it), and the control plane allows none. In that minute the exchange is refused and agentgateway answers 400 invalid request, which a client that refreshes on 401 does not treat as a reason to refresh. The script checks both sides of it.

Guide

site/content/docs/guides/agentgateway.md, linked from the guides index, walks through the configuration step by step, lists where each refusal is logged, and what to change for a real deployment. The example's README points to it, as for the Keycloak and OpenShell examples.

Tested

  • ./run.sh passes all 16 checks from a clean state with the published images pinned in compose.yaml (ghcr.io/google/agentmesh-*:8de8bd76…, ghcr.io/agentgateway/agentgateway:v1.6.0).
  • Run on Windows 11 with Docker Desktop and Git Bash. I have not run it on a Linux or macOS host.
  • agentgateway --validate-only accepts agentgateway.yaml; shellcheck v0.10.0 reports nothing on run.sh.
  • The site builds with Hugo 0.147.5 extended, as in deploy-github-pages.yml; the guide renders, is listed in the guides index, and its links resolve.

The realm mesh has the agentmesh client with a groups claim and an
audience mapper, so the access tokens an agent sends to agentgateway
carry aud agentmesh; alice is in group devs and bob in group ops. The
realm elsewhere is a second issuer the control plane does not trust.
Tokens live 60 seconds, so the script can check expiry quickly.
Nodes may publish mcp://*, and calc-user, granting mcp://calc, is bound
to group:devs only. The provider node publishes the calculator of
../calc-mcp as mcp://calc.
…t a node

jwtAuth (strict) validates the caller's Keycloak access token and
removes it from the request. The backend's oauthTokenExchange sends the
validated token (jwt.rawToken.unredacted()) to the node's /oauth/token
as an RFC 8693 subject token, without client authentication, and puts
the exchanged credential in X-Mesh-Authentication for the node's /mesh/
proxy. A second route applies the same policies in front of a header
echo.
Keycloak, the control plane, one router, the calc provider, the shared
node, agentgateway v1.6.0 and a server that answers with the headers it
received. agentgateway starts once Keycloak is healthy, since it fetches
the realm's keys at startup. The agentmesh images are pinned to the
commit the example was tested with and can be overridden; tokens are
read from files under .run/, which is ignored.
run.sh brings the compose services up in order, then runs each case
and prints whether it got the expected status, exiting non-zero on any
mismatch: a call to mcp://calc through agentgateway as each user; no
token, not a token, a token for another client and one from another
realm, refused at agentgateway; the headers a backend receives; and one
token through its expiry, including the minute after exp in which
agentgateway still accepts it (the jsonwebtoken default leeway) and the
control plane refuses the exchange.

The admin, API and bootstrap tokens are generated at random into .run/
and handed to the components as files and to curl as header files,
never as arguments.
The guide walks through the agentgateway example: access tokens that
carry the mesh's audience, validating the caller with jwtAuth,
exchanging the token at the node's /oauth/token, the calls and what a
backend receives, and what happens around the token's expiry. It lists
where each refusal is logged and what to change for a real deployment,
and is linked from the guides index. The example's README points to it.
@HosniBelfeki
HosniBelfeki requested a review from aojea as a code owner October 20, 2026 21:54

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive example and guide for deploying agentgateway in front of an Agent Mesh. It includes a Docker Compose configuration, Keycloak realm imports, a test script (run.sh), and detailed documentation. The feedback recommends updating the Python image in compose.yaml from the pre-release python:3.14-slim to a stable version like python:3.13-slim to ensure reliability.

# Answers with the request headers it received (echo.py), behind the same
# agentgateway policies as the mesh route.
echo:
image: python:3.14-slim

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Python image is pinned to python:3.14-slim. Python 3.14 is currently in pre-release/development and is not yet stable. It is highly recommended to use a stable, released version of Python such as python:3.13-slim or python:3.12-slim to ensure reliability and avoid potential image pull or runtime issues.

    image: python:3.13-slim

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python 3.14 is a stable release (3.14.0 on 7 October 2025; this image reports 3.14.8, release level final), and the repository's other Python images use it too (calc-mcp, chat-a2a, greeter-mcp, the e2e images). I pinned the echo image to the same digest those Dockerfiles use, python:3.14-slim@sha256:a2b82f3c…, in 7d0834d.

The header echo ran python:3.14-slim by tag. It now names the digest the
repository's Python Dockerfiles already pin (calc-mcp, chat-a2a,
greeter-mcp and the e2e images), so the example runs the image it was
tested with.
@aojea
aojea merged commit eb31816 into google:main Oct 22, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

agentgateway: per-caller token exchange to a shared node, a runnable recipe (#734)

2 participants