Skip to content

Commit 7090592

Browse files
committed
fix(session): return METHOD_NOT_FOUND for unknown request methods
1 parent b33c811 commit 7090592

4 files changed

Lines changed: 94 additions & 3 deletions

File tree

src/mcp/client/session.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@
1212
from mcp.client.experimental.task_handlers import ExperimentalTaskHandlers
1313
from mcp.shared._context import RequestContext
1414
from mcp.shared.message import SessionMessage
15-
from mcp.shared.session import BaseSession, ProgressFnT, RequestResponder
15+
from mcp.shared.session import (
16+
BaseSession,
17+
ProgressFnT,
18+
RequestResponder,
19+
request_methods_for_union,
20+
)
1621
from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS
1722
from mcp.types._types import RequestParamsMeta
1823

1924
DEFAULT_CLIENT_INFO = types.Implementation(name="mcp", version="0.1.0")
25+
KNOWN_SERVER_REQUEST_METHODS = request_methods_for_union(types.ServerRequest)
2026

2127
logger = logging.getLogger("client")
2228

@@ -141,6 +147,10 @@ def __init__(
141147
def _receive_request_adapter(self) -> TypeAdapter[types.ServerRequest]:
142148
return types.server_request_adapter
143149

150+
@property
151+
def _known_request_methods(self) -> frozenset[str]:
152+
return KNOWN_SERVER_REQUEST_METHODS
153+
144154
@property
145155
def _receive_notification_adapter(self) -> TypeAdapter[types.ServerNotification]:
146156
return types.server_notification_adapter

src/mcp/server/session.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ async def handle_list_prompts(ctx: RequestContext, params) -> ListPromptsResult:
4747
from mcp.shared.session import (
4848
BaseSession,
4949
RequestResponder,
50+
request_methods_for_union,
5051
)
5152
from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS
5253

@@ -63,6 +64,8 @@ class InitializationState(Enum):
6364
RequestResponder[types.ClientRequest, types.ServerResult] | types.ClientNotification | Exception
6465
)
6566

67+
KNOWN_CLIENT_REQUEST_METHODS = request_methods_for_union(types.ClientRequest)
68+
6669

6770
class ServerSession(
6871
BaseSession[
@@ -100,6 +103,10 @@ def __init__(
100103
def _receive_request_adapter(self) -> TypeAdapter[types.ClientRequest]:
101104
return types.client_request_adapter
102105

106+
@property
107+
def _known_request_methods(self) -> frozenset[str]:
108+
return KNOWN_CLIENT_REQUEST_METHODS
109+
103110
@property
104111
def _receive_notification_adapter(self) -> TypeAdapter[types.ClientNotification]:
105112
return types.client_notification_adapter

src/mcp/shared/session.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections.abc import Callable
55
from contextlib import AsyncExitStack
66
from types import TracebackType
7-
from typing import Any, Generic, Protocol, TypeVar
7+
from typing import Any, Generic, Protocol, TypeVar, get_args
88

99
import anyio
1010
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
@@ -17,6 +17,7 @@
1717
from mcp.types import (
1818
CONNECTION_CLOSED,
1919
INVALID_PARAMS,
20+
METHOD_NOT_FOUND,
2021
REQUEST_TIMEOUT,
2122
CancelledNotification,
2223
ClientNotification,
@@ -45,6 +46,16 @@
4546
RequestId = str | int
4647

4748

49+
def request_methods_for_union(request_union: Any) -> frozenset[str]:
50+
methods: set[str] = set()
51+
for request_type in get_args(request_union):
52+
field = getattr(request_type, "model_fields", {}).get("method")
53+
default = getattr(field, "default", None)
54+
if isinstance(default, str):
55+
methods.add(default)
56+
return frozenset(methods)
57+
58+
4859
class ProgressFnT(Protocol):
4960
"""Protocol for progress notification callbacks."""
5061

@@ -326,6 +337,10 @@ def _receive_request_adapter(self) -> TypeAdapter[ReceiveRequestT]:
326337
"""Each subclass must provide its own request adapter."""
327338
raise NotImplementedError
328339

340+
@property
341+
def _known_request_methods(self) -> frozenset[str]:
342+
return frozenset()
343+
329344
@property
330345
def _receive_notification_adapter(self) -> TypeAdapter[ReceiveNotificationT]:
331346
raise NotImplementedError
@@ -360,10 +375,18 @@ async def _receive_loop(self) -> None:
360375
# response instead of crashing the server
361376
logging.warning("Failed to validate request", exc_info=True)
362377
logging.debug(f"Message that failed validation: {message.message}")
378+
if message.message.method not in self._known_request_methods:
379+
error = ErrorData(code=METHOD_NOT_FOUND, message="Method not found")
380+
else:
381+
error = ErrorData(
382+
code=INVALID_PARAMS,
383+
message="Invalid request parameters",
384+
data="",
385+
)
363386
error_response = JSONRPCError(
364387
jsonrpc="2.0",
365388
id=message.message.id,
366-
error=ErrorData(code=INVALID_PARAMS, message="Invalid request parameters", data=""),
389+
error=error,
367390
)
368391
session_message = SessionMessage(message=error_response)
369392
await self._write_stream.send(session_message)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""Test for issue #1561: unknown methods should return METHOD_NOT_FOUND."""
2+
3+
import anyio
4+
import pytest
5+
6+
from mcp.server.models import InitializationOptions
7+
from mcp.server.session import ServerSession
8+
from mcp.shared.message import SessionMessage
9+
from mcp.types import METHOD_NOT_FOUND, JSONRPCError, JSONRPCRequest, ServerCapabilities
10+
11+
12+
@pytest.mark.anyio
13+
async def test_invalid_method_returns_method_not_found() -> None:
14+
read_send_stream, read_receive_stream = anyio.create_memory_object_stream[SessionMessage | Exception](10)
15+
write_send_stream, write_receive_stream = anyio.create_memory_object_stream[SessionMessage](10)
16+
17+
try:
18+
async with ServerSession(
19+
read_stream=read_receive_stream,
20+
write_stream=write_send_stream,
21+
init_options=InitializationOptions(
22+
server_name="test_server",
23+
server_version="1.0.0",
24+
capabilities=ServerCapabilities(),
25+
),
26+
):
27+
await read_send_stream.send(
28+
SessionMessage(
29+
message=JSONRPCRequest(
30+
jsonrpc="2.0",
31+
id=1,
32+
method="invalid/method",
33+
params={},
34+
)
35+
)
36+
)
37+
38+
await anyio.sleep(0.1)
39+
40+
response_message = write_receive_stream.receive_nowait()
41+
response = response_message.message
42+
43+
assert isinstance(response, JSONRPCError)
44+
assert response.id == 1
45+
assert response.error.code == METHOD_NOT_FOUND
46+
assert response.error.message == "Method not found"
47+
finally: # pragma: no cover
48+
await read_send_stream.aclose()
49+
await write_send_stream.aclose()
50+
await read_receive_stream.aclose()
51+
await write_receive_stream.aclose()

0 commit comments

Comments
 (0)