Skip to content

Commit ba142df

Browse files
authored
feat(jsonrpc): add option to disable oversized payload check in JSONRPC applications (#544)
This commit adds a constructor boolean variable disable_content_length_check to the base class JSONRPCApplication and it's derived classes A2AFastAPIApplication and A2AStarletteApplication. In JSONRPCApplication's method _handle_requests it adds a if disable_content_length_check check before checking if the payload exceeds the MAX_CONTENT_LENGTH limit. This enables agent creators to disable the 10MB payload size limitation.
1 parent ef020c5 commit ba142df

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed

src/a2a/server/apps/jsonrpc/fastapi_app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def __init__( # noqa: PLR0913
7777
[AgentCard, ServerCallContext], AgentCard
7878
]
7979
| None = None,
80+
max_content_length: int | None = 10 * 1024 * 1024, # 10MB
8081
) -> None:
8182
"""Initializes the A2AFastAPIApplication.
8283
@@ -94,6 +95,8 @@ def __init__( # noqa: PLR0913
9495
extended_card_modifier: An optional callback to dynamically modify
9596
the extended agent card before it is served. It receives the
9697
call context.
98+
max_content_length: The maximum allowed content length for incoming
99+
requests. Defaults to 10MB. Set to None for unbounded maximum.
97100
"""
98101
if not _package_fastapi_installed:
99102
raise ImportError(
@@ -108,6 +111,7 @@ def __init__( # noqa: PLR0913
108111
context_builder=context_builder,
109112
card_modifier=card_modifier,
110113
extended_card_modifier=extended_card_modifier,
114+
max_content_length=max_content_length,
111115
)
112116

113117
def add_routes_to_app(

src/a2a/server/apps/jsonrpc/jsonrpc_app.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@
9191
Response = Any
9292
HTTP_413_REQUEST_ENTITY_TOO_LARGE = Any
9393

94-
MAX_CONTENT_LENGTH = 10_000_000
95-
9694

9795
class StarletteUserProxy(A2AUser):
9896
"""Adapts the Starlette User class to the A2A user representation."""
@@ -185,6 +183,7 @@ def __init__( # noqa: PLR0913
185183
[AgentCard, ServerCallContext], AgentCard
186184
]
187185
| None = None,
186+
max_content_length: int | None = 10 * 1024 * 1024, # 10MB
188187
) -> None:
189188
"""Initializes the JSONRPCApplication.
190189
@@ -202,6 +201,8 @@ def __init__( # noqa: PLR0913
202201
extended_card_modifier: An optional callback to dynamically modify
203202
the extended agent card before it is served. It receives the
204203
call context.
204+
max_content_length: The maximum allowed content length for incoming
205+
requests. Defaults to 10MB. Set to None for unbounded maximum.
205206
"""
206207
if not _package_starlette_installed:
207208
raise ImportError(
@@ -220,6 +221,7 @@ def __init__( # noqa: PLR0913
220221
extended_card_modifier=extended_card_modifier,
221222
)
222223
self._context_builder = context_builder or DefaultCallContextBuilder()
224+
self._max_content_length = max_content_length
223225

224226
def _generate_error_response(
225227
self, request_id: str | int | None, error: JSONRPCError | A2AError
@@ -261,6 +263,22 @@ def _generate_error_response(
261263
status_code=200,
262264
)
263265

266+
def _allowed_content_length(self, request: Request) -> bool:
267+
"""Checks if the request content length is within the allowed maximum.
268+
269+
Args:
270+
request: The incoming Starlette Request object.
271+
272+
Returns:
273+
False if the content length is larger than the allowed maximum, True otherwise.
274+
"""
275+
if self._max_content_length is not None:
276+
with contextlib.suppress(ValueError):
277+
content_length = int(request.headers.get('content-length', '0'))
278+
if content_length and content_length > self._max_content_length:
279+
return False
280+
return True
281+
264282
async def _handle_requests(self, request: Request) -> Response: # noqa: PLR0911
265283
"""Handles incoming POST requests to the main A2A endpoint.
266284
@@ -291,18 +309,14 @@ async def _handle_requests(self, request: Request) -> Response: # noqa: PLR0911
291309
request_id, str | int
292310
):
293311
request_id = None
294-
# Treat very large payloads as invalid request (-32600) before routing
295-
with contextlib.suppress(Exception):
296-
content_length = int(request.headers.get('content-length', '0'))
297-
if content_length and content_length > MAX_CONTENT_LENGTH:
298-
return self._generate_error_response(
299-
request_id,
300-
A2AError(
301-
root=InvalidRequestError(
302-
message='Payload too large'
303-
)
304-
),
305-
)
312+
# Treat payloads lager than allowed as invalid request (-32600) before routing
313+
if not self._allowed_content_length(request):
314+
return self._generate_error_response(
315+
request_id,
316+
A2AError(
317+
root=InvalidRequestError(message='Payload too large')
318+
),
319+
)
306320
logger.debug('Request body: %s', body)
307321
# 1) Validate base JSON-RPC structure only (-32600 on failure)
308322
try:

src/a2a/server/apps/jsonrpc/starlette_app.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__( # noqa: PLR0913
5959
[AgentCard, ServerCallContext], AgentCard
6060
]
6161
| None = None,
62+
max_content_length: int | None = 10 * 1024 * 1024, # 10MB
6263
) -> None:
6364
"""Initializes the A2AStarletteApplication.
6465
@@ -76,6 +77,8 @@ def __init__( # noqa: PLR0913
7677
extended_card_modifier: An optional callback to dynamically modify
7778
the extended agent card before it is served. It receives the
7879
call context.
80+
max_content_length: The maximum allowed content length for incoming
81+
requests. Defaults to 10MB. Set to None for unbounded maximum.
7982
"""
8083
if not _package_starlette_installed:
8184
raise ImportError(
@@ -90,6 +93,7 @@ def __init__( # noqa: PLR0913
9093
context_builder=context_builder,
9194
card_modifier=card_modifier,
9295
extended_card_modifier=extended_card_modifier,
96+
max_content_length=max_content_length,
9397
)
9498

9599
def routes(

tests/server/apps/jsonrpc/test_serialization.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,42 @@ def test_handle_oversized_payload(agent_card_with_api_key: AgentCard):
136136
assert data['error']['code'] == InvalidRequestError().code
137137

138138

139+
@pytest.mark.parametrize(
140+
'max_content_length',
141+
[
142+
None,
143+
11 * 1024 * 1024,
144+
30 * 1024 * 1024,
145+
],
146+
)
147+
def test_handle_oversized_payload_with_max_content_length(
148+
agent_card_with_api_key: AgentCard,
149+
max_content_length: int | None,
150+
):
151+
"""Test handling of JSON payloads with sizes within custom max_content_length."""
152+
handler = mock.AsyncMock()
153+
app_instance = A2AStarletteApplication(
154+
agent_card_with_api_key, handler, max_content_length=max_content_length
155+
)
156+
client = TestClient(app_instance.build())
157+
158+
large_string = 'a' * 11 * 1_000_000 # 11MB string
159+
payload = {
160+
'jsonrpc': '2.0',
161+
'method': 'test',
162+
'id': 1,
163+
'params': {'data': large_string},
164+
}
165+
166+
response = client.post('/', json=payload)
167+
assert response.status_code == 200
168+
data = response.json()
169+
# When max_content_length is set, requests up to that size should not be
170+
# rejected due to payload size. The request might fail for other reasons,
171+
# but it shouldn't be an InvalidRequestError related to the content length.
172+
assert data['error']['code'] != InvalidRequestError().code
173+
174+
139175
def test_handle_unicode_characters(agent_card_with_api_key: AgentCard):
140176
"""Test handling of unicode characters in JSON payload."""
141177
handler = mock.AsyncMock()

0 commit comments

Comments
 (0)