-
Couldn't load subscription status.
- Fork 2.7k
Simplify code on stdio_client
#1181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,13 @@ | ||
| import logging | ||
| import os | ||
| import sys | ||
| from contextlib import asynccontextmanager | ||
| from contextlib import AsyncExitStack, asynccontextmanager | ||
| from pathlib import Path | ||
| from typing import Literal, TextIO | ||
|
|
||
| import anyio | ||
| import anyio.lowlevel | ||
| from anyio.abc import Process | ||
| from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream | ||
| from anyio.streams.text import TextReceiveStream | ||
| from pydantic import BaseModel, Field | ||
|
|
||
|
|
@@ -107,33 +106,19 @@ async def stdio_client(server: StdioServerParameters, errlog: TextIO = sys.stder | |
| Client transport for stdio: this will connect to a server by spawning a | ||
| process and communicating with it over stdin/stdout. | ||
| """ | ||
| read_stream: MemoryObjectReceiveStream[SessionMessage | Exception] | ||
| read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception] | ||
| read_stream_writer, read_stream = anyio.create_memory_object_stream[SessionMessage | Exception](0) | ||
| write_stream, write_stream_reader = anyio.create_memory_object_stream[SessionMessage](0) | ||
|
|
||
| write_stream: MemoryObjectSendStream[SessionMessage] | ||
| write_stream_reader: MemoryObjectReceiveStream[SessionMessage] | ||
| command = _get_executable_command(server.command) | ||
|
|
||
| read_stream_writer, read_stream = anyio.create_memory_object_stream(0) | ||
| write_stream, write_stream_reader = anyio.create_memory_object_stream(0) | ||
|
|
||
| try: | ||
| command = _get_executable_command(server.command) | ||
|
|
||
| # Open process with stderr piped for capture | ||
| process = await _create_platform_compatible_process( | ||
| command=command, | ||
| args=server.args, | ||
| env=({**get_default_environment(), **server.env} if server.env is not None else get_default_environment()), | ||
| errlog=errlog, | ||
| cwd=server.cwd, | ||
| ) | ||
| except OSError: | ||
| # Clean up streams if process creation fails | ||
| await read_stream.aclose() | ||
| await write_stream.aclose() | ||
| await read_stream_writer.aclose() | ||
| await write_stream_reader.aclose() | ||
| raise | ||
| # Open process with stderr piped for capture | ||
| process = await _create_platform_compatible_process( | ||
| command=command, | ||
| args=server.args, | ||
| env=({**get_default_environment(), **server.env} if server.env is not None else get_default_environment()), | ||
| errlog=errlog, | ||
| cwd=server.cwd, | ||
| ) | ||
|
|
||
| async def stdout_reader(): | ||
| assert process.stdout, "Opened process is missing stdout" | ||
|
|
@@ -177,14 +162,13 @@ async def stdin_writer(): | |
| except anyio.ClosedResourceError: | ||
| await anyio.lowlevel.checkpoint() | ||
|
|
||
| async with ( | ||
| anyio.create_task_group() as tg, | ||
| process, | ||
| ): | ||
| async with anyio.create_task_group() as tg, process: | ||
| tg.start_soon(stdout_reader) | ||
| tg.start_soon(stdin_writer) | ||
|
|
||
| try: | ||
| yield read_stream, write_stream | ||
| async with read_stream, write_stream: | ||
| yield read_stream, write_stream | ||
| finally: | ||
| # MCP spec: stdio shutdown sequence | ||
| # 1. Close input stream to server | ||
|
|
@@ -208,10 +192,6 @@ async def stdin_writer(): | |
| except ProcessLookupError: | ||
| # Process already exited, which is fine | ||
| pass | ||
| await read_stream.aclose() | ||
| await write_stream.aclose() | ||
| await read_stream_writer.aclose() | ||
| await write_stream_reader.aclose() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to close There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it seems I was wrong... 👀 |
||
|
|
||
|
|
||
| def _get_executable_command(command: str) -> str: | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Readability changes - I can revert if wanted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It actually never triggers
OSErrorhere.anyio.open_processdoesn't trigger.Maybe the Windows logic does, but even if it does... The streams don't need to be closed because they are not even open yet, so just removing the
exceptis fine.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the test suite I'm wrong... I'm not sure how.