@@ -238,7 +238,13 @@ def session_manager(self) -> StreamableHTTPSessionManager:
238238 return self ._lowlevel_server .session_manager # pragma: no cover
239239
240240 @overload
241- def run (self , transport : Literal ["stdio" ] = ...) -> None : ...
241+ def run (
242+ self ,
243+ transport : Literal ["stdio" ] = ...,
244+ * ,
245+ stdin : anyio .AsyncFile [str ] | None = ...,
246+ stdout : anyio .AsyncFile [str ] | None = ...,
247+ ) -> None : ...
242248
243249 @overload
244250 def run (
@@ -270,12 +276,19 @@ def run(
270276 def run (
271277 self ,
272278 transport : Literal ["stdio" , "sse" , "streamable-http" ] = "stdio" ,
279+ * ,
280+ stdin : anyio .AsyncFile [str ] | None = None ,
281+ stdout : anyio .AsyncFile [str ] | None = None ,
273282 ** kwargs : Any ,
274283 ) -> None :
275284 """Run the MCP server. Note this is a synchronous function.
276285
277286 Args:
278287 transport: Transport protocol to use ("stdio", "sse", or "streamable-http")
288+ stdin: Optional async text stream for MCP input (stdio transport only).
289+ When omitted, uses process stdin. See :func:`mcp.server.stdio.stdio_server`.
290+ stdout: Optional async text stream for MCP output (stdio transport only).
291+ When omitted, uses process stdout.
279292 **kwargs: Transport-specific options (see overloads for details)
280293 """
281294 TRANSPORTS = Literal ["stdio" , "sse" , "streamable-http" ]
@@ -284,7 +297,7 @@ def run(
284297
285298 match transport :
286299 case "stdio" :
287- anyio .run (self .run_stdio_async )
300+ anyio .run (lambda : self .run_stdio_async ( stdin = stdin , stdout = stdout ) )
288301 case "sse" : # pragma: no cover
289302 anyio .run (lambda : self .run_sse_async (** kwargs ))
290303 case "streamable-http" : # pragma: no cover
@@ -836,9 +849,25 @@ def decorator( # pragma: no cover
836849
837850 return decorator # pragma: no cover
838851
839- async def run_stdio_async (self ) -> None :
840- """Run the server using stdio transport."""
841- async with stdio_server () as (read_stream , write_stream ):
852+ async def run_stdio_async (
853+ self ,
854+ * ,
855+ stdin : anyio .AsyncFile [str ] | None = None ,
856+ stdout : anyio .AsyncFile [str ] | None = None ,
857+ ) -> None :
858+ """Run the server using stdio transport.
859+
860+ Args:
861+ stdin: Async text stream to read JSON-RPC lines from. When ``None``,
862+ uses the process stdin (see :func:`mcp.server.stdio.stdio_server`).
863+ stdout: Async text stream to write JSON-RPC lines to. When ``None``,
864+ uses the process stdout.
865+
866+ Custom streams are useful when the process ``sys.stdout`` / ``sys.stdin``
867+ must be redirected (for example so logging or subprocess output does not
868+ corrupt the MCP JSON-RPC stream on fd 1).
869+ """
870+ async with stdio_server (stdin = stdin , stdout = stdout ) as (read_stream , write_stream ):
842871 await self ._lowlevel_server .run (
843872 read_stream ,
844873 write_stream ,
0 commit comments