Let Client take StdioServerParameters directly
Client resolves its transport from the type of its one positional argument: a server object connects in-process, a str is a Streamable HTTP URL, and anything else is entered as a Transport. stdio was the odd one out — you had to write Client(stdio_client(StdioServerParameters(...))), and the transports page had a paragraph explaining why the parameters object alone doesn't work. This adds the missing arm:
from mcp import Client, StdioServerParameters
server = StdioServerParameters(command="uv", args=["run", "server.py"])
async with Client(server) as client:
...
Client(stdio_client(server, errlog=...)) still works and is now documented as the way to redirect the child's stderr (the one knob stdio_client has beyond the params).
Motivation and Context
Connecting to a local server over stdio is one of the two normal ways to use a client, and it was the only one that needed a wrapper and a second import. The examples harness already carried a # becomes Client(params) once that overload lands note for exactly this.
Deliberately not in scope: accepting SseServerParameters / StreamableHttpParameters (the URL string already covers HTTP; SSE is legacy), and any string/path inference.
How Has This Been Tested?
- New end-to-end test in
tests/client/test_client.pythat handsClientaStdioServerParametersfor a one-lineMCPServerchild interpreter and asserts the negotiatedserver_infoand atools/listround trip. It uses a real subprocess because the process boundary is the behaviour; thefail_afteris wider than the usual 5s for interpreter cold start + shutdown escalation, with the reason in a comment. tests/docs_src/test_client_transports.pyupdated for the newtutorial004.py; the gated stories smoke test (MCP_EXAMPLES_SMOKE=1, real stdio) passes with the harness returning bare params.- Ran it by hand outside the test suite: a docs-shaped
server.py/client.pypair withcommand="uv", args=["run", "server.py"], env={...}— tool call round-trips at2026-07-28,env=reaches the child, child stderr lands on the terminal; a missing binary raisesFileNotFoundErrorfromasync with; the same params object serves two sequential clients;Client(stdio_client(server, errlog=f))sends stderr to the file.python -m stories.tools.client(stdio) exitsOK. ./scripts/test(full suite, coverage 100%, strict-no-cover) passes locally.
Breaking Changes
None. Additive: Client(stdio_client(...)) keeps working unchanged. Passing a bare StdioServerParameters previously failed at async with (it isn't an async context manager), so no working code changes meaning.
Types of changes
- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing functionality to change)
- Documentation update
Checklist
- I have read the MCP Documentation
- My code follows the repository's style guidelines
- New and existing tests pass locally
- I have added appropriate error handling
- I have added or updated documentation as needed
Additional context
Docs touched: client/transports.md (stdio section, resolution rule, recap), client/index.md ("What you can pass"), and the two other sentences that enumerate the connection forms (whats-new.md, get-started/real-host.md). The Client class and server field docstrings list the new arm. Translations under i18n/ regenerate from these and are not edited here.