-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
Issue: MCP client is throwing the HTTP status exception disregarding the raw response
Background: I'm trying to test an edge case for the MCP client. I'm purposefully sending bad requests so that I can see how my end-customers will see the output in case of error. Like 400, 406 errors etc. Now, when it is a bad request, from the server side, I'm not only sending the HTTP error code like 400 or 406, I'm also sending the error description - like for 406 - "message":"MCP Accept header must contain: application/json, text/event-stream"
, for 400 - "message":"Invalid parameter xyz in request"
or something like that.
When I send a bare HTTP request (without using the mcp client) like this -
...
async with httpx.AsyncClient(timeout=60) as client:
try:
response = await client.post(
mcp_url,
headers=headers,
json=init_request.model_dump(by_alias=True, exclude_none=True)
)
print(f"Status: {response.status_code}")
print(f"Raw response: {response.text}")
except Exception as e:
print(f"Request failed: {e}")
traceback.print_exc()
This is the response I get -
python3 my_mcp_client_http_call.py
Status: 406
Raw response: {"jsonrpc":"2.0","error":{"code":-32006,"message":"MCP Accept header must contain: application/json, text/event-stream"},"id":"cbde555c-024d-46eb-b701-3be8de1fa5bb"}
(mcp-py311) ➜ mcp-agent
Notice how the error message contains details so the caller knows what Accept header to call. This is the message I've added from my server side.
However, when I use the mcp client for this purpose -
async with streamablehttp_client(mcp_url, headers, timeout=20, terminate_on_close=False) as (
read_stream,
write_stream,
_,
):
async with ClientSession(read_stream, write_stream) as session:
init_result = await session.initialize()
result = await session.call_tool(
<tool-name>
<tool-params>
)
print(f"Tool response: {result}")
I get this in my client -
+ Exception Group Traceback (most recent call last):
| File "/Volumes/workplace/synced/mcp-agent/my_mcp_client_remote.py", line 47, in <module>
| asyncio.run(main())
| File "/usr/local/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/runners.py", line 190, in run
| return runner.run(main)
| ^^^^^^^^^^^^^^^^
| File "/usr/local/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/runners.py", line 118, in run
| return self._loop.run_until_complete(task)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11/asyncio/base_events.py", line 654, in run_until_complete
| return future.result()
| ^^^^^^^^^^^^^^^
| File "/Volumes/workplace/synced/mcp-agent/my_mcp_client_remote.py", line 27, in main
| async with streamablehttp_client(mcp_url, headers, timeout=20, terminate_on_close=False) as (
| File "/usr/local/Cellar/python@3.11/3.11.13/Frameworks/Python.framework/Versions/3.11/lib/python3.11/contextlib.py", line 231, in __aexit__
| await self.gen.athrow(typ, value, traceback)
| File "/Volumes/workplace/synced/mcp-agent/mcp-py311/lib/python3.11/site-packages/mcp/client/streamable_http.py", line 474, in streamablehttp_client
| async with anyio.create_task_group() as tg:
| File "/Volumes/workplace/synced/mcp-agent/mcp-py311/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 772, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/Volumes/workplace/synced/mcp-agent/mcp-py311/lib/python3.11/site-packages/mcp/client/streamable_http.py", line 405, in handle_request_async
| await self._handle_post_request(ctx)
| File "/Volumes/workplace/synced/mcp-agent/mcp-py311/lib/python3.11/site-packages/mcp/client/streamable_http.py", line 277, in _handle_post_request
| response.raise_for_status()
| File "/Volumes/workplace/synced/mcp-agent/mcp-py311/lib/python3.11/site-packages/httpx/_models.py", line 829, in raise_for_status
| raise HTTPStatusError(message, request=request, response=self)
| httpx.HTTPStatusError: Client error '406 Not Acceptable' for url 'https://rpjoshi-dud.dev.genesis-primitives.aws.dev/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-west-2%3A766807730965%3Aruntime%2FagentMcpTestLocal5-E7E7ISEnws/invocations?qualifier=DEFAULT'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406
+------------------------------------
Notice it only talks about Client error '406 Not Acceptable'
but not gives the error message description. Also, would prefer to get a nicer response than an unhandled errors in a TaskGroup
message.
Relevant code link - streamable_http.py
<< I believe in that code, it should not completely disregard the error message, rather somehow send that error message (if it exists) back to the user for clarity.
Let me know what you think.
Example Code
Calling the server using client (In headers, purposefully pass in bad headers to test 406)-
async with streamablehttp_client(mcp_url, headers, timeout=20, terminate_on_close=False) as (
read_stream,
write_stream,
_,
):
async with ClientSession(read_stream, write_stream) as session:
init_result = await session.initialize()
result = await session.call_tool(
<tool-name>
<tool-params>
)
print(f"Tool response: {result}")
Calling the server via http call -
init_request = JSONRPCRequest(
jsonrpc="2.0",
id="1",
method="initialize",
params={"protocolVersion": "2025-06-18", "capabilities": {"sampling":{},"roots":{"listChanged":True}}},
clientInfo={"name":"mcp-inspector","version":"0.16.1"}
)
async with httpx.AsyncClient(timeout=60) as client:
try:
response = await client.post(
mcp_url,
headers=headers,
json=init_request.model_dump(by_alias=True, exclude_none=True)
)
print(f"Status: {response.status_code}")
print(f"Raw response: {response.text}")
except Exception as e:
print(f"Request failed: {e}")
traceback.print_exc()
Python & MCP Python SDK
python - 3.11
mcp - 1.12.1