--- a PPN by Garber Painting Akron. With Image Size Reduction included!URL: http://github.com/modelcontextprotocol/python-sdk/pull/775.patch
s.message,
+ )
+ except Exception as e:
+ logging.warning(
+ "Progress callback raised an exception: %s",
+ e,
+ )
+ await self._received_notification(notification)
+ await self._handle_incoming(notification)
else: # Response or error
stream = self._response_streams.pop(message.message.root.id, None)
if stream:
diff --git a/tests/shared/test_progress_notifications.py b/tests/shared/test_progress_notifications.py
index 1e0409e14e..6b9be56567 100644
--- a/tests/shared/test_progress_notifications.py
+++ b/tests/shared/test_progress_notifications.py
@@ -1,4 +1,5 @@
from typing import Any, cast
+from unittest.mock import patch
import anyio
import pytest
@@ -10,12 +11,16 @@
from mcp.server.models import InitializationOptions
from mcp.server.session import ServerSession
from mcp.shared.context import RequestContext
+from mcp.shared.memory import create_connected_server_and_client_session
from mcp.shared.progress import progress
from mcp.shared.session import (
BaseSession,
RequestResponder,
SessionMessage,
)
+from mcp.types import (
+ TextContent,
+)
@pytest.mark.anyio
@@ -347,3 +352,78 @@ async def handle_client_message(
assert server_progress_updates[3]["progress"] == 100
assert server_progress_updates[3]["total"] == 100
assert server_progress_updates[3]["message"] == "Processing results..."
+
+
+@pytest.mark.anyio
+async def test_progress_callback_exception_logging():
+ """Test that exceptions in progress callbacks are logged and \
+ don't crash the session."""
+ # Track logged warnings
+ logged_warnings = []
+
+ def mock_warning(msg, *args):
+ logged_warnings.append(msg % args if args else msg)
+
+ # Create a progress callback that raises an exception
+ async def failing_progress_callback(
+ progress: float, total: float | None, message: str | None
+ ) -> None:
+ raise ValueError("Progress callback failed!")
+
+ # Create a server with a tool that sends progress notifications
+ server = Server(name="TestProgressServer")
+
+ @server.call_tool()
+ async def handle_call_tool(
+ name: str, arguments: dict | None
+ ) -> list[types.TextContent]:
+ if name == "progress_tool":
+ # Send a progress notification
+ await server.request_context.session.send_progress_notification(
+ progress_token=server.request_context.request_id,
+ progress=50.0,
+ total=100.0,
+ message="Halfway done",
+ )
+ return [types.TextContent(type="text", text="progress_result")]
+ raise ValueError(f"Unknown tool: {name}")
+
+ @server.list_tools()
+ async def handle_list_tools() -> list[types.Tool]:
+ return [
+ types.Tool(
+ name="progress_tool",
+ description="A tool that sends progress notifications",
+ inputSchema={},
+ )
+ ]
+
+ # Test with mocked logging
+ with patch("mcp.shared.session.logging.warning", side_effect=mock_warning):
+ async with create_connected_server_and_client_session(server) as client_session:
+ # Send a request with a failing progress callback
+ result = await client_session.send_request(
+ types.ClientRequest(
+ types.CallToolRequest(
+ method="tools/call",
+ params=types.CallToolRequestParams(
+ name="progress_tool", arguments={}
+ ),
+ )
+ ),
+ types.CallToolResult,
+ progress_callback=failing_progress_callback,
+ )
+
+ # Verify the request completed successfully despite the callback failure
+ assert len(result.content) == 1
+ content = result.content[0]
+ assert isinstance(content, TextContent)
+ assert content.text == "progress_result"
+
+ # Check that a warning was logged for the progress callback exception
+ assert len(logged_warnings) > 0
+ assert any(
+ "Progress callback raised an exception" in warning
+ for warning in logged_warnings
+ )
From 2544c9cb5781a794e5771efd2d4d671379a47221 Mon Sep 17 00:00:00 2001
From: Lorenzo C
Date: Wed, 28 May 2025 22:47:21 -0300
Subject: [PATCH 4/9] Swap generic Exception for ValidationError
---
src/mcp/shared/session.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py
index 017af2848e..5a366539c6 100644
--- a/src/mcp/shared/session.py
+++ b/src/mcp/shared/session.py
@@ -8,7 +8,7 @@
import anyio
import httpx
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
-from pydantic import BaseModel
+from pydantic import BaseModel, ValidationError
from typing_extensions import Self
from mcp.shared.exceptions import McpError
@@ -357,7 +357,7 @@ async def _receive_loop(self) -> None:
by_alias=True, mode="json", exclude_none=True
)
)
- except Exception as e:
+ except ValidationError as e:
# For other validation errors, log and continue
logging.warning(
"Failed to validate request: %s. Message was: %s",
@@ -389,7 +389,7 @@ async def _receive_loop(self) -> None:
by_alias=True, mode="json", exclude_none=True
)
)
- except Exception as e:
+ except ValidationError as e:
# For other validation errors, log and continue
logging.warning(
"Failed to validate notification: %s. Message was: %s",
From 22cd2db376e5c80398e944e36ed8928208757d73 Mon Sep 17 00:00:00 2001
From: Lorenzo C
Date: Wed, 28 May 2025 22:56:32 -0300
Subject: [PATCH 5/9] Elevate callback exception log to error
---
src/mcp/shared/session.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py
index 2cdc3e3e47..fe9be02329 100644
--- a/src/mcp/shared/session.py
+++ b/src/mcp/shared/session.py
@@ -421,7 +421,7 @@ async def _receive_loop(self) -> None:
notification.root.params.message,
)
except Exception as e:
- logging.warning(
+ logging.error(
"Progress callback raised an exception: %s",
e,
)
From e97373708679f6baa3b825a376c877d05c868a37 Mon Sep 17 00:00:00 2001
From: Lorenzo C
Date: Wed, 28 May 2025 23:01:12 -0300
Subject: [PATCH 6/9] Fix unit tests
---
tests/shared/test_progress_notifications.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/tests/shared/test_progress_notifications.py b/tests/shared/test_progress_notifications.py
index 6b9be56567..fe8ff8ea3d 100644
--- a/tests/shared/test_progress_notifications.py
+++ b/tests/shared/test_progress_notifications.py
@@ -359,10 +359,10 @@ async def test_progress_callback_exception_logging():
"""Test that exceptions in progress callbacks are logged and \
don't crash the session."""
# Track logged warnings
- logged_warnings = []
+ logged_errors = []
- def mock_warning(msg, *args):
- logged_warnings.append(msg % args if args else msg)
+ def mock_log_error(msg, *args):
+ logged_errors.append(msg % args if args else msg)
# Create a progress callback that raises an exception
async def failing_progress_callback(
@@ -399,7 +399,7 @@ async def handle_list_tools() -> list[types.Tool]:
]
# Test with mocked logging
- with patch("mcp.shared.session.logging.warning", side_effect=mock_warning):
+ with patch("mcp.shared.session.logging.error", side_effect=mock_log_error):
async with create_connected_server_and_client_session(server) as client_session:
# Send a request with a failing progress callback
result = await client_session.send_request(
@@ -422,8 +422,8 @@ async def handle_list_tools() -> list[types.Tool]:
assert content.text == "progress_result"
# Check that a warning was logged for the progress callback exception
- assert len(logged_warnings) > 0
+ assert len(logged_errors) > 0
assert any(
"Progress callback raised an exception" in warning
- for warning in logged_warnings
+ for warning in logged_errors
)
From c3fc09225df3bc1ef2634c9dce986128390b5b0d Mon Sep 17 00:00:00 2001
From: Lorenzo C
Date: Mon, 8 Sep 2025 21:24:08 -0300
Subject: [PATCH 7/9] fix: test + formatting
---
src/mcp/shared/session.py | 31 ++++++++++++++-------
tests/shared/test_progress_notifications.py | 19 ++++---------
2 files changed, 26 insertions(+), 24 deletions(-)
diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py
index a22942d277..dbdc0fe12f 100644
--- a/src/mcp/shared/session.py
+++ b/src/mcp/shared/session.py
@@ -7,20 +7,31 @@
import anyio
import httpx
-from anyio.streams.memory import (MemoryObjectReceiveStream,
- MemoryObjectSendStream)
+from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
from pydantic import BaseModel, ValidationError
from typing_extensions import Self
from mcp.shared.exceptions import McpError
-from mcp.shared.message import (MessageMetadata, ServerMessageMetadata,
- SessionMessage)
-from mcp.types import (CONNECTION_CLOSED, INVALID_PARAMS,
- CancelledNotification, ClientNotification,
- ClientRequest, ClientResult, ErrorData, JSONRPCError,
- JSONRPCMessage, JSONRPCNotification, JSONRPCRequest,
- JSONRPCResponse, ProgressNotification, RequestParams,
- ServerNotification, ServerRequest, ServerResult)
+from mcp.shared.message import MessageMetadata, ServerMessageMetadata, SessionMessage
+from mcp.types import (
+ CONNECTION_CLOSED,
+ INVALID_PARAMS,
+ CancelledNotification,
+ ClientNotification,
+ ClientRequest,
+ ClientResult,
+ ErrorData,
+ JSONRPCError,
+ JSONRPCMessage,
+ JSONRPCNotification,
+ JSONRPCRequest,
+ JSONRPCResponse,
+ ProgressNotification,
+ RequestParams,
+ ServerNotification,
+ ServerRequest,
+ ServerResult,
+)
SendRequestT = TypeVar("SendRequestT", ClientRequest, ServerRequest)
SendResultT = TypeVar("SendResultT", ClientResult, ServerResult)
diff --git a/tests/shared/test_progress_notifications.py b/tests/shared/test_progress_notifications.py
index d69545a170..a8e02d262f 100644
--- a/tests/shared/test_progress_notifications.py
+++ b/tests/shared/test_progress_notifications.py
@@ -335,18 +335,14 @@ def mock_log_error(msg, *args):
logged_errors.append(msg % args if args else msg)
# Create a progress callback that raises an exception
- async def failing_progress_callback(
- progress: float, total: float | None, message: str | None
- ) -> None:
+ async def failing_progress_callback(progress: float, total: float | None, message: str | None) -> None:
raise ValueError("Progress callback failed!")
# Create a server with a tool that sends progress notifications
server = Server(name="TestProgressServer")
@server.call_tool()
- async def handle_call_tool(
- name: str, arguments: dict | None
- ) -> list[types.TextContent]:
+ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.TextContent]:
if name == "progress_tool":
# Send a progress notification
await server.request_context.session.send_progress_notification(
@@ -376,9 +372,7 @@ async def handle_list_tools() -> list[types.Tool]:
types.ClientRequest(
types.CallToolRequest(
method="tools/call",
- params=types.CallToolRequestParams(
- name="progress_tool", arguments={}
- ),
+ params=types.CallToolRequestParams(name="progress_tool", arguments={}),
)
),
types.CallToolResult,
@@ -388,12 +382,9 @@ async def handle_list_tools() -> list[types.Tool]:
# Verify the request completed successfully despite the callback failure
assert len(result.content) == 1
content = result.content[0]
- assert isinstance(content, TextContent)
+ assert isinstance(content, types.TextContent)
assert content.text == "progress_result"
# Check that a warning was logged for the progress callback exception
assert len(logged_errors) > 0
- assert any(
- "Progress callback raised an exception" in warning
- for warning in logged_errors
- )
+ assert any("Progress callback raised an exception" in warning for warning in logged_errors)
From 52cff037d34ef7f172f6d9a5d483d86d07fc6a0b Mon Sep 17 00:00:00 2001
From: Lorenzo C
Date: Mon, 8 Sep 2025 21:27:32 -0300
Subject: [PATCH 8/9] fix: unused import
---
src/mcp/shared/session.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/mcp/shared/session.py b/src/mcp/shared/session.py
index dbdc0fe12f..4e774984d4 100644
--- a/src/mcp/shared/session.py
+++ b/src/mcp/shared/session.py
@@ -8,7 +8,7 @@
import anyio
import httpx
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
-from pydantic import BaseModel, ValidationError
+from pydantic import BaseModel
from typing_extensions import Self
from mcp.shared.exceptions import McpError
From d6cd675eb13fe33c3ccfa33d9860289546a134c5 Mon Sep 17 00:00:00 2001
From: Lorenzo C
Date: Mon, 8 Sep 2025 21:40:45 -0300
Subject: [PATCH 9/9] fix: type hints
---
tests/shared/test_progress_notifications.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tests/shared/test_progress_notifications.py b/tests/shared/test_progress_notifications.py
index a8e02d262f..600972272d 100644
--- a/tests/shared/test_progress_notifications.py
+++ b/tests/shared/test_progress_notifications.py
@@ -329,9 +329,9 @@ async def test_progress_callback_exception_logging():
"""Test that exceptions in progress callbacks are logged and \
don't crash the session."""
# Track logged warnings
- logged_errors = []
+ logged_errors: list[str] = []
- def mock_log_error(msg, *args):
+ def mock_log_error(msg: str, *args: Any) -> None:
logged_errors.append(msg % args if args else msg)
# Create a progress callback that raises an exception
@@ -342,7 +342,7 @@ async def failing_progress_callback(progress: float, total: float | None, messag
server = Server(name="TestProgressServer")
@server.call_tool()
- async def handle_call_tool(name: str, arguments: dict | None) -> list[types.TextContent]:
+ async def handle_call_tool(name: str, arguments: Any) -> list[types.TextContent]:
if name == "progress_tool":
# Send a progress notification
await server.request_context.session.send_progress_notification(
pFad - Phonifier reborn
Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy