Compare commits

...

2 commits

Author SHA1 Message Date
claude[bot]
139801718f Fix static analysis issues and address review feedback
- Fix unused variable in test (settings → _settings)
- Add type annotation for __context parameter
- Remove unused info parameter from validator
- Fix circular import with configure_logging
- Add production safety warnings to documentation

Co-authored-by: Bill Easton <strawgate@users.noreply.github.com>
2026-01-18 16:16:23 +00:00
claude[bot]
b2fbe1a1bb 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>
2026-01-18 15:58:21 +00:00
6 changed files with 153 additions and 6 deletions

View file

@ -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. **WARNING**: Only enable in development - debug tracebacks expose detailed internal information
- **`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. **WARNING**: Only enable in development - exposes detailed internal information in HTTP responses
- **`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

View file

@ -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

View file

@ -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,
)

View file

@ -260,7 +260,59 @@ 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) -> bool:
"""When debug is enabled, set log_level to DEBUG."""
return v
def model_post_init(self, __context: Any) -> 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
# Only reconfigure if fastmcp module is fully initialized
# to avoid AttributeError during import
if self.log_enabled:
import fastmcp
# Check if fastmcp.settings exists (module fully initialized)
if hasattr(fastmcp, "settings"):
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[

View file

@ -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

View file

@ -0,0 +1,86 @@
"""Tests for debug and starlette_debug settings."""
import logging
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"