> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of all docs pages are available by appending .md to any docs URL.

# MCP authorization

Attaches to:[](/docs/standalone/latest/documentation/configuration/routes/ "Route")

[Route](https://agentgateway.dev/docs/standalone/latest/documentation/configuration/routes/ "Route")

[Backend](https://agentgateway.dev/docs/standalone/latest/documentation/configuration/backends/ "Backend")

(MCP Backends only)

> [!NOTE]
> Agentgateway supports more than one configuration style. Where a feature can also be configured in the simplified `llm` or `mcp` modes, the examples on this page show each option in tabs. For more information, see [Routing-based configuration](https://agentgateway.dev/docs/standalone/latest/documentation/llm/configuration-modes/).

The MCP authorization policy works similarly to [HTTP authorization](https://agentgateway.dev/docs/standalone/latest/documentation/configuration/security/http-authz/), but runs in the context of an MCP request.

> [!NOTE]
> This policy works only for [MCP](https://modelcontextprotocol.io/docs/getting-started/intro) traffic. Note that all standard HTTP policies also apply to MCP traffic.

Instead of running against an HTTP request, MCP authorization policies run against specific MCP method invocations such as `list_tools` and `call_tools`.

If a tool or other resource is not allowed, the gateway automatically filters it from the `list` response, so unauthorized clients never see it.

You can attach `mcpAuthorization` at the route level or directly to an MCP backend. A backend-level policy applies to every MCP target in that backend. To vary the rules per target instead, keep one route-level policy and match on the `mcp.tool.target` variable, as shown in [Different rules per target](#different-rules-per-target). For the other policies that you can scope to an individual target, see [MCP target policies](https://agentgateway.dev/docs/standalone/latest/documentation/mcp/mcp-target-policies/).

You can choose from the following options.

**Option: Simplified (MCP)**

```yaml
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
mcp:
  port: 3000
  policies:
    mcpAuthorization:
      rules:
      # Allow anyone to call 'echo'
      - 'mcp.tool.name == "echo"'
      # Only the test-user can call 'add'
      - 'jwt.sub == "test-user" && mcp.tool.name == "add"'
      # Any authenticated user with the claim `nested.key == value` can access 'printEnv'
      - 'mcp.tool.name == "printEnv" && jwt.nested.key == "value"'
  targets:
  - name: everything
    stdio:
      cmd: npx
      args: ["@modelcontextprotocol/server-everything"]
```

**Option: Routing-based**

```yaml
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
gateways:
  default:
    port: 3000
routes:
- policies:
    mcpAuthorization:
      rules:
      # Allow anyone to call 'echo'
      - 'mcp.tool.name == "echo"'
      # Only the test-user can call 'add'
      - 'jwt.sub == "test-user" && mcp.tool.name == "add"'
      # Any authenticated user with the claim `nested.key == value` can access 'printEnv'
      - 'mcp.tool.name == "printEnv" && jwt.nested.key == "value"'
  backends:
  - mcp:
      targets:
      - name: everything
        stdio:
          cmd: npx
          args: ["@modelcontextprotocol/server-everything"]
```

> [!NOTE]
> Try out CEL expressions in the built-in [CEL playground](https://agentgateway.dev/docs/standalone/latest/reference/cel/playground/) in the agentgateway UI before using them in your configuration.

## Role-based access with JWT claims

When you combine MCP authorization with [MCP authentication](https://agentgateway.dev/docs/standalone/latest/documentation/configuration/security/mcp-authn/), you can write rules that reference JWT claims. The following configuration restricts tools based on the authenticated user's identity and role:

- The MCP authentication policy validates JWTs against a local authorization server, such as Keycloak, running on port 9000.
- Any authenticated user can call the `echo` tool.
- Only the user `test-user` can call the `add` tool.
- Only users with the nested claim `nested.key == "value"` can call the `printEnv` tool.

```yaml
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
mcp:
  port: 3000
  policies:
    mcpAuthentication:
      issuer: http://localhost:9000
      audiences:
      - http://localhost:3000/mcp
      jwks:
        url: http://localhost:9000/.well-known/jwks.json
      resourceMetadata:
        resource: http://localhost:3000/mcp
        scopesSupported:
        - read:all
        bearerMethodsSupported:
        - header
    mcpAuthorization:
      rules:
      # Any authenticated user can call 'echo'
      - 'mcp.tool.name == "echo"'
      # Only the test-user can call 'add'
      - 'jwt.sub == "test-user" && mcp.tool.name == "add"'
      # Claim-based access for 'printEnv'
      - 'mcp.tool.name == "printEnv" && jwt.nested.key == "value"'
  targets:
  - name: everything
    stdio:
      cmd: npx
      args: ["@modelcontextprotocol/server-everything"]
```

## Different rules per target

When you multiplex several MCP servers behind a single agentgateway listener, you can apply different authorization rules to each target by matching on the `mcp.tool.target` variable in a single policy. In the following configuration:

- Any user can access tools on the `public-tools` target.
- Only users with `admin` in the JWT `roles` claim can access tools on the `admin-tools` target.
- The JWT is validated against a local authorization server running on port 9000.

```yaml
# yaml-language-server: $schema=https://agentgateway.dev/schema/config
mcp:
  port: 3000
  policies:
    cors:
      allowOrigins: ["*"]
      allowHeaders:
      - mcp-protocol-version
      - content-type
      - cache-control
    mcpAuthentication:
      mode: optional
      issuer: http://localhost:9000
      audiences:
      - http://localhost:3000/mcp
      jwks:
        url: http://localhost:9000/.well-known/jwks.json
      resourceMetadata:
        resource: http://localhost:3000/mcp
        scopesSupported:
        - read:all
        bearerMethodsSupported:
        - header
    mcpAuthorization:
      rules:
      # Allow anyone to access tools on the public-tools target
      - 'mcp.tool.target == "public-tools"'
      # Only authenticated admins can access tools on the admin-tools target
      - 'mcp.tool.target == "admin-tools" && has(jwt.sub) && "admin" in jwt.roles'
  targets:
  - name: public-tools
    stdio:
      cmd: npx
      args: ["@modelcontextprotocol/server-everything"]
  - name: admin-tools
    stdio:
      cmd: npx
      args: ["@mycompany/admin-server"]
```

## CEL variables

The following MCP-specific CEL variables are available in authorization rules:

| Variable | Type | Availability | Description |
| --- | --- | --- | --- |
| `mcp.tool.name` | `string` | Request-time | The name of the tool being called. |
| `mcp.tool.target` | `string` | Request-time | The target backend handling the tool call. |
| `mcp.tool.arguments` | `map` | Post-request | The JSON arguments passed to the tool call (not set in `mcpAuthorization` rules). |
| `mcp.tool.result` | `any` | Post-request | The tool call result payload (access logs only). |
| `mcp.tool.error` | `any` | Post-request | The tool call error payload (access logs only). |
| `mcp.prompt.name` | `string` | Request-time | The name of the prompt being accessed. |
| `mcp.resource.name` | `string` | Request-time | The name of the resource being accessed. |
| `mcp.methodName` | `string` | Request-time | The MCP JSON-RPC method name, such as `tools/call` or `tools/list`. |
| `mcp.sessionId` | `string` | Post-request | The MCP session ID. |

Request-time variables are available during authorization and can be used in `mcpAuthorization` rules. Post-request variables are available in access log CEL expressions. Route-level HTTP policies, such as `authorization` and `localRateLimit`, can also read `mcp.sessionId` and `mcp.tool.arguments` when the request arrives. For more information, see the [CEL variables reference](https://agentgateway.dev/docs/standalone/latest/reference/cel/variables/).

When you also configure [MCP authentication](https://agentgateway.dev/docs/standalone/latest/documentation/configuration/security/mcp-authn/), claims from the validated JWT are available to your rules as well:

| Variable | Type | Availability | Description |
| --- | --- | --- | --- |
| `jwt.sub` | `string` | Request-time | The `sub` (subject) claim from the JWT. |
| `jwt.<claim>` | `any` | Request-time | Any top-level or nested JWT claim, such as `jwt.roles` or `jwt.nested.key`. |
| `has(jwt.<claim>)` | `bool` | Request-time | Whether a JWT claim is present. |

### Tool arguments are not available during authorization

`mcp.tool.arguments` isn't set when `mcpAuthorization` rules run, so the rules can't reference it. Base authorization decisions on `mcp.tool.name` and `mcp.tool.target` instead. To allow or deny a call based on its arguments, use a route-level `authorization` policy, which can read `mcp.tool.arguments`.

To inspect tool arguments, use an access log policy, which evaluates post-request:

```yaml
frontendPolicies:
  accessLog:
    add:
      tool_args: 'mcp.tool.arguments'
```

See [MCP observability](https://agentgateway.dev/docs/standalone/latest/documentation/mcp/mcp-observability/) for the full example, and the [CEL reference](https://agentgateway.dev/docs/standalone/latest/reference/cel/) for additional variables.
