FastMCP2 の MCP Middleware でデバッグログを有効化する方法
はじめに
MCP (Model Context Protocol) Server を開発・運用していると、「なぜこのリクエストが失敗したのか?」「どのような処理が行われているのか?」といったデバッグが必要になることがあります。
FastMCP2 には MCP Middleware というデバッグに便利な機能がありますが、公式ドキュメントだけでは具体的な設定方法が分からない部分がありました。
本記事では、実際に MCP サーバーにデバッグログを追加した経験を基に、FastMCP2 の Middleware の具体的な使用方法を紹介します。
対象読者
- FastMCP2 を使用している方
- MCP Server の動作をデバッグしたい方
- FastMCP2 のミドルウェア機能に興味がある方
環境・バージョン
- FastMCP2: 2.10.5
- Python: 3.11+
FastMCP2 の Middleware とは
FastMCP2 の Middleware は、FastAPI の Middleware と似た仕組みを提供する機能です。MCP Server に対して横断的な処理を適用でき、ログ出力、認証、リクエスト変換などの用途に使用できます。
Middleware の特徴
- パイプライン処理: 複数のミドルウェアを連鎖的に実行できます
- リクエスト・レスポンス制御: メッセージの検査、変更、制御が可能
- Transport 非依存: stdio, HTTP などの異なる Transport で動作
- MCP 専用設計: Model Context Protocol に特化した設計
利用可能なフック
FastMCP2 のミドルウェアでは、以下のフックが利用できます:
フック名 | 説明 | 用途例 |
---|---|---|
on_message |
すべての MCP メッセージに対して実行 | 全般的なログ出力 |
on_request |
レスポンスを期待するメッセージに対して実行 | 認証チェック |
on_notification |
通知メッセージに対して実行 | イベント監視 |
on_call_tool |
ツール呼び出しに対して実行 | ツール実行制御 |
on_read_resource |
リソース読み込みに対して実行 | アクセス制御 |
基本的なミドルウェアの構造
from fastmcp.server.middleware import Middleware, MiddlewareContext
class CustomMiddleware(Middleware):
async def on_message(self, context: MiddlewareContext, call_next):
# リクエスト前処理
print(f"Processing {context.method} from {context.source}")
# 次のミドルウェア or 実際の処理を実行
result = await call_next(context)
# レスポンス後処理
print(f"Completed {context.method}")
return result
実行フロー
-
コンテキスト受信: ミドルウェアが
MiddlewareContext
オブジェクトを受け取る - 前処理: 入力されるリクエストの検査・変更
-
処理継続:
call_next()
で次のミドルウェアまたは実際の処理を呼び出し - 後処理: レスポンスの検査・変更してから返却
ミドルウェアを使用することで、MCP Server の全てのメッセージ処理に対して共通的な処理を適用できます。
このミドルウェアの利用例の1つとして、ログ記録ミドルウェア Logging Middlware
がありますのでこちらを利用します。
Built-in Logging Middleware の設定
ログ記録をするためのカスタムミドルウェアを実装し利用することもできますが、FastMCP にはベストプラクティスを実証した複数のミドルウェア実装が組み込まれていますので、こちらを利用してみます。
1. 必要なインポートの追加
まず、FastMCP2 のロギング ユーティリティと Built-in Logging Middleware の1つである LoggingMiddleware
をインポートします。
# LoggingMiddleware のインポート
from fastmcp.utilities.logging import configure_logging, get_logger
from fastmcp.server.middleware.logging import (
LoggingMiddleware,
)
2. ログ設定の追加
FastMCP2 のログユーティリティを使用してデバッグレベルのログを有効化します。
def setup_logging():
"""FastMCPの公式ログ設定"""
configure_logging(
level='DEBUG',
enable_rich_tracebacks=True
)
この設定により、以下が有効になります:
- level='DEBUG': デバッグレベル以上のすべてのログを出力
- enable_rich_tracebacks=True: エラー発生時により詳細なスタックトレースを出力
3. Logging Middleware の追加
FastMCP2 で作成した MCP Server に Logging Middleware を追加します。
def setup() -> FastMCP | None:
setup_logging()
mcp_server: FastMCP = FastMCP(name="main")
# FastMCP専用のロガーを取得
mcp_logger = get_logger("main") # "FastMCP.main"になる
# LoggingMiddleware を追加
mcp_server.add_middleware(LoggingMiddleware(
logger=mcp_logger, # FastMCPロググループに統一
include_payloads=True, # リクエスト/レスポンスのペイロードをログ出力
max_payload_length=1000 # ペイロードの最大長を制限
))
return mcp_server
4. この設定にしている背景
この設定にしている理由ですが、configure_logging 関数の実装を確認してみると
def configure_logging(
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | int = "INFO",
logger: logging.Logger | None = None,
enable_rich_tracebacks: bool = True,
) -> None:
"""
Configure logging for FastMCP.
Args:
logger: the logger to configure
level: the log level to use
"""
if logger is None:
logger = logging.getLogger("FastMCP")
# Only configure the FastMCP logger namespace
handler = RichHandler(
console=Console(stderr=True),
rich_tracebacks=enable_rich_tracebacks,
)
formatter = logging.Formatter("%(message)s")
handler.setFormatter(formatter)
logger.setLevel(level)
# Remove any existing handlers to avoid duplicates on reconfiguration
for hdlr in logger.handlers[:]:
logger.removeHandler(hdlr)
logger.addHandler(handler)
# Don't propagate to the root logger
logger.propagate = False
となっており、loggerを指定しない場合、"FastMCP" を親ロガーとした logger を作成し使用します。
この logger に標準エラー出力への handler を結びつけています。
get_logger
関数の実装も見てみます。
def get_logger(name: str) -> logging.Logger:
"""Get a logger nested under FastMCP namespace.
Args:
name: the name of the logger, which will be prefixed with 'FastMCP.'
Returns:
a configured logger instance
"""
return logging.getLogger(f"FastMCP.{name}")
"FastMCP" をプレフィックスとしてつけることで、階層的ロガー構造を作ることがわかります。
そのため、このロガー構造を崩さないように、先ほど configure_logging 関数で設定した親ロガーに伝播( propagate )させ、ログを出力させる方針としました。
4. Logging Middleware のパラメータ説明
LoggingMiddleware
の主要なパラメータは以下の通りです:
パラメータ | 説明 | デフォルト |
---|---|---|
logger |
ログ出力に使用するロガー | None |
include_payloads |
リクエスト・レスポンスのペイロードをログに含めるか | False |
max_payload_length |
ペイロードのログ出力時の最大文字数 | 1000 |
-
logger
: FastMCP のget_logger()
で取得したロガーを指定することで、FastMCP のログ体系に統一できます -
include_payloads=True
: リクエストとレスポンスの内容が確認できるため、デバッグ時に非常に有用です -
max_payload_length
: 大きなペイロードでログが見にくくならないよう制限します
補足ですが、logger を指定しない場合、'fastmcp.requests' と名前付けされたロガーが内部で利用されます。
動作確認
ログ出力例
LoggingMiddleware を有効化すると、以下のようなデバッグログが出力されます:
[09/07/25 22:02:25] INFO Processing message: source=client type=request method=tools/list payload={"method": "tools/list", "params": null} logging.py:77
INFO Completed message: tools/list logging.py:81
INFO Processing message: source=client type=request method=prompts/list payload={"method": "prompts/list", "params": null} logging.py:77
INFO Completed message: prompts/list logging.py:81
tools と prompts の list が呼ばれていることがわかりました。
FastAPI Middleware との類似点
FastMCP2 の Middleware は FastAPI の Middleware パターンをベースにしており、以下の類似点があります:
-
リクエスト前後の処理:
call_next()
の前後で処理を分けることができる - ミドルウェアの順序: 複数のミドルウェアは追加した順序で実行される
- 横断的な関心事の処理: ログ、認証、エラーハンドリングなどに使用
ただし、MCP プロトコル特有の context.method
や context.source
といった情報にアクセスできる点が特徴的です。
まとめ
FastMCP2 の LoggingMiddleware を使用することで、MCP Server の詳細な動作をデバッグできるようになります。
MCP Server の開発・運用時のデバッグ作業が格段に効率化されるので、ぜひ活用してみてください!
参考記事
Discussion