mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
Enhance debug settings with dual-purpose flag and granular control
Add starlette_debug setting for Starlette-specific debug control and enhance the debug flag to set both log level and Starlette debug mode. - Add starlette_debug setting for granular control over Starlette debug mode - Update debug flag to set log_level=DEBUG and enable Starlette debug - Update HTTP app creators to accept both debug and starlette_debug parameters - Document both settings in server.mdx with clear usage guidelines - Add comprehensive tests for new settings behavior - Update existing test to reflect new debug behavior Co-authored-by: Bill Easton <strawgate@users.noreply.github.com>
This commit is contained in:
parent
2eac7ab1d1
commit
b2fbe1a1bb
6 changed files with 154 additions and 6 deletions
|
|
@ -362,6 +362,8 @@ print(fastmcp.settings.strict_input_validation) # Default: False
|
|||
|
||||
Common global settings include:
|
||||
- **`log_level`**: Logging level ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), set with `FASTMCP_LOG_LEVEL`
|
||||
- **`debug`**: Global debug mode that sets log level to DEBUG and enables Starlette debug tracebacks for HTTP/SSE transports, set with `FASTMCP_DEBUG`. Provides a convenient way to enable comprehensive debugging. For granular control, use `log_level` and `starlette_debug` separately
|
||||
- **`starlette_debug`**: Enable Starlette debug mode for HTTP/SSE transports only, set with `FASTMCP_STARLETTE_DEBUG`. When enabled, detailed error tracebacks are returned in HTTP responses. Only affects HTTP/SSE transports; has no effect on stdio transport
|
||||
- **`mask_error_details`**: Whether to hide detailed error information from clients, set with `FASTMCP_MASK_ERROR_DETAILS`
|
||||
- **`strict_input_validation`**: Controls tool input validation mode (default: False for flexible coercion), set with `FASTMCP_STRICT_INPUT_VALIDATION`. See [Input Validation Modes](/servers/tools#input-validation-modes)
|
||||
- **`env_file`**: Path to the environment file to load settings from (default: ".env"), set with `FASTMCP_ENV_FILE`. Useful when your project uses a `.env` file with syntax incompatible with python-dotenv
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ def create_sse_app(
|
|||
sse_path: str,
|
||||
auth: AuthProvider | None = None,
|
||||
debug: bool = False,
|
||||
starlette_debug: bool = False,
|
||||
routes: list[BaseRoute] | None = None,
|
||||
middleware: list[Middleware] | None = None,
|
||||
) -> StarletteWithLifespan:
|
||||
|
|
@ -152,7 +153,8 @@ def create_sse_app(
|
|||
message_path: Path for SSE messages
|
||||
sse_path: Path for SSE connections
|
||||
auth: Optional authentication provider (AuthProvider)
|
||||
debug: Whether to enable debug mode
|
||||
debug: Whether to enable global debug mode (sets log level and Starlette debug)
|
||||
starlette_debug: Whether to enable Starlette debug mode only
|
||||
routes: Optional list of custom routes
|
||||
middleware: Optional list of middleware
|
||||
Returns:
|
||||
|
|
@ -252,7 +254,7 @@ def create_sse_app(
|
|||
app = create_base_app(
|
||||
routes=server_routes,
|
||||
middleware=server_middleware,
|
||||
debug=debug,
|
||||
debug=debug or starlette_debug,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
# Store the FastMCP server instance on the Starlette app state
|
||||
|
|
@ -272,6 +274,7 @@ def create_streamable_http_app(
|
|||
json_response: bool = False,
|
||||
stateless_http: bool = False,
|
||||
debug: bool = False,
|
||||
starlette_debug: bool = False,
|
||||
routes: list[BaseRoute] | None = None,
|
||||
middleware: list[Middleware] | None = None,
|
||||
) -> StarletteWithLifespan:
|
||||
|
|
@ -287,7 +290,8 @@ def create_streamable_http_app(
|
|||
auth: Optional authentication provider (AuthProvider)
|
||||
json_response: Whether to use JSON response format
|
||||
stateless_http: Whether to use stateless mode (new transport per request)
|
||||
debug: Whether to enable debug mode
|
||||
debug: Whether to enable global debug mode (sets log level and Starlette debug)
|
||||
starlette_debug: Whether to enable Starlette debug mode only
|
||||
routes: Optional list of custom routes
|
||||
middleware: Optional list of middleware
|
||||
|
||||
|
|
@ -365,7 +369,7 @@ def create_streamable_http_app(
|
|||
app = create_base_app(
|
||||
routes=server_routes,
|
||||
middleware=server_middleware,
|
||||
debug=debug,
|
||||
debug=debug or starlette_debug,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
# Store the FastMCP server instance on the Starlette app state
|
||||
|
|
|
|||
|
|
@ -2769,6 +2769,7 @@ class FastMCP(Provider, Generic[LifespanResultT]):
|
|||
else self._deprecated_settings.stateless_http
|
||||
),
|
||||
debug=self._deprecated_settings.debug,
|
||||
starlette_debug=self._deprecated_settings.starlette_debug,
|
||||
middleware=middleware,
|
||||
)
|
||||
elif transport == "sse":
|
||||
|
|
@ -2778,6 +2779,7 @@ class FastMCP(Provider, Generic[LifespanResultT]):
|
|||
sse_path=path or self._deprecated_settings.sse_path,
|
||||
auth=self.auth,
|
||||
debug=self._deprecated_settings.debug,
|
||||
starlette_debug=self._deprecated_settings.starlette_debug,
|
||||
middleware=middleware,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -260,7 +260,57 @@ class Settings(BaseSettings):
|
|||
sse_path: str = "/sse"
|
||||
message_path: str = "/messages/"
|
||||
streamable_http_path: str = "/mcp"
|
||||
debug: bool = False
|
||||
|
||||
debug: Annotated[
|
||||
bool,
|
||||
Field(
|
||||
description=inspect.cleandoc(
|
||||
"""
|
||||
Global debug mode. When enabled, sets log level to DEBUG and enables
|
||||
Starlette debug tracebacks for HTTP/SSE transports. This provides a
|
||||
convenient way to enable comprehensive debugging. For granular control,
|
||||
use log_level and starlette_debug separately.
|
||||
"""
|
||||
)
|
||||
),
|
||||
] = False
|
||||
|
||||
starlette_debug: Annotated[
|
||||
bool,
|
||||
Field(
|
||||
description=inspect.cleandoc(
|
||||
"""
|
||||
Enable Starlette debug mode for HTTP/SSE transports. When enabled,
|
||||
detailed error tracebacks will be returned in HTTP responses. Only
|
||||
affects HTTP/SSE transports; has no effect on stdio transport.
|
||||
"""
|
||||
)
|
||||
),
|
||||
] = False
|
||||
|
||||
@field_validator("debug")
|
||||
@classmethod
|
||||
def _update_log_level_for_debug(cls, v: bool, info) -> bool:
|
||||
"""When debug is enabled, set log_level to DEBUG."""
|
||||
if v:
|
||||
# When debug is True, we need to ensure log_level is set to DEBUG
|
||||
# This is checked in model_post_init
|
||||
pass
|
||||
return v
|
||||
|
||||
def model_post_init(self, __context) -> None:
|
||||
"""Post-initialization hook to handle debug mode."""
|
||||
if self.debug and self.log_level != "DEBUG":
|
||||
# When debug is enabled, force log_level to DEBUG
|
||||
self.log_level = "DEBUG"
|
||||
# Reconfigure logging if it's enabled
|
||||
if self.log_enabled:
|
||||
from fastmcp.utilities.logging import configure_logging
|
||||
|
||||
configure_logging(
|
||||
level=self.log_level,
|
||||
enable_rich_tracebacks=self.enable_rich_tracebacks,
|
||||
)
|
||||
|
||||
# error handling
|
||||
mask_error_details: Annotated[
|
||||
|
|
|
|||
|
|
@ -252,7 +252,8 @@ class TestDeprecatedServerInitKwargs:
|
|||
server = FastMCP("TestServer")
|
||||
|
||||
# Verify settings are inherited from global settings
|
||||
assert server._deprecated_settings.log_level == "WARNING"
|
||||
# Note: When debug=True, log_level is forced to DEBUG (new behavior)
|
||||
assert server._deprecated_settings.log_level == "DEBUG"
|
||||
assert server._deprecated_settings.debug is True
|
||||
assert server._deprecated_settings.host == "0.0.0.0"
|
||||
assert server._deprecated_settings.port == 3000
|
||||
|
|
|
|||
89
tests/test_debug_settings.py
Normal file
89
tests/test_debug_settings.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
"""Tests for debug and starlette_debug settings."""
|
||||
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
from fastmcp import settings as global_settings
|
||||
from fastmcp.settings import Settings
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
|
||||
class TestDebugSettings:
|
||||
"""Test debug and starlette_debug settings behavior."""
|
||||
|
||||
def test_debug_sets_log_level(self):
|
||||
"""Test that enabling debug sets log_level to DEBUG."""
|
||||
settings = Settings(debug=True, log_enabled=False)
|
||||
assert settings.log_level == "DEBUG"
|
||||
|
||||
def test_debug_false_preserves_log_level(self):
|
||||
"""Test that debug=False doesn't change log_level."""
|
||||
settings = Settings(debug=False, log_level="INFO", log_enabled=False)
|
||||
assert settings.log_level == "INFO"
|
||||
|
||||
def test_debug_with_explicit_log_level(self):
|
||||
"""Test that debug=True overrides explicit log_level."""
|
||||
# When debug is enabled, it forces log_level to DEBUG
|
||||
settings = Settings(debug=True, log_level="WARNING", log_enabled=False)
|
||||
assert settings.log_level == "DEBUG"
|
||||
|
||||
def test_starlette_debug_independent(self, monkeypatch):
|
||||
"""Test that starlette_debug works independently."""
|
||||
# Clear any env vars that might affect the test
|
||||
monkeypatch.delenv("FASTMCP_LOG_LEVEL", raising=False)
|
||||
settings = Settings(starlette_debug=True, log_enabled=False)
|
||||
assert settings.starlette_debug is True
|
||||
# log_level should not be affected (defaults to INFO)
|
||||
assert settings.log_level == "INFO"
|
||||
|
||||
def test_both_debug_and_starlette_debug(self):
|
||||
"""Test that both settings can be enabled together."""
|
||||
settings = Settings(debug=True, starlette_debug=True, log_enabled=False)
|
||||
assert settings.debug is True
|
||||
assert settings.starlette_debug is True
|
||||
assert settings.log_level == "DEBUG"
|
||||
|
||||
def test_debug_reconfigures_logging(self):
|
||||
"""Test that enabling debug reconfigures logging."""
|
||||
# Create a settings instance with debug enabled
|
||||
settings = Settings(debug=True, log_enabled=True)
|
||||
|
||||
# Verify logging was reconfigured
|
||||
logger = get_logger("test")
|
||||
assert logger.getEffectiveLevel() == logging.DEBUG
|
||||
|
||||
def test_debug_respects_log_enabled(self):
|
||||
"""Test that debug respects log_enabled setting."""
|
||||
# When log_enabled is False, logging should not be reconfigured
|
||||
settings = Settings(debug=True, log_enabled=False)
|
||||
assert settings.log_level == "DEBUG"
|
||||
# Logger should not be reconfigured, but we can't easily test this
|
||||
# without side effects
|
||||
|
||||
def test_starlette_debug_default_false(self):
|
||||
"""Test that starlette_debug defaults to False."""
|
||||
settings = Settings(log_enabled=False)
|
||||
assert settings.starlette_debug is False
|
||||
|
||||
def test_debug_default_false(self):
|
||||
"""Test that debug defaults to False."""
|
||||
settings = Settings(log_enabled=False)
|
||||
assert settings.debug is False
|
||||
|
||||
def test_env_var_debug(self, monkeypatch):
|
||||
"""Test that FASTMCP_DEBUG environment variable works."""
|
||||
monkeypatch.setenv("FASTMCP_DEBUG", "true")
|
||||
settings = Settings(log_enabled=False)
|
||||
assert settings.debug is True
|
||||
assert settings.log_level == "DEBUG"
|
||||
|
||||
def test_env_var_starlette_debug(self, monkeypatch):
|
||||
"""Test that FASTMCP_STARLETTE_DEBUG environment variable works."""
|
||||
# Clear any env vars that might affect the test
|
||||
monkeypatch.delenv("FASTMCP_LOG_LEVEL", raising=False)
|
||||
monkeypatch.setenv("FASTMCP_STARLETTE_DEBUG", "true")
|
||||
settings = Settings(log_enabled=False)
|
||||
assert settings.starlette_debug is True
|
||||
# log_level should not be affected (defaults to INFO)
|
||||
assert settings.log_level == "INFO"
|
||||
Loading…
Add table
Add a link
Reference in a new issue