@@ -78,77 +78,25 @@ async def _send_request(
7878 async with self ._session .post (
7979 url , json = payload , headers = req_headers
8080 ) as response :
81- if response .status == 400 :
81+ json_resp = None
82+ if not response .ok :
8283 try :
8384 json_resp = await response .json ()
84- if "error" in json_resp :
85- err_val = json_resp ["error" ]
86- if isinstance (err_val , dict ) and err_val .get ("code" ) == - 32022 :
87- server_supported = err_val .get ("data" , {}).get (
88- "supported" , []
89- )
90-
91- client_supported = (
92- self ._supported_protocols
93- or Protocol .get_supported_mcp_versions ()
94- )
95- mutually_supported = [
96- v for v in client_supported if v in server_supported
97- ]
98-
99- if mutually_supported :
100- raise ProtocolNegotiationError (mutually_supported [0 ])
101- else :
102- raise RuntimeError (
103- "No mutually supported protocol version. "
104- f"Client supports: { client_supported } , "
105- f"Server supports: { server_supported } "
106- )
107- elif (
108- isinstance (err_val , str )
109- and "invalid protocol version" in err_val .lower ()
110- ):
111- # Cascading Fallback: Legacy servers throw this string error.
112- # We pick the next version from the user's supported list.
113- client_supported = (
114- self ._supported_protocols
115- or Protocol .get_supported_mcp_versions ()
116- )
117- try :
118- current_idx = client_supported .index (
119- self ._protocol_version
120- )
121- if current_idx + 1 < len (client_supported ):
122- raise ProtocolNegotiationError (
123- client_supported [current_idx + 1 ]
124- )
125- else :
126- raise RuntimeError (
127- "Server threw 'invalid protocol version' but no fallback versions "
128- "remain in the user's supported protocols array."
129- )
130- except ValueError :
131- # Current version not in list somehow, just fallback to highest stateful
132- raise ProtocolNegotiationError (Protocol .MCP_v20251125 )
133- except Exception as e :
134- if isinstance (e , (RuntimeError , ProtocolNegotiationError )):
135- raise e
136-
137- if not response .ok :
138- error_text = await response .text ()
139- raise RuntimeError (
140- "API request failed with status"
141- f" { response .status } ({ response .reason } ). Server response:"
142- f" { error_text } "
143- )
144-
145- if response .status == 204 or response .content .at_eof ():
146- return None
147-
148- json_resp = await response .json ()
85+ except Exception :
86+ # Not JSON, fallback to raw text
87+ error_text = await response .text ()
88+ raise RuntimeError (
89+ "API request failed with status"
90+ f" { response .status } ({ response .reason } ). Server response:"
91+ f" { error_text } "
92+ )
93+ else :
94+ if response .status == 204 or response .content .at_eof ():
95+ return None
96+ json_resp = await response .json ()
14997
15098 # Check for JSON-RPC Error
151- if "error" in json_resp :
99+ if json_resp and isinstance ( json_resp , dict ) and "error" in json_resp :
152100 err_val = json_resp ["error" ]
153101 if isinstance (err_val , dict ) and err_val .get ("code" ) == - 32022 :
154102 server_supported = err_val .get ("data" , {}).get ("supported" , [])
@@ -167,6 +115,31 @@ async def _send_request(
167115 f"Client supports: { client_supported } , "
168116 f"Server supports: { server_supported } "
169117 )
118+ elif (
119+ isinstance (err_val , str )
120+ and "invalid protocol version" in err_val .lower ()
121+ ):
122+ # Cascading Fallback: Legacy servers throw this string error.
123+ # We pick the next version from the user's supported list.
124+ client_supported = (
125+ self ._supported_protocols
126+ or Protocol .get_supported_mcp_versions ()
127+ )
128+ try :
129+ current_idx = client_supported .index (self ._protocol_version )
130+ if current_idx + 1 < len (client_supported ):
131+ raise ProtocolNegotiationError (
132+ client_supported [current_idx + 1 ]
133+ )
134+ else :
135+ raise RuntimeError (
136+ "Server threw 'invalid protocol version' but no fallback versions "
137+ "remain in the user's supported protocols array."
138+ )
139+ except ValueError :
140+ # Current version not in list somehow, just fallback to highest stateful
141+ raise ProtocolNegotiationError (Protocol .MCP_v20251125 )
142+
170143 try :
171144 err = types .JSONRPCError .model_validate (json_resp ).error
172145 raise RuntimeError (
@@ -177,6 +150,13 @@ async def _send_request(
177150 raw_error = json_resp .get ("error" , {})
178151 raise RuntimeError (f"MCP request failed: { raw_error } " )
179152
153+ # If response.ok was False, but there was no JSON-RPC "error" field
154+ if not response .ok :
155+ raise RuntimeError (
156+ f"API request failed with status { response .status } ({ response .reason } ). "
157+ f"Server response: { json_resp } "
158+ )
159+
180160 # Parse Result
181161 if isinstance (request , types .MCPRequest ):
182162 try :
0 commit comments