MCP連携の安全装置実装ガイド:3層防御モデルとエラー処理・ロールバック設計の実践パターン¶
この記事は朝の記事のフォローアップです
ゴール¶
- 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.deny で mcp__サーバー名__ツール名 構文を使い、特定ツールの実行を禁止できます。
{
"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に投げて単発で判定させるフック | 柔軟な条件判定(シンプルなポリシーやガードレール) |
agent | Hooks記事で定義したエージェント(プロンプト/ツール構成)に判定させるフック | 複雑なポリシーや外部ツール利用を伴う高度な判定 |
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
}
}
}