MCP プロトコル統合実践:AI Agent に任意のツールを呼び出させる
あなたが AI に航空券を予約してほしいと頼んだとします。AI は「私は言語モデルなので外部システムにアクセスできません」と答えます。別の Agent 製品に変えると、使えると言いながらパスワードや管理者権限を要求されます。あなたはためらいます。
LLM の次の 10 年の戦いはモデル自体ではなく、ツール呼び出しです。Anthropic は 2024 年末に Model Context Protocol (MCP) をオープンソース化し、「AI が外部ツールを呼び出す」ことを標準化しました。本稿では、IHUI AI が 8 端全スタック AI OS に MCP を統合し、Agent を実際に働かせる方法を解説します。
一、課題:AI は会話はできても仕事はできない
LLM アプリケーションの最も厄介な現状:モデルは賢いがガラスケースの中に閉じ込められている。
課題 1:各 Agent フレームワークが独自のツールプロトコルを実装
- OpenAI Function Calling:
tools+tool_calls - Anthropic Tool Use:
tool_useブロック - LangChain Tools:
BaseToolクラス - AutoGen Tools:
@toolデコレータ - Coze / Dify:ビジュアルプラグイン
「注文照会」ツールを 1 つ書くのに、5 つのフレームワークに合わせて 5 つのコードを書く必要があります。
課題 2:ツール再利用率がほぼゼロ
A 社が「天気照会」ツールを書いても、自社の Agent でしか使えません。B 社が再利用しようとすると一から実装する必要があります。業界全体が車輪の再発明を繰り返しています。
課題 3:権限の喪失
Agent にデータベースを照会させるために DB アカウントを渡し、メールを送らせるために SMTP パスワードを渡し、GitHub を操作させるために PAT を渡す。prompt injection 攻撃 1 つで本番データベースが丸裸になります。
課題 4:サンドボックスなし
Agent にコードスニペットを実行させると、サーバー上で直接 eval() される?本番環境はあっという間に破壊されます。
二、解決策:MCP プロトコル+サンドボックス実行
2.1 MCP とは
Model Context Protocol は Anthropic が主導するオープンソースの AI ツール呼び出しプロトコルです。核心思想:ツール/リソース/Prompt を標準 server として公開し、MCP 対応クライアントなら誰でも呼び出せる。
アナロジー:MCP を AI Agent に例えるなら、LSP をエディタに例えるのと同じです。LSP が 1 つの言語サーバーを VSCode/Vim/Emacs で共有できるように、MCP は 1 つのツールを Claude Desktop/Cursor/任意の Agent で共有できるようにします。
2.2 MCP の 3 要素
-
Tools(ツール):実行可能な関数(例:
query_order(orderId)、send_email(to, subject, body)) -
Resources(リソース):読み取り可能なデータ(例:
file:///path/to/doc.md、db://users/123) -
Prompts(プロンプトテンプレート):再利用可能な prompt(例:
summarize_meeting(transcript))
2.3 IHUI AI の MCP アーキテクチャ
┌──────────────────────────────────────────┐
│ AI Agent (LangGraph 編成) │
│ ↓ ツール呼び出し判断 │
│ MCP Client (統一呼び出し層) │
└──────────────┬───────────────────────────┘
│ JSON-RPC over stdio/SSE
↓
┌──────────────────────────────────────────┐
│ MCP Server Registry(ツール登録センター)│
│ ↓ 権限検証+ルーティング │
└──────────────┬───────────────────────────┘
│
┌──────┼──────┬──────┬──────┐
↓ ↓ ↓ ↓ ↓
組み込みツール 企業DB GitHub Slack サンドボックスコード実行
(ローカル) MCP MCP MCP MCP
Enter fullscreen mode Exit fullscreen mode
各 MCP server は独立したプロセスで、Agent は標準プロトコルでのみ通信します。ツール実装と Agent は分離されています。
三、技術詳細
3.1 カスタム MCP Server
IHUI AI は公式 @modelcontextprotocol/sdk で MCP server を書いています。以下は「注文照会」server の例です。
// mcp-servers/order-query/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { db } from './db.js';
const server = new Server(
{ name: 'order-query', version: '1.0.0' },
{ capabilities: { tools: {} } },
);
// ツール登録
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'query_order',
description: '注文詳細を照会。ユーザー自身の注文 ID が必要',
inputSchema: {
type: 'object',
properties: {
orderId: { type: 'string', description: '注文 ID' },
},
required: ['orderId'],
},
},
{
name: 'list_recent_orders',
description: '現在のユーザーの直近 10 件の注文を一覧表示',
inputSchema: {
type: 'object',
properties: {
userId: { type: 'string', description: 'ユーザー ID(認証コンテキストから取得)' },
},
required: ['userId'],
},
},
],
}));
// ツール実装
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'query_order': {
const order = await db.orders.findById(args.orderId);
if (!order) {
return { content: [{ type: 'text', text: '注文が存在しません' }] };
}
// 権限検証:自分の注文のみ照会可能
if (order.userId !== args.userId) {
return { content: [{ type: 'text', text: '他人の注文にはアクセスできません' }] };
}
return {
content: [{
type: 'text',
text: JSON.stringify(order, null, 2),
}],
};
}
case 'list_recent_orders': {
const orders = await db.orders.findRecentByUser(args.userId, 10);
return {
content: [{
type: 'text',
text: JSON.stringify(orders, null, 2),
}],
};
}
}
});
const transport = new StdioServerTransport();
await server.connect(transport);
Enter fullscreen mode Exit fullscreen mode
主要設計:
- ツールは JSON Schema で入力パラメータを記述し、LLM は自動的に呼び出し方を理解します。
- 権限検証はツール内部で行われ、Agent はバイパスできません。
- 戻り値は構造化 JSON で、LLM がさらに処理できます。
3.2 MCP Client 統合
apps/ai-service/src/mcp/client.py:
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from contextlib import asynccontextmanager
import json
class MCPRegistry:
"""MCP Server 登録センター"""
def __init__(self):
self.servers: dict[str, ClientSession] = {}
self.tools_cache: dict[str, list] = {}
@asynccontextmanager
async def connect(self, server_name: str, command: str, args: list[str]):
params = StdioServerParameters(command=command, args=args)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
self.servers[server_name] = session
# ツール一覧をキャッシュ
tools_resp = await session.list_tools()
self.tools_cache[server_name] = tools_resp.tools
yield session
async def call_tool(
self,
server_name: str,
tool_name: str,
arguments: dict,
user_context: dict, # 認証コンテキスト
):
if server_name not in self.servers:
raise ValueError(f"未登録の MCP server: {server_name}")
# ユーザーコンテキストを引数に注入(権限検証用) enriched_args = {**arguments, **user_context}
session = self.servers[server_name]
result = await session.call_tool(tool_name, enriched_args)
return json.loads(result.content[0].text)
def all_tools_as_openai_format(self) -> list[dict]:
"""全 MCP ツールを OpenAI tools 形式に変換し、LLM が呼び出せるようにする"""
all_tools = []
for server_name, tools in self.tools_cache.items():
for tool in tools:
all_tools.append({
"type": "function",
"function": {
"name": f"{server_name}__{tool.name}",
"description": tool.description,
"parameters": tool.inputSchema,
},
})
return all_tools
Enter fullscreen mode Exit fullscreen mode
3.3 LangGraph ツール呼び出しループ
MCP ツールを LangGraph Agent に公開します。
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from litellm import acompletion
class AgentState(TypedDict):
messages: list[dict]
user_context: dict
async def call_llm(state: AgentState):
tools = registry.all_tools_as_openai_format()
response = await acompletion(
model="gpt-4o",
messages=state["messages"],
tools=tools,
)
msg = response.choices[0].message
return {"messages": state["messages"] + [msg.to_dict()]}
async def call_mcp_tool(state: AgentState):
last_msg = state["messages"][-1]
results = []
for tool_call in last_msg.get("tool_calls", []):
# ツール名形式:server_name__tool_name
server_name, tool_name = tool_call["function"]["name"].split("__", 1)
args = json.loads(tool_call["function"]["arguments"])
result = await registry.call_tool(
server_name, tool_name, args, state["user_context"],
)
results.append({
"role": "tool",
"tool_call_id": tool_call["id"],
"content": json.dumps(result),
})
return {"messages": state["messages"] + results}
def should_continue(state: AgentState) -> str:
last_msg = state["messages"][-1]
if last_msg.get("tool_calls"):
return "call_tool"
return END
graph = StateGraph(AgentState)
graph.add_node("llm", call_llm)
graph.add_node("call_tool", call_mcp_tool)
graph.add_conditional_edges("llm", should_continue)
graph.add_edge("call_tool", "llm") # ツール結果を LLM に戻す
graph.set_entry_point("llm")
agent = graph.compile()
Enter fullscreen mode Exit fullscreen mode
呼び出しループ全体:LLM が判断 → MCP ツールを呼び出し → 結果を LLM に返す → 次を判断 → 完了まで。
3.4 権限制御:3 層モデル
IHUI AI の MCP 権限は 3 層に分かれています。
-
ユーザー層:ユーザーは自身がアクセス権を持つツールのみ呼び出せます(例:自分の注文のみ照会)。ツール実装内で
userIdを検証します。 - プラン層:Free ユーザーは組み込みツール 5 個のみ、Pro ユーザーは 50 個、Enterprise は全ツールを利用可能。MCP Client 層でツール一覧をフィルタリングします。
- セッション層:ユーザーは UI 上で明示的に「Agent に X ツールの呼び出しを許可」する必要があります。OAuth 認可に似ています。Agent は一時トークンを取得し、セッション終了時に無効化されます。
def filter_tools_by_permission(
user_id: str,
plan: str,
session_grants: list[str],
all_tools: list[dict],
) -> list[dict]:
allowed = []
for tool in all_tools:
tool_id = tool["function"]["name"]
# プラン層
if tool_id in PLAN_TOOL_WHITELIST[plan]:
allowed.append(tool)
# セッション層
elif tool_id in session_grants:
allowed.append(tool)
return allowed
Enter fullscreen mode Exit fullscreen mode
3.5 サンドボックス実行:コードインタープリタ MCP
最も危険なツールは「コード実行」です。IHUI AI は Docker+リソース制限に基づく独立したサンドボックス MCP server を実装しています。
// mcp-servers/code-sandbox/index.ts
import Docker from 'dockerode';
const docker = new Docker();
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name !== 'execute_code') {
throw new Error(`未知のツール: ${req.params.name}`);
}
const { code, language = 'python' } = req.params.arguments;
// 使い捨てコンテナを起動
const container = await docker.createContainer({
Image: 'ihui/sandbox-python:3.11-slim',
Cmd: ['python', '-c', code],
HostConfig: {
Memory: 256 * 1024 * 1024, // 256MB
NanoCpus: 1e9, // 1 CPU
NetworkMode: 'none', // ネットワーク禁止
AutoRemove: true,
},
});
await container.start();
const output = await container.attach({
stream: true, stdout: true, stderr: true,
});
// ... 出力を収集、5 秒でタイムアウトして kill
return {
content: [{ type: 'text', text: output.toString() }],
};
});
Enter fullscreen mode Exit fullscreen mode
セキュリティ対策:ネットワーク禁止+メモリ/CPU 制限+5 秒タイムアウト+使い捨てコンテナ+非 root ユーザー。Agent がどんなコードを実行してもホストに影響しません。
3.6 A2A プロトコル:MCP 上の Agent 間相互呼び出し
MCP は「Agent がツールを呼び出す」を解決しますが、Agent 同士の相互呼び出しはどうするのでしょうか?IHUI AI は A2A(Agent-to-Agent)プロトコルも統合しています。
- Agent A(計画)→ Agent B(検索)→ Agent C(生成)
- 各 Agent は MCP server を公開し、他の Agent をツールとして呼び出します
- 単一の Agent ではなく Agent ネットワークを形成します
これが IHUI AI の P3 深層です。ユーザーは Agent トポロジをドラッグ&ドロップで設定し、プラットフォームがスケジューリングを担当します。
四、IHUI AI 運用データ
| 指標 | 値 |
|---|---|
| 組み込み MCP server 数 | 28 個(注文/決済/メール/検索/データベース/コードサンドボックスなど) |
| サードパーティ MCP 互換性 | 100%(任意の MCP server をプラグ&プレイ) |
| 平均ツール呼び出しレイテンシ | 120ms(ローカル)/ 800ms(リモート) |
| サンドボックス最大実行時間 | 30 秒(タイムアウトで自動 kill) |
| 権限レイヤー | 3 層(ユーザー/プラン/セッション) |
| プロトコル対応 | MCP+A2A デュアルプロトコル |
実例:ある企業ユーザーが社内 ERP システムを MCP server としてカプセル化し、IHUI AI の Agent がワンクリックで接続。従業員は自然言語で在庫照会、注文作成、レポート生成が可能になりました。従来 3 週間かかっていた統合が 2 日で完了し、権限分離も明確です。
五、落とし穴まとめ
落とし穴 1:ツール記述が不十分だと LLM が誤ったツールを呼び出す
LLM は description フィールドでツールを判断します。記述が曖昧(「注文を照会」)だと LLM は誤ったツールを呼び出します。当社の規約:記述には①いつ使うか、②いつ使わないか、③入力パラメータの意味、④戻り値の構造を必ず含めます。例:「注文詳細を照会。ユーザーが注文状況を明示的に尋ねた場合のみ使用し、雑談中には呼び出さない。注文 JSON(id/items/total/status フィールドを含む)を返却する。」
落とし穴 2:ツール数が爆発し、LLM が選択に苦労する
ツールが 50 以上になると LLM の誤選択率が急上昇します。対策:シナリオごとにグループ化し、LangGraph ルーティングノードで「ツールサブセット」を先に選択してから LLM に渡します。
落とし穴 3:stdio vs SSE
MCP は 2 種類のトランスポートをサポート:stdio(ローカル子プロセス)と SSE(リモート HTTP)。stdio は高速ですがローカルのみ、SSE はネットワーク越えですが遅延があります。IHUI AI は組み込みツールに stdio、サードパーティツールに SSE を使用し、Client 層で透過的に切り替えます。
落とし穴 4:ツール結果のトークン爆発
あるツールが 10 万行の SQL 結果を返すと、コンテキストに収まらなくなります。MCP Client 層で切り詰め処理を実施:tool_result_truncator(result, max_tokens=2000)。長すぎる場合は要約+「完全な結果は保存済み。query_detail ツールで確認可能」と案内します。
六、MCP を導入すべきでないケース
- 1 つの LLM ベンダーにのみ依存する場合:ベンダー固有の Function Calling の方が軽量です。
- ツール数が 5 未満で外部共有しない場合:自前実装の方がシンプルです。
- Agent 間でのツール再利用が不要な場合:MCP の本質的な価値は「1 回書けばすべての Agent で使える」点にあります。自社 Agent 1 つでしか使わないならメリットは限定的です。
MCP の真の価値はエコシステムにあります。ツールを 1 回書けば、すべての MCP 対応クライアント(Claude Desktop/Cursor/Cline/IHUI AI)で利用可能です。これは LSP がエディタエコシステムを統一した再現です。
七、結び
MCP 統合の核心は以下の通りです。
- プロトコル標準化:MCP によりツールを 1 回書けばすべての Agent で呼び出せ、5 つのフレームワークに 5 つのコードを書く必要がなくなります。
- server 分離:各ツールは独立したプロセスで、LangGraph Agent は JSON-RPC で通信するのみです。
- 3 層権限:ユーザー層/プラン層/セッション層。prompt injection 攻撃でも本番データベースは守られます。
- サンドボックス実行:コード実行系ツールは Docker で隔離。ネットワーク禁止+リソース制限+タイムアウト kill。
- A2A 拡張:MCP の上に Agent 間相互呼び出しを構築し、Agent ネットワーク(P3 深層)を形成します。
IHUI AI は MCP で 28 個の組み込みツール+任意のサードパーティ MCP server を接続し、Agent マーケットのツールを 1 回書けば全プラットフォームで通用するようにしました。Agent アプリケーションを開発中の方は、初日から MCP を採用することを強くおすすめします。後から移行するとツールプロトコル適応が大変です。
IHUI AI について
IHUI AI はオールインワンの 8 端全スタック AI OS で、Apache 2.0 でオープンソース化されています。
- 🌐 公式サイト:https://aizhs.top
- 💻 GitHub:https://github.com/IHUI-INF-AI/IHUI-AI(Star サポート ⭐)
- 📦 8 端同一ソース:Web / API / CLI / Desktop / Extension / Mobile / Miniapp
- 🤖 176 モデル:OpenAI / Claude / Gemini / 通義 / DeepSeek / 智譜 / 文心 / 豆包 / Kimi / Ollama
- 💰 料金プラン:Free / Pro ¥49/月 / Team ¥199/人/月 / Enterprise ¥2999/月〜
5 分で Fork から本番稼働まで。ChatGPT Team+Claude Code+Notion AI の代替で、月 $60 以上節約。
この記事は元々 IHUI AI Blog で公開されました。AI エンジニアリングの最新情報は GitHub でフォローしてください。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.