Skip to content

Commit 64601fd

Browse files
Christian-Sidakclaude
andcommitted
fix: make UrlElicitationRequiredError pickle-safe via __reduce__
Pickle reconstructs exceptions by calling cls(*args). Since UrlElicitationRequiredError inherits MCPError's args=(code, message, data), it fails on restore because its __init__ expects (elicitations, message). Adding __reduce__ to return (from_error, (self.error,)) ensures pickle uses the existing from_error classmethod to reconstruct the object correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3d7b311 commit 64601fd

File tree

2 files changed

+36
-0
lines changed

2 files changed

+36
-0
lines changed

src/mcp/shared/exceptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,6 @@ def from_error(cls, error: ErrorData) -> UrlElicitationRequiredError:
104104
raw_elicitations = cast(list[dict[str, Any]], data.get("elicitations", []))
105105
elicitations = [ElicitRequestURLParams.model_validate(e) for e in raw_elicitations]
106106
return cls(elicitations, error.message)
107+
108+
def __reduce__(self) -> tuple:
109+
return (self.from_error, (self.error,))

tests/shared/test_exceptions.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Tests for MCP exception classes."""
22

3+
import pickle
4+
35
import pytest
46

57
from mcp.shared.exceptions import MCPError, UrlElicitationRequiredError
@@ -162,3 +164,34 @@ def test_url_elicitation_required_error_exception_message() -> None:
162164

163165
# The exception's string representation should match the message
164166
assert str(error) == "URL elicitation required"
167+
168+
169+
def test_mcp_error_pickle_roundtrip() -> None:
170+
"""Test that MCPError survives a pickle round-trip."""
171+
original = MCPError(code=-32600, message="Authentication Required")
172+
restored = pickle.loads(pickle.dumps(original))
173+
174+
assert type(restored) is MCPError
175+
assert restored.code == -32600
176+
assert restored.message == "Authentication Required"
177+
assert restored.error.code == -32600
178+
assert restored.error.message == "Authentication Required"
179+
180+
181+
def test_url_elicitation_required_error_pickle_roundtrip() -> None:
182+
"""Test that UrlElicitationRequiredError survives a pickle round-trip."""
183+
elicitation = ElicitRequestURLParams(
184+
mode="url",
185+
message="Auth required",
186+
url="https://example.com/auth",
187+
elicitation_id="test-123",
188+
)
189+
original = UrlElicitationRequiredError([elicitation])
190+
restored = pickle.loads(pickle.dumps(original))
191+
192+
assert type(restored) is UrlElicitationRequiredError
193+
assert restored.message == "URL elicitation required"
194+
assert restored.error.code == URL_ELICITATION_REQUIRED
195+
assert len(restored.elicitations) == 1
196+
assert restored.elicitations[0].elicitation_id == "test-123"
197+
assert restored.elicitations[0].url == "https://example.com/auth"

0 commit comments

Comments
 (0)