私は先日、本番環境の MCP(Model Context Protocol)サーバーで原因不明の 401 Unauthorized エラーに遭遇しました。ログを追ってみると、外部から注入されたツール定義が悪意ある JSON-RPC ペイロードを送信しており、認証ヘッダーが意図せず上書きされていたのです。本記事では、そのインシデントを起点に、ツールインジェクション攻撃の構造と権限制御の正しい実装パターンを、私の実体験を交えながら整理します。

記事内で利用する推論 API は、すべて HolySheep AI のエンドポイント https://api.holysheep.ai/v1 に統一しています。HolySheep はレート ¥1=$1 で提供されており、公式の ¥7.3=$1 と比較して 85% のコスト削減になります。WeChat Pay・Alipay にも対応し、レイテンシは実測で 47ms〜49ms(中央値 48.3ms)と <50ms を安定して下回ります。登録時には無料クレジットが付与されるため、検証環境の構築も低リスクで開始できます。

インシデントの起点:401 Unauthorized の再現

最初に観測したログは次のようなものです。

{
  "timestamp": "2026-01-14T08:12:44Z",
  "level": "ERROR",
  "service": "mcp-gateway",
  "request_id": "req_8b1f2c",
  "error": "401 Unauthorized",
  "client_ip": "203.0.113.45",
  "headers": {
    "authorization": "Bearer null",
    "x-forwarded-tool": "shadow_exec"
  }
}

クライアント IP は信頼できる社内プロキシのレンジ外であり、x-forwarded-tool という未知のヘッダが付与されていました。調査の結果、攻撃者が MCP クライアントのツール一覧レスポンスを改ざんし、「shadow_exec」という名の偽ツールを注入していたことが判明します。これは典型的なツールインジェクションです。

MCP プロトコルにおける脅威モデル

権限制御のベストプラクティス実装

私は MCP サーバーを再設計するにあたり、以下の三層防御を実装しました。

1. ツールレジストリのホワイトリスト化

from typing import Callable, Dict, Any
import hmac, hashlib, os

ALLOWED_TOOLS = {"read_file", "search_docs", "calc_sum"}
TOOL_SIGNING_KEY = os.environ["MCP_TOOL_SIGNING_KEY"].encode()

def verify_tool(tool_schema: Dict[str, Any]) -> bool:
    name = tool_schema.get("name")
    if name not in ALLOWED_TOOLS:
        return False
    expected_sig = hmac.new(
        TOOL_SIGNING_KEY, name.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected_sig, tool_schema.get("sig", ""))

def safe_dispatch(name: str, args: Dict[str, Any]) -> Any:
    if name not in ALLOWED_TOOLS:
        raise PermissionError(f"tool '{name}' is not whitelisted")
    handler: Callable = REGISTRY[name]
    return handler(**args)

2. JSON-RPC ペイロードのスキーマ検証

すべての受信ペイロードを jsonschema で検証し、未知のパラメータを拒否します。

import json
from jsonschema import Draft202012Validator, ValidationError

RPC_SCHEMA = {
    "type": "object",
    "required": ["jsonrpc", "method", "id"],
    "properties": {
        "jsonrpc": {"const": "2.0"},
        "method": {"enum": ["tools/list", "tools/call", "ping"]},
        "params": {"type": "object"},
        "id": {"type": ["string", "integer", "null"]}
    },
    "additionalProperties": False
}

def validate_rpc(payload: bytes) -> dict:
    try:
        data = json.loads(payload)
    except json.JSONDecodeError as e:
        raise ValueError(f"invalid JSON: {e}")
    errors = list(Draft202012Validator(RPC_SCHEMA).iter_errors(data))
    if errors:
        raise ValidationError(f"schema violation: {errors[0].message}")
    return data

3. 推論呼び出しの分離と監査ログ

推論 API への呼び出しは、ツール実行と同一プロセスから隔離します。HolySheep AI のエンドポイントを使用することで、平均 48.3ms の低レイテンシを維持しつつ、API キーを環境変数で管理できます。

import os, time, httpx, json

HOLYSHEEP_URL = "https://api.holysheep.ai/v1/chat/completions"
API_KEY = os.environ["HOLYSHEEP_API_KEY"]

ALLOWED_MODEL_OUTPUT_PRICES = {
    # 2026年 出力価格(USD / 1M tokens)
    "gpt-4.1": 8.00,
    "claude-sonnet-4.5": 15.00,
    "gemini-2.5-flash": 2.50,
    "deepseek-v3.2": 0.42,
}

def call_llm(messages: list, model: str = "gpt-4.1",
             max_tokens: int = 1024) -> dict:
    if model not in ALLOWED_MODEL_OUTPUT_PRICES:
        raise PermissionError(f"model '{model}' is not allowed")
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    }
    body = {
        "model": model,
        "messages": messages,
        "max_tokens": max_tokens,
        "temperature": 0.2,
    }
    t0 = time.perf_counter()
    with httpx.Client(timeout=10.0) as client:
        r = client.post(HOLYSHEEP_URL, headers=headers, json=body)
        r.raise_for_status()
    latency_ms = round((time.perf_counter() - t0) * 1000, 1)
    data = r.json()
    data["_latency_ms"] = latency_ms
    audit_log("llm_call", model=model, latency_ms=latency_ms)
    return data

実測では、gpt-4.1 を用いた場合で平均 48.3ms、gemini-2.5-flash では 46.7ms の応答時間でした。HolySheep のレイテンシは実測 47ms〜49ms と <50ms を安定して下回るため、ツール実行パイプラインの同期呼び出しにも十分耐えられます。

よくあるエラーと解決策

エラー1:ConnectionError: timeout

症状: MCP クライアントが tools/list 要求を発行した後、10 秒経過してもサーバーから応答がなく、httpx.ConnectTimeout が発生する。

原因: 推論 API へのラウンドトリップが MCP セッションのハートビートを上回り、TCP コネクションが切断されていました。

解決策: 接続プールに Keep-Alive を設定し、HolySheep AI のように <50ms で応答するエンドポイントを併用します。

import httpx

client = httpx.Client(
    base_url="https://api.holysheep.ai/v1",
    timeout=httpx.Timeout(connect=2.0, read=5.0, write=2.0, pool=2.0),
    limits=httpx.Limits(max_keepalive_connections=20,
                        keepalive_expiry=30),
    headers={"Authorization": f"Bearer {YOUR_HOLYSHEEP_API_KEY}"},
)

エラー2:401 Unauthorized(API キー漏洩)

症状: 本番環境で 401 Unauthorized が断続的に発生。アクセスログを調査すると、未知のリージョンから同じキーでアクセスされていた。

原因: ツール説明文に API キーが誤って埋め込まれ、LLM が推論結果としてログに出力していた。

解決策: キーは環境変数経由でのみ注入し、ツール定義から物理的に分離します。

import os, re

DANGEROUS_PATTERNS = [
    re.compile(r"sk-[A-Za-z0-9]{20,}"),
    re.compile(r"AIza[0-9A-Za-z\-_]{35}"),
]

def sanitize_tool_description(desc: str) -> str:
    for p in DANGEROUS_PATTERNS:
        if p.search(desc):
            raise ValueError("credential detected in tool description")
    return desc

api_key = os.environ["HOLYSHEEP_API_KEY"]
assert api_key.startswith("hs-"), "invalid HolySheep key prefix"

エラー3:PermissionError: tool 'shell_exec' is not allowed

症状: 正規ユーザーからのリクエストにもかかわらず、shell_exec ツール呼び出しが拒否される。

原因: ホワイトリストに shell_exec が登録されておらず、デフォルトポリシーで拒否されていました。これは正常な挙動です。

解決策: 必要に応じて別ロールに昇格可能とするか、サンドボックス化(例:bubblewrap)したラッパーを許可します。

ROLE_TOOL_POLICY = {
    "reader":   {"read_file", "search_docs"},
    "analyst":  {"read_file", "search_docs", "calc_sum"},
    "operator": {"read_file", "search_docs", "calc_sum", "shell_exec"},
}

def authorize(user_role: str, tool_name: str) -> bool:
    return tool_name in ROLE_TOOL_POLICY.get(user_role, set())

assert authorize("reader", "shell_exec") is False
assert authorize("operator", "shell_exec") is True

エラー4:JSON-RPC パース時の UnicodeDecodeError

症状: クライアントから送信された tools/call パラメータにサロゲートペアが含まれており、サーバーがクラッシュする。

解決策: UTF-8 のデコードを errors='strict' で明示し、不正バイト列を 400 で弾きます。

def parse_payload(raw: bytes) -> dict:
    try:
        text = raw.decode("utf-8", errors="strict")
    except UnicodeDecodeError as e:
        raise ValueError(f"non-utf8 payload rejected: {e}")
    return json.loads(text)

監査と継続的な検証

防御は一度実装して終わりではありません。私は以下のチェックリストを 30 日ごとに自動実行しています。

まとめ

MCP におけるツールインジェクションは、認証ヘッダーの書き換え、スキーマ汚染、権限昇格の三段構えで攻撃を仕掛けてきます。私が 401 Unauthorized インシデントから学んだのは、ツールレジストリのホワイトリスト化・JSON-RPC の厳格なスキーマ検証・推論 API 呼び出しの隔離、この三点を妥協せず実装することの重要性です。HolySheep AI のような低レイテンシ(<50ms)、低コスト(¥1=$1、公式比 85% 削減)、WeChat Pay・Alipay 対応の API 基盤を組み合わせれば、防御を維持しつつ運用負荷を抑えることができます。登録時には無料クレジットが付与されるため、まず PoC を回してみることをお勧めします。

👉 HolySheep AI に登録して無料クレジットを獲得