diff --git a/examples/snippets/clients/logging_client.py b/examples/snippets/clients/logging_client.py new file mode 100644 index 0000000000..3dbd058035 --- /dev/null +++ b/examples/snippets/clients/logging_client.py @@ -0,0 +1,46 @@ +""" +cd to the `examples/snippets/clients` directory and run: + uv run logging-client +""" + +import asyncio +import os + +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client +from mcp.types import LoggingMessageNotificationParams + +# Create server parameters for stdio connection +server_params = StdioServerParameters( + command="uv", # Using uv to run the server + args=["run", "server", "logging", "stdio"], # We're already in snippets dir + env={"UV_INDEX": os.environ.get("UV_INDEX", "")}, +) + + +async def logging_callback(params: LoggingMessageNotificationParams): + print(f"Log Level: {params.level}, Message: {params.data}") + + +async def run(): + fds = os.pipe() + reader = os.fdopen(fds[0], "r") + writer = os.fdopen(fds[1], "w") + async with stdio_client(server_params, errlog=writer) as (read, write): + async with ClientSession(read, write, logging_callback=logging_callback) as session: + await session.initialize() + + await session.list_tools() + await session.call_tool("log", arguments={}) + writer.close() + print("Captured stderr logs:") + print(reader.read()) + + +def main(): + """Entry point for the client script.""" + asyncio.run(run()) + + +if __name__ == "__main__": + main() diff --git a/examples/snippets/pyproject.toml b/examples/snippets/pyproject.toml index 4e68846a09..48f4312603 100644 --- a/examples/snippets/pyproject.toml +++ b/examples/snippets/pyproject.toml @@ -22,3 +22,4 @@ direct-execution-server = "servers.direct_execution:main" display-utilities-client = "clients.display_utilities:main" oauth-client = "clients.oauth_client:run" elicitation-client = "clients.url_elicitation_client:run" +logging-client = "clients.logging_client:main" diff --git a/examples/snippets/servers/logging.py b/examples/snippets/servers/logging.py new file mode 100644 index 0000000000..bee16f4d3f --- /dev/null +++ b/examples/snippets/servers/logging.py @@ -0,0 +1,24 @@ +""" +FastMCP Echo Server that sends log messages and prints to stderr +""" + +import sys + +from mcp.server.fastmcp import Context, FastMCP +from mcp.server.session import ServerSession + +mcp = FastMCP("Logging and stdio test") + + +@mcp.tool() +async def log(ctx: Context[ServerSession, None]) -> str: + await ctx.debug("Debug message") + await ctx.info("Info message") + print("Stderr message", file=sys.stderr) + await ctx.warning("Warning message") + await ctx.error("Error message") + return "done" + + +if __name__ == "__main__": + mcp.run(transport="stdio") diff --git a/src/mcp/client/stdio/__init__.py b/src/mcp/client/stdio/__init__.py index 0d76bb958b..6619780625 100644 --- a/src/mcp/client/stdio/__init__.py +++ b/src/mcp/client/stdio/__init__.py @@ -103,7 +103,7 @@ class StdioServerParameters(BaseModel): @asynccontextmanager -async def stdio_client(server: StdioServerParameters, errlog: TextIO = sys.stderr): +async def stdio_client(server: StdioServerParameters, errlog: TextIO | None = None): """ Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout. @@ -236,7 +236,7 @@ async def _create_platform_compatible_process( command: str, args: list[str], env: dict[str, str] | None = None, - errlog: TextIO = sys.stderr, + errlog: TextIO | None = None, cwd: Path | str | None = None, ): """