Skip to content

Commit 37de501

Browse files
authored
fix: add StatelessModeNotSupported exception and improve tests (#1828)
1 parent bb6cb02 commit 37de501

File tree

3 files changed

+132
-215
lines changed

3 files changed

+132
-215
lines changed

src/mcp/server/session.py

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ async def handle_list_prompts(ctx: RequestContext) -> list[types.Prompt]:
4949
from mcp.server.experimental.session_features import ExperimentalServerSessionFeatures
5050
from mcp.server.models import InitializationOptions
5151
from mcp.server.validation import validate_sampling_tools, validate_tool_use_result_messages
52+
from mcp.shared.exceptions import StatelessModeNotSupported
5253
from mcp.shared.experimental.tasks.capabilities import check_tasks_capability
5354
from mcp.shared.experimental.tasks.helpers import RELATED_TASK_METADATA_KEY
5455
from mcp.shared.message import ServerMessageMetadata, SessionMessage
@@ -157,26 +158,6 @@ def check_client_capability(self, capability: types.ClientCapabilities) -> bool:
157158

158159
return True
159160

160-
def _require_stateful_mode(self, feature_name: str) -> None:
161-
"""Raise an error if trying to use a feature that requires stateful mode.
162-
163-
Server-to-client requests (sampling, elicitation, list_roots) are not
164-
supported in stateless HTTP mode because there is no persistent connection
165-
for bidirectional communication.
166-
167-
Args:
168-
feature_name: Name of the feature being used (for error message)
169-
170-
Raises:
171-
RuntimeError: If the session is in stateless mode
172-
"""
173-
if self._stateless:
174-
raise RuntimeError(
175-
f"Cannot use {feature_name} in stateless HTTP mode. "
176-
"Stateless mode does not support server-to-client requests. "
177-
"Use stateful mode (stateless_http=False) to enable this feature."
178-
)
179-
180161
async def _receive_loop(self) -> None:
181162
async with self._incoming_message_stream_writer:
182163
await super()._receive_loop()
@@ -332,9 +313,10 @@ async def create_message(
332313
Raises:
333314
McpError: If tools are provided but client doesn't support them.
334315
ValueError: If tool_use or tool_result message structure is invalid.
335-
RuntimeError: If called in stateless HTTP mode.
316+
StatelessModeNotSupported: If called in stateless HTTP mode.
336317
"""
337-
self._require_stateful_mode("sampling")
318+
if self._stateless:
319+
raise StatelessModeNotSupported(method="sampling")
338320
client_caps = self._client_params.capabilities if self._client_params else None
339321
validate_sampling_tools(client_caps, tools, tool_choice)
340322
validate_tool_use_result_messages(messages)
@@ -372,7 +354,8 @@ async def create_message(
372354

373355
async def list_roots(self) -> types.ListRootsResult:
374356
"""Send a roots/list request."""
375-
self._require_stateful_mode("list_roots")
357+
if self._stateless:
358+
raise StatelessModeNotSupported(method="list_roots")
376359
return await self.send_request(
377360
types.ServerRequest(types.ListRootsRequest()),
378361
types.ListRootsResult,
@@ -417,9 +400,10 @@ async def elicit_form(
417400
The client's response with form data
418401
419402
Raises:
420-
RuntimeError: If called in stateless HTTP mode.
403+
StatelessModeNotSupported: If called in stateless HTTP mode.
421404
"""
422-
self._require_stateful_mode("elicitation")
405+
if self._stateless:
406+
raise StatelessModeNotSupported(method="elicitation")
423407
return await self.send_request(
424408
types.ServerRequest(
425409
types.ElicitRequest(
@@ -455,9 +439,10 @@ async def elicit_url(
455439
The client's response indicating acceptance, decline, or cancellation
456440
457441
Raises:
458-
RuntimeError: If called in stateless HTTP mode.
442+
StatelessModeNotSupported: If called in stateless HTTP mode.
459443
"""
460-
self._require_stateful_mode("elicitation")
444+
if self._stateless:
445+
raise StatelessModeNotSupported(method="elicitation")
461446
return await self.send_request(
462447
types.ServerRequest(
463448
types.ElicitRequest(

src/mcp/shared/exceptions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@ def __init__(self, error: ErrorData):
1818
self.error = error
1919

2020

21+
class StatelessModeNotSupported(RuntimeError):
22+
"""
23+
Raised when attempting to use a method that is not supported in stateless mode.
24+
25+
Server-to-client requests (sampling, elicitation, list_roots) are not
26+
supported in stateless HTTP mode because there is no persistent connection
27+
for bidirectional communication.
28+
"""
29+
30+
def __init__(self, method: str):
31+
super().__init__(
32+
f"Cannot use {method} in stateless HTTP mode. "
33+
"Stateless mode does not support server-to-client requests. "
34+
"Use stateful mode (stateless_http=False) to enable this feature."
35+
)
36+
self.method = method
37+
38+
2139
class UrlElicitationRequiredError(McpError):
2240
"""
2341
Specialized error for when a tool requires URL mode elicitation(s) before proceeding.

0 commit comments

Comments
 (0)