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で認証が必要になる場面
ローカルのSTDIOで動かすMCPサーバーは、通常、同じ端末上のクライアントが子プロセスとして起動します。一方、Streamable HTTPで起動してネットワークへ公開すると、URLへ到達できる別のクライアントからも接続可能になります。
次のようなサーバーは、公開前に認証を設計してください。
- 社内文書や顧客データを検索する
- 外部APIのキーを使って処理する
- データベースを参照・更新する
- メール送信、購入、削除、公開などを行う
- 利用者ごとに閲覧可能なデータが異なる
HTTPSは通信を暗号化しますが、利用者の身元や権限までは判断しません。HTTPSと認証は両方必要です。
認証と認可の違い
FastMCPでは、AuthenticationとAuthorizationを分けて考えます。
| レイヤー | 判断すること | FastMCPの主な仕組み |
|---|---|---|
| 認証(Authentication) | 誰からのリクエストか | JWTVerifier、RemoteAuthProvider、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クライアントとの違いを吸収します。




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を最小権限にする
読み取りクライアントへadminやwriteを与えません。特に削除・送信・購入・公開ツールは、Scopeだけでなく人間の承認と監査ログも追加します。
ログからトークンを除外する
Authorizationヘッダー、JWT全体、client secretをログへ残しません。調査用にはsubject、client ID、失敗理由、request IDなど必要最小限を記録します。
HTTPSを必須にする
Bearer tokenは、取得した者が権限を行使できる認証情報です。HTTPのまま送信せず、外部公開ではHTTPSを必須にします。
よくあるエラー
401 Unauthorizedになる
Authorization: Bearer ...が送信されているか- クライアントの
authへBearerを重ねていないか - トークンの有効期限が切れていないか
- issuerとaudienceが設定値と一致するか
FastMCPクライアントへ文字列を渡す場合は、Bearer なしのトークン値だけを指定します。
正しいトークンでも拒否される
JWTの署名アルゴリズム、JWKS endpoint、audience、Scopeを確認します。IDプロバイダーによって、Scopeがscope、scpなど別の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で公開する場合は、認証と認可を分けて設計します。
- 開発環境は
StaticTokenVerifierで接続手順を確認する - 本番環境は
JWTVerifierで署名・issuer・audienceを検証する - 対話的ログインには
RemoteAuthProviderやOAuth Proxy系Providerを使う require_scopes()でツール単位の権限を設定する- FastMCPクライアントからBearerまたはOAuthで接続する
まずは読み取り専用ツールをdata:readで保護し、トークンなし・Scope不足で拒否されることをテストしてください。その後、更新系ツールを別Scopeと承認フローへ分離するのが安全です。

コメント