🔍

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 の特徴

  1. パイプライン処理: 複数のミドルウェアを連鎖的に実行できます
  2. リクエスト・レスポンス制御: メッセージの検査、変更、制御が可能
  3. Transport 非依存: stdio, HTTP などの異なる Transport で動作
  4. 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

実行フロー

  1. コンテキスト受信: ミドルウェアが MiddlewareContext オブジェクトを受け取る
  2. 前処理: 入力されるリクエストの検査・変更
  3. 処理継続: call_next() で次のミドルウェアまたは実際の処理を呼び出し
  4. 後処理: レスポンスの検査・変更してから返却

ミドルウェアを使用することで、MCP Server の全てのメッセージ処理に対して共通的な処理を適用できます。
このミドルウェアの利用例の1つとして、ログ記録ミドルウェア Logging Middlware がありますのでこちらを利用します。

Built-in Logging Middleware の設定

ログ記録をするためのカスタムミドルウェアを実装し利用することもできますが、FastMCP にはベストプラクティスを実証した複数のミドルウェア実装が組み込まれていますので、こちらを利用してみます。

1. 必要なインポートの追加

まず、FastMCP2 のロギング ユーティリティと Built-in Logging Middleware の1つである LoggingMiddleware をインポートします。

main.py

# LoggingMiddleware のインポート
from fastmcp.utilities.logging import configure_logging, get_logger

from fastmcp.server.middleware.logging import (
    LoggingMiddleware, 
)

2. ログ設定の追加

FastMCP2 のログユーティリティを使用してデバッグレベルのログを有効化します。

main.py
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 を追加します。

main.py
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 関数の実装を確認してみると

logging.py
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 関数の実装も見てみます。

logging.py
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 パターンをベースにしており、以下の類似点があります:

  1. リクエスト前後の処理: call_next() の前後で処理を分けることができる
  2. ミドルウェアの順序: 複数のミドルウェアは追加した順序で実行される
  3. 横断的な関心事の処理: ログ、認証、エラーハンドリングなどに使用

ただし、MCP プロトコル特有の context.methodcontext.source といった情報にアクセスできる点が特徴的です。

まとめ

FastMCP2 の LoggingMiddleware を使用することで、MCP Server の詳細な動作をデバッグできるようになります。

MCP Server の開発・運用時のデバッグ作業が格段に効率化されるので、ぜひ活用してみてください!

参考記事

https://gofastmcp.com/servers/middleware

https://zenn.dev/simpleform_blog/articles/20241222-fastapi-middleware-log

Discussion