MCP Error -32602: Every Error Code Change in the 2026-07-28 Spec
Nikhil Tiwari
MCP Playground
TL;DR
- Resource not found is now
-32602, not-32002. Landed in the 2026-07-28 spec via SEP-2164. - The server-error range was repartitioned.
-32000–-32019stays implementation-defined;-32020–-32099is now reserved for the spec. - Three codes were renumbered: HeaderMismatch, MissingRequiredClientCapability, and UnsupportedProtocolVersion all moved.
- Clients should accept both
-32602and-32002during the transition — but gate on the method name. - Never return an empty
contentsarray for a missing resource. The spec forbids it.
I lost most of a Tuesday to MCP error -32602.
My client worked against a TypeScript server. The same code, pointed at a Go server, silently stopped detecting missing resources.
Nothing crashed. No stack trace. The error just stopped matching my handler.
The cause turned out to be embarrassing. The two servers returned different error codes for an identical condition.
That was not a bug in either one. Until the 2026-07-28 spec, the SDKs genuinely disagreed.
That revision fixed it — and quietly repartitioned the entire JSON-RPC server-error range while it was in there. Most migration guides skipped the second part.
If your server defines a custom error code, there is a real chance it is now sitting in reserved space.
What MCP Error -32602 Actually Means
-32602 is not an MCP invention. It comes from the JSON-RPC 2.0 specification, where it means "Invalid params."
The method you called exists. The arguments you passed do not satisfy it.
In MCP that now covers three distinct situations.
- A tool call whose arguments fail the tool's JSON schema. Wrong type, missing required field, bad enum value.
- Structured output that does not match the declared output schema.
- A
resources/readfor a URI that does not exist. This is the new one.
That third case is what trips people up. A missing resource is now framed as an invalid parameter — you passed a URI pointing at nothing.
It reads oddly at first. It is the correct JSON-RPC framing, and it avoids inventing a custom code.
Retrying a -32602 will always fail. The parameters are invalid and will still be invalid on attempt two. I have watched a client burn its entire backoff budget re-sending a request that was never going to succeed.
Why -32002 and -32602 Both Mean "Resource Not Found"
Here is the history, because it explains every confusing thing about this error.
The original spec recommended -32002 for resource-not-found. That looked reasonable. It was also wrong, for a specific reason.
JSON-RPC reserves the -32000 to -32099 band for implementation-defined server errors. That range belongs to your application.
It was never meant for protocol-level semantics. A condition every MCP server can hit is protocol-level by definition.
SEP-2164 moved it to -32602, and the change shipped in the 2026-07-28 revision. The changelog entry is blunt: "Change resource not found error code from -32002 to -32602 (Invalid Params) to align with JSON-RPC specification."
Four Codes, One Condition
This is the part that cost me a day. The official SDKs never agreed with each other.
For the exact same condition — reading a resource that does not exist — here is what each SDK sent before the standardization:
| SDK | Error code returned |
|---|---|
| TypeScript | -32602 (InvalidParams) |
| Python | 0 (generic) |
| C# | -32002 (custom) |
| Rust | -32002 |
| Java | -32002 |
| Go | -32002 |
| PHP | -32002 |
| Kotlin | -32603 (INTERNAL_ERROR) |
| Ruby | left to the implementor |
| Swift | no built-in handler |
Four different codes across the eight SDKs that handled it at all. Five sent -32002. One sent -32602. One sent -32603. One sent 0.
That is why my client "broke" when I switched servers. It never broke. It was written against one SDK's dialect.
If you have ever thought "my MCP client works with some servers and not others" — this table is very likely your answer.
Which dialect does your server speak?
Paste a URL, call a resource that doesn't exist, read the raw JSON-RPC error frame.
Test any MCP server free →The Error Code Range Was Repartitioned Too
This is the change almost nobody covered, and it is the one most likely to bite a server author.
The 2026-07-28 spec split the JSON-RPC server-error range in two.
-32000to-32019— still implementation-defined. Existing SDK usage is explicitly grandfathered.-32020to-32099— now reserved for the MCP specification.
If you invented a custom error code anywhere in that upper band, you are now squatting on space the spec has claimed. Nothing breaks today. It will collide eventually.
Three spec codes were renumbered to fit the new policy:
| Error | Was | Now |
|---|---|---|
| HeaderMismatch | -32001 | -32020 |
| MissingRequiredClientCapability | -32003 | -32021 |
| UnsupportedProtocolVersion | -32004 | -32022 |
These were introduced during the 2026-07-28 draft cycle, so you only hit them if you built against a release candidate. If you did, grep for the old literals.
How to Fix -32602 as a Client Author
The migration guidance is explicit: treat both -32602 and -32002 as resource-not-found during the transition.
Do not pick one. Accept both.
const RESOURCE_NOT_FOUND = new Set([
-32602, // canonical, per SEP-2164
-32002, // legacy: C#, Rust, Java, Go, PHP
]);
function isResourceNotFound(err, method) {
if (method !== 'resources/read') return false;
return RESOURCE_NOT_FOUND.has(err.code);
}
Notice the method guard. It matters more than the code check.
-32602 is heavily overloaded — it also means "your tool arguments failed validation."
Without gating on resources/read, you will classify schema failures as missing resources. That is a worse bug than the one you set out to fix.
For the two stragglers you need a fallback.
- Kotlin servers sent
-32603, which genuinely means internal error. Mapping that to not-found would swallow real crashes. - Python servers sent
0, which means nothing at all.
For both, match on the message string as best-effort. Log when you hit that path so you can delete it once the SDKs catch up.
The silent failure to watch for: if your client hardcodes if (err.code === -32002), that branch is now dead against any updated server. No exception, no warning — it just stops being taken.
How to Fix -32602 as a Server Author
Your obligation is short. Return -32602 when the requested resource does not exist. The spec language is MUST.
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32602,
"message": "Resource not found",
"data": {
"uri": "file:///nonexistent.txt"
}
}
}
Put the missing URI in data. The spec marks this SHOULD, and skipping it is a false economy.
A client that gets the URI back can log exactly what failed. One that does not gets "something wasn't found" and no way to act.
If you maintain a server on C#, Rust, Java, Go, or PHP, you were emitting -32002 unless you overrode it. Check your SDK version — the fix may arrive on your next bump.
The Empty Contents Array Trap
This one is subtle, and the spec calls it out directly. Servers MUST NOT return an empty contents array for a resource that does not exist.
It looks harmless. Return { "contents": [] } and let the client work it out.
The problem is that the response is ambiguous. An empty array can mean two different things.
- The resource exists and genuinely has no content.
- The resource does not exist at all.
The client cannot tell them apart. A "success" response for a missing resource is worse than an error — it fails silently, and silent failures surface three layers downstream.
What Else Changed in 2026-07-28
The error codes were a minor entry in a revision that rewrote the transport. If you are debugging after an upgrade, you probably picked up several of these at once.
- Sessions are gone. The
Mcp-Session-Idheader was removed entirely. - The handshake is gone. No more
initialize/notifications/initialized. Every request carries its protocol version and capabilities in_meta. server/discoveris mandatory. Servers MUST implement it to advertise supported versions, capabilities, and identity.subscriptions/listenreplaces the HTTP GET endpoint andresources/subscribe.ping,logging/setLevel, andnotifications/roots/list_changedwere removed.- All results now require a
resultTypefield —"complete"or"input_required". - MRTR replaces server-initiated requests like
sampling/createMessageandelicitation/create. - SSE resumability is gone. No
Last-Event-ID; a broken stream means re-issuing the request with a new ID. - Roots, Sampling, and Logging are deprecated, as is HTTP+SSE and OAuth Dynamic Client Registration.
The stated goal is that any request can now land on any server instance behind a plain round-robin load balancer.
Debug these one at a time. Bump your SDK, verify, then change transport behaviour. Doing both at once is how a one-hour fix becomes a lost Tuesday.
Not migrated yet? Start with the 2026-07-28 migration guide, or read what changed across the whole 2026 roadmap. Building fresh? Use the 2026 spec server guide.
MCP Error Code Reference
| Code | Name | Meaning in MCP |
|---|---|---|
-32700 | Parse error | Malformed JSON. Usually a serialization bug. |
-32600 | Invalid request | Valid JSON, invalid JSON-RPC envelope. |
-32601 | Method not found | Method doesn't exist. Check for version drift. |
-32602 | Invalid params | Bad arguments — or a resource that doesn't exist. |
-32603 | Internal error | Server-side crash. Kotlin also sent this for not-found. |
-32000–-32019 | Implementation-defined | Yours to use. -32000 is commonly "connection closed." |
-32020–-32099 | Reserved for MCP | Do not use for custom errors. |
For connection-level failures — -32000, timeouts, spawn errors, 406s — I wrote a separate troubleshooting guide. Auth failures have their own guide.
New to the protocol? Start with what the Model Context Protocol actually is, or see how MCP differs from a REST API.
How MCP Playground Helps
Reproducing this locally means wiring a client, pointing it at a server, and reading logs.
I built MCP Playground to skip that. Paste a server URL in the browser, call resources/read against a URI you know doesn't exist, and read the raw JSON-RPC error frame — code, message, and data payload.
That tells you in seconds which dialect a server speaks, before you write a line of client code against it.
See the real error frame in your browser
No install. No signup for the basic test.
Test any MCP server free → Scan your MCP server →Frequently Asked Questions
What does MCP error -32602 mean?+
Should I retry on -32602?+
Is -32002 deprecated?+
Should my client handle both -32002 and -32602?+
Can I still use custom error codes?+
Can I return an empty contents array instead of an error?+
Wrapping Up
-32602 is the canonical MCP code for resource-not-found, and -32002 is the legacy code five official SDKs still emit. Accept both on the client, gated on the method name. Send -32602 with the missing URI in data on the server, and never an empty contents array.
Then check your custom error codes. If any sit between -32020 and -32099, move them down.
Test any MCP server free → — paste a URL, call a missing resource, and see exactly which code comes back.
Written by Nikhil Tiwari
15+ years in product development. AI enthusiast building developer tools that make complex technologies accessible to everyone.
Free MCP Tools (no install)
Build, compare & ship MCP agents — free
Connect any MCP server, compare 40+ models side-by-side, deploy hosted servers, and save reusable agents you can export as an API — all in your browser.
✦ Free credits on sign-up · no credit card needed