LLM(大規模言語モデル)を自社の業務に組み込む際、多くの開発現場では「LLMと社内データベースを連携させるための専用API」を都度開発しています。しかし、この「とりあえずAPIを叩かせる」というアプローチは、モデルやツールが増えるたびにカスタム接続のコードが乱立し、保守不可能な技術的負債を生み出す原因となります。
こうした課題を根本から解決するために注目されているのが、AIモデルと外部システムを標準化された規格で接続するアプローチ(Model Context Protocol、以下MCP)です。これは、LLMとツール間の通信をJSON-RPCベースで標準化し、セキュアかつスケーラブルな統合を実現するものです。
本記事では、既存の「使ってみた」レベルの解説から一歩踏み込み、自社サーバーを構築するための「仕様書(リファレンス)」として、MCPプロトコルのアーキテクチャと具体的な実装手順を詳細に解説します。
MCP(Model Context Protocol)のアーキテクチャ:Client-Serverモデルの核心
MCPの基本構造は、従来のWebアプリケーションにおけるクライアント・サーバーアーキテクチャを踏襲しつつ、AIエージェントのコンテキスト管理に最適化されています。このプロトコルは、各コンポーネントの責務を明確に分離することで、セキュリティ境界を適切に維持します。
Host、Client、Serverの3層構造
MCPのアーキテクチャは、大きく以下の3つの層で構成されています。
- Host(ホスト):ClaudeなどのLLMアプリケーション本体を指します。ユーザーとのインターフェースを提供し、LLMの推論を実行する基盤となります。
- Client(クライアント):Hostの内部で動作し、外部のServerとの通信を仲介します。プロトコルの解釈やルーティングを担当します。
- Server(サーバー):ローカルファイル、社内データベース、外部SaaSなどのデータソースやツールをカプセル化し、Clientからの要求に応じて機能を提供するエンドポイントです。
この3層構造により、Host(LLM側)は各データソースの具体的な接続仕様を知る必要がなくなり、Server側もどのLLMから呼び出されるかを意識せずに単一のインターフェースを実装するだけで済むようになります。
トランスポート層(Stdio vs SSE)の選択基準
MCPサーバーとクライアント間の通信(トランスポート層)には、主に「Stdio(標準入出力)」と「SSE(Server-Sent Events)」の2種類が存在します。システム要件に応じてこれらを適切に選択することが、インフラ設計の第一歩となります。
Stdio(標準入出力):
ローカル環境でのプロセス間通信に使用されます。ネットワークポートを開く必要がないためセキュリティリスクが低く、クライアントアプリ(Host)と同じマシン上で動作するサイドカーパターンのデプロイに最適です。スケーラビリティには制限がありますが、個人用ツールやセキュアな閉域網での単一ノード運用に適しています。SSE(Server-Sent Events):
HTTPベースの通信であり、リモートサーバーへの接続に必須となります。クライアントからサーバーへのリクエストは通常のHTTP POSTで行い、サーバーからのプッシュ通知やストリーミング応答をSSEで受け取ります。ロードバランサーの背後での運用や、OAuthなどの認証レイヤーの追加が容易であり、エンタープライズ規模のシステム連携においてはSSEの採用が標準的な選択肢となります。
認証と接続確立:安全なセッション管理の実装仕様
MCPにおける通信は、厳密なハンドシェイク(接続確立プロセス)から始まります。ここでは、セキュアな接続を確立するための初期化プロセスの仕様を定義します。
初期化プロセス(Initialize Request/Result)
通信が開始されると、クライアントはサーバーに対して initialize リクエストを送信します。このリクエストには、クライアントのメタデータとサポートするプロトコルバージョンが含まれます。具体的なバージョン番号は仕様のアップデートに伴い変化するため、常に最新の公式ドキュメントで指定された protocolVersion を使用することが求められます。
// クライアントからのInitialize Request例
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "LATEST_VERSION",
"clientInfo": {
"name": "example-enterprise-client",
"version": "1.0.0"
},
"capabilities": {
"roots": {
"listChanged": true
}
}
}
}
Capabilitiesのネゴシエーション
サーバー側はリクエストを受け取ると、自身が提供可能な機能(Capabilities)を返します。これにより、クライアントとサーバー間で「どの機能(Resources, Tools, Prompts)が利用可能か」を合意します。
// サーバーからのInitialize Result例
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "LATEST_VERSION",
"serverInfo": {
"name": "internal-db-server",
"version": "2.1.0"
},
"capabilities": {
"resources": {},
"tools": {},
"prompts": {}
}
}
}
このネゴシエーションが完了した後、クライアントから initialized 通知が送信され、正式にセッションが確立されます。運用環境では、この段階で認証トークンの検証やアクセス権限のチェックを行うよう設計することが一般的です。
Resourcesリファレンス:静的データへのアクセス設計
「Resources(リソース)」は、LLMに対してコンテキストとなるデータ(ドキュメント、データベースのレコード、ログファイルなど)を読み取り専用で提供するインターフェースです。
Resource URIの命名規則
リソースは一意のURI(Uniform Resource Identifier)によって識別されます。REST APIのパス設計と同様に、階層的で予測可能なURIスキームを設計することがベストプラクティスです。
例:file:///logs/system.log や postgres://internal-db/customers/schema
List ResourcesとRead Resourceの挙動
サーバーは、利用可能なリソースの一覧を返す resources/list エンドポイントと、特定のリソースの内容を返す resources/read エンドポイントを実装します。
Read Resourceのレスポンスでは、MIMEタイプを使用してコンテンツの形式(プレーンテキスト、JSON、画像など)を明示します。これにより、LLM側がデータを適切にパースし、コンテキストとして活用できるようになります。
// Read Resourceのレスポンス例
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"contents": [
{
"uri": "example://company/guidelines/security",
"mimeType": "text/markdown",
"text": "# 社内セキュリティガイドライン\n..."
}
]
}
}
Toolsリファレンス:アクション実行のインターフェース定義
「Tools(ツール)」は、LLMが外部システムに対してアクション(APIの実行、データベースへの書き込み、計算処理など)を実行するためのインターフェースです。Resourcesが「読み取り」であるのに対し、Toolsは「副作用を伴う操作」や「引数を取る動的な処理」を担当します。
JSON Schemaによる引数の定義
LLMがツールを正確に呼び出せるよう、サーバー側はツールの仕様をJSON Schema形式で厳密に定義して公開します(tools/list)。引数の型、必須項目、説明文(description)を詳細に記述することで、LLMのハルシネーション(パラメータの捏造)を防ぐことができます。
// ツールの定義例
{
"name": "create_jira_ticket",
"description": "Jiraに新しい課題チケットを起票します。",
"inputSchema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "チケットのタイトル"
},
"priority": {
"type": "string",
"enum": ["High", "Medium", "Low"]
}
},
"required": ["title", "priority"]
}
}
Call Toolリクエストの処理フロー
LLMがツールを選択すると、クライアントから tools/call リクエストが送信されます。サーバーはこのリクエストを受け取り、実際のビジネスロジックを実行した後、結果をテキストや画像として返却します。エラーが発生した場合は、プロトコルに準拠したエラーコードを返すことで、LLMに再試行や代替案の提示を促すことができます。
Promptsリファレンス:動的な対話テンプレートの共通化
「Prompts(プロンプト)」は、特定のタスクに最適化されたプロンプトテンプレートをサーバー側で一元管理し、クライアントに提供する仕組みです。
引数付きプロンプトの設計
クライアントごとにプロンプトをハードコーディングするのではなく、サーバー側で「コードレビュー用プロンプト」「議事録要約用プロンプト」などのテンプレートを管理します。これらは引数を受け取ることができ、動的にコンテキストを構築します。
Get Promptによるテンプレート取得
クライアントは prompts/get リクエストに引数を添えて送信し、サーバーは組み立てられたプロンプトメッセージの配列を返します。これにより、プロンプトエンジニアリングの成果を組織全体で標準化・再利用することが可能になります。
実装ガイド:TypeScript/Python SDKを用いた構築手順
概念を理解したところで、実際の実装手順を解説します。本番環境での構築においては、公式またはコミュニティ提供のMCP SDKパッケージを利用することで、JSON-RPC通信の低レイヤーな処理を隠蔽し、ビジネスロジックの実装に集中できます。
MCP Server SDKのセットアップ
TypeScript環境を例にとると、初期化とサーバーのインスタンス化は以下のような構造になります。パッケージ名は環境や言語によって異なるため、最新の公式ドキュメントを参照して適切なものをインストールしてください。
// 公式/コミュニティ提供のMCP SDKをインポート(疑似コード)
import { Server } from "mcp-sdk-package/server";
import { StdioServerTransport } from "mcp-sdk-package/transport/stdio";
// サーバーの初期化
const server = new Server(
{
name: "internal-tools-server",
version: "1.0.0",
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
ハンドラーの実装例(Tool)
SDKのルーティング機能を使用して、特定のツールが呼び出された際のハンドラーを実装します。既存の自社APIをラップする場合、ここで内部のHTTPクライアントやデータベースドライバを呼び出します。
// ツールの実行ハンドラー設定
server.setRequestHandler("tools/call", async (request) => {
if (request.params.name === "create_jira_ticket") {
const { title, priority } = request.params.arguments;
// 内部APIの呼び出し(実装例)
const ticketId = await internalApi.createTicket(title, priority);
return {
content: [
{
type: "text",
text: `チケットが正常に起票されました。ID: ${ticketId}`
}
]
};
}
throw new Error("Unknown tool");
});
// サーバーの起動(Stdioトランスポートを使用)
const transport = new StdioServerTransport();
await server.connect(transport);
console.log("MCP Server is running");
運用設計とトラブルシューティング:レート制限とデバッグ
本番環境にMCPサーバーを導入する際、開発時とは異なる運用上の課題が発生します。安定稼働のための設計とデバッグ手法を確立しておくことが重要です。
MCP Inspectorによるデバッグ手法
MCPはバックグラウンドでJSON-RPC通信を行うため、問題発生時に「LLMの解釈エラー」なのか「サーバーの実装エラー」なのかを切り分けるのが困難です。この課題を解決するため、公式提供のInspectorツール等を使用して、LLMを介さずに直接サーバーの挙動をテストするアプローチが推奨されます。
# 公式提供のInspectorツール等を使用してサーバーを起動・検証する例
npx mcp-inspector-tool node build/index.js
これにより、ブラウザ上でGUIを通じてResourcesの取得やToolsの実行をシミュレートし、レスポンスの構造がプロトコル仕様に適合しているかを検証できます。
エラーコードとリトライ戦略
外部APIのレート制限やタイムアウトに対処するため、サーバー側で適切なエラーハンドリングを実装する必要があります。MCPプロトコルでは標準的なJSON-RPCエラーコード(例:-32603 Internal error)を使用します。LLMはエラーメッセージを読み取って自律的にリトライや引数の修正を行う能力があるため、エラーレスポンスには「何が原因で失敗したか」「どのように修正すべきか」を自然言語で詳細に含めることが、AI統合における重要な設計パラダイムとなります。
導入判断の最終チェックリスト:ROI最大化とリスク管理
最後に、MCPアーキテクチャの採用を組織内で決断するための評価基準を整理します。
独自実装とMCP採用の分岐点
単一のLLMと単一のデータベースを繋ぐだけの小規模なPoCであれば、従来のカスタムスクリプトでも十分かもしれません。しかし、以下のような要件が一つでも当てはまる場合、MCP規格に準拠した設計への移行を強く推奨します。
- 複数の異なるLLMプラットフォームから同じ社内データにアクセスさせたい
- 提供するツールやデータソースの種類が今後増える予定がある
- 認証やアクセス制御のロジックを、LLMアプリ側から分離して一元管理したい
セキュリティコンプライアンスの確認
MCPサーバーを社内ネットワークにデプロイする際は、LLMからのリクエストが意図しない破壊的アクションを引き起こさないよう、Toolsの権限を最小特権の原則に基づいて設計してください。特にデータベースの削除や権限変更などの重大な操作は、Toolsとして公開しないか、実行前に必ず人間の承認(Human-in-the-loop)を挟むワークフローを組み込むべきです。
まとめ
LLMデータ連携の標準化は、AIを業務プロセスに組み込む上での避けて通れないインフラ要件となりつつあります。アーキテクチャの全体像を把握し、プロトコルの仕様に則って実装を進めることで、スケーラブルで保守性の高いシステムを構築することが可能です。
ただし、MCPサーバーの構築や既存システムからの移行、セキュアなインフラ構成の設計は、ドキュメントを読むだけでは直感的に理解しづらい部分も存在します。自社への適用を検討する際は、専門家によるハンズオン形式のセミナーで実際のコードを動かしながら学んだり、個別の状況に応じたアーキテクチャのアドバイスを得ることで、導入リスクを大幅に軽減し、プロジェクトを加速させることが可能です。技術選定と実装の次の一手として、実践的な学習の場を活用することをおすすめします。
コメント