Skip to main content

Command Palette

Search for a command to run...

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.

Updated
β€’8 min read
Reading AgentGateway's config.yaml (From Scary First Line to Writing Your Own)
M
I am a software architect with over a decade of experience in architecting and building software solutions.

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:

  1. matches decides which requests this route handles β€” by path (e.g. pathPrefix: /openai), headers, method, or query. A route with no matches block is the catch-all: it handles anything the earlier routes didn't. That's how you build a default.

  2. policies are 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 $schema line. 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-level config: 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 matches matches 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-version from allowHeaders, 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