FastMCPの認証を実装する|Bearer・OAuthで保護

FastMCPのBearer・OAuth認証とScope制御の実践ガイド LLM

FastMCPサーバーをHTTPで外部公開するとき、認証なしのままでは第三者がツール一覧を取得し、処理を実行できる可能性があります。社内データ検索だけのサーバーでも、入力やレスポンスに機密情報が含まれるなら保護が必要です。

FastMCP 3.xには、Bearer tokenの検証、外部IDプロバイダーとのOAuth連携、Scopeによるツール単位のアクセス制御が用意されています。

この記事では、開発環境で固定トークンを試し、本番向けのJWT/JWKS検証、Remote OAuth、FastMCPクライアントからのBearer・OAuth接続まで進めます。

  • 認証と認可の違い
  • 開発用Bearer token認証
  • 本番用JWT/JWKS検証
  • RemoteAuthProviderによるOAuth discovery
  • Scopeでツールを保護する方法
  • FastMCPクライアントからの認証接続

FastMCPサーバーの認証と認可の構成

スポンサーリンク

FastMCPで認証が必要になる場面

ローカルのSTDIOで動かすMCPサーバーは、通常、同じ端末上のクライアントが子プロセスとして起動します。一方、Streamable HTTPで起動してネットワークへ公開すると、URLへ到達できる別のクライアントからも接続可能になります。

次のようなサーバーは、公開前に認証を設計してください。

  • 社内文書や顧客データを検索する
  • 外部APIのキーを使って処理する
  • データベースを参照・更新する
  • メール送信、購入、削除、公開などを行う
  • 利用者ごとに閲覧可能なデータが異なる

HTTPSは通信を暗号化しますが、利用者の身元や権限までは判断しません。HTTPSと認証は両方必要です。

スポンサーリンク

認証と認可の違い

FastMCPでは、AuthenticationとAuthorizationを分けて考えます。

レイヤー 判断すること FastMCPの主な仕組み
認証(Authentication) 誰からのリクエストか JWTVerifierRemoteAuthProvider、OAuth Provider
認可(Authorization) 何を実行してよいか require_scopes()AuthMiddleware、独自チェック

認証プロバイダーをFastMCP(auth=...)へ設定すると、有効なトークンを持たないHTTPリクエストはMCP処理へ到達する前に拒否されます。

認証後は、トークンに含まれるScopeやClaimを使い、読み取りツールだけを見せる、管理ツールを隠す、といった認可を追加します。

HTTP transportだけが対象

FastMCPのOAuth認証・認可はHTTP系transportで使用します。STDIOにはOAuthの仕組みがなく、ローカル実行環境の権限を引き継ぎます。

この記事のサーバーは次のようにHTTPで起動します。

if __name__ == "__main__":
    mcp.run(transport="http", host="127.0.0.1", port=8000)

FastMCPをインストールする

新しいプロジェクトへFastMCP 3.xを追加します。

mkdir fastmcp-auth
cd fastmcp-auth
uv init
uv add "fastmcp>=3,<4"

pipの場合は、仮想環境を有効にしてからインストールします。

pip install "fastmcp>=3,<4"

この記事では独立パッケージのFastMCPを使用するため、importはfrom fastmcp import FastMCPです。

開発用Bearer token認証を試す

最初から外部のIDプロバイダーを用意すると動作確認が複雑になります。開発環境ではStaticTokenVerifierで固定トークンを定義できます。

開発用サーバーを作る

server.pyを作成します。

from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import StaticTokenVerifier

verifier = StaticTokenVerifier(
    tokens={
        "dev-reader-token": {
            "client_id": "local-reader",
            "scopes": ["data:read"],
        },
        "dev-admin-token": {
            "client_id": "local-admin",
            "scopes": ["data:read", "data:write", "admin"],
        },
    },
    required_scopes=["data:read"],
)

mcp = FastMCP(
    name="Protected Development Server",
    auth=verifier,
)

@mcp.tool
def get_project_status() -> dict:
    """プロジェクトの公開可能な状態を返す。"""
    return {"project": "BUNSHIN", "status": "active"}

if __name__ == "__main__":
    mcp.run(transport="http", host="127.0.0.1", port=8000)

起動後のMCP URLはhttp://127.0.0.1:8000/mcpです。

StaticTokenVerifierはトークンを平文で保持します。公式ドキュメントでも開発・テスト専用とされており、本番環境では使用しないでください。

Bearer token付きで接続する

FastMCPクライアントのauthへトークン文字列を渡します。

import asyncio

from fastmcp import Client

async def main() -> None:
    async with Client(
        "http://127.0.0.1:8000/mcp",
        auth="dev-reader-token",
    ) as client:
        tools = await client.list_tools()
        print([tool.name for tool in tools])

        result = await client.call_tool("get_project_status", {})
        print(result.data)

asyncio.run(main())

文字列で渡す場合、Bearer という接頭辞は付けません。FastMCPクライアントがAuthorization: Bearer ...形式へ変換します。

トークンなし、または未登録のトークンでも接続を試し、拒否されることを確認してください。

本番ではJWTとJWKSで検証する

本番環境では、認証サービスやIDプロバイダーが署名したJWTをJWTVerifierで検証する構成が基本です。

JWTVerifierを設定する

import os

from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import JWTVerifier

verifier = JWTVerifier(
    jwks_uri=os.environ["JWT_JWKS_URI"],
    issuer=os.environ["JWT_ISSUER"],
    audience=os.environ["JWT_AUDIENCE"],
    required_scopes=["mcp:access"],
)

mcp = FastMCP(
    name="Production MCP Server",
    auth=verifier,
)

環境変数には、利用するIDプロバイダーの値を設定します。

export JWT_JWKS_URI="https://auth.example.com/.well-known/jwks.json"
export JWT_ISSUER="https://auth.example.com"
export JWT_AUDIENCE="mcp-production-api"

JWKSはJWT署名を検証する公開鍵の集合です。IDプロバイダーが鍵をローテーションしても、MCPサーバーはJWKS endpointから新しい公開鍵を取得できます。

必ず検証する3項目

項目 確認すること 防げる問題
Signature 信頼する鍵で署名されている トークン改ざん
Issuer 信頼する発行者か 別の認証基盤のトークン受け入れ
Audience このMCPサーバー向けか 他サービス用トークンの流用

JWTVerifierへJWKS URLだけを設定して終わらせず、issuerとaudienceも明示します。トークンの有効期限とScopeも検証対象です。

OAuth discoveryを追加する

JWTVerifier単体は、受け取ったトークンを検証できます。しかしMCPクライアントへ「どの認証サーバーでトークンを取得するか」を知らせるOAuth discovery metadataは提供しません。

ユーザーがブラウザでログインするMCPアプリでは、RemoteAuthProviderや各IDプロバイダー向けProviderを使います。

RemoteAuthProviderを使う

IDプロバイダーがDynamic Client Registration(DCR)へ対応している場合の例です。

import os

from fastmcp import FastMCP
from fastmcp.server.auth import RemoteAuthProvider
from fastmcp.server.auth.providers.jwt import JWTVerifier
from pydantic import AnyHttpUrl

issuer = os.environ["JWT_ISSUER"]

token_verifier = JWTVerifier(
    jwks_uri=os.environ["JWT_JWKS_URI"],
    issuer=issuer,
    audience=os.environ["JWT_AUDIENCE"],
)

auth = RemoteAuthProvider(
    token_verifier=token_verifier,
    authorization_servers=[AnyHttpUrl(issuer)],
    base_url="https://mcp.example.com",
)

mcp = FastMCP(
    name="OAuth Protected Server",
    auth=auth,
)

MCP endpointがhttps://mcp.example.com/mcpなら、base_urlにはhttps://mcp.example.comを指定します。

RemoteAuthProviderは、トークン検証に加えて/.well-known/oauth-protected-resourceなどのmetadataを提供します。対応クライアントはmetadataを読み、認証先を発見できます。

DCR対応の有無でProviderを選ぶ

IDプロバイダーの特徴 選択肢
DCRへ対応している RemoteAuthProvider、AuthKitProviderなど
DCRへ対応していない OAuthProxy、GitHub・Google・Azure向けProvider
既にJWTを別経路で取得できる JWTVerifier
自社でOAuth serverを完全実装する OAuthProvider。専門要件がある場合のみ

GitHubやGoogleなどの従来型Providerは、あらかじめ登録したclient IDとclient secretを使うため、FastMCPのOAuth Proxy系ProviderがMCPクライアントとの違いを吸収します。

FastMCPの認証方式を選ぶフロー

Scopeでツール単位の権限を設定する

サーバーへ接続できることと、すべてのツールを実行できることは別です。require_scopes()を使い、ツールごとに必要なScopeを指定します。

from fastmcp import FastMCP
from fastmcp.server.auth import require_scopes

mcp = FastMCP("Scoped MCP Server")

@mcp.tool(auth=require_scopes("data:read"))
def read_record(record_id: str) -> dict:
    """指定したレコードを読み取る。"""
    return {"id": record_id, "status": "active"}

@mcp.tool(auth=require_scopes("data:write"))
def update_record(record_id: str, status: str) -> dict:
    """指定したレコードの状態を更新する。"""
    return {"id": record_id, "status": status}

@mcp.tool(auth=require_scopes("admin"))
def delete_record(record_id: str) -> dict:
    """管理者権限でレコードを削除する。"""
    return {"deleted": record_id}

認可条件を満たさないコンポーネントはツール一覧から隠され、直接呼び出しても利用できません。

サーバー全体へScopeを要求する

すべてのツールへ共通Scopeを要求する場合はAuthMiddlewareを使います。

from fastmcp import FastMCP
from fastmcp.server.auth import require_scopes
from fastmcp.server.middleware import AuthMiddleware

mcp = FastMCP(
    "Protected MCP Server",
    middleware=[
        AuthMiddleware(auth=require_scopes("mcp:access")),
    ],
)

サーバー全体のmcp:accessと、各ツールのdata:writeなどを組み合わせると、両方の条件を満たした場合だけ実行できます。

OAuthでクライアントから接続する

OAuth discoveryへ対応したMCPサーバーなら、FastMCPクライアントのauth"oauth"を指定できます。

import asyncio

from fastmcp import Client

async def main() -> None:
    async with Client(
        "https://mcp.example.com/mcp",
        auth="oauth",
    ) as client:
        tools = await client.list_tools()
        print([tool.name for tool in tools])

asyncio.run(main())

初回接続では通常、ブラウザが開き、ログインと同意を行います。取得したトークンを保存する場合は、平文ファイルではなくOSのKeychainや暗号化ストレージを使います。

サービスアカウントなど、別経路で取得したBearer tokenを渡す場合は、前述のようにauth="token-value"を使います。

REST API側の認証と混同しない

FastMCPでREST APIをMCP化する方法」では、FastMCPから接続先REST APIへBearer tokenを渡しました。今回の認証とは守る区間が異なります。

AIクライアント
    ↓ MCPサーバーの認証
FastMCP
    ↓ 接続先REST APIの認証
業務API

FastMCPからREST APIへAPIキーを送っていても、MCP endpoint自体は保護されません。2つの認証を別々に設定します。

FastMCPサーバーの基本構築は「PythonでMCPサーバーを作る|FastMCP入門」、OpenAI側からの接続は「Responses APIでMCP連携するPython実装」も参照してください。

セキュリティ設計のポイント

シークレットをコードへ書かない

client secret、HMAC secret、固定トークンをGitやWordPressへ保存しません。デプロイ先のSecret Managerまたは環境変数から読み込みます。

audienceをサービスごとに分ける

複数のAPIで同じaudienceを使うと、別サービス用トークンがMCPサーバーで受理される可能性があります。MCPサーバー専用のaudienceを設定します。

Scopeを最小権限にする

読み取りクライアントへadminwriteを与えません。特に削除・送信・購入・公開ツールは、Scopeだけでなく人間の承認と監査ログも追加します。

ログからトークンを除外する

Authorizationヘッダー、JWT全体、client secretをログへ残しません。調査用にはsubject、client ID、失敗理由、request IDなど必要最小限を記録します。

HTTPSを必須にする

Bearer tokenは、取得した者が権限を行使できる認証情報です。HTTPのまま送信せず、外部公開ではHTTPSを必須にします。

よくあるエラー

401 Unauthorizedになる

  • Authorization: Bearer ...が送信されているか
  • クライアントのauthBearer を重ねていないか
  • トークンの有効期限が切れていないか
  • issuerとaudienceが設定値と一致するか

FastMCPクライアントへ文字列を渡す場合は、Bearer なしのトークン値だけを指定します。

正しいトークンでも拒否される

JWTの署名アルゴリズム、JWKS endpoint、audience、Scopeを確認します。IDプロバイダーによって、Scopeがscopescpなど別のClaimへ格納されることもあります。

OAuthのログイン画面が開かない

MCPサーバーのOAuth metadataが外部から取得できるか、base_urlが実際の公開URLと一致するか確認します。IDプロバイダーがDCR非対応ならRemoteAuthProviderではなくOAuth Proxy系Providerを検討します。

STDIOでScope制御されない

FastMCPのOAuth認可はHTTP transportを前提にしています。STDIOではローカル実行環境の権限とクライアント設定で保護します。

実運用前のチェックリスト

  • ✅ HTTP公開時にHTTPSを有効にした
  • ✅ 開発用StaticTokenVerifierを本番から除外した
  • ✅ JWTのsignature・issuer・audience・期限を検証した
  • ✅ シークレットをSecret Managerで管理した
  • ✅ MCPサーバー専用のaudienceを設定した
  • ✅ 読み取り・更新・管理Scopeを分離した
  • ✅ 更新・削除ツールへ承認と監査ログを追加した
  • ✅ Authorizationヘッダーをログから除外した
  • ✅ トークンなし・期限切れ・Scope不足をテストした
  • ✅ REST API側とMCP側の認証を別々に確認した

まとめ

FastMCPサーバーをHTTPで公開する場合は、認証と認可を分けて設計します。

  1. 開発環境はStaticTokenVerifierで接続手順を確認する
  2. 本番環境はJWTVerifierで署名・issuer・audienceを検証する
  3. 対話的ログインにはRemoteAuthProviderやOAuth Proxy系Providerを使う
  4. require_scopes()でツール単位の権限を設定する
  5. FastMCPクライアントからBearerまたはOAuthで接続する

まずは読み取り専用ツールをdata:readで保護し、トークンなし・Scope不足で拒否されることをテストしてください。その後、更新系ツールを別Scopeと承認フローへ分離するのが安全です。

参考資料

コメント

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