Reading AgentGateway's config.yaml (From Scary First Line to Writing Your Own)
One file runs everything β binds, listeners, routes, backends, policies. Here's how to read it top to bottom, and why the same skeleton powers an MCP gateway, an LLM gateway, and a plain proxy.

In Post 2 we grabbed the Windows binary, dropped a config.yaml next to it, and watched a real MCP tool server show up in the browser. It worked. But if I'm honest, we never actually read that file. We pasted it, ran it, and moved on.
This post fixes that. Every single thing you'll do with AgentGateway from here on β multiplexing tools, fronting OpenAI and Claude, bolting on auth β is just edits to this one file. So if you can read config.yaml, you can read every AgentGateway tutorial on the internet, including the rest of this series. By the end you'll understand each section, know which parts actually matter, and be able to write a config from a blank file instead of copy-pasting one and praying.
If you're brand new, you'll walk away with the mental model. And if you've been lifting configs from the docs without really reading them, this is the post where it clicks.
A quick honesty note about the version, same as last time: I'm on AgentGateway v1.2.1 (current as of June 2026). Pin whatever v1.x you downloaded and write it down, because config schemas shift between majors and you want your notes to stay reproducible.
Line 1: that scary $schema comment
Open almost any AgentGateway example and the very first line is this:
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
It looks important. It isn't β at least not to AgentGateway. It's a comment, and the gateway ignores it completely at runtime. Delete it and your config still runs exactly the same.
What it does do is make your editor smart. I touched on this in Post 2, but here's the full story: with the YAML extension installed in VS Code, that line says "validate this file against AgentGateway's schema." Now you get autocomplete on field names, red squiggles the moment you typo backends as backeds, and docs on hover. It's the highest-value line in the file for not wasting an afternoon on a misspelled key β so keep it, even though the gateway never reads it.
That's the whole trick: it's developer experience, not configuration. Good thing to know before it confuses you.
The one mental model that explains everything
Here's the entire file in a single sentence:
A request arrives on a bind, is accepted by a listener, is matched by a route, has policies applied, and is forwarded to a backend.
Four layers, nested top to bottom:
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
binds:
- port: 3000 # 1. the PORT requests come in on
listeners:
- protocol: HTTP # 2. WHAT you accept (HTTP β or TCP)
routes:
- matches: # 3. MATCH a request...
- path:
pathPrefix: /chat
policies: # ...apply rules (auth, CORS, rate limits)...
cors:
allowOrigins: ["*"]
backends: # 4. ...and send it WHERE it should go
- ai:
name: openai
provider:
openAI:
model: gpt-4o
Read it as a funnel: port β protocol β matching rules β destination. Once you see that shape, every config file looks the same, no matter how big it grows. A 200-line enterprise config is just this skeleton, repeated. Let's take the four layers one at a time.
binds β the port
A bind is simply a port AgentGateway listens on. Most setups have one (port: 3000). Bigger setups have several β one per port or domain. Everything else lives inside a bind.
listeners β what you accept on that port
A listener sets the protocol. Use protocol: HTTP for nearly everything β MCP, LLM, A2A, and ordinary web traffic all ride on HTTP. There's also protocol: TCP for raw TCP services (think a database passthrough), with one wrinkle: TCP listeners use tcpRoutes instead of routes, and those are simpler β no path or header matching, because there's no HTTP to match on.
routes β match first, then decide
This is the brain of the file. A route does two jobs:
matchesdecides which requests this route handles β bypath(e.g.pathPrefix: /openai),headers, method, or query. A route with nomatchesblock is the catch-all: it handles anything the earlier routes didn't. That's how you build a default.policiesare the rules applied to matched requests βcors,jwtAuth,rateLimit,backendAuth, AI guards, and more. This is where most of your real configuration ends up, and almost every later post in this series adds a block right here.
backends β where the request actually goes
A backend is the destination, and it comes in three flavors. This is the "aha" that unlocks the whole product:
backends:
- host: example.com:8080 # a plain HTTP/TCP service
# or
- mcp: # an MCP tool server
targets:
- name: everything
stdio:
cmd: npx
args: ["@modelcontextprotocol/server-everything"]
# or
- ai: # an LLM provider
name: openai
provider:
openAI:
model: gpt-4o
Same skeleton, three jobs. host: makes AgentGateway a normal reverse proxy. mcp: makes it an MCP gateway. ai: makes it an LLM gateway. You can even mix them in one file.
The config from Post 2, now readable
Remember the file we ran last time? Here it is again β except now you can read every line:
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
binds:
- port: 3000
listeners:
- routes:
- policies:
cors:
allowOrigins: ["*"]
allowHeaders:
- mcp-protocol-version
- content-type
- cache-control
exposeHeaders:
- "Mcp-Session-Id"
backends:
- mcp:
targets:
- name: everything
stdio:
cmd: npx
args: ["@modelcontextprotocol/server-everything"]
A bind on port 3000, one listener, one catch-all route (no matches, so it takes everything), a CORS policy on that route, and a single mcp backend that AgentGateway launches itself over stdio. Run it the same way:
.\agentgateway.exe -f config.yaml
Want an LLM gateway instead of an MCP one? Keep the exact same skeleton and swap the backend block:
backends:
- ai:
name: openai
provider:
openAI:
model: gpt-4o
policies:
backendAuth:
key: "$OPENAI_API_KEY" # pulled from an env var β never hardcode
Notice "$OPENAI_API_KEY": AgentGateway substitutes environment variables, so your secrets stay out of the file and the file stays safe to commit.
The shortcut the docs kind of bury
You don't always need the full binds β listeners β routes β backends tree. For simple cases there are two top-level shortcuts:
mcp:connects to MCP servers without the full routing structure.llm:is a model-centric way to route to LLM providers.
Start with these while you're learning, and graduate to the full binds model the moment you need path-based routing, multiple ports, or fine-grained policies. Knowing both styles exist saves you from staring at two tutorials that look completely different β they're just using different levels of the same system.
The stuff that actually bit me
The UI silently ate my comments. If you edit config through the admin UI on port 15000, it overwrites the file and wipes every comment β including that
$schemaline. Keep a backup, or treat the file as the source of truth and use the UI read-only.I edited the top-level
config:section and nothing happened. Most edits hot-reload while the gateway runs. The exception is the top-levelconfig:block (logging and other startup settings) β that one needs a restart. I sat there reloading the page for a minute before the penny dropped.I hardcoded a key. Don't. Use
key: "$MY_SECRET"and export the variable. Now your config can live in Git without leaking anything.My catch-all swallowed everything. A route with no
matchesmatches all requests, so put your specific routes first and the catch-all last, or the catch-all wins before the others get a look in.A trimmed config broke CORS. Same trap as Post 2: if you copy a slimmed-down config and lose
mcp-protocol-versionfromallowHeaders, the browser inspector connects and instantly drops. The headers in that CORS block aren't decoration.
Small reminder from Post 2: JSON works too. AgentGateway accepts JSON or YAML, so if your team prefers JSON, nothing's stopping you.
Where this leaves you
You can now read any AgentGateway config you'll meet. Port, protocol, match, destination β with policies clipped onto the routes. Every example from here on is a variation on that funnel, and you know exactly where each new block goes.
Next post: instead of one demo server, we point AgentGateway at two or three real MCP servers at once and expose them through a single endpoint, so your IDE or agent gets every tool from one URL. That's where the "gateway" part really starts earning its name β and now that you can read the file, the multiplexing config will read like plain English.
Setup I used: AgentGateway v1.2.1 (binary), Windows 11, Node.js 20 LTS.
Handy links
Configuration overview: https://agentgateway.dev/docs/standalone/latest/configuration/overview/
Routes: https://agentgateway.dev/docs/standalone/latest/configuration/routes/
Backends: https://agentgateway.dev/docs/standalone/latest/configuration/backends/
LLM configuration modes: https://agentgateway.dev/docs/standalone/latest/llm/configuration-modes/
