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.
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 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" }
}
}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 see | It means | The 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 call | Buy it at the checkout address in the error, or use the key of the account that did |
| 401, whoami fails | The key is dead | Mint a new one; check what else used it |
| 403, with a date | The install is paused on this account, stopped by an unpaid invoice, or its subscription has ended | Follow 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 not | Mint a write key, if that is what you meant to allow |
| 429, “a second” or “this minute” | Too fast | Back off; the retry-after header says how long |
| 429, “this install’s plan” | The install’s allowance for the month is used | A larger plan, or wait for the reset |
| 429, “this account has used” | The account’s call ceiling for the month, across every install | Wait for the reset, or move to a plan with a higher one |
| 413 | The request body is over 256 KB | Send less — a page, a projection, a smaller argument |
| 502 | The publisher’s endpoint did not answer, or refused the gateway | Nothing 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.