From e49ef71390d5f8577ff0853f93eb9e3a67fd2cad Mon Sep 17 00:00:00 2001 From: mikemikimike <13286568797@163.com> Date: Thu, 27 Aug 2026 09:52:36 +0800 Subject: [PATCH] fix: make URL elicitation errors pickle-safe --- src/mcp/shared/exceptions.py | 5 +++++ tests/shared/test_exceptions.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/mcp/shared/exceptions.py b/src/mcp/shared/exceptions.py index c2a7fd44e7..d441fe3fcf 100644 --- a/src/mcp/shared/exceptions.py +++ b/src/mcp/shared/exceptions.py @@ -1,5 +1,6 @@ from __future__ import annotations +from collections.abc import Callable from typing import Any, cast from mcp_types import INVALID_REQUEST, URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData, JSONRPCError @@ -117,3 +118,7 @@ def from_error(cls, error: ErrorData) -> UrlElicitationRequiredError: raw_elicitations = cast(list[dict[str, Any]], data.get("elicitations", [])) elicitations = [ElicitRequestURLParams.model_validate(e) for e in raw_elicitations] return cls(elicitations, error.message) + + def __reduce__(self) -> tuple[Callable[[ErrorData], UrlElicitationRequiredError], tuple[ErrorData]]: + """Reconstruct the exception from its wire-compatible error payload.""" + return (self.from_error, (self.error,)) diff --git a/tests/shared/test_exceptions.py b/tests/shared/test_exceptions.py index 9da4f606d3..31772bc2ca 100644 --- a/tests/shared/test_exceptions.py +++ b/tests/shared/test_exceptions.py @@ -1,5 +1,7 @@ """Tests for MCP exception classes.""" +import pickle + import pytest from mcp_types import URL_ELICITATION_REQUIRED, ElicitRequestURLParams, ErrorData, JSONRPCError @@ -117,6 +119,27 @@ def test_url_elicitation_required_error_serialization_roundtrip() -> None: assert reconstructed.elicitations[0].message == original.elicitations[0].message +def test_url_elicitation_required_error_pickle_roundtrip() -> None: + """Pickle reconstruction preserves the specialized exception payload.""" + original = UrlElicitationRequiredError( + [ + ElicitRequestURLParams( + mode="url", + message="Auth required", + url="https://example.com/auth", + elicitation_id="test-123", + ) + ], + message="Please authenticate", + ) + + restored = pickle.loads(pickle.dumps(original)) + + assert isinstance(restored, UrlElicitationRequiredError) + assert restored.error == original.error + assert restored.elicitations == original.elicitations + + def test_url_elicitation_required_error_data_contains_elicitations() -> None: """Test that error data contains properly serialized elicitations.""" elicitation = ElicitRequestURLParams(