Skip to content
Open
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
19 changes: 13 additions & 6 deletions src/mcp/server/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class Settings(BaseSettings, Generic[LifespanResultT]):

# Server settings
debug: bool
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None

# HTTP settings
host: str
Expand Down Expand Up @@ -152,7 +152,7 @@ def __init__( # noqa: PLR0913
*,
tools: list[Tool] | None = None,
debug: bool = False,
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = "INFO",
host: str = "127.0.0.1",
port: int = 8000,
mount_path: str = "/",
Expand Down Expand Up @@ -224,8 +224,9 @@ def __init__( # noqa: PLR0913
# Set up MCP protocol handlers
self._setup_handlers()

# Configure logging
configure_logging(self.settings.log_level)
if self.settings.log_level is not None:
# Configure logging
configure_logging(self.settings.log_level)

@property
def name(self) -> str:
Expand Down Expand Up @@ -745,7 +746,7 @@ async def run_sse_async(self, mount_path: str | None = None) -> None:
starlette_app,
host=self.settings.host,
port=self.settings.port,
log_level=self.settings.log_level.lower(),
**self._get_uvicorn_log_config(),
)
server = uvicorn.Server(config)
await server.serve()
Expand All @@ -760,11 +761,17 @@ async def run_streamable_http_async(self) -> None:
starlette_app,
host=self.settings.host,
port=self.settings.port,
log_level=self.settings.log_level.lower(),
**self._get_uvicorn_log_config(),
)
server = uvicorn.Server(config)
await server.serve()

def _get_uvicorn_log_config(self) -> dict[str, Any]:
"""Map FastMCP log level to Uvicorn log level."""
if self.settings.log_level is None:
return {"log_level": None, "log_config": None}
return {"log_level": self.settings.log_level.lower()}

def _normalize_path(self, mount_path: str, endpoint: str) -> str:
"""
Combine mount path and endpoint to return a normalized path.
Expand Down
33 changes: 32 additions & 1 deletion tests/server/fastmcp/test_server.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base64
from pathlib import Path
from typing import TYPE_CHECKING, Any
from unittest.mock import patch
from unittest.mock import AsyncMock, patch

import pytest
from pydantic import AnyUrl, BaseModel
Expand Down Expand Up @@ -769,6 +769,37 @@ def get_data() -> str:
assert resource.name == "test_get_data"
assert resource.mimeType == "text/plain"

def test_server_no_log_reconfigure(self):
"""Test that the server does not reconfigure logging if already configured."""
with patch("mcp.server.fastmcp.server.configure_logging") as configure_logging_mock:
# First instantiation should configure logging
FastMCP(log_level=None)
assert configure_logging_mock.call_count == 0

def test_server_uvicorn_no_logging(self):
"""Test that the server does not reconfigure logging if uvicorn logging is set."""
with (
patch("mcp.server.fastmcp.server.configure_logging") as configure_logging_mock,
patch("uvicorn.Config") as uvicorn_config_mock,
patch("uvicorn.Server") as uvicorn_server_mock,
):
uvicorn_server_mock.return_value.serve = AsyncMock()
# First instantiation should configure logging
# Launch mock server
mcp = FastMCP(log_level=None)
mcp.run(transport="streamable-http")
# check that logging was not configured
assert configure_logging_mock.call_count == 0
config_call_args = uvicorn_config_mock.call_args
# Verify that log_config and log_level are None on uvicorn Config
assert "log_config" in config_call_args.kwargs
assert config_call_args.kwargs["log_config"] is None
assert "log_level" in config_call_args.kwargs
assert config_call_args.kwargs["log_level"] is None
# Verify that the server was started
assert uvicorn_server_mock.call_count == 1
assert uvicorn_server_mock.return_value.serve.call_count == 1


class TestServerResourceTemplates:
@pytest.mark.anyio
Expand Down