コンテンツにスキップ

MCP連携の安全装置実装ガイド:3層防御モデルとエラー処理・ロールバック設計の実践パターン

この記事は朝の記事のフォローアップです

朝の記事: AIコーディング時代のGitHub操作:ghコマンド vs MCP連携の完全比較ガイド

ゴール

  • Claude CodeのMCP連携における3層防御モデルを理解し実装
  • 大出力事故(トークン爆発)の防止戦略を設計
  • 実践的なエラー処理とロールバック戦略を設計可能に
  • エンタープライズ環境での安全なMCP運用体制を構築

3層防御モデル:アーキテクチャ概要

Claude CodeのMCP連携ガードレールは、3つの組み込みレイヤーアプリケーションレベルのガードレールで多層防御を構成します。

flowchart TD
    A[MCP Tool Request] --> B{Layer 1: managed-mcp}
    B -->|Server allowed| C{Layer 2: permissions}
    B -->|Server blocked| D[Blocked]
    C -->|Tool allowed| E{Layer 3: hooks}
    C -->|Tool denied| D
    E -->|Hook approved| F[Execute]
    E -->|Hook blocked| D
    F --> G{Output size check}
    G -->|Under limit| H[Return result]
    G -->|Over limit| I[Truncate + warn]

各レイヤーは独立して機能しつつ、前のレイヤーを通過したリクエストのみが次のレイヤーに到達する直列フィルター構造です。


Layer 1: managed-mcp(構成制御)

概要

managed-mcp.json をシステムディレクトリに配置することで、MCPサーバー構成をロックダウンします。このファイルが配置された環境では、ユーザーはMCPサーバーの追加・変更・削除ができなくなります。

配置パス

OSパス
macOS/Library/Application Support/ClaudeCode/managed-mcp.json
Linux/etc/claude-code/managed-mcp.json

設定例

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "{{ GITHUB_TOKEN }}"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/approved/path"]
    }
  }
}

ポイント

  • 管理者権限でのみ書き込み可能なパスに配置
  • ユーザーは managed-mcp.json で定義されたサーバーのみ使用可能
  • 環境変数のテンプレート構文で機密情報を外部注入

Layer 2: permissions(ポリシー制御)

概要

.claude/settings.json または管理者設定でMCPサーバーおよびツールレベルのアクセス制御を行います。

allowedMcpServers(許可リスト)

サーバー名、コマンド、URLパターンによるホワイトリストマッチングです。ワイルドカードも使用可能です。

{
  "permissions": {
    "allowedMcpServers": [
      "github",
      "filesystem",
      "custom-*"
    ]
  }
}

deniedMcpServers(拒否リスト)

拒否リストは許可リストに対して絶対優先です。許可リストに含まれていても、拒否リストに一致すれば必ずブロックされます。

{
  "permissions": {
    "deniedMcpServers": [
      "untrusted-server",
      "experimental-*"
    ]
  }
}

ツールレベルの権限制御

permissions.denymcp__サーバー名__ツール名 構文を使い、特定ツールの実行を禁止できます。

{
  "permissions": {
    "deny": [
      "mcp__github__delete_repository",
      "mcp__filesystem__write_file",
      "mcp__github__create_pull_request"
    ]
  }
}

サブエージェントのMCP権限

サブエージェントがMCPツールを呼び出す場合の権限モードも設定できます。メインエージェントより制限的な権限を割り当てることで、多段階のAI処理でも安全性を確保します。


Layer 3: hooks(実行時制御)

概要

PreToolUse / PostToolUse フックでMCPツール呼び出しの前後にバリデーションを実行します。

PreToolUseフック(実行前ゲート)

MCPツール呼び出し前に条件を検証し、不適切な操作をブロックします。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__github__create_pull_request",
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Verify this PR creation is targeting the correct branch and has a meaningful description. Return {ok: false} if the target branch is main/master without explicit approval."
          }
        ]
      }
    ]
  }
}

PostToolUseフック(実行後検証)

MCPツール実行結果を検証し、問題があれば警告やブロックを行います。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "mcp__github__*",
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Review the MCP tool result. Check for error responses, unexpected data, or signs of rate limiting. Return {ok: false} with reason if issues are detected."
          }
        ]
      }
    ]
  }
}

フックタイプ

タイプ説明用途
commandシェルコマンドを実行して判定するフックスクリプトベースの検証
promptフックに定義したプロンプトをLLMに投げて単発で判定させるフック柔軟な条件判定(シンプルなポリシーやガードレール)
agentHooks記事で定義したエージェント(プロンプト/ツール構成)に判定させるフック複雑なポリシーや外部ツール利用を伴う高度な判定

prompt / agent タイプはいずれも LLM ベースのフックで、ツール呼び出しの内容を自然言語で評価し、{ok: true} または {ok: false, reason: "..."} を返すことで承認・拒否を判定します。prompt はフック設定内のプロンプト文字列をそのまま LLM に渡すのに対し、agent は Hooks 記事で定義されたエージェント名を指定し、そのエージェントのプロンプトやツール構成を使ってよりリッチな判定を行います。


大出力事故(トークン爆発)の防止

問題

MCPツールが大量のデータを返すと、コンテキストウィンドウを圧迫し、処理速度の低下やエラーを引き起こします。

MAXMCPOUTPUT_TOKENS

Claude Codeは MAX_MCP_OUTPUT_TOKENS(デフォルト: 25,000トークン)でMCPツール出力サイズを制限しています。

閾値動作
10,000 トークン警告を表示
25,000 トークン(デフォルト上限)出力を切り捨て

環境変数で上限を調整可能です:

export MAX_MCP_OUTPUT_TOKENS=15000

ページネーション設計パターン

MCPツール側で page / limit パラメータを受け付け、大量データを分割取得する設計が推奨されます。

// MCPツールのページネーション対応例
interface PaginatedRequest {
  query: string;
  page?: number;   // デフォルト: 1
  limit?: number;  // デフォルト: 20, 最大: 100
}

interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    page: number;
    limit: number;
    total: number;
    hasMore: boolean;
  };
}

トランケーション戦略

出力が上限を超える場合は、サマリー+詳細取得オプションを返す設計とします。

function truncateResponse(data: any[], limit: number): TruncatedResult {
  if (JSON.stringify(data).length > limit) {
    return {
      summary: `${data.length} items found. Showing first 10.`,
      data: data.slice(0, 10),
      truncated: true,
      hint: "Use page/limit parameters to retrieve remaining items."
    };
  }
  return { data, truncated: false };
}

Layer 4: アプリケーションレベルのガードレール

Claude Codeの組み込み3層防御に加えて、アプリケーション側で追加のガードレールを実装することで、より堅牢なシステムを構築できます。

ステップ1: 基本的なガードレールクラスの設計

class MCPGuardrail {
  private readonly safeOperations = ['read', 'list', 'search'];
  private operationLog: Operation[] = [];

  async executeWithGuards(operation: Operation): Promise<Result> {
    // 1. 危険操作の検出
    if (!this.safeOperations.includes(operation.type)) {
      const dryResult = await this.dryRun(operation);
      if (!dryResult.safe) {
        throw new GuardError(dryResult.risks);
      }
    }

    // 2. 実行前スナップショット
    const snapshot = await this.createSnapshot();

    try {
      const result = await this.execute(operation);
      this.operationLog.push({...operation, result, snapshot});
      return result;
    } catch (error) {
      await this.rollback(snapshot);
      throw error;
    }
  }
}

ステップ2: Dry-run機能の実装パターン

interface DryRunConfig {
  maxChanges: number;        // 最大変更数
  allowedScopes: string[];   // 許可スコープ
  requireConfirm: boolean;   // 確認必須フラグ
}

async function performDryRun(
  action: GitHubAction,
  config: DryRunConfig
): Promise<DryRunResult> {
  // APIコールをシミュレート
  const simulated = await simulateAction(action);

  // リスク評価
  const risks = [];
  if (simulated.affectedFiles > config.maxChanges) {
    risks.push(`Large impact: ${simulated.affectedFiles} files`);
  }
  if (!config.allowedScopes.includes(action.scope)) {
    risks.push(`Out of scope: ${action.scope}`);
  }

  return {
    safe: risks.length === 0,
    changes: simulated.changes,
    risks,
    requiresConfirm: config.requireConfirm || risks.length > 0
  };
}

ステップ3: インタラクティブ確認プロンプトの設計

class ConfirmationHandler {
  private readonly templates = {
    delete: 'This will permanently delete {resource}. Continue?',
    merge: 'Merging {branch} into {target}. {conflicts} conflicts found.',
    deploy: 'Deploying to {environment}. Last deploy: {lastDeploy}'
  };

  async confirm(operation: Operation): Promise<boolean> {
    const message = this.buildMessage(operation);
    const details = await this.gatherDetails(operation);

    // ユーザー向け詳細表示
    console.log(`\n⚠️  ${message}`);
    console.log('Affected resources:');
    details.forEach(d => console.log(`  - ${d}`));

    // タイムアウト付き確認
    return await this.promptWithTimeout(
      'Proceed? (y/N): ',
      30000  // 30秒タイムアウト
    );
  }
}

ベンチマーク / 比較

ガードレール機能実装コスト防御効果パフォーマンス影響
Layer 1: managed-mcp低(ファイル配置のみ)最高なし
Layer 2: permissions低(JSON設定)最小 (< 1ms)
Layer 3: hooks中(ロジック実装)中(LLM/スクリプト依存)
Layer 4: Dry-run中 (API依存)
Layer 4: 確認プロンプトユーザー依存
Layer 4: スナップショット最高高 (ストレージI/O)
Layer 4: 自動ロールバック最高

失敗パターンと回避策

症状原因回避策
未承認サーバーの接続managed-mcp未配置システムディレクトリに即座に配置
禁止ツールの実行permissions設定漏れdeny リストにクリティカル操作を明示追加
フック回避matcher パターンの不備ワイルドカード mcp__* で全サーバーをカバー
大出力によるコンテキスト圧迫ページネーション未対応MAX_MCP_OUTPUT_TOKENS を設定しツール側もページネーション対応
タイムアウトで処理中断確認待ちの放置デフォルト動作を「拒否」に設定
ロールバック失敗スナップショット不整合多段階バックアップ実装
権限エラーの頻発スコープ不足起動時の権限事前チェック
dry-run結果の誤判定API仕様変更定期的な検証テスト実施

実践的なエラー処理戦略

段階的エラー処理の実装

class MCPErrorHandler {
  async handleError(error, context) {
    // レベル1: 自動リトライ可能
    if (error.code === 'RATE_LIMIT') {
      await this.delay(error.retryAfter);
      return { action: 'retry', delay: error.retryAfter };
    }

    // レベル2: ユーザー介入で回復可能
    if (error.code === 'AUTH_REQUIRED') {
      const token = await this.promptForAuth();
      return { action: 'retry', newContext: {...context, token} };
    }

    // レベル3: ロールバック必須
    if (error.code === 'PARTIAL_SUCCESS') {
      await this.rollbackPartial(context.completed);
      return { action: 'abort', rolled_back: true };
    }

    // レベル4: 致命的エラー
    await this.emergencyShutdown(context);
    return { action: 'fatal', logged: true };
  }
}

ロールバック戦略の比較

戦略実装例適用場面
Gitベースgit reset --hardコード変更
APIベース逆操作のAPI呼び出しIssue/PR操作
スナップショット状態の完全復元複雑な連鎖操作
補償トランザクション個別の取り消し処理部分的失敗

エンタープライズガバナンス

3層防御モデルの各レイヤーを管理者が一元制御するためのエンタープライズ設定オプションです。これらを組み合わせることで、組織全体のMCPセキュリティポリシーを強制できます。

設定項目説明
allowManagedHooksOnly管理者が配置したフックのみ有効にし、ユーザー定義フックを無効化
allowManagedPermissionRulesOnly管理者が定義した権限ルールのみ有効にし、ユーザー定義ルールを無効化
disableBypassPermissionsMode権限バイパスモードを無効化し、全操作で権限チェックを強制

エンタープライズ構成例

{
  "managedConfig": {
    "allowManagedHooksOnly": true,
    "allowManagedPermissionRulesOnly": true,
    "disableBypassPermissionsMode": true
  },
  "permissions": {
    "allowedMcpServers": ["github", "filesystem"],
    "deniedMcpServers": ["*-experimental"],
    "deny": [
      "mcp__github__delete_repository",
      "mcp__filesystem__write_file"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__*",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/local/bin/mcp-audit-log"
          }
        ]
      }
    ]
  }
}

プロダクション向け設定例

{
  "mcp_guardrails": {
    "enabled": true,
    "output_limits": {
      "max_mcp_output_tokens": 25000,
      "warning_threshold": 10000,
      "truncation_strategy": "summary_with_pagination"
    },
    "dry_run": {
      "default": true,
      "skip_for": ["read", "search"],
      "max_simulated_changes": 50
    },
    "confirmation": {
      "require_for": ["delete", "merge", "deploy"],
      "timeout_ms": 30000,
      "default_action": "deny"
    },
    "rollback": {
      "strategy": "snapshot",
      "max_depth": 3,
      "retention_hours": 24
    },
    "logging": {
      "level": "info",
      "rotate_size_mb": 100,
      "compress": true
    }
  }
}

次のステップ