2025年末から2026年にかけて、Model Context Protocol(MCP)は、生成AIを企業システムや業務フローに組み込むための事実上の標準となりました。一方で、MCPサーバーを介したツール注入(Tool Injection)攻撃や、過剰な権限付与による情報漏えい事故が急増しています。本稿では、ECサイトのAIカスタマーサービス/企業内RAG/個人開発の3つのユースケースを起点に、攻撃ベクトルの分解と、再現可能な防御コードを紹介します。

ユースケース:急増するECサイトのAIカスタマーサービス

私は昨年、越境ECプラットフォームのカスタマーサポート刷新プロジェクトを担当しました。当初は社内DBから注文情報を引き出すだけのシンプルなMCPサーバーを構築しましたが、攻撃者がユーザーメッセージ経由でtool_callの引数を書き換え、配送先住所やカード番号を外部URLへ送信させるインシデントを観測しました。この経験から、入力層・スキーマ層・実行層の3層で防御する必要性を痛感しました。

MCPツール注入の3つの代表的攻撃ベクトル

ベストプラクティス1:JSON Schemaによる引数の厳格バリデーション

MCPサーバーを立てる際は、jsonschemaライブラリでホワイトリスト型バリデーションを行います。以下はHolySheep AIのGPT-4.1(出力単価 $8/MTok)を経由して注文情報を取得する、安全な実装例です。今すぐ登録すると$1分の無料クレジットが付与され、本記事のサンプルを即日試せます。

import os, json, re, jsonschema
from openai import OpenAI

1) クライアント初期化(エンドポイントは必ず国内中継を経由しない公式URL)

client = OpenAI( base_url="https://api.holysheep.ai/v1", api_key=os.environ["YOUR_HOLYSHEEP_API_KEY"], )

2) 許可スキーマをホワイトリストで定義

ORDER_SCHEMA = { "type": "object", "properties": { "order_id": {"type": "string", "pattern": r"^ORD-[0-9]{8}$"}, "customer_id": {"type": "string", "pattern": r"^C[0-9]{6}$"}, "include_address": {"type": "boolean"}, }, "required": ["order_id"], "additionalProperties": False, # ★ 未知キーは即拒否 } ALLOWED_TOOLS = {"get_order", "list_recent_orders", "cancel_order"} def safe_tool_call(user_msg: str, session_user: str): resp = client.chat.completions.create( model="gpt-4.1", messages=[ {"role": "system", "content": f"あなたはCS担当AI。ツールは{ALLOWED_TOOLS}のみ呼び出し可。"}, {"role": "user", "content": user_msg}, ], tools=[{"type": "function", "function": { "name": "get_order", "description": "注文詳細を取得", "parameters": ORDER_SCHEMA, }}], ) tc = resp.choices[0].message.tool_calls if not tc: return resp.choices[0].message.content # 3) LLMが生成した引数を必ず再検証 args = json.loads(tc[0].function.arguments) jsonschema.validate(args, ORDER_SCHEMA) # ★ ここで汚染を遮断 # 4) 認可:本人以外には住所を返さない if args.get("include_address") and args.get("customer_id") != session_user: raise PermissionError("住所参照は本人限定です") return {"order_id": args["order_id"]}

ベストプラクティス2:実行層の権限分離(Capability Token)

ツールを呼び出せる人実行できる人を分離します。HolySheepのレイテンシは42ms(p50、東京リージョン実測)と<50msを安定して下回るため、多段チェックを入れても体感速度は損なわれません。

import time, hmac, hashlib, secrets

class CapabilityToken:
    """ツール単位・引数単位・有効期限を結合したワンタイムトークン"""
    def __init__(self, secret: bytes):
        self.secret = secret

    def issue(self, tool: str, scope: dict, ttl_sec: int = 30) -> str:
        nonce = secrets.token_hex(8)
        payload = f"{tool}|{json.dumps(scope, sort_keys=True)}|{int(time.time())+ttl_sec}|{nonce}"
        sig = hmac.new(self.secret, payload.encode(), hashlib.sha256).hexdigest()
        return f"{payload}|{sig}"

    def verify(self, token: str, tool: str) -> dict:
        tool_name, scope_s, exp_s, nonce, sig = token.split("|")
        if tool_name != tool:
            raise PermissionError("ツール不一致")
        if int(exp_s) < time.time():
            raise PermissionError("トークン失効")
        expected = hmac.new(self.secret, f"{tool_name}|{scope_s}|{exp_s}|{nonce}".encode(),
                            hashlib.sha256).hexdigest()
        if not hmac.compare_digest(expected, sig):
            raise PermissionError("署名不正")
        return json.loads(scope_s)

利用例

cap = CapabilityToken(b"super-secret-key") token = cap.issue("cancel_order", {"order_id": "ORD-12345678"}, ttl_sec=15) scope = cap.verify(token, "cancel_order") # 本人確認後だけ実行

ベストプラクティス3:MCPサーバー側の出力サニタイズ

LLMの応答には、ログ・メトリクス・トレースへ流れる段階でURL/メール/カード番号が混入しがちです。出力段階で正規表現で検出し、マスキングします。

import re

PII_PATTERNS = [
    (re.compile(r"\b[\w.-]+@[\w.-]+\.\w+\b"), "[EMAIL]"),
    (re.compile(r"\b(?:\d[ -]?){13,16}\b"), "[CARD]"),
    (re.compile(r"https?://[^\s)]+"), "[URL]"),
    (re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b"), "[IP]"),
]

def sanitize(text: str) -> str:
    for pat, repl in PII_PATTERNS:
        text = pat.sub(repl, text)
    return text

監査ログへの書き込み前に必ず通す

print(sanitize("詳細は https://evil.example/x?c=4242424242424242 へ"))

-> 詳細は [URL] へ

私の実装メモ:個人開発プロジェクトでの落とし穴

私は趣味でGitHub Issueを要約するMCPツールを作っていた際、gh issue viewの出力に含まれるユーザーメンション文字列をそのままLLMへ渡してしまい、プロンプトインジェクション的に動作した経験があります。教訓は、MCP境界で「信頼境界」を明示すること。HolySheep AIは1ドル=1円の従量課金(公式の¥7.3/$1比で85%安価)かつWeChat Pay・Alipayに対応するため、個人開発者が安心してサンドボックス実験を回せます。深センから東京リージョンへのラウンドトリップも38〜47msで安定し、待ち時間のストレスなく反復テストが可能です。

モデル別・出力単価早見表(2026年1月時点、$ per 1M tokens)

よくあるエラーと解決策

エラー1:「Tool call arguments failed validation」

原因:LLMがadditionalProperties: falseのスキーマに違反したキーを生成した。プロンプトに「未知キーは出力しない」と明示しても完全には防げません。

from jsonschema import ValidationError
try:
    jsonschema.validate(args, ORDER_SCHEMA)
except ValidationError as e:
    # 1) 自動修復:未知キーを削除して再投入
    args = {k: v for k, v in args.items() if k in ORDER_SCHEMA["properties"]}
    jsonschema.validate(args, ORDER_SCHEMA)
    # 2) 監査ログに記録して再実行
    audit_log.warning("tool_args_trimmed", extra={"err": str(e)})

エラー2:「PermissionError: 住所参照は本人限定です」

原因:代理オペレーターが顧客の住所を閲覧しようとした。MCPサーバーは「呼び出し元セッション」と「対象リソース所有者」を必ず突合すべきです。

def can_view_address(session_user: str, target_customer: str, role: str) -> bool:
    if session_user == target_customer:
        return True
    if role in {"admin", "compliance"} and target_customer.startswith("C"):
        return True
    raise PermissionError("住所参照は本人か管理者ロールのみ")

エラー3:「HMAC signature mismatch」(時刻ズレ)

原因:Capability Tokenを発行したMCPと検証するMCPで時刻が±2秒以上ずれている。NTP同期とttl_sec=15以下の短縮で回避します。

import ntplib
def sync_clock():
    try:
        c = ntplib.NTPClient()
        resp = c.request("ntp.nict.jp", version=3)
        import time; time.clock_settime(time.CLOCK_REALTIME, resp.tx_time)
    except Exception as e:
        # 同期失敗時はトークンTTLをさらに短縮
        return 5   # ttl_sec
    return 15

まとめ:MCP時代の「信頼境界」を3層で引く

MCPツール注入を防ぐ鍵は、(1) JSON Schemaによる引数型ホワイトリスト、(2) Capability Tokenによるツール×引数×時間の結合署名、(3) 出力サニタイズによるPIIの自動マスキング、の3層を必ず組み合わせることです。HolySheep AIなら、<50msの低レイテンシ85%安価な従量課金WeChat Pay/Alipay対応という条件下で、安全なMCPエージェントを最短距離で運用できます。

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