mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
113 lines
3.5 KiB
Text
113 lines
3.5 KiB
Text
---
|
|
title: Server Logging
|
|
sidebarTitle: Logging
|
|
description: Receive and handle log messages from MCP servers.
|
|
icon: receipt
|
|
---
|
|
|
|
import { VersionBadge } from '/snippets/version-badge.mdx'
|
|
|
|
<VersionBadge version="2.0.0" />
|
|
|
|
Use this when you need to capture or process log messages sent by the server.
|
|
|
|
MCP servers can emit log messages to clients. The client handles these through a log handler callback.
|
|
|
|
## Log Handler
|
|
|
|
Provide a `log_handler` function when creating the client:
|
|
|
|
```python
|
|
import logging
|
|
from fastmcp import Client
|
|
from fastmcp.client.logging import LogMessage
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
LOGGING_LEVEL_MAP = {
|
|
"DEBUG": logging.DEBUG,
|
|
"INFO": logging.INFO,
|
|
"NOTICE": logging.INFO,
|
|
"WARNING": logging.WARNING,
|
|
"ERROR": logging.ERROR,
|
|
"CRITICAL": logging.CRITICAL,
|
|
"ALERT": logging.CRITICAL,
|
|
"EMERGENCY": logging.CRITICAL,
|
|
}
|
|
|
|
async def log_handler(message: LogMessage):
|
|
"""Forward MCP server logs to Python's logging system."""
|
|
data = message.data
|
|
if isinstance(data, dict):
|
|
msg = data.get('msg', data)
|
|
extra = data.get('extra')
|
|
else:
|
|
msg = data
|
|
extra = None
|
|
|
|
# Python's logging requires `extra` to be a mapping, but a server can send
|
|
# any JSON value, so fold anything else into the message instead.
|
|
if extra is not None and not isinstance(extra, dict):
|
|
msg = f"{msg} ({extra})"
|
|
extra = None
|
|
|
|
level = LOGGING_LEVEL_MAP.get(message.level.upper(), logging.INFO)
|
|
logger.log(level, msg, extra=extra)
|
|
|
|
client = Client(
|
|
"my_mcp_server.py",
|
|
log_handler=log_handler,
|
|
)
|
|
```
|
|
|
|
The handler receives a `LogMessage` object:
|
|
|
|
<Card icon="code" title="LogMessage">
|
|
<ResponseField name="level" type='Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]'>
|
|
The log level
|
|
</ResponseField>
|
|
|
|
<ResponseField name="logger" type="str | None">
|
|
The logger name (may be None)
|
|
</ResponseField>
|
|
|
|
<ResponseField name="data" type="Any">
|
|
The JSON-serializable log payload sent by the server. FastMCP's structured logger uses a dictionary with `msg` and `extra` keys, but other MCP servers may send any JSON value.
|
|
</ResponseField>
|
|
</Card>
|
|
|
|
## Structured Logs
|
|
|
|
The `message.data` attribute contains the server's JSON-serializable log payload. FastMCP servers commonly send a dictionary with `msg` and `extra` keys, which enables structured logging with rich contextual information.
|
|
|
|
```python
|
|
async def detailed_log_handler(message: LogMessage):
|
|
data = message.data
|
|
msg = data.get('msg', data) if isinstance(data, dict) else data
|
|
extra = data.get('extra') if isinstance(data, dict) else None
|
|
|
|
if message.level == "error":
|
|
print(f"ERROR: {msg} | Details: {extra}")
|
|
elif message.level == "warning":
|
|
print(f"WARNING: {msg} | Details: {extra}")
|
|
else:
|
|
print(f"{message.level.upper()}: {msg}")
|
|
```
|
|
|
|
This structure is preserved even when logs are forwarded through a FastMCP proxy, making it useful for debugging multi-server applications.
|
|
|
|
## Default Behavior
|
|
|
|
If you do not provide a custom `log_handler`, FastMCP's default handler routes server logs to Python's logging system at the appropriate severity level. The MCP levels map as follows: `notice` becomes INFO; `alert` and `emergency` become CRITICAL.
|
|
|
|
```python
|
|
client = Client("my_mcp_server.py")
|
|
|
|
async with client:
|
|
# Server logs are forwarded at proper severity automatically
|
|
await client.call_tool("some_tool")
|
|
```
|