CLAUDE LABEN
FORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられていますFORK — Claude Code 2.1.212で/forkの挙動が変わりました。会話を新しいバックグラウンドセッションへ複製し、作業を続けたまま並走できます。従来のセッション内サブエージェントは/subtaskに移りましたLIMITS — WebSearchの呼び出しがセッション単位で既定200回に制限されました。サブエージェントの起動も既定200回が上限で、暴走した検索・委譲のループを止められますMCPBG — 2分を超えるMCPツール呼び出しは自動的にバックグラウンドへ移り、セッションが固まらなくなりました。しきい値はCLAUDE_CODE_MCP_AUTO_BACKGROUND_MSで調整できますPLANFIX — プランモードがtouchやrmといったファイルを変更するBashコマンドを、許可プロンプトもcanUseToolコールバックも通さずに実行してしまう不具合が修正されましたSONNET5 — Claude Sonnet 5は導入価格として入力100万トークンあたり2ドル、出力10ドルで提供中です。8月31日を過ぎると3ドルと15ドルに戻りますIPO — Anthropicが早ければ10月の株式公開を視野に、引受銀行が投資家との面談を組み始めたと報じられています
記事一覧/API & SDK
API & SDK/2026-03-29中級

Claude MCP ハイブリッドアーキテクチャ入門 — 決定論的ツールとAI推論を組み合わせる設計パターン

Claude MCPのハイブリッドアーキテクチャで決定論的ツールとAI推論を組み合わせる設計パターンを解説。Andrew Ng提唱のTool Useパターンを踏まえた実装例付き。

MCP45ハイブリッドアーキテクチャTool Use5設計パターン2エージェント13

プレミアム記事

Claude MCP ハイブリッドアーキテクチャとは

AIエージェントを構築するとき、すべてをLLMに任せるアプローチはコストがかかるだけでなく、結果の再現性にも問題が生じます。一方で、従来のプログラムだけでは複雑な判断に対応できません。この2つの領域を橋渡しするのが Claude MCP ハイブリッドアーキテクチャ です。

この設計パターンでは、MCPサーバーを通じて「決定論的ツール」(データベースクエリ、計算処理、ファイル操作など)と「確率的AI推論」(Claude による自然言語理解・判断・生成)を組み合わせます。Stanford の Andrew Ng 教授も、エージェント設計において「LLM はオーケストレーターとして機能し、確定的なツール呼び出しと連携すべき」と提唱しており、この考え方がハイブリッドアーキテクチャの理論的基盤となっています。

以下では、Claude MCP ハイブリッドアーキテクチャの設計パターンを、実際に動くコードと本番運用で計測した実測値を交えながら掘り下げていきます。MCP の基本概念については MCP 実践ガイド もあわせてご覧いただければと思います。

実装の前提:MCP サーバーをゼロから構築する

ハイブリッドアーキテクチャの「決定論的ツール層」を実際に作るには、MCP サーバーの実装が必須です。既存の MCP サーバー(GitHub、Slack、Notion 等)では対応できない独自のユースケースには、Node.js + TypeScript で MCP サーバーをゼロから構築するのが最適解です。以下では、実装に必要な構造・ステップ・運用パターンを順に整理します。

MCP サーバーの基本設計

アーキテクチャの概要

MCP サーバーは3つの主要機能を提供できます:

  1. Tools(ツール): Claude が呼び出せる関数(データ取得、計算、外部API呼び出し等)
  2. Resources(リソース): Claude が読み取れるデータソース(ファイル、DB レコード等)
  3. Prompts(プロンプト): 定型のプロンプトテンプレート

今回は最も実用的な Tools を中心に、データベースを検索する MCP サーバーを構築します。

プロジェクト構造

src/
├── index.ts          # エントリポイント(サーバー起動)
├── server.ts         # MCP サーバー定義
├── tools/
│   ├── search.ts     # 検索ツール
│   └── stats.ts      # 統計ツール
└── lib/
    └── database.ts   # データベース接続

ステップ 1: サーバーの基本構造を作る

まず src/server.ts で MCP サーバーのインスタンスを作成します:

// src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 
export function createServer() {
  const server = new McpServer({
    name: "my-data-server",
    version: "1.0.0",
  });
 
  return server;
}

エントリポイント src/index.ts では、サーバーを stdio トランスポートで起動します:

// src/index.ts
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createServer } from "./server.js";
import { registerSearchTool } from "./tools/search.js";
import { registerStatsTool } from "./tools/stats.js";
 
const server = createServer();
 
// ツールを登録
registerSearchTool(server);
registerStatsTool(server);
 
// stdio トランスポートで接続(Claude Desktop / Claude Code が使用)
const transport = new StdioServerTransport();
await server.connect(transport);
 
// 期待する出力:
// サーバーが起動し、stdin/stdout 経由で MCP プロトコルのメッセージを待受

ステップ 2: ツールを定義する

検索ツールの実装

src/tools/search.ts でデータベースを検索するツールを作ります。Zod でパラメータのバリデーションを行い、型安全にします:

// src/tools/search.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { searchDatabase } from "../lib/database.js";
 
export function registerSearchTool(server: McpServer) {
  server.tool(
    "search_records",
    "データベースからレコードをキーワード検索する。結果は関連度順にソートされる。",
    {
      query: z.string().describe("検索キーワード"),
      limit: z.number().min(1).max(50).default(10).describe("最大取得件数"),
      category: z.enum(["all", "articles", "users", "logs"]).default("all").describe("検索対象カテゴリ"),
    },
    async ({ query, limit, category }) => {
      try {
        const results = await searchDatabase(query, { limit, category });
 
        if (results.length === 0) {
          return {
            content: [
              {
                type: "text" as const,
                text: `「${query}」に一致するレコードは見つかりませんでした。`,
              },
            ],
          };
        }
 
        const formatted = results
          .map((r, i) => `${i + 1}. [${r.category}] ${r.title}\n   ${r.summary}\n   ID: ${r.id}`)
          .join("\n\n");
 
        return {
          content: [
            {
              type: "text" as const,
              text: `${results.length} 件の結果:\n\n${formatted}`,
            },
          ],
        };
      } catch (error) {
        const message = error instanceof Error ? error.message : "Unknown error";
        return {
          content: [{ type: "text" as const, text: `検索エラー: ${message}` }],
          isError: true,
        };
      }
    }
  );
}

統計ツールの実装

// src/tools/stats.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { getStats } from "../lib/database.js";
 
export function registerStatsTool(server: McpServer) {
  server.tool(
    "get_statistics",
    "データベースの統計情報を取得する。件数、カテゴリ分布、最新更新日時などを返す。",
    {
      category: z.enum(["all", "articles", "users", "logs"]).default("all"),
    },
    async ({ category }) => {
      const stats = await getStats(category);
      return {
        content: [
          {
            type: "text" as const,
            text: JSON.stringify(stats, null, 2),
          },
        ],
      };
    }
  );
}
 
// 期待する出力(Claude がこのツールを呼んだ場合):
// {
//   "totalRecords": 1523,
//   "categories": { "articles": 890, "users": 412, "logs": 221 },
//   "lastUpdated": "2026-03-14T10:30:00Z"
// }

ステップ 3: データベース接続層

src/lib/database.ts はデータソースへの接続を抽象化します。実際のプロジェクトでは PostgreSQL や SQLite に置き換えてください:

// src/lib/database.ts
interface Record {
  id: string;
  title: string;
  summary: string;
  category: string;
  score: number;
}
 
interface SearchOptions {
  limit: number;
  category: string;
}
 
// デモ用のインメモリデータ(本番では DB に置き換え)
const DEMO_DATA: Record[] = [
  { id: "001", title: "MCP プロトコル仕様", summary: "Model Context Protocol の技術仕様書", category: "articles", score: 0.95 },
  { id: "002", title: "Claude API リファレンス", summary: "Anthropic Claude API の完全リファレンス", category: "articles", score: 0.88 },
  { id: "003", title: "エージェント設計パターン", summary: "AI エージェントの設計パターン集", category: "articles", score: 0.82 },
];
 
export async function searchDatabase(query: string, options: SearchOptions): Promise<Record[]> {
  const lowerQuery = query.toLowerCase();
  let results = DEMO_DATA.filter(
    (r) =>
      r.title.toLowerCase().includes(lowerQuery) ||
      r.summary.toLowerCase().includes(lowerQuery)
  );
 
  if (options.category !== "all") {
    results = results.filter((r) => r.category === options.category);
  }
 
  return results.slice(0, options.limit);
}
 
export async function getStats(category: string) {
  const data = category === "all" ? DEMO_DATA : DEMO_DATA.filter((r) => r.category === category);
  const categories: { [key: string]: number } = {};
  data.forEach((r) => {
    categories[r.category] = (categories[r.category] || 0) + 1;
  });
 
  return {
    totalRecords: data.length,
    categories,
    lastUpdated: new Date().toISOString(),
  };
}

ステップ 4: Claude Desktop への接続

設定ファイルの編集

MCP サーバーを Claude Desktop から使うには、設定ファイルにサーバー情報を追加します。

macOS の場合: ~/Library/Application Support/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "my-data-server": {
      "command": "node",
      "args": ["/path/to/my-mcp-server/dist/index.js"],
      "env": {
        "DATABASE_URL": "postgresql://localhost:5432/mydb"
      }
    }
  }
}

開発中は tsx を使って直接 TypeScript を実行することもできます:

{
  "mcpServers": {
    "my-data-server": {
      "command": "npx",
      "args": ["tsx", "/path/to/my-mcp-server/src/index.ts"]
    }
  }
}

Claude Code での利用

Claude Code の場合は .mcp.json をプロジェクトルートに配置します:

{
  "mcpServers": {
    "my-data-server": {
      "command": "node",
      "args": ["./dist/index.js"]
    }
  }
}

ステップ 5: エラーハンドリングとロギング

本番環境では堅牢なエラーハンドリングが必須です。MCP サーバーでは stderr をログ出力に使えます(stdout は MCP プロトコル通信に使われるため):

// src/lib/logger.ts
export function log(level: "info" | "warn" | "error", message: string, data?: unknown) {
  const timestamp = new Date().toISOString();
  const entry = { timestamp, level, message, ...(data ? { data } : {}) };
  // stderr に出力(stdout は MCP 通信用なので使えない)
  console.error(JSON.stringify(entry));
}
 
// 使用例:
// log("info", "Search executed", { query: "MCP", results: 3 });
// 期待する出力(stderr):
// {"timestamp":"2026-03-14T10:30:00Z","level":"info","message":"Search executed","data":{"query":"MCP","results":3}}

ツール内でのエラーハンドリングのベストプラクティス:

server.tool("risky_operation", "外部APIを呼び出す操作", {
  endpoint: z.string().url(),
}, async ({ endpoint }) => {
  try {
    const response = await fetch(endpoint, {
      signal: AbortSignal.timeout(10000), // 10秒タイムアウト
    });
 
    if (!response.ok) {
      return {
        content: [{ type: "text" as const, text: `API エラー: ${response.status} ${response.statusText}` }],
        isError: true,
      };
    }
 
    const data = await response.json();
    return {
      content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
    };
  } catch (error) {
    log("error", "External API call failed", { endpoint, error: String(error) });
    return {
      content: [{ type: "text" as const, text: `接続エラー: ${error instanceof Error ? error.message : "Unknown"}` }],
      isError: true,
    };
  }
});

ステップ 6: テストとデバッグ

MCP Inspector の活用

MCP SDK に同梱されているインスペクタを使って、サーバーの動作を確認できます:

npx @modelcontextprotocol/inspector node dist/index.js

ブラウザで http://localhost:5173 が開き、ツール一覧の確認や手動でのツール呼び出しが可能です。

単体テスト

// tests/search.test.ts
import { searchDatabase } from "../src/lib/database.js";
 
// テスト例
const results = await searchDatabase("MCP", { limit: 10, category: "all" });
console.assert(results.length > 0, "検索結果が0件");
console.assert(results[0].title.includes("MCP"), "タイトルにMCPが含まれない");
console.log("✅ All tests passed");
 
// 期待する出力:
// ✅ All tests passed

本番デプロイのベストプラクティス

npm パッケージとして公開する場合

npm run build
npm publish

公開後は npx my-mcp-server で起動できるようになります。

Docker コンテナとして配布する場合

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/
ENTRYPOINT ["node", "dist/index.js"]

セキュリティ考慮事項

  • 環境変数で秘密情報を管理: API キーや DB 接続情報はコードに含めない
  • 入力バリデーション: Zod で全パラメータを検証(すでに実装済み)
  • レート制限: 外部 API 呼び出しにはレート制限を設ける
  • 最小権限の原則: DB アクセスは読み取り専用ユーザーを推奨

ここまでが実装の基礎です。次節以降では、構築したサーバーをハイブリッドアーキテクチャに組み込む際の設計判断を扱います。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
すべてをLLMに任せる方式と比較し、トークンコストを約71%・p95レイテンシを約74%削減したハイブリッド構成の実測値
本番投入で踏んだ5つの落とし穴(ツール定義の肥大化・タイムアウト・リトライ暴走など)と、実際に効いた回避策
壁紙アプリのレビュー分類×Crashlytics連携で運用している、決定論層とAI層の責務分割の判断基準
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

API & SDK2026-07-08
MCP コネクタを申請・無人運用に回す前に、各ツールを契約テストで確かめる
手元で動いた MCP コネクタをそのまま無人ジョブに繋ぐと、応答形状の誤読や書き込みの二重発火で静かに壊れます。ツール記述・応答契約・冪等性・レイテンシを機械検証する小さなハーネスの作り方を、実測値とともにまとめました。
API & SDK2026-06-30
ツール出力が大きすぎてコンテキストを溶かす問題 — カーソルで小分けに返すページング設計
一覧系のツールが数百件をそのまま返すと、エージェントのコンテキストは一回の呼び出しで溶けます。カーソルベースのページングでツール出力を小分けに返し、トークン予算を守る設計を実装コード付きで解説します。
API & SDK2026-06-28
接続が切れた瞬間、その投稿は通ったのか — 無人パイプラインで MCP 書き込みを二重実行せずにやり直す
接続断で中断した MCP の書き込みツール呼び出しは、サーバー側で実行されたかどうか分かりません。素朴な再試行が二重投稿を生む理由と、冪等キーと照合読み取りで安全にやり直すラッパーの実装を、無人パイプラインの実例とともにまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →