CLAUDE LABEN
PRICING — 本日9月1日は Sonnet 5 の値上げ予定日でしたが、引き上げは実施されませんでした。導入価格の $2/$10 per MTok がそのまま標準価格として確定していますPARTNER — Salesforce と Anthropic が拡張提携「Claudeforce」を発表しました。Salesforce in Claude プラグインは商談準備やパイプライン管理など37のセールススキルを同梱しますTRUST — Claudeforce の推論は Amazon Bedrock 経由で Salesforce Trust Boundary の内側に留まります。データが境界の外へ出ない構成が、規制業種への回答になっていますBETA — Salesforce in Claude は現在は選抜パイロット顧客向けです。9月中のオープンベータ移行が予告されていますLIMITS — 週次上限の50%増は9月13日までです。9月14日からはプロモ前比+25%が恒久水準になります。今日の水準と比べると約17%の削減にあたりますRELEASE — Claude Code は v2.1.251(8月28日)から新しいリリースが出ていません。約0.8日に1本というペースからすると、4日の間隔は最長クラスですPRICING — 本日9月1日は Sonnet 5 の値上げ予定日でしたが、引き上げは実施されませんでした。導入価格の $2/$10 per MTok がそのまま標準価格として確定していますPARTNER — Salesforce と Anthropic が拡張提携「Claudeforce」を発表しました。Salesforce in Claude プラグインは商談準備やパイプライン管理など37のセールススキルを同梱しますTRUST — Claudeforce の推論は Amazon Bedrock 経由で Salesforce Trust Boundary の内側に留まります。データが境界の外へ出ない構成が、規制業種への回答になっていますBETA — Salesforce in Claude は現在は選抜パイロット顧客向けです。9月中のオープンベータ移行が予告されていますLIMITS — 週次上限の50%増は9月13日までです。9月14日からはプロモ前比+25%が恒久水準になります。今日の水準と比べると約17%の削減にあたりますRELEASE — Claude Code は v2.1.251(8月28日)から新しいリリースが出ていません。約0.8日に1本というペースからすると、4日の間隔は最長クラスです
記事一覧/API & SDK
API & SDK/2026-03-29中級

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

決定論的ツールとAI推論を組み合わせるClaude MCPハイブリッドアーキテクチャの設計パターンを解説します。MCPサーバーをゼロから構築する前提知識、3層構成での役割分担、Andrew Ng提唱のTool Useパターンの実装への活かし方を、売上分析エージェントの実践例とともに紹介します。

MCP51ハイブリッドアーキテクチャTool Use6設計パターン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/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

API & SDK2026-08-19
サーバーレスに置いた MCP サーバーで、listen ストリームが張り直され続けていました
エラーは何も出ていないのに、MCP サーバーへの接続だけが増え続ける。固定タイムアウトで切られるストリームと再購読が噛み合ったときの挙動を、最小の再現コードで確かめました。
API & SDK2026-08-01
会話の途中でツールを差し替える — プロンプトキャッシュを守るツールレジストリの設計
ターン間でツールを追加・削除できるようになった一方、ツール定義はキャッシュプレフィックスの最前列にあります。12種類の変更を指紋で実測し、安定コアと揮発テールに分けるレジストリ設計とガード実装をまとめました。
API & SDK2026-07-31
400文字だけ残したはずのツール出力が、1MB を握り続けていた
自作の MCP サーバーやツールラッパーで出力を切り詰めても、Node の sliced string は元の全文を握り続けます。境界が13文字であること、平坦化が効く書き方と効かない書き方、ヒープスナップショットから親を数える監査スクリプトまで、すべて実測して記録しました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →