Engineering 8 Aug 2026 · 5 min read

Every error an MCP gateway returns, and what to do about each one

A handful of checks run before your call reaches a publisher. The first one that fails is the error you get — and knowing the order tells you exactly where to look.

MR mcprush team · The people who run the gateway

When a tool call fails through a gateway, the model reports "the tool failed" and moves on, which is the least useful sentence in software. Underneath, exactly one check refused it — and the checks run in a fixed order, so the first that fails is the error you get and none of the later ones ran.

That ordering is the whole debugging trick. If you know which check produced the code, you know which half of the system to stop looking at.

The admission path. Everything left of a refusal ran; everything right of it did not, and the publisher never saw the call.The admission path. Everything left of a refusal ran; everything right of it did not, and the publisher never saw the call.
The admission path. Everything left of a refusal ran; everything right of it did not, and the publisher never saw the call.

The checks, in order

  • 1 · Is the key live? Revoked, expired or unknown → 401. This is the only check that runs before anybody knows which server you meant.
  • 2 · Is the listing still routable through us? Off the catalogue → 404; frozen while a report about it is looked at → 403; no verified endpoint behind it → 503. The version pinned in the address is read off it and does not change the answer — no release is refused here for being withdrawn or out of date.
  • 3 · Does this account hold the install? An install belongs to an account, not to a machine. A live key minted on an account that never installed a paid listing fails here with a 403 — the key is fine, the install is missing. A free listing behind the gateway is taken on the first call instead, so this check never refuses one.
  • 4 · Is the entitlement current? A paused install, or a cancelled one whose paid period has ended → 403, naming the date it lapsed. A publisher’s endpoint that has been failing → 503, held for a moment rather than piled onto.
  • 5 · Is the account inside its rate? The per-second limit from the account’s plan → 429 with a retry-after header. It runs before the tool is even looked at, so a refusal costs the same as a call. The handshake — initialize, tools/list and the rest — has a wider bucket of its own, so connecting never trips it.
  • 6 · Is the tool one the listing publishes? A name the listing does not publish → 404; a tool that writes, called with a read-only key → 403; a tool a release added that nobody on the account has accepted yet → 403, and it waits under Changes.
  • 7 · Is there allowance left? Two ceilings, both hard and both counted in calls: the account’s calls for the month, and the plan’s calls on this listing. Over either → 429, refused before the call is made. Then the plan’s own per-minute rate, if the publisher sold one → 429 with retry-after. The money cap is a separate thing, enforced at checkout rather than here.
  • 8 · Is the body a size a server would read? Over 256 KB → 413, without a publisher ever seeing it.

Only then does the call reach the publisher. What comes back is counted on the way out — and a failure on their side does not count, because a counter that records requests rather than work is a counter that bills you for their outage. Nor does the handshake, a list, or a call the publisher answered with an error: what is counted is a tool call that did work. A 502 is the one code that is never yours: the endpoint did not answer, answered with more than 256 KB, or refused the gateway’s own credential — and nothing is charged for any of the three.

Reading the three you will actually see

401 is a key question and nothing else: revoked, expired, or a token we do not know. An install question answers 403, so if whoami works and the call still fails, stop looking at the key — usually the install sits on a different account than the one the key was minted on.

403 is the entitlement answer as well, and it names the date: an install paused on this account, one stopped by an unpaid invoice, or one cancelled with its paid period behind it — each with the one link that lifts it, in the sentence and in data.fix. It is also the check a local server cannot have, which is the whole reason a local server cannot honestly carry a subscription.

429 is three different things wearing one status code: a rate limit (wait), the install’s allowance for the month (a larger plan), or the account’s ceiling for the month (a larger plan, or the reset). All three carry the same code, -32003, so the message is what tells them apart.

{ "jsonrpc": "2.0", "id": 41, "error": { "code": -32003, "message": "This install's plan includes 6,000 calls a month and they are used until 1 Nov 2026. Move it to a larger plan at https://mcprush.com/manage/<listing> and the next call goes through; nothing is charged for this call.", "data": { "state": "allowance", "fix": "https://mcprush.com/manage/<listing>", "resetsOn": "1 Nov 2026" } } }
A code, a sentence and two fields: data.state says which wall it is, data.fix is the one link that lifts it, so an agent can branch without parsing the sentence.

This is worth reading closely when something sits in front of your calls. A rate refusal here carries a retry-after of a second or a few, so a client knows how long to wait; an allowance or ceiling refusal carries one that runs to the start of next month, because a second’s wait is not what fixes it — the plan in data.fix is. An agent that gets a bare 429 tries again, and again, and files a support ticket in your voice.

The two ceilings, and only one of them is a 429

Teams that set only the money one find out that a monthly maximum does not stop the maximum being spent in four minutes. Teams that set only the call one find out that eleven small subscriptions add up while every single install stayed inside its allowance.

What to do about each

You seeIt meansThe fix
403, “has not bought it”The listing is paid and this account does not hold it; a free one is taken onto the account by its first callBuy it at the checkout address in the error, or use the key of the account that did
401, whoami failsThe key is deadMint a new one; check what else used it
403, with a dateThe install is paused on this account, stopped by an unpaid invoice, or its subscription has endedFollow the link in the error: Resume on the install’s page, pay the invoice under Billing, or subscribe again at the checkout
403, “this key is read-only”The tool writes and the key does notMint a write key, if that is what you meant to allow
429, “a second” or “this minute”Too fastBack off; the retry-after header says how long
429, “this install’s plan”The install’s allowance for the month is usedA larger plan, or wait for the reset
429, “this account has used”The account’s call ceiling for the month, across every installWait for the reset, or move to a plan with a higher one
413The request body is over 256 KBSend less — a page, a projection, a smaller argument
502The publisher’s endpoint did not answer, or refused the gatewayNothing on your side; the listing’s status shows it, and nothing was charged

The debugging order that saves the most time

  • whoami — is the key alive, and whose is it?
  • list — does the account hold this install, and is it paused?
  • What the listing publishes now — is the tool you are calling still one of its tools?
  • The call log — did the call reach the publisher at all, or was it refused before it left?

Four steps, in that order, and they follow the admission path exactly. That is not a coincidence: any check that cannot be inspected afterwards is a check nobody can debug, which is a reasonable thing to demand of whatever sits in the middle of your calls.