FastMCPをDockerで本番デプロイ|HTTPS・ヘルスチェック

FastMCPをDockerとHTTPSで本番公開し、ヘルスチェックと監視を行う構成 LLM

FastMCPでMCPサーバーを作れたら、次に必要なのは「ローカルでは動く」を「外部から安全に使える」へ変える作業です。

本番運用では、Pythonコードだけでなく、コンテナ化、HTTPS、ヘルスチェック、ログ、再起動、認証までを1つの仕組みとして考える必要があります。

この記事では、FastMCP 3系のサーバーをDockerで動かし、nginxでHTTPS化し、監視できる状態まで持っていく最小構成を解説します。クラウド固有の設定に寄せすぎず、VPS・AWS・Google Cloud・Azure・Renderなどへ応用できる形にします。

この記事のゴール

  • FastMCPをDockerコンテナとして起動できる
  • /mcpをHTTPSで公開する構成が分かる
  • /healthでコンテナの死活監視ができる
  • 本番運用で避けるべき設定が分かる
FastMCPをDockerとHTTPSで本番公開する構成図
FastMCP本番構成:クライアントからHTTPSリバースプロキシを経由し、Docker内のMCPサーバーへ接続する
スポンサーリンク

FastMCPの本番構成

最小の本番構成は、次の5要素に分けると整理しやすくなります。

要素 役割
FastMCP ツール、リソース、プロンプトをMCPとして提供する
Uvicorn FastMCPのASGIアプリをHTTPサーバーとして動かす
Docker 実行環境と依存関係を固定する
nginx / クラウドLB TLS終端、HTTPS、転送、アクセス制御を担当する
監視基盤 ヘルスチェック、ログ、アラートを扱う

FastMCPのHTTPトランスポートでは、標準のMCPエンドポイントは/mcpです。本番ではFastMCPをインターネットへ直接さらすより、手前にnginxやクラウドのロードバランサーを置き、HTTPSとアクセス制御を任せる構成が扱いやすいです。

スポンサーリンク

最小プロジェクトを作る

今回は、次の4ファイルを用意します。

fastmcp-production/
├── app.py
├── requirements.txt
├── Dockerfile
└── .dockerignore

1. FastMCPサーバーを作る

app.pyに、MCPツールとヘルスチェック用のHTTPルートを定義します。

from fastmcp import FastMCP
from starlette.requests import Request
from starlette.responses import JSONResponse

mcp = FastMCP("Production MCP Server")

@mcp.tool
def ping(message: str = "pong") -> dict[str, str]:
    """疎通確認用のMCPツール。"""
    return {"message": message}

@mcp.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> JSONResponse:
    return JSONResponse({"status": "healthy"})

app = mcp.http_app()

mcp.http_app()は、Uvicornなどから起動できるASGIアプリを返します。MCPの接続先は/mcp、監視用URLは/healthになります。

/healthにはAPIキーや内部ホスト名などの情報を含めません。外部監視サービスやロードバランサーから呼ばれる可能性があるため、正常かどうかだけを短く返します。

2. 依存パッケージを固定する

requirements.txtを作ります。

fastmcp>=3,<4
uvicorn[standard]>=0.30,<1

再現性をさらに高めたい場合は、検証済みのバージョンへ固定し、Dependabotなどで定期更新します。FastMCPのメジャーバージョンを無制限に上げないことが重要です。

3. Dockerfileを作る

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

RUN useradd --no-log-init --create-home --uid 10001 appuser

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py ./

USER appuser

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
  CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)"]

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

ポイントは次のとおりです。

  • python:3.12-slimで不要なパッケージを減らす
  • 依存ファイルを先にコピーしてDockerのビルドキャッシュを活用する
  • USER appuserでroot権限のまま実行しない
  • --reloadを付けない。本番での自動リロードは不要
  • HEALTHCHECKには追加インストール不要のPython標準ライブラリを使う
  • Uvicornは全インターフェースで待ち受け、公開範囲はDockerやファイアウォール側で制御する

Docker公式ドキュメントでも、不要なパッケージを減らすこと、絶対パスのWORKDIRを使うこと、権限が不要なサービスを非rootユーザーで動かすことが推奨されています。

4. 不要ファイルをイメージへ入れない

.dockerignoreを作ります。

.git
.gitignore
.env
.venv
__pycache__/
*.py[cod]
*.log
tests/

特に.envを含めないことが重要です。秘密情報はイメージへ焼き込まず、実行時の環境変数やクラウドのSecret Managerから渡します。

Dockerイメージをビルドして起動する

プロジェクトのディレクトリで、次を実行します。

docker build --check .
docker build -t fastmcp-server:latest .
docker run --rm \
  --name fastmcp-server \
  -p 127.0.0.1:8000:8000 \
  --env-file .env \
  fastmcp-server:latest

-p 127.0.0.1:8000:8000とすると、ホストの外部インターフェースへ直接公開せず、同じマシン上のnginxからだけ接続しやすくなります。構成によってはDockerネットワーク内だけでつなぎ、ホストへポート公開しない方法も選べます。

別のターミナルからヘルスチェックを確認します。

curl --fail http://127.0.0.1:8000/health

次のレスポンスが返れば、Webサーバーは起動しています。

{"status":"healthy"}

コンテナの状態も確認できます。

docker inspect \
  --format='{{json .State.Health}}' \
  fastmcp-server

healthyになるまでには、start-periodと最初のチェック分の時間がかかることがあります。

MCPクライアントから疎通確認する

/healthが通っても、MCPの初期化やツール呼び出しが成功するとは限りません。本番投入前には、MCPクライアントから/mcpへ接続して確認します。

import asyncio

from fastmcp import Client

async def main() -> None:
    async with Client("http://127.0.0.1:8000/mcp") as client:
        result = await client.call_tool(
            "ping",
            {"message": "docker-ok"},
        )
        print(result.data)

asyncio.run(main())

期待する出力は次のとおりです。

{'message': 'docker-ok'}

ここまで通れば、FastMCP、Uvicorn、Docker、MCP通信の一連の経路を確認できています。

nginxでHTTPS化する

MCPサーバーを外部公開する場合は、平文HTTPではなくHTTPSを使います。nginxの基本的な転送設定は次のようになります。

server {
    listen 443 ssl http2;
    server_name mcp.example.com;

    ssl_certificate     /etc/letsencrypt/live/mcp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mcp.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;

        proxy_buffering off;
        proxy_read_timeout 300s;
        proxy_set_header Connection "";

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

FastMCPのHTTP通信はストリーミングを使うため、通常の短いREST APIとは違う注意点があります。

  • proxy_buffering offでレスポンスを溜め込まない
  • proxy_read_timeoutをツールの最大処理時間より長くする
  • proxy_http_version 1.1を指定する
  • 転送元のプロトコルとIPアドレスをヘッダーで渡す

TLS証明書の取得・更新はCertbotやクラウドのマネージド証明書へ任せます。証明書をコンテナイメージへコピーしないでください。

nginxを含めてDocker Composeで管理する場合は、FastMCPコンテナを外部公開せず、nginxだけに443番ポートを公開すると境界が明確になります。

ヘルスチェックは3段階で考える

FastMCPのliveness readiness MCP疎通確認の監視フロー
軽い死活確認と、依存先を含む準備確認、MCPの実疎通を分離する

監視を1本のURLへ詰め込むと、障害の切り分けが難しくなります。役割を3段階に分けると運用しやすくなります。

確認 目的 失敗時の扱い
Liveness /health Pythonプロセスが応答するか コンテナ再起動の判断に使う
Readiness /ready DBや外部APIを利用できるか 新規リクエストの振り分けを止める
MCP疎通テスト 初期化と主要ツールが動くか アラートを上げ、原因を調査する

Livenessで外部APIやデータベースを毎回呼ぶと、一時的な外部障害で正常なコンテナまで再起動されます。/healthは軽量に保ち、依存先の確認は/readyや定期的なMCP疎通テストへ分けます。

ログとアラートで最低限見るもの

本番では、標準出力・標準エラーへログを出し、コンテナ基盤側で収集します。最低限、次を追える状態にします。

  • リクエストIDまたはトレースID
  • 呼び出されたツール名
  • 成功・失敗
  • 所要時間
  • HTTPステータス
  • タイムアウトと再試行回数
  • コンテナの再起動回数
  • CPU、メモリ、ディスク使用量

一方で、Authorizationヘッダー、アクセストークン、ユーザーの入力全文、ツールの機密な戻り値は、そのままログへ残しません。

アラートは「1回失敗した」だけで鳴らすより、5分間のエラー率、連続ヘルスチェック失敗、p95レイテンシなどで設定するとノイズを減らせます。

複数ワーカーと水平スケールの注意点

FastMCPのStreamable HTTPセッションは、標準ではステートフルです。そのまま複数ワーカーや複数コンテナへ増やすと、同じクライアントの後続リクエストが別のプロセスへ入り、セッションを見つけられない場合があります。

最初は1ワーカーで動かし、CPUや同時接続数を計測するのが安全です。水平スケールが必要になったら、利用機能を確認したうえでstateless_http=Trueを検討します。

mcp = FastMCP(
    "Production MCP Server",
    stateless_http=True,
)

ただし、elicitationやsamplingなど、サーバーからクライアントへ働きかける機能はステートレス化の影響を受けます。「ワーカー数を増やせば速くなる」と決めつけず、セッション、イベントストア、ロードバランサーの構成を含めて設計してください。

CORSはブラウザクライアントがある場合だけ設定する

デスクトップMCPクライアントやサーバー間通信だけなら、一般的なブラウザCORS設定は不要です。ブラウザから接続する場合のみ、許可するオリジンを明示します。

本番でallow_origins=["*"]にするのは避け、必要なWebアプリのURLだけを許可します。また、MCPで必要となるmcp-protocol-versionmcp-session-idAuthorizationContent-Typeなどのヘッダーを許可・公開する必要があります。

公開前のセキュリティ確認

MCPサーバーをHTTPS化しても、認証がなければURLを知る第三者がツールを呼べる可能性があります。外部公開する場合は、BearerトークンやOAuthで保護してください。

認証実装は、FastMCPの認証を実装する|Bearer・OAuthで保護で詳しく解説しています。

また、REST APIをMCP化したサーバーを公開する場合は、FastMCPでREST APIをMCP化|OpenAPIから自動生成も参考になります。

FastMCP自体の作り方から確認したい場合は、PythonでMCPサーバーを作る|FastMCP入門から読むと流れをつかめます。

本番公開前チェックリスト

  • /healthが200を返す
  • □ MCPクライアントから/mcpへ接続できる
  • □ コンテナを非rootユーザーで実行している
  • .envや秘密鍵がイメージに含まれていない
  • --reloadを本番で使っていない
  • □ HTTPSが有効で、HTTPをHTTPSへ転送している
  • □ nginxのバッファリングを無効にした
  • □ タイムアウトがツールの最大処理時間より長い
  • □ 認証・認可を有効にした
  • □ CORSの許可元を必要なURLだけに限定した
  • □ トークンや入力全文をログへ残していない
  • □ ヘルスチェック失敗とエラー率のアラートがある
  • □ コンテナ再起動後にも正常復旧することを確認した
  • □ 依存パッケージとベースイメージの更新手順がある

まとめ

FastMCPを本番公開するときは、単にmcp.run()をサーバー上で動かすだけでは不十分です。

まずFastMCPをASGIアプリとしてUvicornで起動し、Dockerで実行環境を固定します。次にnginxやクラウドのロードバランサーでHTTPS化し、ストリーミングに必要なプロキシ設定を加えます。最後に、軽量なヘルスチェック、MCP実疎通、ログ、アラートを分けて監視します。

最初は1ワーカーの小さな構成から始め、実測データを見てからスケールさせるのが堅実です。公開範囲が広がる前に認証まで入れておけば、MCPサーバーを安全に育てやすくなります。

参考資料

コメント

タイトルとURLをコピーしました