Skip to content

Commit 7ae4483

Browse files
committed
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
1 parent 52ad0a8 commit 7ae4483

6 files changed

Lines changed: 11 additions & 11 deletions

File tree

src/mcp/client/auth/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,4 +424,4 @@ async def handle_token_response_scopes(
424424
token_response = OAuthToken.model_validate_json(content)
425425
return token_response
426426
except ValidationError as e: # pragma: no cover
427-
raise OAuthTokenError(f"Invalid token response: {e}")
427+
raise OAuthTokenError(f"Invalid token response: {e}") from e

src/mcp/client/session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ def _output_schema_validator(self, name: str, output_schema: dict[str, Any]) ->
11311131
try:
11321132
validator_cls.check_schema(output_schema)
11331133
except SchemaError as e:
1134-
raise RuntimeError(f"Invalid schema for tool {name}: {e}")
1134+
raise RuntimeError(f"Invalid schema for tool {name}: {e}") from e
11351135
# jsonschema ships no `py.typed`, so pyright reads typeshed's stub, which declares
11361136
# `registry` as required (concrete validators default it); cast to a schema-only ctor.
11371137
validator = cast("Callable[[dict[str, Any]], Validator]", validator_cls)(output_schema)

src/mcp/server/auth/middleware/client_auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ async def authenticate_request(self, request: Request) -> OAuthClientInformation
8080

8181
if basic_client_id != client_id:
8282
raise AuthenticationError("Client ID mismatch in Basic auth")
83-
except (ValueError, UnicodeDecodeError, binascii.Error):
84-
raise AuthenticationError("Invalid Basic authentication header")
83+
except (ValueError, UnicodeDecodeError, binascii.Error) as e:
84+
raise AuthenticationError("Invalid Basic authentication header") from e
8585

8686
elif client.token_endpoint_auth_method == "client_secret_post":
8787
raw_form_data = form_data.get("client_secret")

src/mcp/server/mcpserver/prompts/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,4 @@ async def render(
197197
except MCPError:
198198
raise
199199
except Exception as e:
200-
raise ValueError(f"Error rendering prompt {self.name}: {e}")
200+
raise ValueError(f"Error rendering prompt {self.name}: {e}") from e

src/mcp/server/mcpserver/resources/types.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ async def read(self) -> str | bytes:
106106
except MCPError:
107107
raise
108108
except Exception as e:
109-
raise ValueError(f"Error reading resource {self.uri}: {e}")
109+
raise ValueError(f"Error reading resource {self.uri}: {e}") from e
110110

111111
@classmethod
112112
def from_function(
@@ -188,7 +188,7 @@ async def read(self) -> str | bytes:
188188
return await anyio.to_thread.run_sync(self.path.read_bytes)
189189
return await anyio.to_thread.run_sync(partial(self.path.read_text, encoding=self.encoding))
190190
except Exception as e:
191-
raise ValueError(f"Error reading file {self.path}: {e}")
191+
raise ValueError(f"Error reading file {self.path}: {e}") from e
192192

193193

194194
class HttpResource(Resource):
@@ -233,7 +233,7 @@ def list_files(self) -> list[Path]: # pragma: no cover
233233
return list(self.path.glob(self.pattern)) if not self.recursive else list(self.path.rglob(self.pattern))
234234
return list(self.path.glob("*")) if not self.recursive else list(self.path.rglob("*"))
235235
except Exception as e:
236-
raise ValueError(f"Error listing directory {self.path}: {e}")
236+
raise ValueError(f"Error listing directory {self.path}: {e}") from e
237237

238238
async def read(self) -> str: # Always returns JSON string # pragma: no cover
239239
"""Read the directory listing."""
@@ -242,4 +242,4 @@ async def read(self) -> str: # Always returns JSON string # pragma: no cover
242242
file_list = [str(f.relative_to(self.path)) for f in files if f.is_file()]
243243
return json.dumps({"files": file_list}, indent=2)
244244
except Exception as e:
245-
raise ValueError(f"Error reading directory {self.path}: {e}")
245+
raise ValueError(f"Error reading directory {self.path}: {e}") from e

src/mcp/server/mcpserver/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,9 +435,9 @@ async def _handle_read_resource(
435435
try:
436436
results = await self.read_resource(params.uri, context)
437437
except ResourceNotFoundError as err:
438-
raise MCPError(code=INVALID_PARAMS, message=str(err), data={"uri": str(params.uri)})
438+
raise MCPError(code=INVALID_PARAMS, message=str(err), data={"uri": str(params.uri)}) from err
439439
except ResourceError as err:
440-
raise MCPError(code=INTERNAL_ERROR, message=str(err), data={"uri": str(params.uri)})
440+
raise MCPError(code=INTERNAL_ERROR, message=str(err), data={"uri": str(params.uri)}) from err
441441
if isinstance(results, InputRequiredResult):
442442
return results
443443
contents: list[TextResourceContents | BlobResourceContents] = []

0 commit comments

Comments
 (0)