NGINX Plus as MCP Security Gateway

The 2026-07-28 MCP specification introduces two new HTTP headers (Mcp-Method and Mcp-Name) that surface client intent directly in HTTP request headers.

POST /mcp HTTP/1.1
Authorization: Bearer ...
Mcp-Method: tools/call    # NEW in 2026-07-28
Mcp-Name: get_weather     # NEW in 2026-07-28

...

{"jsonrpc":"2.0","method":"tools/call","params":{"name":"get_weather"}}

While these headers were intended for more efficient routing at the API/MCP gateways, they also open up possibilities for MCP access controls at the edge.

In this article, I will be walk through how NGINX Plus use these headers to further strengthen the security of your MCP architecture.

Table of Contents

NGINX Plus Capabilities

1. Client authentication via token validation

MCP specifies the use of OAuth 2.1 for authorization, which requires clients to authenticate via access tokens. These tokens can be validated by NGINX Plus before passing the client traffic to the backend MCP servers.

Access tokens come in several forms:

JSON Web Tokens (JWT)

NGINX Plus includes the auth_jwt directive to validate a JWT, covering basic checks such as

  • token integrity by checking signature against a public key
  • token usage within window defined by exp and nbf claims

Further authentication/authorization checks possible with the auth_jwt_require directive, e.g. verifying who issued the token (iss claim) or what the token was issued for (aud claim):

location /mcp {
    auth_jwt          "MCP Gateway";
    auth_jwt_key_file /etc/nginx/jwk/key.jwk;
    auth_jwt_require  $valid_jwt_issuer;
    auth_jwt_require  $valid_jwt_audience;

    proxy_pass http://mcp_backend;
}

Opaque tokens

There are also authentication flows which have clients sending opaque tokens, which the server would have to verify with an identity provider (IdP) for validation.

NGINX Plus supports this external verification flow with the auth_request directive, essentially making a sideband call containing the opaque token to the IdP and checking the response to determine if the client is allowed to proceed. The token validation logic will likely be unique for different IdP and organizations, with more complex validation flows supported via NGINX Javascript (as seen in this example).

Should a client fail to authenticate itself, NGINX Plus responds with a 401 Unauthorized error.

For brevity’s sake, I will assume the use of JWT for the remainder of the article.

2. Client authorization via MCP header inspection

Authorization can be boiled down to “Who is allowed to do What”, information that NGINX Plus has easy access to:

  • Who is defined by a JWT claim, typical ones being sub, email, or even role. These can be extracted via the jwt_claim_* variables
  • What would be the intent, or the MCP tools called. This is split into two parts - the MCP tool name and method, now present in the HTTP headers Mcp-Method and Mcp-Name, and accessible via the variables http_mcp_name and http_mcp_method.

Using the NGINX Plus map directive, we can map out a series of “who is trying to do what” to a “decision”:

map "$jwt_claim_role:$http_mcp_method:$http_mcp_name" $mcp_authz_decision {
    # Format: "role:method:name"

    # User role permissions
    "user:tools/list:"                  1;
    "user:tools/call:get_weather"       1;
    "user:tools/call:search_docs"       1;

    # Admin role permissions
    "admin:tools/list:"                 1;
    "admin:tools/call:get_weather"      1;
    "admin:tools/call:search_docs"      1;
    "admin:tools/call:deploy_service"   1;

    # Default deny: any unlisted combination
    default                             "";
}

The decision is then enforced with the auth_jwt_require directive, setting up NGINX Plus as an inline policy management and enforcement engine:

location /mcp {
    # Step 1: JWT Claim Validation (401 on failure)
    auth_jwt          "MCP Gateway";
    auth_jwt_key_file /etc/nginx/jwk/key.jwk;
    auth_jwt_require  $valid_jwt_issuer;
    auth_jwt_require  $valid_jwt_audience;

    # Step 2: MCP Header Authorization (403 on failure)
    auth_jwt_require  $mcp_authz_decision error=403;

    proxy_pass http://mcp_backend;
}

Any unauthorized tool call will be met with a 403 Forbidden error.

Readers may question the use of auth_jwt_require directive over alternatives such as the if directive. See the Appendix for more info.

3. Client payload integrity check by matching MCP intent in header and body

While header-based authorization provides extremely fast evaluation, a malicious actor could attempt header spoofing - specifying allowed operations in Mcp-Method/Mcp-Name headers (e.g. get_weather) while embedding a restricted tool in the JSON-RPC request body (deploy_service), which is why the MCP specification requires the server to perform a match of the header values to the body values.

Once again, this security requirement can be offloaded to NGINX Plus, performing an additional payload parity check with the use of NGINX JavaScript (njs) and the js_access directive.

The example below shows a custom njs function verifyMcpMatch() that performs the header-body value matching, returning 400 Bad Requesterror:

async function verifyMcpMatch(r) {
  ...
  var headerMethod = r.headersIn['Mcp-Method'] || '';
  var headerName = r.headersIn['Mcp-Name'] || '';

  var body = JSON.parse(r.requestText);
  var bodyMethod = body.method || '';
  var bodyName = (body.params && body.params.name) ? body.params.name : '';

  if (headerMethod !== bodyMethod || headerName !== bodyName) {
    r.return(400, JSON.stringify({
      error: "Header and body mismatch",
      header_mcp_method: headerMethod,
      body_method: bodyMethod,
      header_mcp_name: headerName,
      body_mcp_name: bodyName
    }));
  }
}

The function is referenced by a js_access directive to enforce the MCP header and payload alignment, giving us a final NGINX Plus location configuration as such:

location /mcp {
  # Step 1: JWT Claim Validation (401 on failure)
  auth_jwt          "MCP Gateway";
  auth_jwt_key_file /etc/nginx/jwk/key.jwk;
  auth_jwt_require  $valid_jwt_issuer;
  auth_jwt_require  $valid_jwt_audience;

  # Step 2: MCP Header Authorization (403 on failure)
  auth_jwt_require  $mcp_authz_decision error=403;

  # Step 3: MCP Request Body & Header Parity Check via NJS (400 on failure)
  client_max_body_size 1m;
  client_body_buffer_size 1m;
  js_access mcp_body_match.verifyMcpMatch;

  proxy_pass http://mcp_backend;
}

Hands-on Setup

If you would like to see this in action, I have prepared a test setup at

GitHub - leonseng/nginx-plus-mcp-security-gateway · GitHub.

The full NGINX Plus configuration can be seen in the /nginx directory, including the definition of policies in the mcp_policy.conf file for easy maintenance and version control (via GitOps for example).

To see some MCP call flows in action, simply follow the Quick Start section to generate JWT tokens for supported roles and make some tool calls which are allowed/blocked per the defined NGINX policy.

For example, an unprivileged client calling a restricted tool results in NGINX Plus responding with a 403 Forbidden error:

$ curl -i -X POST http://localhost:8080/mcp \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "Mcp-Method: tools/call" \
      -H "Mcp-Name: deploy_service" \
      -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"deploy_service"}}'
HTTP/1.1 403 Forbidden
Server: nginx/1.29.8
...

NGINX Plus logs:

127.0.0.1 [...] status=403 mcp_method="tools/call" mcp_name="deploy_service" mcp_user="alice" mcp_role="user"

More examples can be seen in the Examples section.

Closing

By combining native NGINX Plus capabilities with njs, you can enforce strict, policy-driven security for MCP clients and AI agents without modifying upstream MCP server code. Beyond authentication and authorization, NGINX Plus brings capabilities such as rate limiting, central logging, WAF and more for a comprehensive gateway solution to help scale MCP deployments across enterprises.

Appendix

Use of if directive for MCP header authorization as below revealed an edge case during testing - if an MCP client calls a tool without a JWT present, NGINX Plus incorrectly presents a 403 Forbiddenerror, when a 401 Unauthorizedis expected.

location /mcp {
  ...
  # Step 2: MCP Header Authorization (403 on failure)
  if ($mcp_authz_decision = "") {
    return 403;
  }

  proxy_pass http://mcp_backend;
}

Further investigation revealed that this is a result of if directive executing earlier than the auth_jwt_* directives.

When a request is sent, the if directive triggers the resolution of the mcp_authz_decision variable, defined in the NGINX Plus policy map. If the request has an invalid JWT or if the JWT is absent, the source variable jwt_claim_role referenced in the policy map will be empty, causing mcp_authz_decision to be assigned the default value of “”. This sets the condition for the if directive to be true, and an incorrect 403 Forbiddenerror to be returned.

2 Likes