From 7ae44835960fb9aebe0656dc5f6ddd5201a2b9c0 Mon Sep 17 00:00:00 2001 From: 72004 Date: Sun, 16 Aug 2026 01:32:38 +0500 Subject: [PATCH] Preserve exception chains with raise ... from Add `from` clause to 11 raise statements that re-raise a new exception after catching another, preserving the original traceback and __cause__ for debugging. Follows the pattern established in #2542 for the remaining sites. Fixes #2564 --- src/mcp/client/auth/utils.py | 2 +- src/mcp/client/session.py | 2 +- src/mcp/server/auth/middleware/client_auth.py | 4 ++-- src/mcp/server/mcpserver/prompts/base.py | 2 +- src/mcp/server/mcpserver/resources/types.py | 8 ++++---- src/mcp/server/mcpserver/server.py | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mcp/client/auth/utils.py b/src/mcp/client/auth/utils.py index 31e2e5cade..81b6b03df4 100644 --- a/src/mcp/client/auth/utils.py +++ b/src/mcp/client/auth/utils.py @@ -424,4 +424,4 @@ async def handle_token_response_scopes( token_response = OAuthToken.model_validate_json(content) return token_response except ValidationError as e: # pragma: no cover - raise OAuthTokenError(f"Invalid token response: {e}") + raise OAuthTokenError(f"Invalid token response: {e}") from e diff --git a/src/mcp/client/session.py b/src/mcp/client/session.py index 895339ca18..c64e2ad2d7 100644 --- a/src/mcp/client/session.py +++ b/src/mcp/client/session.py @@ -1131,7 +1131,7 @@ def _output_schema_validator(self, name: str, output_schema: dict[str, Any]) -> try: validator_cls.check_schema(output_schema) except SchemaError as e: - raise RuntimeError(f"Invalid schema for tool {name}: {e}") + raise RuntimeError(f"Invalid schema for tool {name}: {e}") from e # jsonschema ships no `py.typed`, so pyright reads typeshed's stub, which declares # `registry` as required (concrete validators default it); cast to a schema-only ctor. validator = cast("Callable[[dict[str, Any]], Validator]", validator_cls)(output_schema) diff --git a/src/mcp/server/auth/middleware/client_auth.py b/src/mcp/server/auth/middleware/client_auth.py index 3d5067d611..84b64701f7 100644 --- a/src/mcp/server/auth/middleware/client_auth.py +++ b/src/mcp/server/auth/middleware/client_auth.py @@ -80,8 +80,8 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation if basic_client_id != client_id: raise AuthenticationError("Client ID mismatch in Basic auth") - except (ValueError, UnicodeDecodeError, binascii.Error): - raise AuthenticationError("Invalid Basic authentication header") + except (ValueError, UnicodeDecodeError, binascii.Error) as e: + raise AuthenticationError("Invalid Basic authentication header") from e elif client.token_endpoint_auth_method == "client_secret_post": raw_form_data = form_data.get("client_secret") diff --git a/src/mcp/server/mcpserver/prompts/base.py b/src/mcp/server/mcpserver/prompts/base.py index 0a010de7d2..2cd53013fe 100644 --- a/src/mcp/server/mcpserver/prompts/base.py +++ b/src/mcp/server/mcpserver/prompts/base.py @@ -197,4 +197,4 @@ async def render( except MCPError: raise except Exception as e: - raise ValueError(f"Error rendering prompt {self.name}: {e}") + raise ValueError(f"Error rendering prompt {self.name}: {e}") from e diff --git a/src/mcp/server/mcpserver/resources/types.py b/src/mcp/server/mcpserver/resources/types.py index 2edf342337..ffcbedee95 100644 --- a/src/mcp/server/mcpserver/resources/types.py +++ b/src/mcp/server/mcpserver/resources/types.py @@ -106,7 +106,7 @@ async def read(self) -> str | bytes: except MCPError: raise except Exception as e: - raise ValueError(f"Error reading resource {self.uri}: {e}") + raise ValueError(f"Error reading resource {self.uri}: {e}") from e @classmethod def from_function( @@ -188,7 +188,7 @@ async def read(self) -> str | bytes: return await anyio.to_thread.run_sync(self.path.read_bytes) return await anyio.to_thread.run_sync(partial(self.path.read_text, encoding=self.encoding)) except Exception as e: - raise ValueError(f"Error reading file {self.path}: {e}") + raise ValueError(f"Error reading file {self.path}: {e}") from e class HttpResource(Resource): @@ -233,7 +233,7 @@ def list_files(self) -> list[Path]: # pragma: no cover return list(self.path.glob(self.pattern)) if not self.recursive else list(self.path.rglob(self.pattern)) return list(self.path.glob("*")) if not self.recursive else list(self.path.rglob("*")) except Exception as e: - raise ValueError(f"Error listing directory {self.path}: {e}") + raise ValueError(f"Error listing directory {self.path}: {e}") from e async def read(self) -> str: # Always returns JSON string # pragma: no cover """Read the directory listing.""" @@ -242,4 +242,4 @@ async def read(self) -> str: # Always returns JSON string # pragma: no cover file_list = [str(f.relative_to(self.path)) for f in files if f.is_file()] return json.dumps({"files": file_list}, indent=2) except Exception as e: - raise ValueError(f"Error reading directory {self.path}: {e}") + raise ValueError(f"Error reading directory {self.path}: {e}") from e diff --git a/src/mcp/server/mcpserver/server.py b/src/mcp/server/mcpserver/server.py index bc79c44a36..3d2b5b65a1 100644 --- a/src/mcp/server/mcpserver/server.py +++ b/src/mcp/server/mcpserver/server.py @@ -435,9 +435,9 @@ async def _handle_read_resource( try: results = await self.read_resource(params.uri, context) except ResourceNotFoundError as err: - raise MCPError(code=INVALID_PARAMS, message=str(err), data={"uri": str(params.uri)}) + raise MCPError(code=INVALID_PARAMS, message=str(err), data={"uri": str(params.uri)}) from err except ResourceError as err: - raise MCPError(code=INTERNAL_ERROR, message=str(err), data={"uri": str(params.uri)}) + raise MCPError(code=INTERNAL_ERROR, message=str(err), data={"uri": str(params.uri)}) from err if isinstance(results, InputRequiredResult): return results contents: list[TextResourceContents | BlobResourceContents] = []