🔌

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つ。

  1. Tool Discovery ― エージェントがサーバーに「何ができるか?」を問い合わせる
  2. Tool Invocation ― エージェントがサーバーにツール実行をリクエストする
  3. 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サーバーの設計・構築・セキュリティレビューについてのご相談はお気軽にどうぞ。

https://nupwhite.com/contact

Discussion