MCPのセキュリティと認証
OAuth 2.0トークンを使ってMCPサーバーに認証を追加し、インジェクション攻撃を防ぐ入力検証を実装して、ツール権限に最小権限の原則を適用します。
「MCPのセキュリティと認証」はCoddyKit上の無料AI Engineering Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAI Engineering Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 AI Engineering Academyコースには全4レッスンが含まれています。
MCPセキュリティが重要な理由
MCPサーバーは、システムへのゲートウェイです。適切なセキュリティ対策がなければ、侵害されたAIクライアントや悪意のあるプロンプトによって、機密データの読み取り、破壊的な操作の実行、ツール呼び出しチャネルを介した情報の持ち出しが行われる可能性があります。MCPサーバーのセキュリティには、多層防御が必要です。トランスポート層での認証、ツールレベルでの認可、すべての呼び出しに対する入力検証を行います。
ローカルサーバーとリモートサーバーのセキュリティ
Claude Desktopが使用するstdioトランスポートには、サーバーがローカルプロセスとして実行され、起動したユーザーだけがアクセスできるという本質的なセキュリティがあります。一方、HTTP/SSEを使用してネットワークに公開するMCPサーバーは、認証バイパス、インジェクション攻撃、不正アクセスなど、Webセキュリティ上のあらゆる脅威にさらされます。必要なセキュリティ対策は、デプロイモードによって大きく異なります。
- ローカルstdio: ローカルユーザーを信頼し、入力検証に重点を置く
- リモートHTTP/SSE: 完全な認証、TLS、レート制限、入力サニタイズを行う
リモートサーバーのAPIキー認証
リモートMCPサーバーで最も簡単な認証方法は、HTTPヘッダーでAPIキーを検証することです。すべての受信リクエストでAuthorization: Bearer <token>ヘッダーを確認し、認証されていないリクエストにはHTTP 401を返して拒否します。有効なAPIキーはユーザーメタデータとともにデータベースへ保存し、個々のキーを失効できるようにします。
# For HTTP/SSE MCP servers using FastAPI or similar:
from fastapi import FastAPI, HTTPException, Depends, Header
from typing import Optional
import secrets
app_http = FastAPI()
# In production: store in database with user_id, created_at, last_used
VALID_KEYS = {'sk-mcp-abc123': {'user': 'alice', 'scopes': ['read']},
'sk-mcp-def456': {'user': 'bob', 'scopes': ['read', 'write']}}
async def verify_api_key(authorization: Optional[str] = Header(None)) -> dict:
if not authorization or not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail='Missing API key')
key = authorization.removeprefix('Bearer ')
if key not in VALID_KEYS:
raise HTTPException(status_code=401, detail='Invalid API key')
return VALID_KEYS[key] # Returns user context
# Use in route handlers:
# @app_http.get('/sse')
# async def sse_endpoint(user=Depends(verify_api_key)):エンタープライズMCPサーバー向けOAuth 2.0
エンタープライズ環境では、OAuth 2.0を使用して、ユーザーが企業のIDプロバイダー(Okta、Azure AD、Google Workspace)で認証できるようにします。MCPクライアントはOAuthアクセストークンを取得し、ツール呼び出しリクエストに含めます。サーバーでは、python-joseまたはauthlibを使い、IDプロバイダーの公開鍵に対してトークンの署名を検証します。
from jose import jwt, JWTError
import httpx
AUTH_DOMAIN = 'your-tenant.auth0.com'
AUDIENCE = 'https://api.your-mcp-server.com'
async def get_jwks():
async with httpx.AsyncClient() as client:
resp = await client.get(f'https://{AUTH_DOMAIN}/.well-known/jwks.json')
return resp.json()
async def verify_oauth_token(token: str) -> dict:
jwks = await get_jwks()
try:
payload = jwt.decode(
token,
jwks,
algorithms=['RS256'],
audience=AUDIENCE,
issuer=f'https://{AUTH_DOMAIN}/'
)
return payload # Contains sub (user ID), scope, exp, etc.
except JWTError as e:
raise ValueError(f'Invalid token: {e}')スコープベースの認可
すべてのMCPツールをすべてのユーザーに公開する必要はありません。OAuthスコープまたはJWTトークンのロールクレームを使って、認証済みユーザーが呼び出せるツールを決定します。データベースやAPIを呼び出す前に、すべてのツール実行の開始時点で認可を確認します。
TOOL_REQUIRED_SCOPES = {
'list_products': ['read:products'],
'search_products': ['read:products'],
'create_order': ['write:orders'],
'delete_order': ['admin:orders']
}
def check_authorization(tool_name: str, token_payload: dict):
'''Raise ValueError if user lacks required scope for the tool.'''
required = TOOL_REQUIRED_SCOPES.get(tool_name, [])
if not required:
return # No scope required
user_scopes = set(token_payload.get('scope', '').split())
missing = [s for s in required if s not in user_scopes]
if missing:
raise ValueError(
f'Access denied. Tool "{tool_name}" requires scopes: {missing}. '
f'Your token has: {list(user_scopes)}'
)
# In call_tool handler:
# check_authorization(name, current_user_token)
# ... then execute the tool入力検証とインジェクション対策
すべてのツール入力は、最終的にはLLMが生成する文字列です。信頼できない入力として扱ってください。使用前に、すべての入力を想定される型とパターンに対して検証します。特に、SQLインジェクション(パラメーター化クエリを使用し、文字列補間したSQLは決して使わない)、コマンドインジェクション(ユーザー入力をシェルコマンドに決して渡さない)、パストラバーサル(ファイルパスを正規化して検証する)に注意します。
import re
from pathlib import Path
BASE_DATA_DIR = Path('/data/mcp-files')
def safe_file_path(user_input: str) -> Path:
'''Validate and normalize a file path to prevent traversal attacks.'''
# Remove any path traversal sequences
clean = re.sub(r'\.\./', '', user_input)
clean = re.sub(r'\.\.\\\\', '', clean)
path = (BASE_DATA_DIR / clean).resolve()
# Ensure the resolved path is still within the allowed base directory
if not str(path).startswith(str(BASE_DATA_DIR)):
raise ValueError(f'Path traversal detected: {user_input}')
return path
def safe_identifier(value: str) -> str:
'''Validate a database identifier (table/column name).'''
if not re.match(r'^[a-z_][a-z0-9_]{0,63}$', value, re.IGNORECASE):
raise ValueError(f'Invalid identifier: {value}')
return valueツール呼び出しのレート制限
ループ内で動作するLLMエージェントは、コストの高いツールを1分間に何百回も呼び出し、データベース接続、サードパーティAPIのクォータ、計算リソースの予算を使い果たす可能性があります。Redisでトークンバケットアルゴリズムを使い、ユーザーごとのレート制限を実装します。制限を超えたツール呼び出しは、エージェントが待機すべきことを理解できる説明的なエラーとともに拒否します。
import redis
import time
r = redis.Redis.from_url('redis://localhost:6379')
def check_rate_limit(user_id: str, tool_name: str, limit: int = 60, window: int = 60) -> None:
'''Allow at most `limit` calls per `window` seconds per user per tool.'''
key = f'rate:{user_id}:{tool_name}'
pipe = r.pipeline()
pipe.incr(key)
pipe.expire(key, window)
count, _ = pipe.execute()
if count > limit:
retry_after = r.ttl(key)
raise ValueError(
f'Rate limit exceeded for {tool_name}. '
f'Limit: {limit} calls/{window}s. '
f'Retry after {retry_after} seconds.'
)最小権限の原則
MCPサーバーのすべての層に最小権限の原則を適用します。データベースユーザーには、サーバーが必要とするテーブルに対するSELECTのみを許可します。サーバープロセスはroot以外のOSユーザーとして実行します。ツールには必要な権限だけを要求させます。APIキーには用途に必要な最小限のスコープだけを付与します。付与しない各権限が、攻撃の成功を防ぐことにつながります。
-- PostgreSQL: Create a dedicated read-only database user for your MCP server
CREATE ROLE mcp_reader LOGIN PASSWORD 'strong_random_password';
-- Grant SELECT on only the tables the server needs
GRANT SELECT ON products, categories, public_content TO mcp_reader;
-- Explicitly deny access to sensitive tables
REVOKE ALL ON users, api_keys, payment_methods FROM mcp_reader;
-- Never grant: INSERT, UPDATE, DELETE, TRUNCATE, or DDL permissionsTLSとトランスポートセキュリティ
リモートMCPサーバーでは、転送中のデータを保護するためにTLSを必ず使用します。サーバーがHTTPS接続のみを受け付けるように設定します。本番環境では、リバースプロキシ(nginx、Caddy)にTLS終端を任せ、証明書を自動更新して最新の状態に保ちます(Certbot経由のLet's Encrypt、またはCaddy組み込みのACMEサポートを使用します)。
# Example Caddyfile for TLS-terminating MCP server at a subdomain:
#
# mcp.yourcompany.com {
# reverse_proxy localhost:8080
# encode gzip
# tls internal # Use Let's Encrypt in production
# header {
# Strict-Transport-Security 'max-age=31536000; includeSubDomains'
# X-Content-Type-Options nosniff
# X-Frame-Options DENY
# }
# }MCPリソースを介したプロンプトインジェクション
見落としやすい攻撃経路があります。MCPサーバーが外部ソース(Webページ、ユーザーがアップロードしたファイル、信頼できないデータベース)からコンテンツを読み取り、ツールの結果として返す場合、攻撃者はそのコンテンツに敵対的な指示を埋め込めます。モデルが取得したデータに隠された指示に従う可能性があり、これは間接プロンプトインジェクションと呼ばれます。取得したコンテンツをサニタイズし、信頼できない生のテキストをツールの出力として直接返さないでください。
import re
def sanitize_for_mcp_output(text: str) -> str:
'''Remove patterns that look like instructions to the LLM.'''
# Remove common injection patterns
dangerous_patterns = [
r'ignore previous instructions',
r'ignore all prior instructions',
r'system:',
r'<\|.*?\|>', # Special tokens
r'\[INST\]',
r'<s>',
]
for pattern in dangerous_patterns:
text = re.sub(pattern, '[FILTERED]', text, flags=re.IGNORECASE)
return text[:10000] # Also cap length to prevent context stuffingセキュリティ監査とモニタリング
セキュリティに関係するすべてのイベントをログに記録します。認証の成功と失敗、レート制限違反、認可の拒否、バリデーションエラー、通常とは異なるアクセスパターンなどです。次の項目に対するアラートを設定します。複数回の認証失敗(ブルートフォース攻撃)、1人のユーザーによる破壊的なツールの短時間での連続呼び出し、極端に大きな入力を伴うツール呼び出しです。監査ログを定期的に確認し、異常検知を自動化します。
クイックチェック
MCPのセキュリティと認証に関する概念の理解度を確認します。
レッスンのまとめ
このレッスンでは、リモートMCPサーバーではすべてのリクエストでOAuth 2.0またはAPIキー認証が必要であること、スコープベースの認可によって、認証済みユーザーごとに呼び出せるツールを制御できること、そしてインジェクション攻撃やパストラバーサルを防ぐため、すべてのツール入力を検証する必要があることを学びました。これでMCPモジュールは終了です。次は、高精度なRAG検索のための高度なチャンク分割戦略について学びます。
よくある質問
「MCPのセキュリティと認証」レッスンは無料ですか?
はい。「MCPのセキュリティと認証」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、AI Engineering Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 AI Engineering Academyコースには全4レッスンが含まれています。
「MCPのセキュリティと認証」で何を学びますか?
OAuth 2.0トークンを使ってMCPサーバーに認証を追加し、インジェクション攻撃を防ぐ入力検証を実装して、ツール権限に最小権限の原則を適用します。 ブラウザで直接実行するハンズオンコードでAI Engineering Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
AI Engineering Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAI Engineering Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「MCPのセキュリティと認証」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAI Engineering Academyレッスンでコードを書いて実行できますか?
はい。すべてのAI Engineering Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- MCPとは何か、なぜ重要なのか
- 初めてのMCPサーバーを構築する
- MCPでデータベースリソースを公開する
- MCPのセキュリティと認証