MCP: Advanced Topics ってどんなコース?

MCP上級編コースの概要

「MCP: Advanced Topics」は、Anthropic Academyの技術系コースの中でも最も高度な内容を扱う上級者向けコースです。MCPの入門コース「Introduction to MCP」を修了した開発者が次に進むべきコースとして位置づけられています。

入門コースでは「MCPとは何か」「ツール・リソース・プロンプトの3つのプリミティブ」「簡単なサーバーの作り方」を学びました。この上級コースでは、その知識を土台に本番環境で動くMCPサーバーを構築・運用するための実践的なテクニックを体系的に学びます。

認証とセキュリティ、Sampling(サーバーからAIに補完をリクエストする仕組み)、マルチサーバーオーケストレーション、パフォーマンス最適化、テスト手法、デプロイ戦略、MCPクライアントの自作まで。「MCPで何かを作ってみた」レベルから「MCPを本番で安全に運用できる」レベルへの飛躍に必要なすべてがここにあります。

🤖 メカメモ

MCP: Advanced Topics 基本情報

レベル — 上級(開発者向け)

所要時間 — 約2〜3時間

構成 — 6〜8レクチャー + 最終アセスメント

修了証 — あり(最終アセスメント合格後)

前提条件 — Introduction to MCP 修了が必須

対象 — MCPサーバーを本番運用する開発者

主なトピック — 認証・セキュリティ、Sampling、マルチサーバー構成、パフォーマンス最適化、テスト、デプロイ

リア(興味津々)
リア

入門の次のステップだ! でもこれ「上級」って書いてあるよね……。入門コースだけで大丈夫かな? MCPサーバー1個も作ったことないんだけど……。

メカ(通常)
メカ

率直に言えば、入門コースの知識「だけ」では厳しい場面があります。このコースを最大限活かすには、入門コースを修了した上で実際にMCPサーバーを1つ以上構築した経験があることが望ましい。「作ったことがある」と「理論を知っている」の間には大きな差があります。

リア(なるほど)
リア

じゃあ入門コース → 実際にサーバー1個作る → この上級コース、って流れがベストなんだね。

メカ(満足)
メカ

その通りです。入門で理論を学び、自分で手を動かして体験し、その上でこの上級コースを受けると「あのとき悩んだ問題の解決策がここにあった」という体験ができます。

MCPの基本概念を復習しよう

MCPの基本概念の復習

上級トピックに入る前に、入門コースで学んだMCPの基本概念を簡潔に振り返ります。ここが曖昧だと上級の内容が理解しにくくなるため、しっかり確認しておきましょう。

MCPとは何か

MCP(Model Context Protocol)は、AIモデル(Claude等)と外部のツールやデータソースを接続するためのオープンスタンダードなプロトコルです。「AIにとってのUSB-C」と例えられることが多く、1つの統一規格であらゆる外部システムと繋がれることを目指しています。

3つのプリミティブ

MCPは3つの基本的なプリミティブ(構成要素)を提供します。

クライアント-サーバーアーキテクチャ

MCPはクライアント-サーバーモデルで動作します。MCPクライアント(Claude Desktop, Claude Code等のホストアプリケーション)がMCPサーバーに接続し、サーバーが提供するツール・リソース・プロンプトを利用します。

┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   AIモデル       │     │  MCPクライアント  │     │  MCPサーバー     │
│  (Claude等)      │ ←→ │  (ホストアプリ)   │ ←→ │  (ツール提供)    │
│                  │     │                  │     │                  │
│  推論・生成      │     │  接続管理        │     │  外部API接続     │
│  ツール呼び出し  │     │  権限制御        │     │  データ変換      │
│  判断            │     │  ユーザーUI      │     │  ビジネスロジック │
└──────────────────┘     └──────────────────┘     └──────────────────┘
リア(考え中)
リア

クライアントとサーバーが分かれてるのが大事なんだよね? サーバー側がツールを提供して、クライアント側がそれを使うっていう……えーっと、レストランで言うとメニュー(サーバー)と注文する人(クライアント)みたいな?

メカ(分析)
メカ

概ね正しい比喩です。補足すると、レストラン(MCPサーバー)はメニュー(ツール一覧)を提示するだけでなく、注文を受けたら実際に料理(処理結果)を返します。そしてお客様(MCPクライアント)は複数のレストランに同時に注文できる。これが「1つのクライアントから複数のMCPサーバーに接続できる」というMCPの特徴です。

コースで学ぶ上級トピック

MCP上級トピックの全体像

ここからが本題です。MCP: Advanced Topics で学ぶ各トピックを、コード例を交えながら詳しく解説していきます。

認証とセキュリティ

本番環境でMCPサーバーを動かす場合、認証とセキュリティは最も重要な課題です。入門コースで作ったMCPサーバーはローカル環境で動かすだけだったので認証は不要でしたが、本番では「誰がこのサーバーにアクセスしているか」「このユーザーはこのツールを使う権限があるか」を厳密に管理する必要があります。

OAuth 2.0 による認証フロー

MCPサーバーの認証でよく使われるのがOAuth 2.0です。ユーザーが外部サービス(GitHub, Google, Slack等)にログインしている認証情報をMCPサーバーに委譲する仕組みです。

// MCPサーバーでのOAuth認証ミドルウェアの例
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const server = new McpServer({
  name: "secure-server",
  version: "1.0.0",
});

// 認証ミドルウェアの設定
function authenticateRequest(req) {
  const token = req.headers?.authorization?.replace("Bearer ", "");
  if (!token) {
    throw new Error("認証トークンがありません");
  }

  // トークンの検証(JWT検証、データベース照合など)
  const user = verifyToken(token);
  if (!user) {
    throw new Error("無効なトークンです");
  }

  return user;
}

// ツールごとのアクセス制御
server.tool(
  "delete-record",
  "データベースからレコードを削除する",
  { recordId: { type: "string", description: "削除するレコードID" } },
  async (args, extra) => {
    const user = authenticateRequest(extra.request);

    // 管理者権限チェック
    if (!user.roles.includes("admin")) {
      return {
        content: [{ type: "text", text: "エラー: この操作には管理者権限が必要です" }],
        isError: true,
      };
    }

    // 実際の削除処理
    await database.delete(args.recordId);
    return {
      content: [{ type: "text", text: `レコード ${args.recordId} を削除しました` }],
    };
  }
);
リア(軽い驚き)
リア

おお、ツールごとに「このユーザーは使っていいか」をチェックするのか! 削除みたいな危険な操作は管理者だけにするのは当然だよね。

メカ(通常)
メカ

重要なポイントは最小権限の原則です。ユーザーに必要最低限の権限だけを付与する。「全部許可」は楽ですが、セキュリティの穴になります。MCPサーバーは外部システムへのゲートウェイになるため、ここの設計を間違えるとデータ漏洩や不正操作に直結します。

APIキーの安全な管理

MCPサーバーは外部APIと連携することが多いため、APIキーの管理が重要です。コースではAPIキーをソースコードに直接書かない方法と、環境変数やシークレット管理サービスの使い方が解説されます。

// ❌ 絶対にやってはいけない
const API_KEY = "sk-abc123...";

// ✅ 環境変数から読み取る
const API_KEY = process.env.EXTERNAL_API_KEY;
if (!API_KEY) {
  throw new Error("EXTERNAL_API_KEY 環境変数が設定されていません");
}

// ✅ さらに安全: シークレット管理サービスを使う
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
const client = new SecretManagerServiceClient();

async function getApiKey() {
  const [version] = await client.accessSecretVersion({
    name: "projects/my-project/secrets/external-api-key/versions/latest",
  });
  return version.payload.data.toString();
}

入力バリデーションとサニタイズ

MCPサーバーが受け取るツール引数は、必ずバリデーション(検証)とサニタイズ(無害化)を行います。AIモデルからの入力であっても信頼せず、すべての入力を検証するのが鉄則です。

import { z } from "zod";

// Zodスキーマで入力を厳密に定義
const QuerySchema = z.object({
  tableName: z.string()
    .regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/, "テーブル名に不正な文字が含まれています")
    .max(64, "テーブル名が長すぎます"),
  limit: z.number()
    .int()
    .min(1, "最小1件")
    .max(1000, "最大1000件")
    .default(100),
  where: z.string()
    .max(500, "WHERE句が長すぎます")
    .optional(),
});

server.tool(
  "query-database",
  "データベースにクエリを実行する",
  QuerySchema.shape,
  async (args) => {
    // Zodで自動バリデーション
    const validated = QuerySchema.parse(args);

    // SQLインジェクション対策: パラメータ化クエリを使用
    const result = await db.query(
      `SELECT * FROM ?? WHERE ? LIMIT ?`,
      [validated.tableName, validated.where, validated.limit]
    );
    return { content: [{ type: "text", text: JSON.stringify(result) }] };
  }
);
🤖 メカメモ

MCPサーバーのセキュリティチェックリスト

認証 — OAuth 2.0 / APIキー / JWTトークンでアクセスを認証する

認可 — ツールごとにロールベースのアクセス制御(RBAC)を実装する

入力検証 — Zod等のスキーマライブラリですべての入力をバリデーションする

シークレット管理 — APIキーは環境変数またはシークレット管理サービスで管理する

ログ監査 — 誰がいつ何のツールを使ったかを記録する

レート制限 — 短時間での大量リクエストを防止する

リア(画面に集中)
リア

セキュリティって項目が多いね……。でも考えてみたら、MCPサーバーってAIがデータベースとかにアクセスする窓口になるわけだから、ここが甘いと大変なことになるんだ。

メカ(真面目)
メカ

その認識は正しい。MCPサーバーは「AIと外部システムの境界」に位置するため、セキュリティの要です。入門レベルでは省略できたこれらの要件が、本番では絶対に省略できない。このコースが「上級」と位置づけられる最大の理由のひとつです。

Sampling ― サーバーからAIに補完をリクエストする

MCPの上級トピックの中で最もユニークで強力な機能Sampling(サンプリング)です。通常のMCPの流れは「AIモデル → MCPサーバー」という一方通行ですが、SamplingではMCPサーバーからAIモデルに対して「補完(テキスト生成)をリクエストする」ことができます。

Samplingの仕組み

通常のMCPフロー(AIがサーバーのツールを呼ぶ)に加えて、Samplingでは逆方向のリクエストが発生します。

通常のフロー:
  AIモデル  →  MCPクライアント  →  MCPサーバー
  「このツールを呼んで」→「ツール実行結果を返す」

Samplingのフロー(逆方向が追加):
  MCPサーバー  →  MCPクライアント  →  AIモデル
  「このテキストを生成して」→「生成結果を返す」

つまり、MCPサーバーのツール処理の途中で「この判断はAIにやってもらいたい」「この要約はAIに生成してもらいたい」というケースに対応できます。

Samplingの実装例

例えば、データベースから取得した大量のログを分析して、異常を検知するMCPサーバーを考えてみましょう。

server.tool(
  "analyze-logs",
  "直近のログを分析して異常を検知する",
  {
    timeRange: { type: "string", description: "分析期間(例: '1h', '24h')" },
    severity: { type: "string", description: "最低重要度(info/warn/error)" },
  },
  async (args, extra) => {
    // 1. データベースからログを取得
    const logs = await fetchLogs(args.timeRange, args.severity);

    // 2. ログが大量にある場合、AIに要約・分析を依頼(Sampling)
    if (logs.length > 100) {
      const analysisRequest = {
        messages: [
          {
            role: "user",
            content: {
              type: "text",
              text: `以下の${logs.length}件のサーバーログを分析してください。
異常パターン、エラーの傾向、推奨アクションをまとめてください。

ログデータ:
${formatLogs(logs)}`,
            },
          },
        ],
        modelPreferences: {
          hints: [{ name: "claude-sonnet-4-20250514" }],
        },
        maxTokens: 2000,
      };

      // MCPクライアント経由でAIモデルに補完をリクエスト
      const analysis = await extra.sampling.createMessage(analysisRequest);

      return {
        content: [
          { type: "text", text: `## ログ分析結果(${logs.length}件を分析)\n\n` },
          { type: "text", text: analysis.content.text },
        ],
      };
    }

    // ログが少ない場合はそのまま返す
    return {
      content: [{ type: "text", text: formatLogs(logs) }],
    };
  }
);
リア(ひらめき)
リア

えっ、MCPサーバーからAIに「これ分析して!」って頼めるの!? すごい! サーバーがデータを集めて、分析だけAIにやらせるって、まさに人間の仕事の仕方と同じじゃん!

メカ(分析)
メカ

的確な指摘です。Samplingの本質は「AIとツールの協調パターン」を柔軟にすることです。データの取得はサーバーが得意で、データの理解・要約・判断はAIが得意。この役割分担を1つのツール実行の中で実現できる。ただし重要な注意点があります。

リア(調べ中)
リア

注意点?

メカ(通常)
メカ

SamplingリクエストはMCPクライアント(ホストアプリ)が仲介します。つまり、MCPサーバーが勝手にAIモデルにアクセスするのではなく、クライアントが「このリクエストを許可するかどうか」を制御できます。これはセキュリティ上非常に重要な設計です。サーバーが無制限にAI補完を呼べてしまうと、コスト爆発やプロンプトインジェクションのリスクがあります。

🤖 メカメモ

Samplingの主なユースケース

データ分析 — 大量のデータを取得後、AIに要約・パターン検出を依頼する

コンテンツ生成 — テンプレートにデータを埋め込んだ後、AIに自然な文章に仕上げてもらう

判断支援 — ルールベースで判断できない曖昧なケースをAIに委ねる

多段推論 — ツール実行の途中結果をAIに評価させ、次のステップを決定する

マルチサーバーオーケストレーション

実際の業務環境では、1つのMCPサーバーですべてを賄うことは稀です。GitHub用、データベース用、Slack用、監視用など、複数のMCPサーバーを連携させるのが一般的な構成です。

この「複数のMCPサーバーをどう構成し、どう連携させるか」がマルチサーバーオーケストレーションのテーマです。

マルチサーバー構成の設定例

Claude Desktopでの複数MCPサーバー設定例を見てみましょう。

// claude_desktop_config.json
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_TOKEN": "ghp_xxxxxxxxxxxx"
      }
    },
    "database": {
      "command": "node",
      "args": ["./mcp-servers/database-server.js"],
      "env": {
        "DB_HOST": "localhost",
        "DB_NAME": "production"
      }
    },
    "slack": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-slack"],
      "env": {
        "SLACK_BOT_TOKEN": "xoxb-xxxxxxxxxxxx"
      }
    },
    "monitoring": {
      "command": "node",
      "args": ["./mcp-servers/monitoring-server.js"],
      "env": {
        "SENTRY_DSN": "https://xxxx@sentry.io/yyyy",
        "DATADOG_API_KEY": "ddxxxxxxxxxxxx"
      }
    }
  }
}
リア(キラキラ)
リア

4つもサーバーを同時に動かすんだ! これなら「GitHubのIssue確認して、データベースの関連データ調べて、Slackに報告して、Sentryのエラーも確認」みたいなことが一気にできるね!

メカ(通常)
メカ

その通りです。AIモデルから見ると、全MCPサーバーのツールが統合的に利用できるため、サーバー間の境界を意識せず横断的なタスクを実行できます。「GitHubのPR一覧を取得」→「関連するデータベースのマイグレーション状況を確認」→「Slackのチャンネルにレビュー依頼を投稿」という一連の流れがシームレスに行えます。

サーバー間の依存関係と起動順序

マルチサーバー構成では、サーバー間の依存関係を意識する必要があります。例えば、監視サーバーがデータベースサーバーの正常稼働を前提としている場合、起動順序を制御しなければなりません。

// サーバー起動マネージャーの例
class McpServerManager {
  constructor() {
    this.servers = new Map();
    this.dependencies = new Map();
  }

  // 依存関係を定義
  addServer(name, config, dependsOn = []) {
    this.servers.set(name, config);
    this.dependencies.set(name, dependsOn);
  }

  // 依存関係の順序で起動
  async startAll() {
    const started = new Set();
    const startOrder = this.topologicalSort();

    for (const serverName of startOrder) {
      // 依存サーバーがすべて起動済みか確認
      const deps = this.dependencies.get(serverName) || [];
      for (const dep of deps) {
        if (!started.has(dep)) {
          throw new Error(
            `${serverName} の依存先 ${dep} が起動していません`
          );
        }
      }

      console.log(`[起動] ${serverName}...`);
      await this.startServer(serverName);
      started.add(serverName);
      console.log(`[完了] ${serverName} が起動しました`);
    }
  }

  // トポロジカルソートで起動順序を決定
  topologicalSort() {
    // ...(依存関係のグラフをソート)
  }
}

// 使用例
const manager = new McpServerManager();
manager.addServer("database", dbConfig);
manager.addServer("github", ghConfig);
manager.addServer("monitoring", monitorConfig, ["database"]);
manager.addServer("notification", notifyConfig, ["monitoring", "github"]);

await manager.startAll();
// 起動順序: database → github → monitoring → notification

名前空間の衝突を避ける

複数のMCPサーバーが同じ名前のツールを提供していると衝突が起きます。コースではサーバーごとにプレフィックス(名前空間)を付与する設計パターンが紹介されます。

// ❌ 衝突する命名
// GitHub サーバー: "search" ツール
// データベースサーバー: "search" ツール
// → AIモデルがどちらの "search" を呼ぶべきか混乱する

// ✅ 名前空間で衝突を回避
// GitHub サーバー: "github_search" ツール
// データベースサーバー: "db_search" ツール
// → 明確に区別できる

パフォーマンス最適化

MCPサーバーが本番環境で多くのリクエストを処理する場合、パフォーマンスの最適化が不可欠です。レスポンスが遅いとAIモデルの応答全体が遅くなり、ユーザー体験に直結します。

キャッシュ戦略

頻繁にアクセスされるが更新頻度の低いデータはキャッシュすることで、外部APIへのリクエスト数を大幅に削減できます。

// TTL(有効期限)付きキャッシュの実装例
class McpCache {
  constructor() {
    this.cache = new Map();
  }

  get(key) {
    const entry = this.cache.get(key);
    if (!entry) return null;

    // 有効期限チェック
    if (Date.now() > entry.expiresAt) {
      this.cache.delete(key);
      return null;
    }
    return entry.value;
  }

  set(key, value, ttlMs) {
    this.cache.set(key, {
      value,
      expiresAt: Date.now() + ttlMs,
    });
  }
}

const cache = new McpCache();

server.tool(
  "get-user-profile",
  "ユーザープロフィールを取得する",
  { userId: { type: "string" } },
  async (args) => {
    const cacheKey = `user:${args.userId}`;

    // キャッシュから取得を試みる
    const cached = cache.get(cacheKey);
    if (cached) {
      return {
        content: [{
          type: "text",
          text: `[キャッシュ] ${JSON.stringify(cached)}`,
        }],
      };
    }

    // キャッシュにない場合はAPIから取得
    const profile = await externalApi.getUser(args.userId);

    // 5分間キャッシュ
    cache.set(cacheKey, profile, 5 * 60 * 1000);

    return {
      content: [{ type: "text", text: JSON.stringify(profile) }],
    };
  }
);

バッチ処理

複数のリクエストをまとめて処理することで、外部APIへの呼び出し回数を削減します。

// バッチ処理の例: 複数ユーザーの情報を一括取得
server.tool(
  "get-team-members",
  "チームメンバーの情報を一括取得する",
  {
    userIds: {
      type: "array",
      items: { type: "string" },
      description: "ユーザーIDの配列",
    },
  },
  async (args) => {
    // ❌ 1件ずつリクエスト(N+1問題)
    // for (const id of args.userIds) {
    //   const user = await api.getUser(id);  // N回のAPIコール
    // }

    // ✅ バッチAPIで一括取得(1回のAPIコール)
    const users = await api.getUsersBatch(args.userIds);

    return {
      content: [{
        type: "text",
        text: JSON.stringify(users, null, 2),
      }],
    };
  }
);

接続プーリング

データベースや外部APIへの接続を使い回す(プーリング)ことで、接続の確立と切断にかかるオーバーヘッドを削減します。

import { Pool } from "pg";

// 接続プールの設定
const pool = new Pool({
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  max: 20,            // 最大接続数
  idleTimeoutMillis: 30000,  // アイドル接続のタイムアウト
  connectionTimeoutMillis: 5000, // 接続タイムアウト
});

server.tool(
  "query",
  "SQLクエリを実行する",
  { sql: { type: "string" }, params: { type: "array" } },
  async (args) => {
    // プールから接続を取得(使い終わったら自動で返却)
    const result = await pool.query(args.sql, args.params);
    return {
      content: [{
        type: "text",
        text: JSON.stringify(result.rows, null, 2),
      }],
    };
  }
);

// サーバー終了時にプールをクリーンアップ
process.on("SIGTERM", async () => {
  await pool.end();
  process.exit(0);
});
リア(分析中)
リア

キャッシュ、バッチ処理、接続プーリング……。これってMCPに限った話じゃなくて、普通のWebアプリのパフォーマンス最適化と同じ考え方だよね?

メカ(ドヤ顔)
メカ

良い気づきです。MCPサーバーは本質的にはNode.jsやPythonのサーバーアプリケーションです。Webアプリで培われたパフォーマンス最適化のベストプラクティスがそのまま適用できます。「MCPだから特別」ではなく、「サーバー開発の基礎が正しくできているか」が問われるのです。

エラーハンドリングパターン

本番環境では「正常に動くこと」だけでなく「異常時にどう振る舞うか」が極めて重要です。MCPサーバーのエラーハンドリングには、通常のサーバーアプリケーションとは異なる特有の考慮点があります。

構造化されたエラーレスポンス

MCPサーバーでエラーが発生した場合、AIモデルが理解できる形でエラー情報を返す必要があります。

// 構造化エラーハンドリングの例
class McpToolError extends Error {
  constructor(code, message, details = {}) {
    super(message);
    this.code = code;
    this.details = details;
  }
}

// エラーの種類を定義
const ErrorCodes = {
  VALIDATION_ERROR: "VALIDATION_ERROR",
  AUTH_ERROR: "AUTH_ERROR",
  RATE_LIMIT: "RATE_LIMIT",
  EXTERNAL_API_ERROR: "EXTERNAL_API_ERROR",
  TIMEOUT: "TIMEOUT",
  NOT_FOUND: "NOT_FOUND",
};

server.tool(
  "fetch-data",
  "外部APIからデータを取得する",
  { endpoint: { type: "string" } },
  async (args) => {
    try {
      const data = await callExternalApi(args.endpoint);
      return {
        content: [{ type: "text", text: JSON.stringify(data) }],
      };
    } catch (error) {
      // エラーの種類に応じた構造化レスポンス
      if (error.status === 429) {
        return {
          content: [{
            type: "text",
            text: `[エラー: レート制限] APIのリクエスト制限に到達しました。
${error.retryAfter}秒後に再試行してください。
対処法: 時間をおいてから再度このツールを呼び出してください。`,
          }],
          isError: true,
        };
      }

      if (error.status === 404) {
        return {
          content: [{
            type: "text",
            text: `[エラー: 未検出] エンドポイント "${args.endpoint}" が見つかりません。
利用可能なエンドポイント: /users, /repos, /issues`,
          }],
          isError: true,
        };
      }

      // 予期しないエラー(内部詳細は漏洩させない)
      console.error("予期しないエラー:", error);
      return {
        content: [{
          type: "text",
          text: `[エラー: 内部エラー] 予期しない問題が発生しました。
管理者に連絡してください。エラーID: ${generateErrorId()}`,
        }],
        isError: true,
      };
    }
  }
);

リトライとサーキットブレーカー

外部APIが一時的に不安定な場合に自動リトライを行い、長時間の障害時にはサーキットブレーカーで過剰なリトライを防ぐパターンです。

// サーキットブレーカーの実装例
class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.resetTimeout = options.resetTimeout || 60000; // 1分
    this.failureCount = 0;
    this.state = "CLOSED";       // CLOSED = 通常, OPEN = 遮断, HALF_OPEN = 試行
    this.lastFailureTime = null;
  }

  async execute(fn) {
    // OPEN状態: リクエストを即座に拒否
    if (this.state === "OPEN") {
      if (Date.now() - this.lastFailureTime > this.resetTimeout) {
        this.state = "HALF_OPEN"; // タイムアウト後に試行状態へ
      } else {
        throw new Error(
          "サーキットブレーカー OPEN: 外部サービスが利用不可です。" +
          `${Math.ceil((this.resetTimeout - (Date.now() - this.lastFailureTime)) / 1000)}秒後に再試行します`
        );
      }
    }

    try {
      const result = await fn();
      // 成功したらカウンターをリセット
      this.failureCount = 0;
      this.state = "CLOSED";
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();

      if (this.failureCount >= this.failureThreshold) {
        this.state = "OPEN";
        console.warn(
          `サーキットブレーカー OPEN: ${this.failureCount}回連続失敗`
        );
      }
      throw error;
    }
  }
}

// 使用例
const apiBreaker = new CircuitBreaker({
  failureThreshold: 3,
  resetTimeout: 30000,
});

server.tool("get-status", "外部サービスの状態を取得", {}, async () => {
  const result = await apiBreaker.execute(() => externalApi.getStatus());
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
});
リア(軽い困惑)
リア

サーキットブレーカーって……あの電気のブレーカーと同じ考え方? 電気使いすぎたらバチンって落ちるやつ。

メカ(満足)
メカ

まさにその通りです。電気のブレーカーが過電流から家を守るように、ソフトウェアのサーキットブレーカーは障害の連鎖から他のシステムを守るパターンです。外部APIが落ちている時にリトライし続けると、自分のサーバーまで巻き添えになる。一定回数失敗したらリクエスト自体を止めて、時間をおいてから再試行する。非常に実用的なパターンです。

MCPサーバーのテスト手法

MCPサーバーは外部システムとAIモデルの間に位置するため、テストの設計に工夫が必要です。コースではユニットテスト、統合テスト、E2Eテストの3段階でのテスト戦略が解説されます。

ユニットテスト ― ツールのロジックを単体テスト

// ツールのビジネスロジックを分離してテスト
// src/tools/user-tools.js
export function formatUserProfile(rawData) {
  return {
    name: rawData.full_name || "不明",
    email: rawData.email_address,
    role: rawData.is_admin ? "管理者" : "一般ユーザー",
    lastLogin: new Date(rawData.last_login_at).toLocaleDateString("ja-JP"),
  };
}

// tests/user-tools.test.js
import { describe, it, expect } from "vitest";
import { formatUserProfile } from "../src/tools/user-tools.js";

describe("formatUserProfile", () => {
  it("正常なデータを正しくフォーマットする", () => {
    const result = formatUserProfile({
      full_name: "田中太郎",
      email_address: "tanaka@example.com",
      is_admin: true,
      last_login_at: "2026-04-07T10:00:00Z",
    });
    expect(result.name).toBe("田中太郎");
    expect(result.role).toBe("管理者");
  });

  it("名前がnullの場合は「不明」を返す", () => {
    const result = formatUserProfile({
      full_name: null,
      email_address: "test@example.com",
      is_admin: false,
      last_login_at: "2026-04-07T10:00:00Z",
    });
    expect(result.name).toBe("不明");
  });
});

統合テスト ― MCPプロトコルレベルでのテスト

// MCP SDKのテストユーティリティを使用
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

describe("MCPサーバー統合テスト", () => {
  let server;
  let client;

  beforeEach(async () => {
    server = createMyMcpServer(); // テスト対象のサーバー

    // インメモリトランスポートで接続(ネットワーク不要)
    const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();

    await server.connect(serverTransport);
    client = new Client({ name: "test-client", version: "1.0.0" });
    await client.connect(clientTransport);
  });

  it("ツール一覧を正しく返す", async () => {
    const tools = await client.listTools();
    expect(tools.tools).toContainEqual(
      expect.objectContaining({ name: "get-user-profile" })
    );
  });

  it("ツールを正しく実行する", async () => {
    const result = await client.callTool({
      name: "get-user-profile",
      arguments: { userId: "test-user-1" },
    });
    expect(result.isError).toBeFalsy();
    expect(result.content[0].text).toContain("test-user-1");
  });

  it("無効な引数でエラーを返す", async () => {
    const result = await client.callTool({
      name: "get-user-profile",
      arguments: { userId: "" }, // 空文字列
    });
    expect(result.isError).toBe(true);
  });
});
リア(驚き)
リア

InMemoryTransport! ネットワーク使わずにテストできるんだ! これならCIでも高速に回せるね。

メカ(通常)
メカ

MCP SDKが提供するInMemoryTransportは統合テストの要です。実際のstdioやHTTP通信なしにクライアント-サーバー間の通信を再現できるため、テストが高速かつ安定します。ネットワーク依存のフレークテスト(不安定なテスト)を排除できるのは大きなメリットです。

本番環境へのデプロイ戦略

MCPサーバーのデプロイ方法は、トランスポートの種類によって大きく異なります。コースではstdioベースのローカルデプロイから、HTTP/SSEベースのリモートデプロイまで、段階的にカバーされます。

ローカルデプロイ(stdio)

最もシンプルなデプロイ方法です。MCPサーバーをローカルのプロセスとして起動し、標準入出力(stdio)でクライアントと通信します。

# npm パッケージとして配布するパターン
npm publish @your-org/mcp-server-custom

# ユーザーはclaude_desktop_config.jsonで以下のように設定
{
  "mcpServers": {
    "custom": {
      "command": "npx",
      "args": ["-y", "@your-org/mcp-server-custom"],
      "env": {
        "API_KEY": "your-api-key"
      }
    }
  }
}

リモートデプロイ(HTTP + SSE)

チーム全体で共有するMCPサーバーや、クラウドサービスと連携するサーバーは、HTTPサーバーとしてデプロイします。Server-Sent Events(SSE)を使ったストリーミング通信が標準的です。

// HTTP + SSEでのMCPサーバーデプロイ例
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

const app = express();
const server = new McpServer({
  name: "remote-mcp-server",
  version: "1.0.0",
});

// ツールの定義
server.tool("ping", "サーバーの生存確認", {}, async () => ({
  content: [{ type: "text", text: "pong" }],
}));

// SSEエンドポイント
app.get("/sse", async (req, res) => {
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

// メッセージ受信エンドポイント
app.post("/messages", async (req, res) => {
  // SSEトランスポートがメッセージを処理
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`MCPサーバーがポート ${PORT} で起動しました`);
});

Dockerコンテナでのデプロイ

# Dockerfile
FROM node:20-slim

WORKDIR /app

# 依存関係のインストール
COPY package*.json ./
RUN npm ci --production

# アプリケーションコードのコピー
COPY src/ ./src/

# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 非rootユーザーで実行
USER node

EXPOSE 3000

CMD ["node", "src/index.js"]
# docker-compose.yml(複数MCPサーバーの構成例)
version: "3.8"

services:
  mcp-github:
    build: ./mcp-servers/github
    environment:
      - GITHUB_TOKEN=${GITHUB_TOKEN}
    ports:
      - "3001:3000"
    restart: unless-stopped

  mcp-database:
    build: ./mcp-servers/database
    environment:
      - DB_HOST=postgres
      - DB_NAME=production
    depends_on:
      - postgres
    ports:
      - "3002:3000"
    restart: unless-stopped

  mcp-monitoring:
    build: ./mcp-servers/monitoring
    environment:
      - SENTRY_DSN=${SENTRY_DSN}
    depends_on:
      - mcp-database
    ports:
      - "3003:3000"
    restart: unless-stopped

  postgres:
    image: postgres:16
    environment:
      - POSTGRES_DB=production
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
リア(真剣)
リア

Docker Composeで複数のMCPサーバーをまとめて管理するのか! depends_onで起動順序も制御できてるし、これなら本番運用でも安定しそうだね。

メカ(通常)
メカ

本番デプロイで見落としがちなのがヘルスチェックグレースフルシャットダウンです。コンテナが正常に動いているか定期的に確認し、停止時には実行中のリクエストを完了してからシャットダウンする。これらが欠けていると、デプロイのたびに一瞬サービスが落ちる問題が発生します。

MCPクライアントの実装

MCPの上級トピックとして、サーバーだけでなくクライアント側の実装も学びます。Claude DesktopやClaude Codeは既製のMCPクライアントですが、独自のアプリケーションにMCPクライアント機能を組み込むことで、自分だけのAIツール統合環境を構築できます。

// カスタムMCPクライアントの実装例
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

async function createMcpClient(serverCommand, serverArgs) {
  const transport = new StdioClientTransport({
    command: serverCommand,
    args: serverArgs,
  });

  const client = new Client({
    name: "my-custom-client",
    version: "1.0.0",
  });

  await client.connect(transport);
  return client;
}

// クライアントを使ってMCPサーバーの機能を利用
async function main() {
  const client = await createMcpClient("node", ["./my-mcp-server.js"]);

  // 利用可能なツール一覧を取得
  const { tools } = await client.listTools();
  console.log("利用可能なツール:");
  tools.forEach((tool) => {
    console.log(`  - ${tool.name}: ${tool.description}`);
  });

  // ツールを実行
  const result = await client.callTool({
    name: "get-user-profile",
    arguments: { userId: "user-123" },
  });
  console.log("結果:", result.content[0].text);

  // リソースを取得
  const { resources } = await client.listResources();
  for (const resource of resources) {
    const data = await client.readResource({ uri: resource.uri });
    console.log(`リソース ${resource.name}:`, data.contents[0].text);
  }

  // 接続を終了
  await client.close();
}

main().catch(console.error);
リア(テンション高い)
リア

自分でMCPクライアント作れるの!? じゃあ例えば、自分の作ったWebアプリからMCPサーバーのツールを呼べるってこと?

メカ(驚き)
メカ

その通りです。MCPクライアントを自作できるということは、AIモデルを介さずにMCPサーバーのツールを直接呼ぶことも可能になります。例えば管理用ダッシュボードからMCPサーバーの状態を監視したり、自動化スクリプトからツールを定期実行したり。MCPはAI専用のプロトコルではなく、汎用的なツール統合プロトコルとして機能します。

高度なトランスポート設定

MCPのトランスポート(クライアントとサーバーの通信方式)には、stdioとHTTP/SSEの2つがあります。上級コースではそれぞれの特性を理解し、ユースケースに応じて選択・設定する方法を学びます。

🤖 メカメモ

トランスポートの比較

stdio(標準入出力) — ローカル実行向け。セットアップが簡単。プロセス間通信で高速。ネットワーク不要。1対1の接続。

HTTP + SSE — リモート実行向け。ネットワーク経由で複数クライアントが接続可能。認証の追加が容易。ロードバランシングが可能。

選択基準 — ローカルの個人利用ならstdio、チーム共有やクラウドデプロイならHTTP + SSEが適切。

HTTPトランスポートの認証付き設定

// 認証付きHTTPトランスポートの設定例
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
import cors from "cors";
import helmet from "helmet";

const app = express();

// セキュリティヘッダーの設定
app.use(helmet());

// CORSの設定(必要なオリジンのみ許可)
app.use(cors({
  origin: ["https://your-app.example.com"],
  methods: ["GET", "POST"],
}));

// Bearer認証ミドルウェア
function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  if (!token || !isValidToken(token)) {
    return res.status(401).json({ error: "認証が必要です" });
  }
  req.user = decodeToken(token);
  next();
}

// ヘルスチェック(認証不要)
app.get("/health", (req, res) => {
  res.json({ status: "ok", uptime: process.uptime() });
});

// MCP接続(認証必要)
app.get("/sse", authMiddleware, async (req, res) => {
  console.log(`[接続] ユーザー: ${req.user.name}`);
  const transport = new SSEServerTransport("/messages", res);
  await server.connect(transport);
});

app.post("/messages", authMiddleware, express.json(), async (req, res) => {
  // メッセージ処理
});
リア(なるほど)
リア

helmetでセキュリティヘッダー付けて、CORSで接続元を制限して、Bearer認証で本人確認して……。これWebアプリのセキュリティと完全に同じだ!

メカ(通常)
メカ

繰り返しになりますが、MCPサーバーのHTTPデプロイはExpressアプリのデプロイと本質的に同じです。Web開発の経験がある人はそのスキルをそのまま活かせます。逆に言えば、Webセキュリティの基礎がない状態でHTTPデプロイするのは危険です。

実践アーキテクチャ例 ― 企業内での複数MCPサーバー構成

企業内での複数MCPサーバー構成例

ここまでの個別トピックを統合して、実際の企業環境でのMCPアーキテクチャの全体像を見てみましょう。

┌─────────────────────────────────────────────────────────────┐
│                     開発者のワークステーション                │
│                                                             │
│  ┌─────────────┐                                           │
│  │ Claude Code  │ ← MCPクライアント(ホストアプリ)         │
│  │             │                                           │
│  │ ┌─────────┐ │     stdio       ┌────────────────────┐   │
│  │ │MCP Client├─┼────────────────→│ ローカルMCPサーバー │   │
│  │ └────┬────┘ │                  │ (ファイル操作等)    │   │
│  │      │      │                  └────────────────────┘   │
│  └──────┼──────┘                                           │
│         │ HTTP/SSE                                          │
└─────────┼───────────────────────────────────────────────────┘
          │
          ▼ (社内ネットワーク)
┌─────────────────────────────────────────────────────────────┐
│                     社内MCPサーバー群                         │
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │ GitHub MCP   │  │ Jira MCP     │  │ Confluence   │     │
│  │ サーバー     │  │ サーバー     │  │ MCPサーバー  │     │
│  │              │  │              │  │              │     │
│  │ ・PR管理     │  │ ・チケット   │  │ ・ドキュメント│     │
│  │ ・コードレビュー│ │ ・スプリント │  │ ・ナレッジ   │     │
│  │ ・Issue管理  │  │ ・バックログ │  │ ・検索       │     │
│  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘     │
│         │                 │                 │               │
│         ▼                 ▼                 ▼               │
│  ┌──────────────────────────────────────────────────┐      │
│  │              認証・認可ゲートウェイ               │      │
│  │         (OAuth 2.0 + RBAC + 監査ログ)           │      │
│  └──────────────────────────────────────────────────┘      │
│                                                             │
│  ┌──────────────┐  ┌──────────────┐                        │
│  │ データベース  │  │ 監視・通知    │                        │
│  │ MCPサーバー  │  │ MCPサーバー  │                        │
│  │              │  │              │                        │
│  │ ・クエリ実行 │  │ ・Sentry連携 │                        │
│  │ ・スキーマ   │  │ ・Slack通知  │                        │
│  │ ・マイグレーション│ │ ・メトリクス│                        │
│  └──────────────┘  └──────────────┘                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
リア(軽い驚き)
リア

これ全体像で見るとすごいね……! 開発者がClaude Codeから全部の社内ツールにアクセスできるってこと? GitHub見て、Jiraのチケット確認して、Confluenceのドキュメント探して、データベースにクエリ投げて……全部AIから操作できるんだ。

メカ(分析)
メカ

注目すべきは認証・認可ゲートウェイの存在です。各MCPサーバーに個別の認証機構を実装するのではなく、ゲートウェイで一元管理する。これにより、誰がどのサーバーのどのツールを使ったかを一箇所で監査できます。企業環境ではこの監査能力が必須です。

このアーキテクチャの利点は、各MCPサーバーが単一責任を持つことです。GitHubサーバーはGitHub操作のみに専念し、データベースサーバーはデータベース操作のみに専念する。サーバーの追加・削除・更新が他のサーバーに影響しないため、マイクロサービスアーキテクチャの考え方がそのまま適用できます。

受講のコツ ― 実際にMCPサーバーを本番デプロイしてみる

本番デプロイの受講のコツ

このコースを最大限に活かすためのコツを紹介します。上級コースだからこそ、「聞いて終わり」ではなく「手を動かして実感する」ことが不可欠です。

コツ1: 入門で作ったサーバーを「本番レベル」に改修する

Introduction to MCPで作った簡単なサーバーがあるはずです。そのサーバーに、このコースで学ぶ認証、バリデーション、エラーハンドリング、テストを追加してみましょう。ゼロから作るより、既存のサーバーを改修するほうが「何が足りなかったか」を実感できます。

コツ2: 実際のAPIに接続するサーバーを作る

ダミーデータではなく、実際の外部API(GitHub API, Notion API, OpenWeather API等)に接続するMCPサーバーを作ってみてください。実際のAPIに接続すると、レート制限、認証、エラーハンドリングの必要性を身をもって体験できます。

コツ3: テストを先に書く(TDD)

コースで学ぶテスト手法を使って、新しいツールを追加する前にテストを先に書いてみましょう。テストファーストで開発すると、ツールの仕様(入力は何か、出力は何か、エラー時はどうなるか)を事前に明確化でき、設計品質が上がります。

リア(やる気)
リア

入門で作ったやつを改修するっていうアイデア、めっちゃいいね! 「あの時のサーバー、セキュリティガバガバだったな……」って気づけるのが大事なんだ。

メカ(満足)
メカ

その「気づき」がこのコースの最大の学びです。入門レベルで動くサーバーと、本番レベルで安全に動くサーバーの差はコード量で5〜10倍になることもあります。その差の大部分が、認証、エラーハンドリング、テスト、ログ、監視です。「コア機能」以外の品質を支えるコードの重要性を体感してください。

コツ4: docker-compose で複数サーバー構成を試す

マルチサーバー構成は、手元のdocker-composeで実際に動かすのが最も理解が深まります。GitHub + データベース + 通知の3サーバー構成を作り、AIモデルから横断的なタスクを実行してみてください。「こういう連携ができるのか!」という発見があるはずです。

学習パス全体のまとめ ― 技術系コースの集大成として

技術系コースの学習パス全体像

MCP: Advanced Topicsは、Anthropic Academyの技術系コースの集大成に位置します。ここに到達するまでの学習パスを振り返り、このコースの位置づけを確認しましょう。

🤖 メカメモ

MCP上級に至る推奨学習パス

Step 1 — Claude 101(Claudeの基本操作を学ぶ)

Step 2 — Building with the Claude API(APIの基礎を学ぶ)

Step 3 — Claude Code in Action(開発ツールとしてのClaudeを学ぶ)

Step 4 — Introduction to MCP(MCPの基本概念とサーバー構築入門)

Step 5MCP: Advanced Topics(本番運用のための上級テクニック) ← 本コース

実践 — 実際にMCPサーバーを本番デプロイして運用する

このコースを修了すると、以下のスキルが身に付きます。

リア(自信あり)
リア

これだけのスキルが身に付いたら、もう「MCPサーバー作れます」って堂々と言えるね! 入門と上級を両方やれば、MCPに関してはかなりの自信が持てそう。

メカ(通常)
メカ

補足します。コースを修了しただけでは「学んだ」レベルです。「身に付いた」レベルにするには、実際にプロダクションでMCPサーバーを運用し、障害対応やパフォーマンスチューニングを経験する必要があります。コースは最短で正しい知識を得る手段であり、スキルの定着には実践が不可欠です。

リア(クール)
リア

いつも通り厳しいことを言うねメカは……。でもそれが正しいのは分かってる。コースで学んで、手を動かして、本番で使って、初めて「できる」になるんだね。

まとめ ― MCPを「知っている」から「使いこなす」へ

MCP上級編コースのまとめ

MCP: Advanced Topicsは、MCPの入門知識を持つ開発者が本番環境で通用するMCPサーバーを構築・運用する力を身につけるためのコースです。

MCPはまだ新しいプロトコルですが、AIとツールの連携は今後ますます重要になります。このコースで学ぶ設計パターンやセキュリティの考え方は、MCPに限らずあらゆるAIツール統合の基盤になるでしょう。

リア(笑顔)
リア

MCP上級編、めちゃくちゃ濃い内容だったね! 認証からSampling、マルチサーバー、テスト、デプロイまで……。これを2〜3時間で学べるのはすごい! 入門コースと合わせて受ければ、MCPマスターになれるかも!

メカ(満足)
メカ

MCP: Advanced Topicsは、Anthropic Academyの技術系コースの中でも実務への直結度が最も高いコースです。「AIをただ使う」から「AIと外部システムを安全に統合するインフラを設計・構築する」へのレベルアップ。開発者としてAIエコシステムの中核を担う力が身に付きます。

リア(笑顔)
リア

Anthropic Academyシリーズもいよいよ終盤! MCPの入門と上級で技術系は一区切りかな。次はどのコースを紹介するか楽しみにしててね〜!