Secure MCP Tool Calls with Guardrails and Rate Limits

Yilia Lin

Yilia Lin

August 14, 2026

Technology

Key Takeaways

  • Secure MCP tool calls require four separate controls: caller identity, tool authorization, upstream credentials, and runtime limits or content policies.
  • AISIX AI Gateway keeps the caller API key separate from the credential it presents to an upstream MCP server or REST API.
  • Request and concurrency limits control tool-call volume; token limits do not meter MCP calls because those calls carry no model tokens.
  • Per-server limits prevent a loop against one MCP server from consuming the caller's allowance for every other server.
  • AISIX guardrails can inspect tool arguments before an upstream call, plus text blocks and string values in structuredContent before results return to the client.
  • A guardrail block returns HTTP 200 with a failed MCP tool result marked isError: true; an input block never reaches the upstream server.

An AI agent can turn one human request into a chain of machine actions. A planning loop may search a runbook, query an inventory system, open a ticket, check deployment status, and repeat one of those calls when the result is ambiguous. That autonomy is useful, but it changes the risk profile of an API request.

A valid credential no longer means one predictable call. It may authorize a software loop whose volume and arguments are chosen at runtime. The upstream tool may also return sensitive text that should not enter model context or reach the end user. Securing the connection alone is therefore not enough.

Effective MCP tool security treats identity, traffic, and content as separate enforcement problems. A control that answers one of them should not be credited with solving the others.

AISIX AI Gateway applies multiple controls to the MCP tools/call path. It authenticates the caller, verifies access to the namespaced tool, checks request and concurrency limits, evaluates applicable AISIX Cloud budgets, inspects arguments with guardrails, attaches the configured upstream credential, inspects supported result content, and records a usage event. Each layer addresses a different failure mode.

Build Two Separate Authentication Boundaries

An MCP gateway sits between two trust relationships:

  1. The MCP client authenticates to the gateway.
  2. The gateway authenticates to the upstream MCP server or REST API.

AISIX uses a caller API key for the first boundary. That key identifies the calling application or agent and carries its model, MCP tool, and A2A access settings. The MCP client sends it as Authorization: Bearer <caller-api-key> to the AISIX /mcp endpoint.

The second boundary is configured on the registered MCP server. AISIX supports:

auth_typeUpstream behaviorTypical use
noneSends no credentialIsolated internal or local test server
bearerSends Authorization: Bearer <secret>Static service token
api_keySends x-api-key: <secret>API-key-protected service
oauth2Obtains and caches a client-credentials tokenMachine-to-machine OAuth server

The caller API key is not forwarded to the upstream. The upstream secret is held by AISIX and is not exposed to the client. This separation lets a security team revoke one caller without rotating the backend credential, or rotate one backend credential without updating every agent.

For clients and servers that implement an OAuth-based MCP flow, use the MCP authorization specification as the protocol reference. AISIX's documented upstream OAuth2 mode is specifically the client-credentials flow for gateway-to-server authentication; it should not be described as interactive end-user authorization.

For OAuth2 upstream authentication, AISIX obtains a token with the configured client_id, token_url, secret, and optional scopes. It reuses the token until shortly before expiry. If the upstream rejects the token as unauthorized, AISIX discards the cached token and requests a new one on the next call.

Use environment variables for open-source resource secrets rather than committing them to a YAML file. Remember that adding or changing an environment variable in the host shell does not modify the environment of an already running gateway. Recreate the process or container when the secret value changes. A simple configuration reload is appropriate only when the process already has every referenced variable.

flowchart LR
    Client["MCP client"] -->|"Caller API key"| AISIX["AISIX AI Gateway"]
    AISIX --> Authz["Tool authorization"]
    Authz -->|"Gateway-held bearer"| GitHub["MCP server"]
    Authz -->|"Gateway-held API key"| ERP["OpenAPI REST service"]
    Authz -->|"OAuth2 client credentials"| Orders["Protected MCP server"]

A credential failure should have a narrow blast radius. If the ERP token is revoked, ERP tools fail, but AISIX does not reveal the credential and other registered servers keep operating.

Stop Runaway Agents with Layered Rate Limits

Authentication establishes who the caller is. It does not constrain how aggressively that caller can act. Agent loops need request and concurrency limits designed for tool traffic.

An AISIX caller API key can have a general rate_limit that is shared by its model and MCP traffic. For the MCP path, the directly useful fields are requests per second, minute, hour, or day (rps, rpm, rph, and rpd) and concurrency.

Token limits require special care. A tools/call request carries no model tokens, so it does not add to tpm or tpd counters. A token limit by itself cannot control MCP volume. If model traffic on the same caller key has already exhausted a token window, however, tool calls on that key are also rejected until the window resets. This shared-key behavior is another reason to decide deliberately whether one application should use the same key for model and tool traffic.

AISIX also supports mcp_rate_limits, a map from registered MCP server names to request and concurrency limits. This isolates tool sources. An agent that exhausts its payments allowance can still use runbooks, provided the caller-wide limit has not also been exhausted.

_format_version: "1" api_keys: - display_name: operations-agent key_env: OPERATIONS_AGENT_KEY allowed_models: [] allowed_tools: - runbooks__* - payments__get_status rate_limit: rpm: 120 concurrency: 10 mcp_rate_limits: runbooks: rpm: 100 concurrency: 5 payments: rpm: 10 concurrency: 2

Every matching limit must pass. A call to payments__get_status counts against both the caller-wide limit and the payments limit. A server omitted from mcp_rate_limits is still governed by the caller's general limit.

AISIX checks these controls only for tools/call. A throttled client can still initialize a session and list the tools it is authorized to discover. When a tool call exceeds a limit, AISIX returns HTTP 429 before contacting the upstream and still records the rejected attempt as a usage event.

AISIX Cloud budgets add another shared-key gate. If a budget that covers the caller API key is exhausted, its MCP tool calls are rejected before upstream routing. MCP calls do not add model token cost themselves, and the budget remains key-wide rather than per MCP server. Use request or concurrency limits to cap tool volume; do not describe Cloud budgets as per-tool billing or as an MCP token meter.

Inspect Tool Arguments and Results with Guardrails

Rate limits answer “How often?” Guardrails answer “What content is passing through this tool call?” These controls are complementary.

AISIX runs MCP guardrails only on tools/call. The initialization handshake and tools/list do not carry tool arguments or results, so they are not scanned. For a tool call, the gateway resolves the guardrail chain once and evaluates both directions:

  • Input: The arguments object is inspected before the upstream request. If a guardrail blocks it, AISIX rejects the call and never contacts the MCP server.
  • Output: AISIX decodes and inspects the tool result's text content blocks. It also walks structuredContent and scans its string values, which covers machine-readable output that is not repeated in a text block. It does not scan field names or the serialized JSON envelope, preventing schema keys or escaping from producing misleading matches.

If a guardrail-attached result cannot be parsed in the expected response format, AISIX blocks it rather than returning content it could not inspect. A protocol error with no result payload has no output to scan and passes through.

The following open-source configuration creates a simple bidirectional keyword policy. In an open-source gateway, every enabled guardrail in resources.yaml applies to every request handled by that gateway:

guardrails: - name: block-sensitive-markers enabled: true hook_point: both enforcement_mode: block kind: keyword patterns: - kind: literal value: supersecret-banned-token - kind: regex value: "(?i)private[-_ ]key"

In AISIX Cloud, attach guardrails at a scope that can apply to non-model traffic: the environment, a specific MCP server, the caller API key, or the team. The mcp_server scope is the way to narrow a guardrail below the environment to calls routed to one registered server. A model-specific attachment does not apply to MCP because a tool call has no model.

enforcement_mode: block rejects a matching input or output. monitor records the match but lets the call continue, making it useful for measuring false positives before enforcement. A safe rollout often starts in monitor mode with representative traffic, tunes literal or Rust-compatible regular-expression patterns, and then moves the rule to block mode.

When a guardrail blocks an MCP call or result, AISIX returns HTTP 200 with a normal JSON-RPC result whose isError field is true, rather than the HTTP 422 used when a guardrail blocks a model request:

{ "jsonrpc": "2.0", "id": 1, "result": { "content": [ { "type": "text", "text": "tool call blocked by content policy (guardrail 'block-sensitive-markers')" } ], "isError": true } }

This is a failed tool result, not a JSON-RPC protocol error. The request was valid, so the calling agent receives tool output it can interpret and act on. The block message identifies the guardrail without repeating the sensitive content that matched.

The associated usage event sets guardrail_blocked to true. AISIX inspects the content in flight; the usage event does not store the tool arguments or result.

You can verify both branches against the Everything test server from the AISIX MCP setup guide. With the keyword guardrail above enabled, the first call should return a normal result. The second should return HTTP 200 with result.isError set to true, and the upstream should not receive it:

curl -sS "$AISIX_PROXY/mcp" -H "Authorization: Bearer $AISIX_MCP_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"everything__echo","arguments":{"message":"health check"}}}' curl -sS -w '\nHTTP %{http_code}\n' "$AISIX_PROXY/mcp" \ -H "Authorization: Bearer $AISIX_MCP_KEY" -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"everything__echo","arguments":{"message":"supersecret-banned-token"}}}'

For the blocked call, verify the printed status is HTTP 200, the parsed result.isError value is true, and there is no top-level JSON-RPC error member. Also verify at the test server that its request count did not increase.

Guardrails are not a complete agent-security system. A keyword or external content policy cannot determine whether every business action is appropriate. Tool authorization, backend validation, human approval for high-impact workflows, credential scoping, and audit controls remain necessary.

A Defense-in-Depth MCP Request Flow

A strong design rejects bad calls as early as possible and records the result at each boundary.

sequenceDiagram
    participant Client as MCP Client
    participant AISIX as AISIX AI Gateway
    participant Controls as Access, Limits, and Guardrails
    participant Upstream as MCP Server
    participant Telemetry as Usage Pipeline

    Client->>AISIX: tools/call + caller API key
    AISIX->>Controls: Authenticate and authorize tool
    Controls->>Controls: Check caller and per-server limits
    Controls->>Controls: Check applicable Cloud budget
    Controls->>Controls: Inspect input arguments
    alt Access, limit, or budget rejects
        Controls-->>AISIX: Deny
        AISIX->>Telemetry: Record rejected attempt
        AISIX-->>Client: Authorization result or HTTP 429
    else Input guardrail blocks
        Controls-->>AISIX: Block input
        AISIX->>Telemetry: Record guardrail block
        AISIX-->>Client: HTTP 200 + result.isError=true
    else Allowed
        AISIX->>Upstream: Forward with upstream credential
        Upstream-->>AISIX: Tool result
        AISIX->>Controls: Inspect text and structured result values
        alt Output blocked
            AISIX->>Telemetry: Record guardrail block
            AISIX-->>Client: HTTP 200 + result.isError=true
        else Output allowed
            AISIX->>Telemetry: Record successful call
            AISIX-->>Client: Tool result
        end
    end

Validate the flow with a compact security test matrix:

ScenarioExpected gateway behaviorUpstream contacted?Evidence
Disallowed tool nameNeutral authorization errorNoFiltered list and rejected call
Caller or server limit exceededHTTP 429NoRejected usage event
Sensitive input argumentHTTP 200 with failed tool result (isError: true)Noguardrail_blocked=true
Sensitive text or structured value in tool resultWithhold result; return failed tool result (isError: true)YesGuardrail block after upstream latency
Invalid upstream credentialIsolate failure to that serverYesServer/tool failure telemetry without secret value
Clean permitted callReturn normal MCP resultYesSuccessful usage event

Run the tests with non-production markers and a controlled upstream. Confirm the upstream request count, not just the caller response, when verifying that an input policy or limit blocks before routing.

MCP Tool Security FAQ

Do token limits meter MCP tool calls?

No. MCP tool calls contain no model tokens, so use request and concurrency limits. Exhausted model token limits on a shared caller key can still reject later tool calls until the window resets.

Is an AISIX Cloud budget scoped to one MCP server?

No. A budget covers the caller key. Use mcp_rate_limits for independent server-level request and concurrency ceilings.

What MCP content do guardrails scan?

They inspect tools/call arguments before routing, then text content blocks and string values in result structuredContent before delivery. They do not scan initialize, tools/list, structured field names, or the serialized JSON envelope.

Secure the Entire Tool-Call Lifecycle

MCP tool security is not one plugin or one credential. It is a sequence of independent decisions: identify the caller, authorize the tool, constrain request volume, inspect arguments, authenticate to the upstream, inspect the result, and record the outcome.

Start with exact tool grants and separate caller and upstream credentials. Add request and concurrency limits before giving an autonomous agent write-capable tools. Use per-server limits where one dependency is expensive or fragile. Deploy guardrails in monitor mode, validate them against representative tool arguments and results, and then enforce policies with a documented response procedure.

The next step is to make those decisions operationally visible. See MCP Observability: Monitor Every AI Agent Tool Call, or configure the controls directly with the AISIX guides for Upstream Authentication, Rate Limits and Budgets, and MCP Guardrails.

Tags:
Share article link