Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions src/mcp/server/mcpserver/utilities/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import logging
from typing import Literal

# Namespace logger for all MCP SDK logging.
# Per Python logging best practices, library code should only configure
# its own namespace logger, never the root logger.
_MCP_LOGGER_NAME = "mcp"


def get_logger(name: str) -> logging.Logger:
"""Get a logger nested under MCP namespace.
Expand All @@ -21,19 +26,28 @@ def configure_logging(
) -> None:
"""Configure logging for MCP.

Configures only the ``mcp`` namespace logger so that application-level
logging configuration is not overridden. Per the Python logging docs,
library code should never call ``logging.basicConfig()`` or add handlers
to the root logger.

Args:
level: The log level to use.
"""
handlers: list[logging.Handler] = []
mcp_logger = logging.getLogger(_MCP_LOGGER_NAME)
mcp_logger.setLevel(level)

# Avoid adding duplicate handlers on repeated calls.
if mcp_logger.handlers:
return

try:
from rich.console import Console
from rich.logging import RichHandler

handlers.append(RichHandler(console=Console(stderr=True), rich_tracebacks=True))
handler: logging.Handler = RichHandler(console=Console(stderr=True), rich_tracebacks=True)
except ImportError: # pragma: no cover
pass

if not handlers: # pragma: no cover
handlers.append(logging.StreamHandler())
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(message)s"))

logging.basicConfig(level=level, format="%(message)s", handlers=handlers)
mcp_logger.addHandler(handler)
7 changes: 5 additions & 2 deletions src/mcp/server/stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ async def run_server():
```
"""

import os
import sys
from contextlib import asynccontextmanager
from io import TextIOWrapper
Expand All @@ -38,10 +39,12 @@ async def stdio_server(stdin: anyio.AsyncFile[str] | None = None, stdout: anyio.
# standard process handles. Encoding of stdin/stdout as text streams on
# python is platform-dependent (Windows is particularly problematic), so we
# re-wrap the underlying binary stream to ensure UTF-8.
# We duplicate the file descriptors via os.dup() to avoid closing the
# real sys.stdin/sys.stdout when the wrapped streams are closed.
if not stdin:
stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding="utf-8"))
stdin = anyio.wrap_file(TextIOWrapper(os.fdopen(os.dup(sys.stdin.fileno()), "rb"), encoding="utf-8"))
if not stdout:
stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding="utf-8"))
stdout = anyio.wrap_file(TextIOWrapper(os.fdopen(os.dup(sys.stdout.fileno()), "wb"), encoding="utf-8"))

read_stream: MemoryObjectReceiveStream[SessionMessage | Exception]
read_stream_writer: MemoryObjectSendStream[SessionMessage | Exception]
Expand Down