Skip to content

Commit 0560151

Browse files
committed
feat(fastmcp): disable logs reconfigure on startup
1 parent 3a9f118 commit 0560151

File tree

2 files changed

+45
-7
lines changed

2 files changed

+45
-7
lines changed

src/mcp/server/fastmcp/server.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
8888

8989
# Server settings
9090
debug: bool
91-
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
91+
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None
9292

9393
# HTTP settings
9494
host: str
@@ -152,7 +152,7 @@ def __init__( # noqa: PLR0913
152152
*,
153153
tools: list[Tool] | None = None,
154154
debug: bool = False,
155-
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
155+
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = "INFO",
156156
host: str = "127.0.0.1",
157157
port: int = 8000,
158158
mount_path: str = "/",
@@ -224,8 +224,9 @@ def __init__( # noqa: PLR0913
224224
# Set up MCP protocol handlers
225225
self._setup_handlers()
226226

227-
# Configure logging
228-
configure_logging(self.settings.log_level)
227+
if self.settings.log_level is not None:
228+
# Configure logging
229+
configure_logging(self.settings.log_level)
229230

230231
@property
231232
def name(self) -> str:
@@ -745,7 +746,7 @@ async def run_sse_async(self, mount_path: str | None = None) -> None:
745746
starlette_app,
746747
host=self.settings.host,
747748
port=self.settings.port,
748-
log_level=self.settings.log_level.lower(),
749+
**self._get_uvicorn_log_config(),
749750
)
750751
server = uvicorn.Server(config)
751752
await server.serve()
@@ -760,11 +761,17 @@ async def run_streamable_http_async(self) -> None:
760761
starlette_app,
761762
host=self.settings.host,
762763
port=self.settings.port,
763-
log_level=self.settings.log_level.lower(),
764+
**self._get_uvicorn_log_config(),
764765
)
765766
server = uvicorn.Server(config)
766767
await server.serve()
767768

769+
def _get_uvicorn_log_config(self) -> dict[str, Any]:
770+
"""Map FastMCP log level to Uvicorn log level."""
771+
if self.settings.log_level is None:
772+
return {"log_level": None, "log_config": None}
773+
return {"log_level": self.settings.log_level.lower()}
774+
768775
def _normalize_path(self, mount_path: str, endpoint: str) -> str:
769776
"""
770777
Combine mount path and endpoint to return a normalized path.

tests/server/fastmcp/test_server.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import base64
22
from pathlib import Path
33
from typing import TYPE_CHECKING, Any
4-
from unittest.mock import patch
4+
from unittest.mock import AsyncMock, patch
55

66
import pytest
77
from pydantic import AnyUrl, BaseModel
@@ -769,6 +769,37 @@ def get_data() -> str:
769769
assert resource.name == "test_get_data"
770770
assert resource.mimeType == "text/plain"
771771

772+
def test_server_no_log_reconfigure(self):
773+
"""Test that the server does not reconfigure logging if already configured."""
774+
with patch("mcp.server.fastmcp.server.configure_logging") as configure_logging_mock:
775+
# First instantiation should configure logging
776+
FastMCP(log_level=None)
777+
assert configure_logging_mock.call_count == 0
778+
779+
def test_server_uvicorn_no_logging(self):
780+
"""Test that the server does not reconfigure logging if uvicorn logging is set."""
781+
with (
782+
patch("mcp.server.fastmcp.server.configure_logging") as configure_logging_mock,
783+
patch("uvicorn.Config") as uvicorn_config_mock,
784+
patch("uvicorn.Server") as uvicorn_server_mock,
785+
):
786+
uvicorn_server_mock.return_value.serve = AsyncMock()
787+
# First instantiation should configure logging
788+
# Launch mock server
789+
mcp = FastMCP(log_level=None)
790+
mcp.run(transport="streamable-http")
791+
# check that logging was not configured
792+
assert configure_logging_mock.call_count == 0
793+
config_call_args = uvicorn_config_mock.call_args
794+
# Verify that log_config and log_level are None on uvicorn Config
795+
assert "log_config" in config_call_args.kwargs
796+
assert config_call_args.kwargs["log_config"] is None
797+
assert "log_level" in config_call_args.kwargs
798+
assert config_call_args.kwargs["log_level"] is None
799+
# Verify that the server was started
800+
assert uvicorn_server_mock.call_count == 1
801+
assert uvicorn_server_mock.return_value.serve.call_count == 1
802+
772803

773804
class TestServerResourceTemplates:
774805
@pytest.mark.anyio

0 commit comments

Comments
 (0)