Return -32602 for resource not found (SEP-2164) (#2920)

This commit is contained in:
Marcelo Trylesinski 2026-06-20 15:55:23 +01:00 • committed by GitHub
commit f253682393
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 113 additions and 43 deletions

View file

@ -1142,8 +1142,10 @@ REQUIREMENTS: dict[str, Requirement] = {
),
"mcpserver:resource:read-throws-surfaced": Requirement(
source="sdk",
behavior="A resource function that raises is surfaced to the caller as a JSON-RPC error response.",
arm_exclusions=(ArmExclusion(reason="modern-error-surface", spec_version="2026-07-28"),),
behavior=(
"A resource function that raises is surfaced to the caller as a JSON-RPC error response "
"(-32603 Internal error), with the original exception text withheld."
),
),
"mcpserver:resource:static": Requirement(
source="sdk",
@ -1161,14 +1163,10 @@ REQUIREMENTS: dict[str, Requirement] = {
),
"mcpserver:resource:unknown-uri": Requirement(
source=f"{SPEC_BASE_URL}/server/resources#error-handling",
behavior="resources/read for a URI matching no registered resource returns JSON-RPC error -32002.",
divergence=Divergence(
note=(
"The spec reserves -32002 for resource-not-found; MCPServer raises ResourceError, which "
"the low-level server converts to error code 0."
),
behavior=(
"resources/read for a URI matching no registered resource returns JSON-RPC error -32602 "
"(invalid params) with the requested URI in error.data, per SEP-2164."
),
arm_exclusions=(ArmExclusion(reason="modern-error-surface", spec_version="2026-07-28"),),
),
# ═══════════════════════════════════════════════════════════════════════════
# Prompts

View file

@ -112,10 +112,7 @@ async def test_read_templated_resource(connect: Connect) -> None:
@requirement("mcpserver:resource:unknown-uri")
async def test_read_unknown_uri_is_error(connect: Connect) -> None:
"""Reading a URI that matches no registered resource fails with a JSON-RPC error.
The spec reserves -32002 for resource-not-found; see the divergence note on the requirement.
"""
"""Reading a URI that matches no registered resource fails with -32602 and the URI in data (SEP-2164)."""
mcp = MCPServer("library")
@mcp.resource("config://app")
@ -127,16 +124,17 @@ async def test_read_unknown_uri_is_error(connect: Connect) -> None:
with pytest.raises(MCPError) as exc_info:
await client.read_resource("config://missing")
assert exc_info.value.error == snapshot(ErrorData(code=0, message="Unknown resource: config://missing"))
assert exc_info.value.error == snapshot(
ErrorData(code=-32602, message="Unknown resource: config://missing", data={"uri": "config://missing"})
)
@requirement("mcpserver:resource:read-throws-surfaced")
async def test_resource_function_that_raises_is_surfaced_as_a_jsonrpc_error(connect: Connect) -> None:
"""An exception raised by a resource function reaches the caller as a JSON-RPC error.
MCPServer wraps the failure in a generic error that names only the URI, so the original
exception text is not leaked to the client. The wrapped exception becomes error code 0 the
same way every other unhandled server-side exception does.
MCPServer wraps the failure in a generic ResourceError that names only the URI, so the original
exception text is not leaked to the client. The wrapped exception surfaces as -32603 Internal error.
"""
mcp = MCPServer("library")
@ -148,7 +146,9 @@ async def test_resource_function_that_raises_is_surfaced_as_a_jsonrpc_error(conn
with pytest.raises(MCPError) as exc_info:
await client.read_resource("res://boom")
assert exc_info.value.error == snapshot(ErrorData(code=0, message="Error reading resource res://boom"))
assert exc_info.value.error == snapshot(
ErrorData(code=-32603, message="Error reading resource res://boom", data={"uri": "res://boom"})
)
@requirement("mcpserver:resource:duplicate-name")