| 330 | raise ValueError(f"Unsupported server config type: {type(config).__name__}") |
| 331 | |
| 332 | async def _receive_loop(self) -> None: |
| 333 | if self._transport is None: |
| 334 | return |
| 335 | try: |
| 336 | while self._transport.is_connected: |
| 337 | msg = await self._transport.receive() |
| 338 | if msg is None: |
| 339 | # Transport closed cleanly. Reject any in-flight futures |
| 340 | # so concurrent callers fail fast instead of waiting out |
| 341 | # the full tool-call timeout (5 min default). Without |
| 342 | # this, a `receive() → None` after a peer-side close left |
| 343 | # every pending request silently hung. |
| 344 | closed_exc = ConnectionError("MCP transport closed") |
| 345 | for future in self._pending_requests.values(): |
| 346 | if not future.done(): |
| 347 | future.set_exception(closed_exc) |
| 348 | self._pending_requests.clear() |
| 349 | break |
| 350 | if msg.id is not None and msg.id in self._pending_requests: |
| 351 | future = self._pending_requests.pop(msg.id) |
| 352 | if msg.error: |
| 353 | future.set_exception( |
| 354 | McpToolCallError( |
| 355 | json.dumps(msg.error), |
| 356 | msg.error.get("message", "MCP error"), |
| 357 | ) |
| 358 | ) |
| 359 | else: |
| 360 | future.set_result(msg.result) |
| 361 | elif msg.method is not None and msg.id is not None: |
| 362 | # Incoming server→client REQUEST (e.g. elicitation/create). |
| 363 | # Handle out-of-band so the loop keeps draining, then reply. |
| 364 | asyncio.get_event_loop().create_task( |
| 365 | self._handle_incoming_request(msg) |
| 366 | ) |
| 367 | except Exception as e: |
| 368 | logger.debug("MCP receive loop error: %s", e) |
| 369 | for future in self._pending_requests.values(): |
| 370 | if not future.done(): |
| 371 | future.set_exception(e) |
| 372 | self._pending_requests.clear() |
| 373 | |
| 374 | async def _handle_incoming_request(self, msg: JsonRpcMessage) -> None: |
| 375 | """Reply to a server→client request (elicitation/create, etc.).""" |