Blog / AI agents

MCP Server Rate Limiting: Keep Agent Tool Calls in Check

Rate limiting an MCP server means capping how many tool calls an agent can make per minute, and the cap belongs in three places at once: a pacing check in the agent's own code, a per-account limit at the gateway, and respect for whatever the source website tolerates. The gateway limit is the one you can't skip, because an agent loop will find it. This article shows where each limit sits and walks through a loop that hits one. It is written from the side of an agent that consumes a limited gateway; if you are building the limiter into your own MCP server, the short section on that below covers what to enforce.

Why do MCP tool calls need rate limiting at all?

An agent decides how many tool calls to make, and nobody reviews that decision in advance. A model that fans out eight searches per step, over ten steps, produces 80 calls in whatever time the loop takes. Without a limit, that is also your credit balance, the source websites' goodwill and your other users' latency, spent by a prompt.

The Model Context Protocol tools specification defines how a client lists and calls tools, and its security considerations say servers must rate limit tool invocations. It doesn't set a number or a window, so each server picks its own policy (MCP specification, 2026). That's why the limit has to live somewhere you control, not in the model's instructions. "Please make at most one call a second" in a system prompt is a request the model may or may not honour.

Two things make agents different from a normal API client. First, they can retry: nothing stops a model from answering an error by sending the same call again, or a near-identical one, straight away, so plan for it rather than hoping it won't happen. Second, they can parallelise: by default, Claude may call multiple tools in a single response, and it's up to your code whether those run concurrently or one at a time (Anthropic parallel tool use docs, 2026). Both patterns can turn a polite average rate into bursts.

Where should the limit live: client, gateway or source?

Each layer protects something different, so the question isn't which one to pick. The gateway limit protects the account and the service, the client limit protects the agent from wasting its own turns, and the source website's limit is the constraint you inherit and can only respect, not set.

Layer What it protects Who sets it What happens when it trips
Client (agent code) Your own latency, token budget and credit balance You The agent waits or drops a call before it is sent
Gateway (the MCP server and API) The account, other accounts, the service The provider, per account 429 with Retry-After; nothing is billed
Source (the website behind the data) The source's tolerance for traffic The source Can show up as a slow or failed call; you don't control it

The client layer is soft and advisory: you can change it any time, but a bug bypasses it. The gateway layer is hard: it applies to every client, including the one with the bug. If you only build one, build the gateway layer (see the section on building it below). If you consume someone else's gateway, as you do with Perceptlayer, your job is the client layer: read the headers the gateway gives you and stay under them.

What does the Perceptlayer rate limit look like?

Limits are per account, shared by all of the account's keys, and counted per minute (rate limits docs). A free account gets 1 request a second, which is 60 a minute. Each credit pack includes a rate: 5 a second for Starter through Pro, 10 for Scale, 20 for Volume, 25 for Volume 10M and Volume 25M, and 50 for Volume 50M. Paying accounts run at the rate of the credits they are using, and at least 5 a second (300 a minute).

A faster rate can be chosen at checkout for a higher price, up to a maximum that depends on the pack; pricing has the details and what credits cost. Because the limit is per account, a second API key doesn't add capacity. Two agents sharing one account share one budget.

Every response says where you stand:

Header Meaning
RateLimit-Limit Requests allowed per minute
RateLimit-Remaining Requests left this minute
RateLimit-Reset Seconds until the minute resets
RateLimit-Policy The policy the limit follows
Retry-After On 429 and 503: seconds to wait
X-Credits-Used / X-Credits-Remaining What the call cost, and the balance after it

A 429 follows the standard in RFC 6585, and Retry-After is defined in RFC 9110. The error body is {"error": {"code", "message", "request_id"}} with the code rate_limited; the errors docs list the rest.

What happens when an agent loop hits the limit?

On a free account, a loop that issues 80 calls in under a minute is over the budget: the limit is 60 requests a minute, so about 20 calls are rejected with rate_limited (429). Requests are counted per minute, so the first 60 in the minute go through and the rest get rate_limited until RateLimit-Reset says the minute has started over. Rejected calls cost nothing: failed calls return X-Credits-Used: 0.

Here is the loop. An agent researches a topic. At each of 10 steps, the model asks for 8 searches through call_endpoint, using the Google Search API search-light endpoint (1 credit per call).

Quantity Value
Calls the loop wants to make 10 steps × 8 searches = 80 calls
Free-account limit 60 requests a minute
Calls the minute's budget covers 60
Calls over budget, rejected with 429 20
Credits spent on 60 accepted calls 60 calls × 1 credit = 60 credits
Credits spent on 20 rejected calls 20 × 0 = 0 credits
Credits once all 80 eventually succeed 80 calls × 1 credit = 80 credits

Two outcomes are possible, and they differ in cost to you in tokens, not credits. If the tool returns the raw error, the model may retry at once and burn turns on more 429 responses. If the tool or the client waits the Retry-After seconds, the loop finishes a little later and each rejected call is made once more, for 80 credits in total.

On a paying account with at least 5 requests a second (300 a minute), the same 80 calls fit inside the 300-a-minute limit without any waiting. This is why the account's rate, not the number of keys, is the figure to plan around.

How do you pace calls in the client?

Read RateLimit-Remaining and RateLimit-Reset on every response, and sleep when the budget is spent. That keeps most calls from ever seeing a 429. Treat Retry-After as the authority when you do get one. The wrapper below does both, and it is the function you would expose to the model as a tool.

import os
import time

import requests

BASE = "https://api.perceptlayer.com"
HEADERS = {"X-API-Key": os.environ["API_KEY"]}


resume_at = 0.0  # when the minute's budget is back, after a call used it up


def search(query: str, max_tries: int = 4) -> dict:
    global resume_at
    time.sleep(max(0.0, resume_at - time.monotonic()))
    wait, remaining = 0, 0
    for attempt in range(max_tries):
        r = requests.get(
            f"{BASE}/v1/google-search/search-light",
            params={"query": query},
            headers=HEADERS,
            timeout=30,
        )
        if r.status_code in (429, 503):
            # Failed calls cost 0 credits, so waiting and retrying is safe.
            wait = int(r.headers.get("Retry-After", 2 ** attempt))
            remaining = int(r.headers.get("RateLimit-Remaining", 0))
            if attempt < max_tries - 1:
                time.sleep(wait)
            continue
        r.raise_for_status()  # 402 insufficient_credits, 422, etc. stop here
        if int(r.headers.get("RateLimit-Remaining", 1)) == 0:
            # Budget used up: return this result now, and wait before the next call.
            resume_at = time.monotonic() + int(r.headers.get("RateLimit-Reset", 1))
        return r.json()
    # Out of retries: hand the model a small result instead of raising.
    return {
        "code": "rate_limited",
        "retry_after_seconds": wait,
        "calls_remaining": remaining,
    }

Three details matter. A requests.Timeout isn't caught on purpose: the request may have succeeded and been billed, because requests carry no idempotency key. Decide per call whether a repeat is acceptable; retrying without paying twice covers the patterns. A 402 (insufficient_credits) isn't retried either, since the service is prepaid and nothing tops up automatically. And max_tries bounds the loop, so a stuck limit ends in a small result the model can act on, not a spin.

For concurrent tool calls, put a semaphore or a shared token bucket around search, sized so a burst from one model turn can't use up the minute's budget on its own.

What should the model see when a limit trips?

Give the model a short, actionable result and keep the retry logic out of its context. A model reasons well about "rate limited, wait 12 seconds, 31 searches still pending" and badly about a raw traceback or an HTML error page. Handle the wait in code when you can; surface it to the model only when waiting changes the plan.

When retries run out, the search function above returns this instead of raising:

{
  "code": "rate_limited",
  "retry_after_seconds": 12,
  "calls_remaining": 0
}

code tells the model what kind of problem it is, retry_after_seconds is the Retry-After value (12 here is an illustration), and calls_remaining is the RateLimit-Remaining budget for this minute. If you expose search through an MCP server, report it as a tool execution error (isError: true), which the specification says clients should pass to the model so it can recover (MCP tools specification, 2026). Don't include request or response bodies in logs; log the request_id from the error body instead, which is what support needs to trace a call.

If you run the Perceptlayer MCP server at https://api.perceptlayer.com/mcp (Streamable HTTP), remember that limits are per account, so pace it the way you would any other client of your key, and call_endpoint reports credits used and remaining on each call. search_endpoints and get_endpoint only look up the catalogue and cost nothing in credits. Your client-side pacing still matters, because the server can only say no; it can't make the agent plan fewer calls. The AI agents docs cover setup, and MCP server for web scraping shows the three tools in use.

What if you are building the limiter into your own MCP server?

Enforce three things on the server, keyed by account rather than by connection or API key. Count requests per window and reject over-budget calls before doing any work. Return the same result shape on every rejection: a code, the seconds to wait and the budget left. Only charge for calls that succeed, so a rejected call is free for the caller. A token bucket or fixed window both work; the choice mostly decides how bursts are treated. Behind a gateway, the gateway usually does the counting and the MCP server just passes the headers through.

Rate limits cap speed, not spend. For a ceiling on total credits per run, pair pacing with a budget, as described in capping web data spend for an agent. The credits docs explain how debits work.

APIs in this post

Frequently asked questions

Do rejected requests cost credits?

No. Only a successful response debits credits, and a failed call returns X-Credits-Used: 0. A 429 is a failed call, so a loop that gets rate limited and waits spends the same total as one that was never limited: each successful call costs the credits shown on its endpoint, which is 1 credit for nearly all of them.

Does a second API key raise my rate limit?

No. Limits are per account and shared by all of its keys, so extra keys don't add capacity. To go faster, use a pack with a higher included rate, or choose a faster rate at checkout up to the pack's maximum. The pricing page lists the details, and the rate limits docs explain how the rate follows the credits in use.

How long should the agent wait after a 429?

Wait the number of seconds in the Retry-After header. If it's missing, fall back to exponential backoff with a small cap. RateLimit-Reset tells you how many seconds remain until the minute resets, which is a useful upper bound when you are pacing before a 429 happens.

Is it safe to retry a call that timed out?

Not automatically. Requests have no idempotency key, so a request whose reply was lost may have succeeded and been billed. Retry on a timeout only when the call is cheap to repeat, such as a 1-credit search, and keep a cap on attempts. For a deeper treatment, see the article on retrying without paying twice.

Where do I start?

Get a free key at signup: 1,000 free credits every month after verifying an email address, no credit card. Then run the search-light call from this article in a loop of your own and watch RateLimit-Remaining fall on each response. The Search Light endpoint page has a playground for a first call.

  1. MCP Server Too Many Tools: Keeping Agent Context Small

    One tool per endpoint for 1,659 endpoints is about 567,000 characters of schema. A 10-step checklist to keep an MCP server's tool list small.

  2. MCP Tool Call Error Handling: When an Agent Should Retry

    Each call_endpoint error code mapped to one agent action (fix, wait, stop or ask the user), with the exact error text the model sees and a system prompt.

  3. MCP Server for Web Scraping: Tools Agents Can Call

    An MCP server gives an agent three tools to find, inspect and call web endpoints. Use the scraper for uncatalogued pages, structured endpoints for the rest.

Start with 1,000 free credits

No password to set. We email you a sign-in link, and your key works on every API.

No credit card. 1,000 free credits a month.