FastMCPサーバーを公開したあと、「エラーは出ているが、どの処理で失敗したのか分からない」「一部のツールだけ遅い気がする」という状態になっていないでしょうか。
そこで役立つのがOpenTelemetryです。FastMCPにはOpenTelemetryによるトレース計測が組み込まれており、ツール・リソース・プロンプトの呼び出しをSpanとして追跡できます。
この記事では、ローカルのJaegerへトレースを送り、FastMCPの処理時間とエラー箇所を確認するところから、ログ相関、カスタムSpan、本番運用の注意点まで順番に解説します。
この記事の結論
最初は自動計測されたTraceをOTLPでJaegerへ送るだけで十分です。その後、原因調査に必要な箇所へカスタムSpanと構造化ログを追加します。入力本文、トークン、個人情報は記録しません。

FastMCPの監視で分かること
可観測性(Observability)は、主に次の3要素で構成されます。
| 種類 | 分かること | FastMCPでの扱い |
|---|---|---|
| Trace | 1リクエスト内の処理順序、所要時間、失敗箇所 | ツールなどの呼び出しを自動計測 |
| Log | その時点で起きた個別イベント | Pythonのloggingなどを別途設定 |
| Metric | エラー率、レイテンシ、件数の時系列傾向 | SDKや監視基盤側で別途設定 |
FastMCPが標準で自動計測する中心はTraceです。アプリケーション独自のログやメトリクスまで自動的に完成するわけではありません。
まずTraceで「どこが遅いか、どこで失敗したか」を見えるようにし、必要になったログとメトリクスだけを追加すると、監視が過剰になりません。
OpenTelemetryとOTLPの役割
OpenTelemetryは、Trace・Log・Metricを共通形式で計測・転送するための標準です。OTLP(OpenTelemetry Protocol)は、そのテレメトリをJaeger、Grafana Tempo、Datadogなどへ送るためのプロトコルです。
典型的な経路は次の通りです。
FastMCP Server
└─ OpenTelemetry SDK / 自動計測
└─ OTLP Exporter
└─ OpenTelemetry Collector または Jaeger
└─ 監視画面・検索・アラート
OpenTelemetry SDKを設定していない場合、FastMCPの計測処理は実質的に何も出力しません。そのため、開発初期は計測を組み込んだままにしておき、必要な環境だけExporterを有効化できます。
最短構成:FastMCPのTraceをJaegerで見る
ここでは、ローカル環境で動作を確認します。本番環境へ進む前に、Traceが期待どおり生成され、機密情報が含まれていないことを確認してください。
1. OpenTelemetryをインストールする
FastMCP本体に加えて、OpenTelemetryの自動計測とOTLP Exporterをインストールします。
pip install fastmcp opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
opentelemetry-bootstrap -a installは、現在のPython環境を調べて対応する計測パッケージを追加します。仮想環境を有効にしてから実行してください。
2. Jaegerを起動する
ローカル確認用にJaegerをDockerで起動します。
docker run --rm -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
jaegertracing/all-in-one:latest
16686:JaegerのWeb画面4317:OTLP/gRPCの受信ポート
ブラウザでhttp://localhost:16686を開くと、Jaegerの画面を確認できます。上のlatestはローカル検証向けです。本番やCIでは再現性を保つため、確認済みのバージョンへ固定してください。
3. FastMCPを自動計測付きで起動する
OTEL_SERVICE_NAME=fastmcp-search \
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317 \
opentelemetry-instrument fastmcp run server.py --transport http
FastMCPクライアントからツールを呼び出したあと、JaegerのService一覧でfastmcp-searchを選択します。Traceが表示されれば最小構成は完成です。
FastMCPとJaegerを別々のDockerコンテナで動かす場合、
127.0.0.1やlocalhostはJaegerを指しません。同じDockerネットワークに接続し、http://jaeger:4317のようにサービス名を指定します。
FastMCPが自動生成するSpan
FastMCPは、主要なMCP操作を次のようなSpan名で記録します。
| 操作 | Span名の例 | 主に確認する属性 |
|---|---|---|
| ツール呼び出し | tools/call search |
gen_ai.tool.name、エラー、所要時間 |
| リソース取得 | resources/read |
mcp.resource.uri |
| プロンプト取得 | prompts/get summarize |
gen_ai.prompt.name |
| マウント先への委譲 | delegate search |
委譲先、処理時間 |
エラーが発生すると、Spanの状態は自動的にERRORになり、例外情報も記録されます。まず赤いSpanを探し、その親子関係と直前の処理を確認するのが基本です。
mcp.method.name、mcp.session.id、fastmcp.server.nameなども調査に役立ちます。ただし、FastMCPのバージョン更新で属性名が変わる可能性があります。古い記事で使われているrpc.*属性ではなく、現在のmcp.*とfastmcp.*属性を基準にしてください。




独自処理にカスタムSpanを追加する
自動Spanだけでは、「外部APIの取得」と「ランキング処理」のどちらが遅いかまでは分からないことがあります。その場合、調べたい境界へカスタムSpanを追加します。
from fastmcp import FastMCP
from fastmcp.telemetry import get_tracer
mcp = FastMCP("search-server")
tracer = get_tracer(__name__)
@mcp.tool
def search(query: str, limit: int = 10) -> list[dict]:
with tracer.start_as_current_span("search.fetch") as span:
results = fetch_results(query=query, limit=limit)
span.set_attribute("search.result_count", len(results))
with tracer.start_as_current_span("search.rank") as span:
ranked = rank_results(results)
span.set_attribute("search.ranked_count", len(ranked))
return ranked
Span名はsearch.fetch、search.rankのように、機能と処理を組み合わせると検索しやすくなります。属性には件数、キャッシュヒット、処理モードなど、集計しやすく安全な値を使います。
次の値は記録しないでください。
- 検索文やプロンプトの全文
- APIキー、Cookie、Bearer token
- メールアドレスなどの個人情報
- モデルの入力・出力全文
- データベースの接続文字列
入力内容が必要に見えても、長さ、種別、ハッシュ化された内部IDなどへ置き換えられないかを先に検討します。
プログラム内でOTLP Exporterを設定する
環境変数とopentelemetry-instrumentが使いにくい場合は、Pythonコード内でTracerProviderを構成できます。
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(
OTLPSpanExporter(endpoint="http://localhost:4317")
)
)
trace.set_tracer_provider(provider)
# OpenTelemetry設定後にFastMCPをimportする
from fastmcp import FastMCP
重要なのは、FastMCPをimportする前にOpenTelemetryを設定することです。また、ネットワーク送信にはSimpleSpanProcessorではなく、処理への影響を抑えやすいBatchSpanProcessorを使います。
本番環境ではコードへ送信先を固定せず、環境変数やSecret管理から渡す方が安全です。
ログとTraceを関連付ける
Traceは処理のつながり、Logはその瞬間の出来事を表します。同じtrace_idとspan_idをログへ含めると、遅いTraceから関連ログへ移動できます。
OpenTelemetryのログ計測を追加する場合は、対応パッケージを導入します。
pip install opentelemetry-instrumentation-logging
アプリケーション側では、検索本文そのものではなく、安全な結果だけを構造化ログへ残します。
import logging
logger = logging.getLogger(__name__)
def record_search_completed(result_count: int, cache_hit: bool) -> None:
logger.info(
"search completed",
extra={
"result_count": result_count,
"cache_hit": cache_hit,
},
)
Trace・Metric・LogをOTLPへ送る起動例は次の通りです。
opentelemetry-instrument \
--traces_exporter otlp \
--metrics_exporter otlp \
--logs_exporter otlp \
--service_name fastmcp-search \
fastmcp run server.py --transport http
OpenTelemetry Python 1.40以降では、ログの自動計測を有効にするための追加フラグは原則不要です。それ以前の環境ではOTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=trueが必要な場合があります。利用中のバージョンに合わせて公式ドキュメントを確認してください。
OTLP/gRPCとOTLP/HTTPの違い
OTLPには主にgRPCとHTTP/protobufがあります。
| 方式 | 一般的なポート | Endpoint例 |
|---|---|---|
| OTLP/gRPC | 4317 | http://collector:4317 |
| OTLP/HTTP | 4318 | http://collector:4318 |
gRPCのEndpointへ/v1/tracesを付けないでください。HTTPではExporterがシグナル別パスを補う構成と、OTEL_EXPORTER_OTLP_TRACES_ENDPOINTへ/v1/tracesまで指定する構成があります。
接続できないときは、次を確認します。
- Exporterと受信側でgRPC/HTTPが一致しているか
- ポートが4317/4318で混ざっていないか
- Docker内から到達できるホスト名か
- CollectorやJaegerがOTLP Receiverを有効にしているか
- TLSが必要なEndpointへ平文HTTPで送っていないか
本番ではCollectorを挟む
小規模な検証ではFastMCPからJaegerへ直接送信できます。本番ではOpenTelemetry Collectorを挟む構成が扱いやすくなります。
Collectorを使うと、アプリケーションを変更せずに次を調整できます。
- 送信先の追加・変更
- バッチ化と再試行
- 不要な属性の削除
- サンプリング
- TLSや認証
- 複数サービスのテレメトリ統合
アプリケーション側の送信先をCollectorへ統一し、Collectorから監視サービスへ転送すると、ベンダー変更や障害時の切り分けも容易になります。
データ量をサンプリングで抑える
全Traceを保存すると、アクセス増加に合わせてコストも増えます。まず開発環境は100%、本番は10%などから始め、調査に必要な量へ調整します。
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
この例は、およそ10%をヘッドサンプリングします。ただし、処理開始時点で保存有無を決めるため、「エラーだけ必ず残す」という動作にはなりません。重要なエラーを残したい場合は、CollectorやバックエンドでTrace完了後に判断するテールサンプリングも検討します。
最初に作るダッシュボードとアラート
最初から多数のグラフを作る必要はありません。次の4項目があれば、主な障害を検出できます。
- ツール別の呼び出し回数
- ツール別のp95レイテンシ
- エラー率と
error.type - テレメトリが突然届かなくなった状態
特に「テレメトリがない」ことも監視対象です。ExporterやCollectorが止まると、サービスが正常なのではなく、単に観測できなくなっている可能性があります。
アラートは、単発エラーではなく一定時間の比率や継続時間を条件にすると、通知疲れを防げます。
OpenTelemetryをテストする
監視設定もコードと同じようにテストできます。FastMCP公式は、テスト用のInMemorySpanExporterを使ってSpanをメモリへ集める方法を案内しています。
確認したいのは次の点です。
- ツール呼び出しで期待するSpanが作られる
- 失敗時にSpanが
ERRORになる - カスタム属性が記録される
- 入力本文や認証情報が属性に含まれない
FastMCP本体のテスト環境をまだ作っていない場合は、先に「FastMCPをpytestでテスト|ツール・リソース・認証の実践例」を参照してください。
セキュリティと運用の注意点
OpenTelemetryは調査を助けますが、記録内容を誤ると機密情報の集積場所にもなります。
- OTLP Endpointはインターネットへ無制限公開しない
- Collectorと監視基盤の通信にTLSと認証を使う
- Span属性とログの許可リストを決める
- 保存期間と閲覧権限を設定する
- 開発・検証・本番を
service.nameやResource属性で分離する - SDK、Exporter、Collectorのバージョンを固定して更新する
- 監視基盤への送信障害が本処理を止めないか確認する
認証方式の基本は「FastMCPの認証を実装|Bearer Token・OAuthの基本」、コンテナ運用は「FastMCPをDockerでデプロイ|本番向け構成と設定」もあわせて確認してください。
よくあるトラブル
JaegerにServiceが表示されない
最低1回はツールを呼び出し、Traceを発生させます。そのうえでOTEL_SERVICE_NAME、送信先、ポート、Dockerネットワークを確認します。
Traceはあるが独自処理の内訳が分からない
FastMCPの自動Span配下へ、外部API、データベース、ランキングなどのカスタムSpanを追加します。細かく分割しすぎず、障害原因を切り分けられる境界に絞ります。
ログにtrace_idが入らない
ログ計測パッケージ、OpenTelemetryのバージョン、起動方法を確認します。また、Traceのコンテキスト外で別スレッドやバックグラウンド処理を開始すると、コンテキストの引き継ぎが必要になる場合があります。
監視を入れたら遅くなった
BatchSpanProcessor、サンプリング、Collectorを使い、Span属性のサイズとカスタムSpan数を減らします。Exporterのタイムアウトや再試行も確認してください。
実運用前のチェックリスト
- □ Jaegerまたは監視基盤でFastMCPのTraceを確認した
- □ ツール別の所要時間とエラー箇所を追える
- □ OTLPの方式とポートをgRPC/HTTPで統一した
- □ Docker内の送信先に適切なサービス名を使った
- □ 入力本文、トークン、個人情報を記録していない
- □ 本番ではBatchSpanProcessorとCollectorを検討した
- □ サンプリング率と保存期間を決めた
- □ テレメトリ欠損も検知できる
- □ Spanの生成と機密情報の非記録をテストした
まとめ
FastMCPにはOpenTelemetryのTrace計測が組み込まれているため、OTLP Exporterを設定するだけで、ツール・リソース・プロンプトの処理を可視化できます。
最初の一歩は、ローカルJaegerへTraceを送ることです。その後、原因調査に必要な処理だけへカスタムSpanを追加し、ログをtrace_idで関連付けます。本番ではCollector、サンプリング、TLS、閲覧権限を整えます。
監視は「たくさん記録すること」ではありません。障害の発見と原因特定に必要な情報を、安全な形で残すことが目的です。
まだFastMCPサーバーを作っていない場合は、「PythonでFastMCPサーバーを作る|MCPツール実装入門」から始めると、この記事の構成をそのまま試せます。

コメント