Skip to content

Commit c326aa0

Browse files
committed
fix: keep FastMCP from configuring app logging
1 parent 3d7b311 commit c326aa0

4 files changed

Lines changed: 69 additions & 7 deletions

File tree

src/mcp/cli/cli.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import subprocess
77
import sys
88
from pathlib import Path
9-
from typing import Annotated, Any
9+
from typing import Annotated, Any, Literal
1010

1111
from mcp.server import MCPServer
1212
from mcp.server import Server as LowLevelServer
@@ -19,7 +19,7 @@
1919

2020
try:
2121
from mcp.cli import claude
22-
from mcp.server.mcpserver.utilities.logging import get_logger
22+
from mcp.server.mcpserver.utilities.logging import configure_logging, get_logger
2323
except ImportError: # pragma: no cover
2424
print("Error: mcp.server is not installed or not in PYTHONPATH")
2525
sys.exit(1)
@@ -31,6 +31,8 @@
3131

3232
logger = get_logger("cli")
3333

34+
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
35+
3436
app = typer.Typer(
3537
name="mcp",
3638
help="MCP development tools",
@@ -116,6 +118,14 @@ def _parse_file_path(file_spec: str) -> tuple[Path, str | None]:
116118
return file_path, server_object
117119

118120

121+
def _get_server_log_level(server: Any) -> LogLevel:
122+
settings = getattr(server, "settings", None)
123+
log_level = getattr(settings, "log_level", "INFO")
124+
if log_level in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):
125+
return log_level
126+
return "INFO"
127+
128+
119129
def _import_server(file: Path, server_object: str | None = None): # pragma: no cover
120130
"""Import an MCP server from a file.
121131
@@ -340,6 +350,8 @@ def run(
340350
# Import and get server object
341351
server = _import_server(file, server_object)
342352

353+
configure_logging(_get_server_log_level(server))
354+
343355
# Run the server
344356
kwargs = {}
345357
if transport:

src/mcp/server/mcpserver/server.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from mcp.server.mcpserver.resources import FunctionResource, Resource, ResourceManager
3737
from mcp.server.mcpserver.tools import Tool, ToolManager
3838
from mcp.server.mcpserver.utilities.context_injection import find_context_parameter
39-
from mcp.server.mcpserver.utilities.logging import configure_logging, get_logger
39+
from mcp.server.mcpserver.utilities.logging import get_logger
4040
from mcp.server.sse import SseServerTransport
4141
from mcp.server.stdio import stdio_server
4242
from mcp.server.streamable_http import EventStore
@@ -203,9 +203,6 @@ def __init__(
203203
self._token_verifier = ProviderTokenVerifier(auth_server_provider)
204204
self._custom_starlette_routes: list[Route] = []
205205

206-
# Configure logging
207-
configure_logging(self.settings.log_level)
208-
209206
@property
210207
def name(self) -> str:
211208
return self._lowlevel_server.name

tests/cli/test_utils.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55

66
import pytest
77

8-
from mcp.cli.cli import _build_uv_command, _get_npx_command, _parse_file_path # type: ignore[reportPrivateUsage]
8+
from mcp.cli.cli import ( # type: ignore[reportPrivateUsage]
9+
_build_uv_command,
10+
_get_npx_command,
11+
_parse_file_path,
12+
run,
13+
)
914

1015

1116
@pytest.mark.parametrize(
@@ -38,6 +43,41 @@ def test_parse_file_exit_on_dir(tmp_path: Path):
3843
_parse_file_path(str(dir_path))
3944

4045

46+
def test_run_configures_logging_at_cli_entrypoint(monkeypatch: pytest.MonkeyPatch, tmp_path: Path):
47+
"""The CLI owns process logging setup; importing or constructing the server does not."""
48+
file = tmp_path / "server.py"
49+
file.write_text("server = object()")
50+
calls: list[tuple[str, Any]] = []
51+
52+
class Settings:
53+
log_level = "DEBUG"
54+
55+
class ImportedServer:
56+
settings = Settings()
57+
58+
def run(self, **kwargs: Any) -> None:
59+
calls.append(("run", kwargs))
60+
61+
def fake_import_server(*_args: Any) -> ImportedServer:
62+
return ImportedServer()
63+
64+
def fake_configure_logging(level: str) -> None:
65+
calls.append(("logging", level))
66+
67+
monkeypatch.setattr("mcp.cli.cli._import_server", fake_import_server)
68+
monkeypatch.setattr(
69+
"mcp.cli.cli.configure_logging",
70+
fake_configure_logging,
71+
)
72+
73+
run(f"{file}:server", transport="stdio")
74+
75+
assert calls == [
76+
("logging", "DEBUG"),
77+
("run", {"transport": "stdio"}),
78+
]
79+
80+
4181
def test_build_uv_command_minimal():
4282
"""Should emit core command when no extras specified."""
4383
cmd = _build_uv_command("foo.py")

tests/server/mcpserver/test_server.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import base64
2+
import logging
23
from pathlib import Path
34
from typing import Any
45
from unittest.mock import AsyncMock, MagicMock, patch
@@ -46,6 +47,18 @@
4647

4748

4849
class TestServer:
50+
def test_create_server_does_not_configure_application_logging(self, monkeypatch: pytest.MonkeyPatch):
51+
calls: list[tuple[tuple[Any, ...], dict[str, Any]]] = []
52+
53+
def fake_basic_config(*args: Any, **kwargs: Any) -> None:
54+
calls.append((args, kwargs))
55+
56+
monkeypatch.setattr(logging, "basicConfig", fake_basic_config)
57+
58+
MCPServer("test")
59+
60+
assert calls == []
61+
4962
async def test_create_server(self):
5063
mcp = MCPServer(
5164
title="MCPServer Server",

0 commit comments

Comments
 (0)