Skip to content

Commit bd4195d

Browse files
committed
fix(server): return METHOD_NOT_FOUND for unregistered spec methods (#3193)
1 parent 6f69a37 commit bd4195d

3 files changed

Lines changed: 10 additions & 12 deletions

File tree

src/mcp/server/runner.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -186,15 +186,9 @@ async def _inner(ctx: ServerRequestContext[LifespanT, Any]) -> HandlerResult:
186186
# Read method/params off `ctx` so a middleware that rewrote them via
187187
# `call_next(replace(ctx, ...))` reaches lookup and the handler.
188188
method, params = ctx.method, ctx.params
189-
# Pinned compat: spec methods are surface-validated before lookup,
190-
# so malformed params are INVALID_PARAMS even with no handler
191-
# registered. Custom methods miss the monolith map and fall through
192-
# to `entry.params_type` exactly as before.
193-
if method in _methods.SPEC_CLIENT_METHODS:
194-
try:
195-
_methods.validate_client_request(method, version, params)
196-
except KeyError:
197-
raise MCPError(code=METHOD_NOT_FOUND, message="Method not found", data=method) from None
189+
# Spec version gate: if a spec method is not valid at this protocol version, reject it.
190+
if method in _methods.SPEC_CLIENT_METHODS and (method, version) not in _methods.CLIENT_REQUESTS:
191+
raise MCPError(code=METHOD_NOT_FOUND, message="Method not found", data=method)
198192
# TODO(L29): the 2026-07-28 spec drops the handshake; this branch and
199193
# the gate become a per-version legacy path then. Initialize runs inline
200194
# (read loop parked), so awaiting the peer anywhere on this path deadlocks.
@@ -211,6 +205,8 @@ async def _inner(ctx: ServerRequestContext[LifespanT, Any]) -> HandlerResult:
211205
if not self.connection.initialize_accepted and method not in _INIT_EXEMPT:
212206
# Pinned compat: the same error shape the union validation produced.
213207
raise MCPError(code=INVALID_PARAMS, message="Invalid request parameters", data="")
208+
if method in _methods.SPEC_CLIENT_METHODS:
209+
_methods.validate_client_request(method, version, params)
214210
# Absent params validate as {} (required fields still reject), so
215211
# the handler receives the model with its defaults, never None.
216212
typed_params = entry.params_type.model_validate({} if params is None else params, by_name=False)

tests/interaction/lowlevel/test_wire.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ async def test_malformed_request_params_are_answered_with_invalid_params() -> No
205205
integer. Reserve this pattern for behaviour the typed API cannot produce.
206206
"""
207207
server = Server("strict")
208+
server.add_request_handler("tools/call", CallToolRequestParams, lambda ctx, p: None)
208209
errors: list[ErrorData] = []
209210

210211
async with create_client_server_memory_streams() as (client_streams, server_streams):

tests/server/test_runner.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,12 @@ async def test_runner_non_spec_method_with_no_handler_raises_method_not_found(se
295295

296296

297297
@pytest.mark.anyio
298-
async def test_runner_malformed_params_for_unregistered_spec_method_raises_invalid_params(server: SrvT):
299-
"""A spec method with malformed params is INVALID_PARAMS even with no handler."""
298+
async def test_runner_unregistered_spec_method_raises_method_not_found(server: SrvT):
299+
"""An unregistered spec method returns METHOD_NOT_FOUND per JSON-RPC 2.0 (issue #3193)."""
300300
async with connected_runner(server) as (client, _):
301301
with pytest.raises(MCPError) as exc:
302302
await client.send_raw_request("tools/call", {"name": 123})
303-
assert exc.value.error == ErrorData(code=INVALID_PARAMS, message="Invalid request parameters", data="")
303+
assert exc.value.error == ErrorData(code=METHOD_NOT_FOUND, message="Method not found", data="tools/call")
304304

305305

306306
@pytest.mark.anyio
@@ -845,6 +845,7 @@ async def greet(ctx: Ctx, params: GreetParams) -> dict[str, Any]:
845845

846846
@pytest.mark.anyio
847847
async def test_runner_spec_method_with_invalid_params_is_invalid_params_at_the_negotiated_version(server: SrvT):
848+
server.add_request_handler("tools/call", CallToolRequestParams, lambda ctx, p: None)
848849
async with connected_runner(server) as (client, runner):
849850
assert runner.connection.protocol_version == LATEST_HANDSHAKE_VERSION
850851
with pytest.raises(MCPError) as exc:

0 commit comments

Comments
 (0)