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つの主要機能を提供できます:
- Tools(ツール): Claude が呼び出せる関数(データ取得、計算、外部API呼び出し等)
- Resources(リソース): Claude が読み取れるデータソース(ファイル、DB レコード等)
- 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 アクセスは読み取り専用ユーザーを推奨
ここまでが実装の基礎です。次節以降では、構築したサーバーをハイブリッドアーキテクチャに組み込む際の設計判断を扱います。