| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
…protocol#348)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…contextprotocol#1564)\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
This PR primarily addresses issue #1564 by preventing stdio_client from crashing when the subprocess stdout stream closes abruptly, treating BrokenResourceError as a normal shutdown condition.
Changes:
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/mcp/client/stdio.py | Swallows BrokenResourceError during stdout reads to avoid transport crashes on abrupt subprocess pipe closure. |
| src/mcp/server/mcpserver/server.py | Adds 3-tuple handling for tool call results to set CallToolResult.is_error. |
| docs/migration.md | Documents the new (unstructured, structured, is_error) 3-tuple tool return option. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sorry, something went wrong.
| # Support either (unstructured_content, structured_content) or | ||
| # (unstructured_content, structured_content, is_error). The third element, | ||
| # if present, controls the CallToolResult.is_error flag. | ||
| if len(result) == 2: | ||
| unstructured_content, structured_content = result | ||
| is_error = False | ||
| elif len(result) == 3: | ||
| unstructured_content, structured_content, is_error = result | ||
| else: | ||
| # Fallback: treat as a sequence of content blocks | ||
| return CallToolResult(content=list(result)) | ||
| return CallToolResult( | ||
| content=list(unstructured_content), # type: ignore[arg-type] | ||
| structured_content=structured_content, # type: ignore[arg-type] | ||
| is_error=bool(is_error), |
| if isinstance(result, tuple): | ||
| # Support either (unstructured_content, structured_content) or | ||
| # (unstructured_content, structured_content, is_error). The third element, | ||
| # if present, controls the CallToolResult.is_error flag. | ||
| if len(result) == 2: | ||
| unstructured_content, structured_content = result | ||
| is_error = False | ||
| elif len(result) == 3: | ||
| unstructured_content, structured_content, is_error = result | ||
| else: | ||
| # Fallback: treat as a sequence of content blocks | ||
| return CallToolResult(content=list(result)) | ||
| return CallToolResult( | ||
| content=list(unstructured_content), # type: ignore[arg-type] | ||
| structured_content=structured_content, # type: ignore[arg-type] | ||
| is_error=bool(is_error), |
| Tools may now return a 3-tuple: (unstructured_content, structured_content, is_error). | ||
| If the third element is True, the resulting CallToolResult sent to clients will have | ||
| is_error=True. This allows returning non-text content (images, audio) while still | ||
| indicating the tool execution failed or produced an error state. | ||
|
|
||
| Alternatively, tools may return a full `CallToolResult` instance directly to control | ||
| is_error and other fields explicitly. |
| async def stdout_reader(): | ||
| assert process.stdout, "Opened process is missing stdout" | ||
|
|
||
| try: | ||
| async with read_stream_writer: | ||
| buffer = "" | ||
| async for chunk in TextReceiveStream( | ||
| process.stdout, | ||
| encoding=server.encoding, | ||
| errors=server.encoding_error_handler, | ||
| ): | ||
| lines = (buffer + chunk).split("\n") | ||
| buffer = lines.pop() | ||
|
|
||
| for line in lines: | ||
| try: | ||
| message = types.jsonrpc_message_adapter.validate_json(line, by_name=False) | ||
| except Exception as exc: # pragma: no cover | ||
| logger.exception("Failed to parse JSONRPC message from server") | ||
| await read_stream_writer.send(exc) | ||
| continue | ||
|
|
||
| session_message = SessionMessage(message) | ||
| await read_stream_writer.send(session_message) | ||
| except anyio.ClosedResourceError: # pragma: lax no cover | ||
| except (anyio.ClosedResourceError, anyio.BrokenResourceError): # pragma: lax no cover | ||
| await anyio.lowlevel.checkpoint() | ||
|
|
| Back | FazBrowse Home | New Git URL |
Fixes #1564 — treat BrokenResourceError from process stdout as normal shutdown and avoid crashing the client. See issue: #1564