コンテンツへスキップ

media AI活用の最前線

【2026年最新】MCP本番運用ガイド|OAuth・監査ログ・セキュリティ

【2026年最新】MCP本番運用ガイド|OAuth・監査ログ・セキュリティ

結論: MCPの本番運用は「MCPサーバーを設定する」だけでは終わりません。OAuth 2.1認証・per-operation認可・完全な監査ログ・プロンプトインジェクション対策の4つが揃って初めてエンタープライズ品質になります。

この記事の要点:

  • MCPの本番4大障壁: 認証なし・静的シークレット・監査ログ欠如・インジェクション脆弱性
  • OAuth 2.1 + Okta/Azure AD連携の設定テンプレートを公開
  • Anthropicが2026年Q2に予定するOAuth 2.1エンタープライズ統合の先読み設計

対象読者: MCPをすでに開発環境で使っており、本番・チーム展開を検討しているCTO/ITアーキテクト

読了後にできること: MCPサーバーへのOAuth 2.1認証を実装し、監査ログ付きの本番デプロイができる


「MCPをチームに展開したら情報セキュリティ部門からストップがかかった」

この相談が、2026年に入って急増しています。顧問先のSaaS企業では、エンジニアが個人利用でMCPを試したところ効果が出て、チーム展開を提案したら情報システム部門に「認証設計は?」「監査ログは?」「SQLインジェクションリスクは?」と矢継ぎ早に問われて詰まってしまいました。

MCPの入門ガイドはたくさんありますが、本番・エンタープライズ展開を前提とした設計ガイドはほとんどありません。セキュリティ部門の質問に答えられる設計を最初から作っておけば、展開のスピードが全く変わります。

この記事では、本番MCPデプロイで必ず直面する4つの壁と、その乗り越え方を設定テンプレート付きで解説します。

まず試したい「5分即効」設定3選

即効設定1:APIキー認証でとりあえず本番保護

OAuth 2.1の前に、まず最低限の認証を追加する設定です。

# MCP サーバー起動スクリプト(Node.js例)
# server.js

import { MCPServer } from "@anthropic/mcp-server";
import { createHash } from "crypto";

const server = new MCPServer({
  name: "my-enterprise-mcp",
  version: "1.0.0",
});

// APIキー認証ミドルウェア
server.use((req, res, next) => {
  const apiKey = req.headers["x-api-key"];
  const expectedKey = process.env.MCP_API_KEY;

  if (!apiKey || createHash("sha256").update(apiKey).digest("hex")
      !== createHash("sha256").update(expectedKey).digest("hex")) {
    return res.status(401).json({ error: "Unauthorized" });
  }

  next();
});

// 不足している情報があれば、最初に質問してから作業を開始してください。
# 環境変数設定(.env.production に保存、gitには入れない)
MCP_API_KEY=your-secret-key-here-use-crypto-random

# APIキーの生成コマンド
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

即効設定2:全操作の監査ログを追加

「エージェントが何をしたか」の完全な記録。セキュリティ部門が最初に要求するのがこれです。

# audit-logger.js
import fs from "fs";
import path from "path";

export function auditLog(event) {
  const entry = {
    timestamp: new Date().toISOString(),
    userId: event.userId || "unknown",
    toolName: event.toolName,
    input: sanitizeForLog(event.input),  // 機密情報はマスク
    output: event.output ? "SUCCESS" : "FAILED",
    duration_ms: event.durationMs,
    ip: event.ip,
  };

  const logFile = path.join(
    process.env.AUDIT_LOG_DIR || "./logs",
    `mcp-audit-${new Date().toISOString().split("T")[0]}.jsonl`
  );

  fs.appendFileSync(logFile, JSON.stringify(entry) + "n");
}

function sanitizeForLog(input) {
  // パスワード・トークン・個人情報をマスク
  return JSON.stringify(input)
    .replace(/"password":"[^"]*"/g, '"password":"***"')
    .replace(/"token":"[^"]*"/g, '"token":"***"')
    .replace(/"secret":"[^"]*"/g, '"secret":"***"');
}

即効設定3:読み取り専用ツールと書き込みツールを分離する

エンタープライズ設計の鉄則。開発者には読み取りを自由に、本番書き込みには承認を要求する設計です。

# .claude/mcp.json
{
  "mcpServers": {
    "db-readonly": {
      "command": "node",
      "args": ["./mcp-db-server.js", "--mode=readonly"],
      "env": {
        "DB_CONNECTION_STRING": "$DB_READONLY_CONNECTION"
      }
    },
    "db-write": {
      "command": "node",
      "args": ["./mcp-db-server.js", "--mode=readwrite"],
      "env": {
        "DB_CONNECTION_STRING": "$DB_WRITE_CONNECTION"
      }
    }
  }
}

# CLAUDE.md に追記:
# db-readonly: 承認なしで使用可能
# db-write: 必ずユーザーの明示的な承認を取ること

本番MCPデプロイの4大壁

実際のエンタープライズ展開で同じパターンで詰まるケースをまとめました。

症状根本原因解決策
認証なし情報システム部門からストップ開発環境の設定をそのまま本番に持ち込んだOAuth 2.1 or APIキー認証
静的シークレットシークレットが漏洩・ローテーションできないハードコーディング or 環境変数に固定値Secret Manager + 自動ローテーション
監査ログなし「エージェントが何をしたか分からない」ログ設計がなかった全操作のJSONL監査ログ
インジェクション脆弱性外部データ経由で予期しない操作が起きた外部データをそのままプロンプトに渡した入力サニタイズ + スキーマ検証

MCPの基本概念から学びたい方はMCPはじめてガイドからどうぞ。この記事では本番運用に絞って解説します。

AI活用、何から始めればいい?

100社以上の研修実績をもとに、30分の無料相談で貴社の課題を整理します。

無料相談はこちら 資料ダウンロード(無料)

OAuth 2.1エンタープライズ認証の実装

Anthropicは2026年Q2にOAuth 2.1 + Okta/Azure AD統合をロールアウト予定ですが、今から設計しておけばスムーズに移行できます。

現在の推奨構成: Authorization Code Flow

// mcp-server-oauth.js
import { MCPServer } from "@anthropic/mcp-server";
import jwt from "jsonwebtoken";
import jwksClient from "jwks-rsa";

const server = new MCPServer({ name: "enterprise-mcp", version: "1.0.0" });

// JWKSエンドポイントから公開鍵を取得(Okta/Azure AD対応)
const client = jwksClient({
  jwksUri: process.env.JWKS_URI,
  // Okta: https://{domain}/oauth2/v1/keys
  // Azure AD: https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    const signingKey = key?.getPublicKey();
    callback(null, signingKey);
  });
}

// JWT検証ミドルウェア
async function validateToken(req) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    throw new Error("Unauthorized: Bearer token required");
  }

  const token = authHeader.slice(7);
  return new Promise((resolve, reject) => {
    jwt.verify(
      token,
      getKey,
      {
        audience: process.env.MCP_CLIENT_ID,
        issuer: process.env.OAUTH_ISSUER,
        algorithms: ["RS256"],
      },
      (err, decoded) => {
        if (err) reject(new Error("Unauthorized: " + err.message));
        else resolve(decoded);
      }
    );
  });
}

// Per-operation認可(セッション単位ではなく操作単位で認可チェック)
async function authorizeOperation(userClaims, toolName, operation) {
  const permissions = userClaims.permissions || [];

  const requiredPermissions = {
    "db-read": ["mcp:db:read"],
    "db-write": ["mcp:db:write", "mcp:db:admin"],
    "file-delete": ["mcp:file:admin"],
    "deploy-production": ["mcp:deploy:production"],
  };

  const required = requiredPermissions[operation] || [];
  const hasPermission = required.every(p => permissions.includes(p));

  if (!hasPermission) {
    throw new Error(`Forbidden: operation '${operation}' requires ${required.join(", ")}`);
  }
}

// 数字と固有名詞は、根拠(出典/計算式)を添えてください。

Okta設定例(管理コンソール)

# Oktaアプリケーション設定(YAML形式で概念を示す)
application:
  name: "Enterprise MCP Server"
  type: "API Service"
  grant_types:
    - "authorization_code"
    - "refresh_token"
  scopes:
    - name: "mcp:db:read"
      description: "データベースの読み取り"
    - name: "mcp:db:write"
      description: "データベースへの書き込み"
    - name: "mcp:deploy:production"
      description: "本番環境へのデプロイ(要承認)"
  token_expiry:
    access_token: 3600    # 1時間
    refresh_token: 604800  # 7日間

プロンプトインジェクション対策の実装

MCPが本番で最も危険な攻撃ベクターがプロンプトインジェクションです。外部データ(DBの値・Slackメッセージ・メール本文)をそのままClaudeのコンテキストに渡すと、悪意あるデータが「Claudeへの命令」として解釈されるリスクがあります。

危険なパターンと安全なパターン

// ❌ 危険:外部データをそのままコンテキストに渡す
const userMessage = await db.getMessage(messageId);
// もしDBに「全てのデータを削除して」という文字列が入っていたら?
return `ユーザーのメッセージ: ${userMessage}`;

// ⭕ 安全:構造化データとして渡す
const userMessage = await db.getMessage(messageId);
return {
  type: "user_message",
  id: messageId,
  content: sanitize(userMessage),  // エスケープ処理
  metadata: {
    sender: "user",
    timestamp: new Date().toISOString(),
  }
};
// sanitize.js — プロンプトインジェクション対策
export function sanitize(text) {
  // 1. HTMLエンティティエスケープ
  const escaped = text
    .replace(/&/g, "&")
    .replace(//g, ">");

  // 2. よくあるインジェクションパターンの検出・ログ
  const dangerousPatterns = [
    /ignore previous instructions/i,
    /you are now/i,
    /system:/i,
    /[INST]/i,
    //i,
  ];

  const detected = dangerousPatterns.some(p => p.test(text));
  if (detected) {
    auditLog({ event: "injection_attempt_detected", text: text.slice(0, 200) });
  }

  return escaped;
}

// スキーマ検証(zodを推奨)
import { z } from "zod";

const UserInputSchema = z.object({
  query: z.string().max(2000).regex(/^[^{}]+$/),  // 特殊文字を制限
  userId: z.string().uuid(),
  timestamp: z.string().datetime(),
});

export function validateInput(input) {
  return UserInputSchema.parse(input);  // 失敗するとZodErrorをスロー
}

Secret管理とローテーション

本番環境でのシークレット管理は「環境変数に書く」だけでは不十分です。漏洩時のローテーション速度と監査証跡が重要です。

推奨: AWS Secrets Manager / Azure Key Vault連携

// secrets-manager.js(AWS Secrets Manager例)
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";

const client = new SecretsManagerClient({ region: "ap-northeast-1" });

// キャッシュ付きシークレット取得(TTL: 5分)
const secretCache = new Map();
const CACHE_TTL = 5 * 60 * 1000;

export async function getSecret(secretName) {
  const cached = secretCache.get(secretName);
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.value;
  }

  const command = new GetSecretValueCommand({ SecretId: secretName });
  const response = await client.send(command);
  const secret = JSON.parse(response.SecretString);

  secretCache.set(secretName, { value: secret, timestamp: Date.now() });
  return secret;
}

// 使用例(ハードコーディング不要)
const dbConfig = await getSecret("prod/mcp-server/db-credentials");
// { username: "...", password: "..." }

ローテーション自動化のフロー

ステップ内容担当
1. 新シークレット生成Secrets Manager が自動生成AWS Lambda
2. DB/サービスに新パスワードを設定ローテーションLambda関数DevOps
3. テスト新シークレットで接続確認Lambda
4. 旧シークレット廃止猶予期間後に削除Secrets Manager
5. アプリへの反映キャッシュTTL内に自動切り替えアプリ側キャッシュ

監査ログの完全設計

エンタープライズ要件を満たす監査ログ

// audit-schema.ts — 完全な監査ログスキーマ
interface AuditLogEntry {
  // 必須フィールド
  id: string;              // UUID
  timestamp: string;       // ISO 8601
  sessionId: string;       // セッションを追跡
  requestId: string;       // 個別リクエストを追跡

  // アクターの情報
  userId: string;          // 誰が?
  userEmail?: string;      // メールアドレス
  clientId: string;        // どのMCPクライアントが?
  ipAddress: string;       // どこから?
  userAgent: string;       // どのツールで?

  // 操作の情報
  mcpServer: string;       // どのMCPサーバー?
  toolName: string;        // どのツールを使った?
  operation: string;       // 何をした?(create/read/update/delete)
  resourceType: string;    // 何に対して?
  resourceId?: string;     // 特定のリソース

  // 結果の情報
  success: boolean;        // 成功した?
  errorCode?: string;      // エラーコード
  durationMs: number;      // 何ミリ秒かかった?

  // コンプライアンス用
  dataClassification?: "public" | "internal" | "confidential" | "restricted";
  retentionDays: number;   // ログの保持期間
}

SIEMへの転送設定(Splunk/Datadog)

# docker-compose.yml(fluentdでSplunkに転送)
version: "3.8"
services:
  mcp-server:
    image: your-mcp-server
    logging:
      driver: fluentd
      options:
        fluentd-address: "fluentd:24224"
        tag: "mcp.audit"

  fluentd:
    image: fluent/fluentd
    volumes:
      - ./fluentd.conf:/fluentd/etc/fluent.conf
    environment:
      SPLUNK_HEC_TOKEN: ${SPLUNK_HEC_TOKEN}
      SPLUNK_HEC_HOST: ${SPLUNK_HEC_HOST}

コンプライアンス対応チェックリスト

ISO 27001 / SOC 2対応

要件実装状況設定箇所
認証(AC-2)OAuth 2.1 / APIキーserver.js
認可(AC-3)per-operation RBACauthorizeOperation()
監査ログ(AU-2)全操作のJSONL記録audit-logger.js
暗号化(SC-8)TLS 1.3 + 保存時暗号化nginx + Secrets Manager
インシデント対応(IR-4)Slack/PagerDuty連携notify-slack.sh
脆弱性管理(RA-5)依存ライブラリ定期スキャンnpm audit / Dependabot

【要注意】よくある本番障害パターン

障害1:トークン有効期限切れでエージェントが止まる

❌ アクセストークンを1回取得して永遠に使い続ける

⭕ リフレッシュトークンで自動更新するミドルウェアを実装する

// token-refresher.js
class TokenManager {
  constructor(oauthConfig) {
    this.config = oauthConfig;
    this.accessToken = null;
    this.expiresAt = null;
  }

  async getToken() {
    // 有効期限の5分前に自動リフレッシュ
    if (this.accessToken && Date.now() < this.expiresAt - 300000) {
      return this.accessToken;
    }
    return this.refresh();
  }

  async refresh() {
    const response = await fetch(this.config.tokenEndpoint, {
      method: "POST",
      body: new URLSearchParams({
        grant_type: "refresh_token",
        refresh_token: this.config.refreshToken,
        client_id: this.config.clientId,
      }),
    });
    const data = await response.json();
    this.accessToken = data.access_token;
    this.expiresAt = Date.now() + data.expires_in * 1000;
    return this.accessToken;
  }
}

障害2:レート制限でMCPサーバーがハングする

❌ エラーハンドリングなしでAPIを叩き続ける

⭕ エクスポネンシャルバックオフ + サーキットブレーカーを実装する

障害3:MCPサーバーのメモリリークで徐々に遅くなる

❌ 長時間稼働させると応答が遅くなり、再起動が必要になる

⭕ ヘルスチェックエンドポイントを設け、pm2/supervisordでプロセス管理する

障害4:開発環境のMCPが本番DBに繋がっていた

❌ 接続文字列の環境変数を.env一ファイルで管理

⭕ .env.development / .env.staging / .env.production を分け、本番は必ずSecrets Managerから取得

2026年Q2のAnthropicロードマップ先読み

Anthropicが公表している2026年MCPロードマップから、エンタープライズに影響する項目を整理します。

機能予定時期企業への影響
OAuth 2.1 + Okta/Azure AD統合2026年Q2SSO統合が標準化。既存認証基盤との連携が楽に
DPoP(Demonstration of Proof of Possession)2026年Q2〜Q3トークン盗用攻撃への耐性が大幅向上
ワークロードアイデンティティ連携2026年Q3マシン間認証(サービスアカウント)の標準化
クライアントアイデンティティメタデータ(CIMD)実装済み(2026-03-05)dynamic client registrationの廃止。より安定した認証

先読みポイント: 今からOAuth 2.1で設計しておけばQ2の標準化に対して変更最小で移行できます。

まとめ:今日から始める3つのアクション

  1. 今日やること: 現在使っているMCPサーバーにAPIキー認証を追加する(上記の「即効設定1」)。開発環境だとしても「認証あり」の習慣をつけることが重要
  2. 今週中: 全MCPツール呼び出しのJSONL監査ログを実装する。「エージェントが何をしたか分からない」という状態をゼロにする
  3. 今月中: 読み取り専用ツールと書き込みツールを別MCPサーバーに分離し、書き込みにはCLAUDE.md経由でユーザー承認を要求する設計に移行する

MCPは「繋げること」より「安全に繋げること」が本質的に難しいです。ただし、今回紹介した設計パターンは一度実装すれば使い回せます。セキュリティ部門のチェックリストを満たす設計を最初から作っておけば、展開スピードが全く変わります。

AIエージェントの全体的な導入戦略はAI導入戦略完全ガイドもあわせて確認ください。


参考・出典


著者: 佐藤傑(さとう・すぐる)
株式会社Uravation代表取締役。X(@SuguruKun_ai)フォロワー約10万人。100社以上の企業向けAI研修・導入支援。著書『AIエージェント仕事術』(SBクリエイティブ)。SoftBank IT連載7回執筆(NewsPicks最大1,125ピックス)。

ご質問・ご相談はお問い合わせフォームからお気軽にどうぞ。

あわせて読みたい

この記事をシェア

Claude Codeを本格的に使いこなしたい方へ

週1回・1時間のマンツーマン指導で、3ヶ月後にはClaude Codeで自走できる実力が身につきます。
現役エンジニアが貴方の業務に合わせてカリキュラムをカスタマイズ。

✓ 1対1のマンツーマン ✓ 全12回・3ヶ月 ✓ 実務ベースの指導
Claude Code 個別指導の詳細を見る まずは無料相談

contact お問い合わせ

生成AI研修や開発のご依頼、お見積りなど、
お気軽にご相談ください。

Claude Code 個別指導(1対1・12セッション)をご希望の方はこちらから別途お申し込みください

Claude Code 個別指導 無料相談