Secure MCP Tool Calls with Guardrails and Rate Limits
August 14, 2026
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
structuredContentbefore results return to the client. - A guardrail block returns HTTP
200with a failed MCP tool result markedisError: 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:
- The MCP client authenticates to the gateway.
- 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_type | Upstream behavior | Typical use |
|---|---|---|
none | Sends no credential | Isolated internal or local test server |
bearer | Sends Authorization: Bearer <secret> | Static service token |
api_key | Sends x-api-key: <secret> | API-key-protected service |
oauth2 | Obtains and caches a client-credentials token | Machine-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
structuredContentand 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:
| Scenario | Expected gateway behavior | Upstream contacted? | Evidence |
|---|---|---|---|
| Disallowed tool name | Neutral authorization error | No | Filtered list and rejected call |
| Caller or server limit exceeded | HTTP 429 | No | Rejected usage event |
| Sensitive input argument | HTTP 200 with failed tool result (isError: true) | No | guardrail_blocked=true |
| Sensitive text or structured value in tool result | Withhold result; return failed tool result (isError: true) | Yes | Guardrail block after upstream latency |
| Invalid upstream credential | Isolate failure to that server | Yes | Server/tool failure telemetry without secret value |
| Clean permitted call | Return normal MCP result | Yes | Successful 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.


