What is an MCP gateway?

Disclosure: I work at Solo.io, which makes agentgateway.

An MCP gateway sits between AI agents and MCP servers: one endpoint, sign-in, per-tool CEL rules and audit. How it differs from LLM and API gateways.

An MCP gateway is a proxy between AI clients and MCP servers, so agents reach many tool servers through one endpoint and every tool list and tool call passes one policy point. It signs users in, decides per tool who may see and call it, and logs each call, which is a different job from an LLM gateway (model calls) or an API gateway (ordinary HTTP APIs).

If you want the wider picture, with model and agent-to-agent traffic in the same gateway, see what an AI agent gateway is .

Every config and output on this page comes from open source agentgateway v1.6.0, run on 2026-10-26: the standalone binary on a Linux box, and the v1.6.0 Helm charts on a k3s cluster. Nothing here needs Solo Enterprise.

What an MCP gateway does

JobWhat it meansHow agentgateway does it
One endpoint for many serversClients connect once and get one tool listSeveral targets in one MCP backend (virtual MCP )
Sign-inThe gateway acts as an OAuth resource server and turns away calls without a valid tokenmcpAuthentication (MCP authentication )
Per-tool authorizationRules decide who may call which toolmcpAuthorization rules in CEL (MCP authorization )
Tool list filteringCallers never see tools they cannot useThe same rules drop denied tools from tools/list
Per-target credentials and TLSEach upstream server gets its own auth and TLS settingsbackendAuth and backendTLS per target (MCP target policies )
Logs and metricsEvery call is recorded with the server and tool nameMCP access log fields and an MCP request counter (MCP observability )
Spec version translationClients and servers on different MCP versions can still talkTranslation between versions (spec compatibility )

How it works, with agentgateway

Standalone: one binary and a YAML file

The simplest form is the top-level mcp section. This is the two-target config I ran:

mcp:
  port: 3000
  targets:
  - name: time
    stdio:
      cmd: uvx
      args: ["--with", "mcp<2", "mcp-server-time"]
  - name: everything
    stdio:
      cmd: npx
      args: ["-y", "@modelcontextprotocol/server-everything"]

agentgateway -f config.yaml starts it, and clients connect to http://localhost:3000/mcp. The routes section is the other way to write it when you need more control. Either way you can put every server behind one endpoint or give each its own path (configuration modes ).

Kubernetes: Gateway API and CRDs

On Kubernetes, the same proxy runs under a controller that reads Gateway API resources and agentgateway’s own CRDs. An AgentgatewayBackend can pick MCP servers by label instead of by name. This is the one I applied:

apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayBackend
metadata:
  name: mcp-fleet
  namespace: agentgateway-system
spec:
  mcp:
    targets:
    - name: fleet
      selector:
        namespaces:
          matchLabels: {kubernetes.io/metadata.name: mcp}
        services:
          matchLabels: {mcp-fleet: demo}

An HTTPRoute on the Gateway sends /mcp to that backend. Two Services in the mcp namespace carried the label, and the gateway listed 26 tools from both, each prefixed with its Service name and port, such as everything-3001_echo and second-3001_echo. The docs note that label selectors support streamable HTTP only. A server that speaks SSE needs a static backend . The full walkthrough is virtual MCP on Kubernetes .

One endpoint, many servers

The client sees one MCP server. This is part of the tools/list from the standalone config above, which returned 15 tools, 2 from time and 13 from everything:

time_get_current_time
time_convert_time
everything_echo
everything_get-env
everything_get-sum

With more than one target, agentgateway prefixes each tool with its target name, so two servers can both have an echo without a clash. Calls go back by prefix: time_get_current_time reached the time server and everything_echo came back with “Echo: hello”. The prefixMode setting has three values: conditional (the default, which prefixes only when there is more than one target), always and never.

The underscore is the join character, which is why target names cannot contain one. I tried a target called my_time and v1.6.0 refused to start, saying “’+’ and ‘_’ are reserved MCP delimiters”.

A target can also carry a CEL condition that decides whether that server joins the request at all. The v1.6 docs evaluate it before the gateway initializes or contacts the target. I put a condition on the time target that checks for an x-include-time: true header. A client without the header got 13 tools. A client with it got 15.

For one endpoint shared across customers, with each one seeing its own tools, see multi-tenant MCP federation .

Per-tool authorization with CEL

Authorization rules are CEL expressions. These are the rules from the v1.6 docs, with the second tool swapped for one the test server has:

mcp:
  policies:
    mcpAuthorization:
      rules:
      - 'mcp.tool.name == "echo"'
      - 'jwt.sub == "test-user" && mcp.tool.name == "get-sum"'

I ran it with two signed tokens. The caller with sub set to test-user got echo and get-sum in tools/list. The caller with sub set to other-user got only echo. The blocked tool is not marked as off limits. It is gone from the list. When that second caller called get-sum anyway, the gateway answered HTTP 400 with JSON-RPC error -32602, “Unknown tool: get-sum”. It does not confirm that the tool exists.

On Kubernetes the same idea is an AgentgatewayPolicy on the backend (tool access ):

apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayPolicy
metadata:
  name: mcp-tool-rule
  namespace: agentgateway-system
spec:
  targetRefs:
  - group: agentgateway.dev
    kind: AgentgatewayBackend
    name: mcp-fleet
  backend:
    mcp:
      authorization:
        action: Allow
        policy:
          matchExpressions:
          - 'mcp.tool.name == "echo"'

With it applied, the 26-tool list shrank to the two echo tools, and a call to second-3001_get-sum got the same “Unknown tool” error. The rule matched the tool name without the prefix. The docs also show expressions that mix a JWT claim with a tool name, such as jwt.sub == "alice" && mcp.tool.name == "get_me".

There is one limit worth knowing. Per the v1.6 docs, mcp.tool.arguments is not set when mcpAuthorization rules run, so those rules cannot look at arguments. A route-level authorization policy can. I tested this deny rule:

mcp:
  policies:
    authorization:
      rules:
      - deny: 'mcp.tool.name == "echo" && mcp.tool.arguments.message.contains("secret")'

echo with “hello” went through. echo with a message containing “secret” got HTTP 403, “authorization failed”. The access log can record the arguments too: adding tool_args: 'mcp.tool.arguments' to the log fields wrote tool_args={"message": "hello"}. More traps are in CEL authorization gotchas .

Sign-in for MCP servers

mcpAuthentication makes the gateway an OAuth 2.0 resource server. It checks the JWT on each request, and when there is no valid token it answers 401 and points the client at its protected resource metadata. This is the docs’ identity provider example, trimmed, which I ran against a local test realm:

mcp:
  policies:
    mcpAuthentication:
      issuer: http://localhost:7080/realms/mcp
      audiences: ["http://localhost:3000/mcp"]
      jwks:
        url: http://localhost:7080/realms/mcp/protocol/openid-connect/certs
      provider:
        keycloak: {}
      resourceMetadata:
        resource: http://localhost:3000/mcp
        scopesSupported: [read:all]
        bearerMethodsSupported: [header]

The first request, with no token:

HTTP/1.1 401 Unauthorized
www-authenticate: Bearer resource_metadata="http://localhost:3000/.well-known/oauth-protected-resource/mcp"

{"error":"unauthorized","error_description":"JWT token required"}

That URL returned the protected resource metadata, and the gateway also served the provider’s authorization server metadata at /.well-known/oauth-authorization-server/mcp. That is how an MCP client finds where to sign in. I tested the 401 and both metadata documents, not a full browser sign-in. The provider field has adapters for six identity providers in v1.6 (MCP authentication ).

Credentials from the gateway to each upstream server are a separate setting, backendAuth, set per target, so the agent never holds them. For the cross-app access flow, see MCP OAuth and XAA .

MCP gateway vs LLM gateway

An LLM gateway sits in front of model providers. It routes completions, holds provider keys, counts tokens and applies prompt guardrails. An MCP gateway sits in front of tool servers. It decides which tools a caller can discover and call, and records each tool call. They see different traffic.

LLM gatewayMCP gateway
UpstreamModel providersMCP tool servers
Unit of policyModel, key, token budgetServer, tool name, caller
Typical controlsRouting, fallback, cost limits, prompt guardsTool list filtering, per-tool rules, sign-in
What it logsModel, tokens, costMethod, server, tool name

You do not need two products. In agentgateway standalone, the llm and mcp sections can attach to one gateway. I ran both on port 3000: a tools/call at /mcp and a chat completion at /v1/chat/completions went through the same process. Setting the top-level llm.port and mcp.port fields both to 3000 does not work. v1.6.0 refuses and says to attach both to the same gateway instead. Cost controls on the model side are in capping LLM spend per team .

MCP gateway vs API gateway

An API gateway routes and protects HTTP APIs by host, path and method. MCP runs over HTTP too, but every call is a POST to the same path. The method (tools/list, tools/call) and the tool name sit inside the JSON-RPC body, and older spec versions add a session on top. A gateway that only reads paths sees one route. An MCP gateway parses the body, so it can filter a tool list and decide on each tool.

agentgateway is also a general HTTP and gRPC proxy (introduction ), so the same binary can front ordinary APIs as well.

Spec versions matter here too. The 2026-07-28 MCP revision is stateless and sessionless. agentgateway translates between versions, and when it federates servers it offers the intersection of the versions every target supports (spec compatibility ).

What to check in any MCP gateway

  • Does tools/list change per caller, or does every caller see every tool? (MCP authorization )
  • Can a rule name one tool on one server, and does a denied call reveal whether the tool exists?
  • Does it answer 401 with WWW-Authenticate and serve protected resource metadata? (MCP authentication )
  • Can each upstream server get its own credentials, so the agent never holds them? (MCP target policies )
  • What happens when two servers expose the same tool name? (virtual MCP )
  • Which MCP spec versions does it support on each side? (spec compatibility )
  • Do the logs and metrics carry the tool name? On v1.6.0 my access log lines carried mcp.target=everything and gen_ai.tool.name=echo, and the metrics endpoint counted agentgateway_mcp_requests_total with method="tools/call", server="time" and resource="get_current_time" labels. (MCP observability )
  • Can you find the servers nobody put behind it? Those are shadow MCP servers .
  • Can a server change a tool description after you approved it? See why a name allowlist is not enough and keeping server instructions out of the system prompt .

When you are ready to compare gateways on the controls they enforce, how to choose an AI agent gateway has the checklist and the tests.

Tested on agentgateway OSS v1.6.0 (git ea56086), standalone binary and v1.6.0 Helm charts, 2026-10-26. The outputs above are copied from those runs.

Frequently asked questions

What is an MCP gateway?

An MCP gateway is a proxy between AI clients and MCP servers. Agents connect to one endpoint and reach many tool servers through it. The gateway signs callers in, filters the tool list per caller, checks each tool call against rules, and logs every call with the tool name. agentgateway does this as a single binary or on Kubernetes.

What does an MCP gateway do that an MCP server does not?

An MCP server exposes tools. A gateway sits in front of many servers and adds what each server would otherwise build alone: one endpoint, sign-in as an OAuth resource server, per-tool rules on who may see and call each tool, separate credentials for each upstream server, and one log of every tool call. Servers stay simple and policy lives in one place.

MCP gateway vs LLM gateway: do I need both?

You need both jobs if your agents call models and tools. An LLM gateway governs model calls: routing, provider keys, token and cost limits. An MCP gateway governs tool calls: discovery, per-tool rules and audit. They can be one product. On agentgateway v1.6.0 I served model traffic and MCP traffic from one standalone gateway on one port.

MCP gateway vs API gateway: can my API gateway do this?

An API gateway matches on host, path and method. Every MCP call is a POST to the same path, with the method and the tool name inside the JSON-RPC body. Unless a gateway parses that body, it sees one route and cannot filter a tool list or allow one tool and block another. It can still handle TLS, routing and rate limits in front.