execute_code* at 60 a minute and 500 an
hour. The first rule whose pattern matches wins.
That choice — executions rather than requests — is what makes the policy worth
having and also what makes it partial. The gateway can only count an execution it
sees. Read Limits before you rely on this for anything expensive.
Configure the policy
Open Policies → Library → Per-Tool Rate Limiter.Rules
Add rule appends a rule card. Rules are evaluated in order and the first whose tool pattern matches wins — a later rule for the same tool is dead. Put the specific patterns above the catch-all.
A rule with several windows enforces all of them: 60 a minute and 500 an
hour means a burst is stopped by the first and a slow grind by the second.
The windows are fixed, not sliding. A window opens when the tool’s counter is
first charged and closes exactly its duration later, at which point the counter
disappears and the whole allowance returns at once.With 10 per hour, if the counter opened at 10:00 it closes at 11:00. Ten
executions at 10:59 exhaust it — and ten more at 11:01 are all allowed, because
by then the window is a new one. Twenty executions in two minutes, inside a
policy that reads as ten an hour. Size the window for the burst you can tolerate,
not the average you want.
Behavior when exceeded
Three choices. On MCP only the first exists.
Default behaviour at the bottom of the form applies to every rule left on
Use default behavior. It starts at Reject.
Scope
This form has no scope of its own. The policy’s own scope is the control — the Requests from and Resources you set alongside the rules. Resources decides which MCP servers and tools the policy runs on at all; Requests from decides how the counters are partitioned.
That choice is not cosmetic. One shared counter is an organisation-level ceiling
on a tool nobody can opt out of; one counter per application keeps a single
misbehaving agent’s problem to itself. Decide which of the two you want before
you set the numbers, because the same limit means something different under
each — and One execution, counted twice, below, is a case where it changes the
answer outright.
Mode
This policy has only one mode, and the console shows no mode selector for it. It always enforces. There is no Observe, so there is no way to find out what it would do before it does it. Roll it out with a generous limit, watch what it does on the Policies tab of the requests in Activity, and tighten from there — that is the nearest thing to a dry run this policy offers.How the decision is made
The counter is charged when the gateway observes an execution, and it is checked on the next request. The two are separate moments, and which request is which depends on whether the traffic is chat or MCP.1
On LLM, an execution is a tool result coming back
The gateway reads the incoming conversation, finds the most recent turn in which
the assistant proposed tool calls, and looks at the messages after it. Each
result answering one of those proposals is one execution, and charges every
window of the matching rule.Each execution is charged once. The gateway recognises a result it has
already counted — an agent retrying, or re-sending its history — and ignores the
repeat for as long as the rule’s longest window.
2
On MCP, an execution is the tool call itself
The gateway is the caller, so it knows the call really ran. Every MCP tool call
that reaches the upstream charges the windows, whether or not it was tied to the
model’s own tool call.
3
The tools the request declares are checked against their counters
On LLM, only the tools the request declares are checked. On MCP, the single tool
the call names.
4
A tool at or above any of its windows triggers its behaviour
Reject refuses the whole request. Strip tool removes that tool from the
request and lets the rest through. Inject error lets the request reach the
model and removes the tool call from the answer instead.
One execution, counted twice
A tool an agent runs through MCP shows up twice at the gateway: once as the MCP call that executes it, and again as the result the agent reports on its next LLM turn. When Requests from is All traffic both hit the same counter, so one execution is charged twice. Narrowing Requests from to Applications is the fix you can make from the console, and the one to reach for: there the two sides keep separate counters and the problem does not arise at all. The other fix is not a console setting — it is a change inside the agent, so this part is for whoever maintains it rather than for you. The agent can tell the gateway that the MCP call and the tool result are the same execution by sending the identifier the model gave that tool call alongside the MCP call, in the call’s metadata under the keyai.neuraltrust/toolCallId.
The identifier has to match the model’s exactly. One longer than 128 characters,
or carrying anything but letters, digits, hyphens and underscores, is quietly
ignored — a malformed one costs you the pairing, not the call.
Only the LLM side can be left uncharged this way, and only by an identifier the
MCP side already claimed. The MCP call itself always charges. Reusing one identifier
across several real MCP calls buys no free executions: each of those really ran,
and each is charged.
What the caller receives
Reject on LLM traffic is429 with these headers, where <scope> is
global when Requests from is All traffic and consumer when it names
applications or groups:
and a refusal naming the tool that ran out of allowance.
On MCP traffic it is an error carried inside a
200 response, under the code
-32004, with the same headers. The upstream tool is never invoked.
Strip tool and Inject error both return 200. Nothing tells the caller a
policy acted, apart from the record in Activity — and, for Inject error,
the assistant message the gateway wrote.
Modes and failures
There is only Enforce. The column is here to be read against the other policies, not because there is a choice.Limits
- No Observe mode. You cannot dry-run this policy. Start loose and tighten.
- On LLM, only declared tools are enforced. The check reads the tools the request declares. A client that stops declaring a tool — or never declares one, because the model is being asked to answer in prose — is not checked at all, however exhausted that tool’s counter is.
- On LLM, only reported executions are counted. The gateway learns a tool ran by seeing its result come back in a later request. A tool the agent executes and never reports through the gateway is free.
- Only the latest turn’s results are counted. Results from earlier turns in the same conversation are skipped, so a history replayed after the window reopened is not re-counted — and a straggling result reported several turns late is not counted at all.
- The windows are fixed, not sliding. See the note above.
- Inject error cannot act on a streaming response, and silently becomes Strip tool.
- On MCP only Reject exists, and a policy configured otherwise cannot be attached to an MCP application.
- Reading a counter and charging it are separate steps, so a burst of simultaneous executions can overshoot a window slightly.
- The counters live in the gateway’s shared store. If it is cleared, every window reopens.
Verify the policy
Set a rule to 1 per minute on a tool you can trigger on demand, then send these in this order. The counting model means you need three turns, not two, and the middle one is the one people forget.1
Control — the path works at all
In the Playground, pick the application the policy applies to, and send a
prompt that makes the model propose your tool. Expect a normal reply carrying the
tool call. If this fails, the problem is the application or the model, not the
policy. Fix it before going on.
2
Report an execution — the counter is charged
The Playground cannot charge the counter. The gateway only learns a tool ran
when it sees the result reported back, and a result is something an agent sends,
not something you type into a prompt box.So this middle turn has to come from whatever already calls this gateway: your
agent running the tool it was just asked for, and reporting the result the way it
normally does. Expect nothing to be refused on that turn — it is the turn that
charges the counter. It is also the one people forget, which is why the check
below so often seems never to fire.
3
The limit acts
Back in the Playground, send the first prompt again.With Reject, expect
429 and a refusal naming your tool. To confirm the
request never reached the model, open Activity, click the request, open its
Policies tab and read Aggregated Latency: the Provider row should be
empty.With Strip tool, expect 200 and a reply that behaves as though the tool did
not exist; the tool is gone from what the model received.With Inject error, expect 200, no tool call in the answer, and an assistant
message naming the rate-limited tool.4
If you run both chat and MCP — one execution, one charge
Set the rule to 2 per minute. Have your agent run the tool once over MCP,
sending the model’s tool-call identifier with it as described above, and then
report that same execution on its next LLM turn. A second MCP call must still be
allowed: a budget of two owes one more execution.If that second call is refused, the two sides are not being recognised as one
execution — the identifier is not arriving, or does not match the model’s
exactly. That is agent-side, and not something the console can show you.
Troubleshooting
Related
- Rate Limiter — a ceiling on requests rather than tool executions
- Tool Injection — putting a tool into every request rather than taking one out
- Policies overview — scope, modes and policy chains
- Event schema — the fields each decision records