From 90f5e917afbcb08fd5a067f9e166aa83feab876a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=93=E6=98=8E=20=E7=8E=8B?= Date: Fri, 30 May 2025 16:04:10 +0800 Subject: [PATCH 1/7] normalize string ID to int in server messages for compatibility Some non-standard SSE servers may return numeric IDs as strings, even when the original request used an integer. This change ensures that message.root.id is always an integer to maintain consistency and avoid type issues in downstream processing. --- src/mcp/client/sse.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index 2013e4199..8887b9635 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -98,6 +98,21 @@ async def sse_reader( message = types.JSONRPCMessage.model_validate_json( # noqa: E501 sse.data ) + + # Normalize ID to int if it's a numeric string. + # Some non-standard SSE servers return numeric IDs as strings, + # even if the original request used an integer ID. + if isinstance(message.root, types.JSONRPCResponse): + msg_id = message.root.id + if isinstance(msg_id, str) and msg_id.isdigit(): + message.root.id = int(msg_id) + elif not isinstance(msg_id, int): + logger.warning( + "Ignored message with " + f"invalid ID: {msg_id!r}" + ) + continue + logger.debug( f"Received server message: {message}" ) From 069a900c22442f162816a87b115d69b84d429b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=93=E6=98=8E=20=E7=8E=8B?= Date: Fri, 30 May 2025 16:11:07 +0800 Subject: [PATCH 2/7] normalize string ID to int in server messages for compatibility Some non-standard SSE servers may return numeric IDs as strings, even when the original request used an integer. This change ensures that message.root.id is always an integer to maintain consistency and avoid type issues in downstream processing. --- src/mcp/client/sse.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index 8887b9635..ad8b0620c 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -102,9 +102,13 @@ async def sse_reader( # Normalize ID to int if it's a numeric string. # Some non-standard SSE servers return numeric IDs as strings, # even if the original request used an integer ID. - if isinstance(message.root, types.JSONRPCResponse): + if isinstance( # noqa: E501 + message.root, + types.JSONRPCResponse + ): msg_id = message.root.id - if isinstance(msg_id, str) and msg_id.isdigit(): + if (isinstance(msg_id, str) and + msg_id.isdigit()): message.root.id = int(msg_id) elif not isinstance(msg_id, int): logger.warning( @@ -112,7 +116,7 @@ async def sse_reader( f"invalid ID: {msg_id!r}" ) continue - + logger.debug( f"Received server message: {message}" ) From 1cf2263811b3cc432a140fe3c782786311dd6c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=93=E6=98=8E=20=E7=8E=8B?= Date: Fri, 30 May 2025 16:13:55 +0800 Subject: [PATCH 3/7] format code: E501 format code --- src/mcp/client/sse.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index ad8b0620c..3172e5feb 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -99,9 +99,8 @@ async def sse_reader( sse.data ) - # Normalize ID to int if it's a numeric string. - # Some non-standard SSE servers return numeric IDs as strings, - # even if the original request used an integer ID. + # Normalize ID to int + # if it's a numeric string. if isinstance( # noqa: E501 message.root, types.JSONRPCResponse From 095d152cfcb04b28d9d6e604aeaa831c2f33c8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=93=E6=98=8E=20=E7=8E=8B?= Date: Fri, 30 May 2025 16:04:10 +0800 Subject: [PATCH 4/7] normalize string ID to int in server messages for compatibility Some non-standard SSE servers may return numeric IDs as strings, even when the original request used an integer. This change ensures that message.root.id is always an integer to maintain consistency and avoid type issues in downstream processing. --- src/mcp/client/sse.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index 2013e4199..8887b9635 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -98,6 +98,21 @@ async def sse_reader( message = types.JSONRPCMessage.model_validate_json( # noqa: E501 sse.data ) + + # Normalize ID to int if it's a numeric string. + # Some non-standard SSE servers return numeric IDs as strings, + # even if the original request used an integer ID. + if isinstance(message.root, types.JSONRPCResponse): + msg_id = message.root.id + if isinstance(msg_id, str) and msg_id.isdigit(): + message.root.id = int(msg_id) + elif not isinstance(msg_id, int): + logger.warning( + "Ignored message with " + f"invalid ID: {msg_id!r}" + ) + continue + logger.debug( f"Received server message: {message}" ) From 5a5144891215d1bbc51c9d2c11874d165f11e5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=93=E6=98=8E=20=E7=8E=8B?= Date: Fri, 30 May 2025 16:11:07 +0800 Subject: [PATCH 5/7] normalize string ID to int in server messages for compatibility Some non-standard SSE servers may return numeric IDs as strings, even when the original request used an integer. This change ensures that message.root.id is always an integer to maintain consistency and avoid type issues in downstream processing. --- src/mcp/client/sse.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index 8887b9635..ad8b0620c 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -102,9 +102,13 @@ async def sse_reader( # Normalize ID to int if it's a numeric string. # Some non-standard SSE servers return numeric IDs as strings, # even if the original request used an integer ID. - if isinstance(message.root, types.JSONRPCResponse): + if isinstance( # noqa: E501 + message.root, + types.JSONRPCResponse + ): msg_id = message.root.id - if isinstance(msg_id, str) and msg_id.isdigit(): + if (isinstance(msg_id, str) and + msg_id.isdigit()): message.root.id = int(msg_id) elif not isinstance(msg_id, int): logger.warning( @@ -112,7 +116,7 @@ async def sse_reader( f"invalid ID: {msg_id!r}" ) continue - + logger.debug( f"Received server message: {message}" ) From cc0942b373e70d06c27641627b1d30ea90031518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=93=E6=98=8E=20=E7=8E=8B?= Date: Fri, 30 May 2025 16:13:55 +0800 Subject: [PATCH 6/7] format code: E501 format code --- src/mcp/client/sse.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index ad8b0620c..3172e5feb 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -99,9 +99,8 @@ async def sse_reader( sse.data ) - # Normalize ID to int if it's a numeric string. - # Some non-standard SSE servers return numeric IDs as strings, - # even if the original request used an integer ID. + # Normalize ID to int + # if it's a numeric string. if isinstance( # noqa: E501 message.root, types.JSONRPCResponse From c3d7cc9332d251f1ccc87940ba5702c879cb3a0f Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Jun 2025 07:49:24 -0700 Subject: [PATCH 7/7] Only change the RequestId --- src/mcp/client/sse.py | 18 ------------------ src/mcp/types.py | 4 ++-- tests/shared/test_sse.py | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/mcp/client/sse.py b/src/mcp/client/sse.py index 3172e5feb..2013e4199 100644 --- a/src/mcp/client/sse.py +++ b/src/mcp/client/sse.py @@ -98,24 +98,6 @@ async def sse_reader( message = types.JSONRPCMessage.model_validate_json( # noqa: E501 sse.data ) - - # Normalize ID to int - # if it's a numeric string. - if isinstance( # noqa: E501 - message.root, - types.JSONRPCResponse - ): - msg_id = message.root.id - if (isinstance(msg_id, str) and - msg_id.isdigit()): - message.root.id = int(msg_id) - elif not isinstance(msg_id, int): - logger.warning( - "Ignored message with " - f"invalid ID: {msg_id!r}" - ) - continue - logger.debug( f"Received server message: {message}" ) diff --git a/src/mcp/types.py b/src/mcp/types.py index bedb3c908..2a0b477e6 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -34,7 +34,7 @@ ProgressToken = str | int Cursor = str Role = Literal["user", "assistant"] -RequestId = str | int +RequestId = Annotated[int | str, Field(union_mode="left_to_right")] AnyFunction: TypeAlias = Callable[..., Any] @@ -353,7 +353,7 @@ class ProgressNotificationParams(NotificationParams): """Total number of items to process (or total progress required), if known.""" message: str | None = None """ - Message related to progress. This should provide relevant human readable + Message related to progress. This should provide relevant human readable progress information. """ model_config = ConfigDict(extra="allow") diff --git a/tests/shared/test_sse.py b/tests/shared/test_sse.py index 78bbbb235..4e3f5f0a9 100644 --- a/tests/shared/test_sse.py +++ b/tests/shared/test_sse.py @@ -8,12 +8,14 @@ import httpx import pytest import uvicorn +from inline_snapshot import snapshot from pydantic import AnyUrl from starlette.applications import Starlette from starlette.requests import Request from starlette.responses import Response from starlette.routing import Mount, Route +import mcp.types as types from mcp.client.session import ClientSession from mcp.client.sse import sse_client from mcp.server import Server @@ -503,3 +505,17 @@ async def test_request_context_isolation(context_server: None, server_url: str) assert ctx["request_id"] == f"request-{i}" assert ctx["headers"].get("x-request-id") == f"request-{i}" assert ctx["headers"].get("x-custom-value") == f"value-{i}" + + +def test_sse_message_id_coercion(): + """Test that string message IDs that look like integers are parsed as integers. + + See for more details. + """ + json_message = '{"jsonrpc": "2.0", "id": "123", "method": "ping", "params": null}' + msg = types.JSONRPCMessage.model_validate_json(json_message) + assert msg == snapshot( + types.JSONRPCMessage( + root=types.JSONRPCRequest(method="ping", jsonrpc="2.0", id=123) + ) + )