MCPサーバーを自前で構築してAIエージェントに社内ツールを繋ぐ実践ガイド
AIエージェントは「繋がらない」と使えない
2026年、AIエージェントの性能は十分に上がった。だが現場で「使えない」と言われるケースの大半は、モデルの性能ではなく、社内ツールとの接続ができていないことが原因だ。
社内DB、Slack、監視ツール、チケット管理。これらにAIエージェントがアクセスできなければ、結局は人間がコピペで橋渡しすることになる。
Model Context Protocol(MCP) は、この「接続」の問題を解決するための標準プロトコルだ。MCPサーバーを自前で構築すれば、AIエージェントに社内の任意のツールを安全に繋ぐことができる。
この記事では、MCPサーバーを自前構築する際のアーキテクチャ設計、セキュリティの考慮点、実装パターンを解説する。
MCPとは何か ― 30秒で理解する
MCPは「AIエージェント(クライアント)が、外部ツール(サーバー)の機能を発見し、呼び出すためのプロトコル」だ。
AIエージェント(MCPクライアント)
↓ JSON-RPC over stdio / HTTP+SSE
MCPサーバー
↓ 内部API / SDK / SQL
社内ツール(DB, Slack, 監視, etc.)
ポイントは3つ。
- Tool Discovery ― エージェントがサーバーに「何ができるか?」を問い合わせる
- Tool Invocation ― エージェントがサーバーにツール実行をリクエストする
- Structured Response ― サーバーが構造化された結果を返す
REST APIとの違いは、AIエージェントが「使い方」を自動で理解できる形式でツールを公開する 点にある。
アーキテクチャ ― MCPサーバーの構成パターン
パターン1: stdioトランスポート(ローカル実行)
Claude Desktop / IDE拡張
↓ stdin/stdout(JSON-RPC)
MCPサーバー(ローカルプロセス)
↓
社内ツール
- 用途: 開発者個人の生産性向上、PoC
- メリット: 構築が簡単、ネットワーク不要
- 制約: 1対1接続、スケールしない
パターン2: HTTP+SSEトランスポート(サーバー配置)
AIエージェント群
↓ HTTP+SSE
MCPサーバー(Cloud Run / ECS / k8s)
├── 認証ミドルウェア
├── レート制限
└── 監査ログ
↓
社内ツール群
- 用途: チーム・組織全体での利用、本番運用
- メリット: 多対1接続、認証・監査が可能
- 制約: インフラ運用が必要
本番で使うならパターン2一択だ。 以降、このパターンを前提に解説する。
実装の骨格 ― TypeScript編
MCPサーバーの最小構成をTypeScriptで示す。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "internal-tools",
version: "1.0.0",
});
// ツールの定義
server.tool(
"search_tickets",
"社内チケットをキーワードで検索する",
{
query: z.string().describe("検索キーワード"),
status: z.enum(["open", "closed", "all"]).default("open"),
limit: z.number().max(50).default(10),
},
async ({ query, status, limit }) => {
// ここに実際の検索ロジックを実装
const results = await searchTicketStore(query, status, limit);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2),
},
],
};
}
);
// サーバー起動
const transport = new StdioServerTransport();
await server.connect(transport);
ポイントは Zodスキーマでパラメータを厳密に定義する こと。これがAIエージェントへの「使い方の説明書」になる。
実装の骨格 ― Python編
Python版の最小構成も示す。
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("internal-tools")
@mcp.tool()
async def query_metrics(
service_name: str,
time_range: str = "1h",
metric_type: str = "error_rate",
) -> str:
"""
指定サービスの監視メトリクスを取得する。
service_name: 対象サービス名
time_range: 取得期間(1h, 6h, 24h, 7d)
metric_type: メトリクス種別(error_rate, latency_p99, throughput)
"""
# 実際の監視APIへのクエリ
data = await fetch_monitoring_data(service_name, time_range, metric_type)
return format_metrics(data)
if __name__ == "__main__":
mcp.run(transport="stdio")
Python版は docstringがそのままツールの説明文になる。型ヒントとdocstringを丁寧に書くことが、エージェントの精度に直結する。
セキュリティ ― MCPサーバーで絶対に外せない5つ
MCPサーバーは 社内システムへのゲートウェイ だ。セキュリティ設計を誤ると、AIエージェント経由で社内データが漏洩する。
1. 認証・認可の分離
エージェント → MCPサーバー: OAuthトークン / APIキー
MCPサーバー → 社内ツール: サービスアカウント(最小権限)
エージェントのユーザーと、社内ツールへのアクセス権限は 必ず分離する。MCPサーバーが「誰のリクエストか」を検証し、そのユーザーの権限範囲内でのみツールを呼び出す。
2. 入力バリデーション
AIエージェントからの入力を 絶対に信用しない。
- SQLインジェクション対策(パラメータバインディング必須)
- パストラバーサル対策(ファイルパスのサニタイズ)
- 入力長制限(巨大なペイロードの拒否)
3. 出力フィルタリング
社内ツールからの応答にも注意が必要だ。
- 個人情報(PII)のマスキング
- 機密レベルに応じたフィールド除外
- トークン数を意識したレスポンスサイズ制限
4. レート制限と課金制御
ユーザーごと: 100 req/min
ツールごと: 50 req/min(DB系は10 req/min)
全体: 1000 req/min
AIエージェントは人間より高速にAPIを叩く。レート制限がないと、エージェントのループで社内システムが落ちる。
5. 監査ログ
すべてのツール呼び出しをログに残す。 例外なし。
{
"timestamp": "2026-04-16T10:30:00Z",
"user": "user-123",
"tool": "search_tickets",
"params": {"query": "障害", "status": "open"},
"result_size": 3,
"latency_ms": 245
}
「誰が、いつ、何を、どのツールで実行したか」を追跡できない状態で本番運用してはいけない。
実践ユースケース ― 何を繋ぐと効果が高いか
ユースケース1: 社内DBへの自然言語クエリ
AIエージェントに「先月のアクティブユーザー数を教えて」と聞くと、MCPサーバーが安全なSQLに変換して結果を返す。
勘所:
-
SELECT *は禁止。返却カラムをホワイトリストで制限 - 集計クエリのみ許可し、個別レコードの取得は認可チェック必須
- クエリのタイムアウトを短く設定(暴走防止)
ユースケース2: 障害対応の自動トリアージ
監視ツールのアラートをMCPサーバー経由でエージェントに渡し、ログ検索・影響範囲の特定・Slackへの報告を自動化する。
勘所:
- 書き込み系のツール(Slack投稿、チケット更新)は 承認ゲート を設ける
- 読み取り系は自動実行OK、書き込み系は人間の確認を挟む
- エスカレーション先の判断はエージェント任せにしない
ユースケース3: ドキュメント検索 + ナレッジ回答
社内WikiやConfluenceをMCPサーバー経由で検索可能にし、エージェントが回答を生成する。
勘所:
- ドキュメントのアクセス権限と連動させる(前述のセキュリティ要件)
- 検索結果にソースURLを必ず含める(幻覚対策)
- 古いドキュメントにはwarningフラグを付与
設計で意識すべき3原則
原則1: ツールは「小さく、単機能に」
❌ do_everything(action, target, params, ...)
✅ search_tickets(query, status, limit)
✅ get_ticket_detail(ticket_id)
✅ update_ticket_status(ticket_id, new_status)
1ツール = 1責務。AIエージェントは 小さなツールの組み合わせ の方が正確に使える。
原則2: 説明文(description)に全力を注ぐ
ツールの description と各パラメータの describe は、AIエージェントがツールを選択する際の判断材料になる。ここが曖昧だと、エージェントが間違ったツールを呼ぶ。
❌ "チケットを操作する"
✅ "指定されたチケットIDのステータスを変更する。open, in_progress, closed のいずれかに変更可能。変更にはチケットのオーナーまたは管理者権限が必要。"
原則3: エラーは構造化して返す
return {
content: [
{
type: "text",
text: JSON.stringify({
error: "PERMISSION_DENIED",
message: "このチケットのステータス変更権限がありません",
required_role: "ticket_owner",
current_role: "viewer",
}),
},
],
isError: true,
};
エラーの構造化は、エージェントが 次のアクションを判断する ために必要だ。
よくあるハマりどころ
| ハマりどころ | 対策 |
|---|---|
| ツールが多すぎてエージェントが迷う | 1サーバーあたり10〜15ツール以下に抑える |
| レスポンスが大きすぎてコンテキストを圧迫 | 要約・ページネーション・フィールド選択を実装 |
| 型が曖昧でエージェントが誤った値を渡す | Zodスキーマ / Pydanticで厳密に定義 |
| 本番DBに直接接続してしまう | 読み取りレプリカ or ビュー経由に限定 |
| エージェントが無限ループで呼び続ける | サーキットブレーカーとレート制限を必ず入れる |
本番運用に向けたチェックリスト
MCPサーバーを本番に出す前に、最低限以下を確認する。
- 認証・認可が実装されているか
- 全ツールに入力バリデーションがあるか
- 出力にPIIが含まれないか(マスキング処理)
- レート制限が設定されているか
- 監査ログが記録されているか
- エラーハンドリングが構造化されているか
- ヘルスチェックエンドポイントがあるか
- タイムアウトが適切に設定されているか
- 読み取りと書き込みの権限が分離されているか
- 負荷テストを実施したか
まとめ
- MCPサーバーは AIエージェントと社内ツールを繋ぐゲートウェイ である
- stdioは検証用。本番では HTTP+SSEトランスポートでサーバー配置 する
- セキュリティの5要素(認証認可・入力検証・出力フィルタ・レート制限・監査ログ)は省略不可
- ツールは 小さく、単機能に、説明文を丁寧に 書く
- 実装自体はシンプル。難しいのは設計とセキュリティ だ
MCPサーバーの構築は、コードを書くこと自体は難しくない。本当に難しいのは、何を繋ぐか、どこまでアクセスさせるか、どう監視するか という設計判断だ。
ここを誤ると、便利なツールが一転してセキュリティホールになる。
MCPサーバーの設計・構築・セキュリティレビューについてのご相談はお気軽にどうぞ。
Discussion