fix: raise clear error for server-to-client requests in stateless mode#1827
fix: raise clear error for server-to-client requests in stateless mode#1827
Conversation
Server-to-client requests (list_roots, create_message, elicit_form, elicit_url) require bidirectional communication which is not available in stateless HTTP mode. Previously, these requests would hang indefinitely. Now they raise an immediate RuntimeError with a clear message explaining the limitation and how to fix it. Changes: - Store _stateless flag in ServerSession.__init__ - Add _require_stateful_mode() helper that raises RuntimeError - Add checks to list_roots(), create_message(), elicit_form(), elicit_url() - Add comprehensive unit tests Github-Issue: #1097 Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%) Claude-Steers: 4 Claude-Permission-Prompts: 26 Claude-Escapes: 0
| create_init_options(), | ||
| stateless=True, | ||
| ) as session: | ||
| with pytest.raises(RuntimeError) as exc_info: |
There was a problem hiding this comment.
Add the match= please, RuntimeError can be a lot.
| ( | ||
| server_to_client_send, | ||
| server_to_client_receive, | ||
| client_to_server_send, | ||
| client_to_server_receive, | ||
| ) = create_test_streams() | ||
|
|
||
| async with ( | ||
| client_to_server_send, | ||
| client_to_server_receive, | ||
| server_to_client_send, | ||
| server_to_client_receive, | ||
| ): | ||
| async with ServerSession( | ||
| client_to_server_receive, | ||
| server_to_client_send, | ||
| create_init_options(), | ||
| stateless=True, | ||
| ) as session: |
There was a problem hiding this comment.
Can't this whole thing be a fixture?
|
doh... didn't realize it was on automerge |
| def _require_stateful_mode(self, feature_name: str) -> None: | ||
| """Raise an error if trying to use a feature that requires stateful mode. | ||
|
|
||
| Server-to-client requests (sampling, elicitation, list_roots) are not | ||
| supported in stateless HTTP mode because there is no persistent connection | ||
| for bidirectional communication. | ||
|
|
||
| Args: | ||
| feature_name: Name of the feature being used (for error message) | ||
|
|
||
| Raises: | ||
| RuntimeError: If the session is in stateless mode | ||
| """ | ||
| if self._stateless: | ||
| raise RuntimeError( | ||
| f"Cannot use {feature_name} in stateless HTTP mode. " | ||
| "Stateless mode does not support server-to-client requests. " | ||
| "Use stateful mode (stateless_http=False) to enable this feature." | ||
| ) |
There was a problem hiding this comment.
This kind of private function adds a bit of overhead while reading the code. If possible, can we create a specific exception and do the check on each feature method itself?
if self._stateless:
raise MethodNotSupported(method="sampling")On MethodNotSupported you can set up the message.
There was a problem hiding this comment.
MethodNotSupported what are you thinking for the error message? As currently it's specific to stateless http and MethodNotSupported sounds more generic
There was a problem hiding this comment.
You can think of a better name, but the point stands.
Although I suggested this, I still think that something is missing conceptually - we should be able to check this or define somehow the capabilities of stateless in a different way - and be able to handle this before it reaches the methods.
There was a problem hiding this comment.
Yea, although this is more of a bandaid fix given that this version of stateless mode isn't really supported in the spec. The upcoming changes to the spec will significantly change how statefulness will work anyway so I didn't want to spend too much time worrying about how it looks since it has to change anyway.
|
... |
Address review feedback from PR #1827: - Create StatelessModeNotSupported exception that inherits from RuntimeError - Replace _require_stateful_mode() helper with inline checks in each method - Add match= parameter to pytest.raises() calls for more specific error matching - Create stateless_session and stateful_session fixtures to reduce test boilerplate - Add test for exception's method attribute Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%) Claude-Steers: 7 Claude-Permission-Prompts: 26 Claude-Escapes: 1
Motivation and Context
Fixes #1097
Server-to-client requests (list_roots, create_message, elicit_form, elicit_url) require bidirectional communication which is not available in stateless HTTP mode. Previously, these requests would hang indefinitely. Now they raise an immediate
RuntimeErrorwith a clear, actionable message:Technical Context: Why This Cannot Work
The Fundamental Problem
In stateless mode (
stateless_http=True), each HTTP request creates a completely fresh transport with no session tracking:When a server tool handler calls
session.elicit()orsession.create_message():send_request()blocks waiting for a response - It creates a response stream and waits:In JSON response mode: The server-initiated request is discarded before reaching the client:
In SSE mode: The request IS sent to the client via SSE, but:
send_request()blocks forever waiting on a response stream that will never receive anythingWhy
related_request_idDoesn't HelpThe
related_request_idmetadata helps route server messages to the correct SSE stream (the one handling the original tool call), but it does NOT solve the fundamental problem:What This Looks Like in Practice
Future Direction (SEP-1442)
The upcoming transport changes will properly address this with:
Until then, failing fast with a clear error is the best we can do.
How Has This Been Tested?
Breaking Changes
Yes, but intentionally so. Code that previously hung indefinitely will now raise a
RuntimeError. This is a strict improvement - failing fast with a clear error is better than hanging.Types of changes
Checklist