Files
Pietro Zullo 30b23271d5 fix(python): respect configured inspector path (#1176)
* fix(python): respect configured inspector path

* fix(python): treat inspector path as a base prefix

* fix(python): format inspector fixes for ruff

* fix(ci): run conformance with node 22

* fix(ci): fail conformance runs with empty results

* chore(ci): drop inline conformance result validation

* docs(python): clarify inspector path behavior
2026-03-09 14:24:53 +01:00

148 lines
4.9 KiB
Python

"""Configurable logging setup for MCP servers."""
import logging
from mcp_use.server.logging.formatters import ColoredFormatter, MCPAccessFormatter, MCPErrorFormatter
class InspectorLogFilter(logging.Filter):
"""Filter that hides inspector-related access logs."""
def __init__(self, inspector_path: str = "/inspector"):
super().__init__()
self.inspector_path = inspector_path
def filter(self, record: logging.LogRecord) -> bool:
# Check if args contain a path that starts with inspector_path
args = record.args
if args is not None and isinstance(args, tuple) and len(args) >= 3:
path = args[2]
if isinstance(path, str) and self.inspector_path in path:
return False
return True
class MCPLogsOnlyFilter(logging.Filter):
"""Filter that drops all uvicorn access log records.
MCP protocol logs are printed directly to stdout by MCPLoggingMiddleware,
bypassing the logging system entirely. This filter drops all uvicorn access
records so only the MCP: lines remain.
"""
def filter(self, record: logging.LogRecord) -> bool:
args = record.args
if args is not None and isinstance(args, tuple) and len(args) >= 3:
return False
return True
def setup_logging(
debug_level: int = 0,
log_level: str = "INFO",
show_inspector_logs: bool = False,
inspector_path: str = "/inspector",
mcp_logs_only: bool = False,
) -> dict:
"""Set up logging configuration for MCP server.
Args:
debug_level: Debug level (0: production, 1: debug+routes, 2: debug+routes+jsonrpc)
log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
show_inspector_logs: Whether to show inspector-related access logs (default: False)
inspector_path: Effective inspector route prefix, typically `/inspector`
or a prefixed route like `/mcp/inspector`
mcp_logs_only: When True, suppress all uvicorn access logs (MCP logs are printed
directly by the middleware). Default: False.
Returns:
Uvicorn logging configuration dict
"""
# Suppress noisy loggers
suppressed_loggers = {
"uvicorn.error": "ERROR",
"mcp.server.lowlevel.server": "CRITICAL",
"mcp.server.streamable_http_manager": "CRITICAL",
"mcp.server.fastmcp": "CRITICAL",
"mcp": "CRITICAL",
"httpx": "WARNING",
}
# Configure loggers
loggers = {
# Access logs with MCP enhancement (handled by middleware)
"uvicorn.access": {"handlers": ["access"], "level": log_level, "propagate": False},
# Error logs with custom formatting
"uvicorn.error": {"handlers": ["error"], "level": "ERROR", "propagate": False},
# Suppress noisy loggers
**{
logger_name: {"handlers": ["null"], "level": level, "propagate": False}
for logger_name, level in suppressed_loggers.items()
if logger_name != "uvicorn.error"
},
}
# Add debug logger if debug mode is enabled
if debug_level >= 2:
loggers["mcp.debug"] = {"handlers": ["debug"], "level": "DEBUG", "propagate": False}
# Build filters
filters = {}
if not show_inspector_logs:
filters["inspector_filter"] = {"()": InspectorLogFilter, "inspector_path": inspector_path}
if mcp_logs_only:
filters["mcp_logs_only_filter"] = {"()": MCPLogsOnlyFilter}
# Build access handler with optional filters
access_filters = []
if not show_inspector_logs:
access_filters.append("inspector_filter")
if mcp_logs_only:
access_filters.append("mcp_logs_only_filter")
access_handler = {
"formatter": "access",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
}
if access_filters:
access_handler["filters"] = access_filters
return {
"version": 1,
"disable_existing_loggers": False,
"filters": filters,
"formatters": {
"access": {
"()": MCPAccessFormatter,
},
"error": {
"()": MCPErrorFormatter,
},
"debug": {
"fmt": "%(levelname)s: %(message)s",
},
"colored": {
"()": ColoredFormatter,
"fmt": "%(levelname)-8s %(name)s: %(message)s",
},
},
"handlers": {
"access": access_handler,
"error": {
"formatter": "error",
"class": "logging.StreamHandler",
"stream": "ext://sys.stderr",
},
"debug": {
"formatter": "colored" if debug_level >= 2 else "debug",
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
},
"null": {
"class": "logging.NullHandler",
},
},
"loggers": loggers,
}