Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/a2a/server/routes/jsonrpc_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,11 @@ async def handle_requests(self, request: Request) -> Response: # noqa: PLR0911,
body = await request.json()
if isinstance(body, dict):
request_id = body.get('id')
# Ensure request_id is valid for JSON-RPC response (str/int/None only)
if request_id is not None and not isinstance(
request_id, str | int
# Ensure request_id is valid for JSON-RPC response (str/int/None only;
# exclude bool since bool is a subclass of int in Python)
if isinstance(request_id, bool) or (
request_id is not None
and not isinstance(request_id, str | int)
):
request_id = None
logger.debug('Request body: %s', body)
Expand All @@ -257,6 +259,13 @@ async def handle_requests(self, request: Request) -> Response: # noqa: PLR0911,
message="Invalid request: 'jsonrpc' must be exactly '2.0'"
),
)
if isinstance(base_request._id, bool): # noqa: SLF001
return self._generate_error_response(
request_id,
InvalidRequestError(
message='Invalid request: id must be a string, integer, or null'
),
)
except Exception as e:
logger.exception('Failed to validate base JSON-RPC request')
return self._generate_error_response(
Expand Down
35 changes: 35 additions & 0 deletions tests/server/routes/test_jsonrpc_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,41 @@ def _make_send_message_request(
}


class TestJsonRpcRequestIds:
"""JSON booleans are not numeric JSON-RPC request identifiers."""

@pytest.mark.parametrize('request_id', [True, False])
def test_boolean_id_is_rejected(
self, client: TestClient, mock_handler: AsyncMock, request_id: bool
) -> None:
request = _make_send_message_request()
request['id'] = request_id

response = client.post('/', json=request)
body = response.json()

assert response.status_code == 200
assert body['id'] is None
assert body['error']['code'] == -32600
mock_handler.on_message_send.assert_not_awaited()

@pytest.mark.parametrize('request_id', [0, 1, '', 'request'])
def test_integer_and_string_ids_are_preserved(
self, client: TestClient, mock_handler: AsyncMock, request_id: int | str
) -> None:
request = _make_send_message_request()
request['id'] = request_id

response = client.post('/', json=request)
body = response.json()

assert response.status_code == 200
assert body['id'] == request_id
assert type(body['id']) is type(request_id)
assert 'result' in body
mock_handler.on_message_send.assert_awaited_once()


class TestUnrecognizedFields:
"""Unrecognized fields are ignored, for forward compatibility."""

Expand Down
Loading