本チュートリアルのゴール:MCPが解決する「API連携の断絶」
LLM(大規模言語モデル)の業務活用が進む中、多くの組織が直面するのが「社内データや既存システムとの連携」という壁です。本記事では、Anthropicが提唱する「Model Context Protocol(MCP)」を用いて、既存の社内REST APIをLLMの「道具(ツール)」として安全かつ効率的に統合する設計手法を解説します。
なぜこれまでのAPI連携は非効率だったのか
LLMに外部APIを叩かせる従来の独自実装では、プロンプト内にAPIの仕様やエンドポイント、リクエスト形式を長々と記述する必要がありました。これはプロンプトの肥大化を招き、トークン消費量を増加させるだけでなく、LLMがハルシネーション(もっともらしい嘘)を起こして誤ったパラメータでAPIを呼び出すリスクを孕んでいました。また、システムごとに異なる認証方式やデータフォーマットの不一致といった「API連携の断絶」は、開発現場において珍しい課題ではありません。
MCP(Model Context Protocol)がもたらす標準化のメリット
MCPは、LLMと外部データソース(API、データベース、ローカルファイルなど)を接続するための「標準化されたインターフェース」です。クライアント(LLMアプリ)とサーバー(データソース側)を分離し、共通のプロトコルで通信を行うことで、LLMはAPIの複雑な内部仕様を知る必要がなくなります。公式ドキュメントに記載されている通り、MCPは「Resources(データ読み取り)」「Tools(アクション実行)」「Prompts(定型指示)」という3つの主要機能を提供します。本チュートリアルを通じて、社内APIをMCPサーバーとしてラップし、LLMに安全なアクセス権を付与する一連の開発フローをマスターしていきましょう。
ステップ1:開発環境の構築とMCP SDKのセットアップ
MCPサーバーの実装には、型安全性が高くJSON Schemaとの相性が良いTypeScript(Node.js)の利用が一般的に推奨されます。ここでは、TypeScriptを用いたプロジェクトの立ち上げ手順を解説します。
必要なツール(Node.js/TypeScript)の準備
開発を始める前に、最新のNode.js環境がインストールされていることを確認してください。バージョン管理ツール(nvmなど)を使用して、安定版(LTS)を利用することを推奨します。
# Node.jsのバージョン確認
node -v
npm -v
MCP SDKのインストールとプロジェクト初期化
適当な作業ディレクトリを作成し、npmプロジェクトを初期化します。その後、Anthropicが提供する公式のMCP SDKと、TypeScriptの実行に必要なパッケージをインストールします。
mkdir mcp-api-server
cd mcp-api-server
npm init -y
# MCP SDKのインストール
npm install @modelcontextprotocol/sdk
# TypeScriptと開発用ツールのインストール
npm install -D typescript @types/node ts-node
次に、tsconfig.json を生成し、TypeScriptの設定を行います。
npx tsc --init
tsconfig.json の内容を以下のように調整し、モダンなモジュール解決ができるようにします。
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist"
},
"include": ["src/**/*"]
}
これで、MCPサーバーを開発するための基本的な土台が整いました。
ステップ2:リソース(Resources)の定義:静的データへのアクセス設計
MCPにおける「Resources」は、LLMに対して読み取り専用のデータを提供する機能です。社内Wikiのドキュメントや、設定ファイル、データベースの参照など、副作用を伴わない静的なデータアクセスに適しています。
URIスキームを用いたデータ特定
Resourcesは、file:// や独自に定義した custom-api:// などのURIスキームを用いてデータを特定します。以下のコードは、社内の従業員ディレクトリAPIからデータを取得し、LLMに提供するResourceの実装例です。
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
// MCPサーバーのインスタンス化
const server = new Server(
{
name: "employee-directory-server",
version: "1.0.0",
},
{
capabilities: {
resources: {},
},
}
);
// リソース一覧の定義
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "api://employees/directory",
name: "Employee Directory",
mimeType: "application/json",
description: "社内の全従業員リストと部署情報を含むディレクトリデータ",
},
],
};
});
LLMが読み取り可能なメタデータ記述のコツ
LLMがデータを正確に解釈するためには、mimeType と description の記述が極めて重要です。単に「従業員データ」と書くのではなく、「社内の全従業員リストと部署情報を含むディレクトリデータ」のように、データに何が含まれているかを具体的に記述します。
// リソースの読み取り処理
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
if (request.params.uri === "api://employees/directory") {
// 実際の社内APIを呼び出す想定のモックデータ
const employeeData = [
{ id: "E001", name: "山田 太郎", department: "情報システム部" },
{ id: "E002", name: "佐藤 花子", department: "営業部" }
];
return {
contents: [
{
uri: request.params.uri,
mimeType: "application/json",
text: JSON.stringify(employeeData, null, 2),
},
],
};
}
throw new Error("Resource not found");
});
このように設計することで、LLMは必要な時に自律的にこのURIを参照し、コンテキストとして情報を読み込むことが可能になります。
ステップ3:ツール(Tools)の実装:副作用を伴うAPI操作の設計
データの更新、メールの送信、チケットの起票など、システムに状態変化(副作用)をもたらす操作は「Tools」として実装します。LLMが外部APIを呼び出す際のインターフェースとなる最も重要なセクションです。
JSON Schemaによる引数の厳密な定義
LLMにツールを使わせる際、最もつまずきやすいのが「引数の型不一致」によるエラーです。これを防ぐためには、JSON Schemaを用いて引数の構造を厳密に定義し、さらに各プロパティに詳細な説明(description)を付与することが不可欠です。
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
// ツール一覧の定義
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "create_support_ticket",
description: "社内ヘルプデスクシステムに新しいサポートチケットを起票します。",
inputSchema: {
type: "object",
properties: {
title: {
type: "string",
description: "チケットのタイトル。簡潔に課題を要約してください。",
},
priority: {
type: "string",
enum: ["low", "medium", "high", "critical"],
description: "インシデントの優先度。",
},
description: {
type: "string",
description: "問題の詳細な説明。発生日時やエラーメッセージを含めてください。",
},
},
required: ["title", "priority", "description"],
},
},
],
};
});
ここで重要なのは、enum を用いて許容される値を制限することと、description でLLMに「どのような形式で値を渡すべきか」を指示することです。この設計が甘いと、LLMは独自の判断で存在しない優先度(例:"urgent")を指定してしまう可能性があります。
既存APIエンドポイントとのマッピング手法
ツールが呼び出された際の処理(CallToolRequestHandler)を実装します。ここで、LLMから渡された引数を既存のREST APIのリクエスト形式に変換(マッピング)して送信します。
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "create_support_ticket") {
// 引数の型アサーション
const args = request.params.arguments as {
title: string;
priority: string;
description: string;
};
try {
// 既存の社内APIへのPOSTリクエストをシミュレート
// 実際には fetch や axios を使用してAPI通信を行います
const apiResponse = {
status: "success",
ticketId: "TICKET-" + Math.floor(Math.random() * 10000),
message: "チケットが正常に作成されました。",
};
return {
content: [
{
type: "text",
text: JSON.stringify(apiResponse, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `API通信エラー: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true, // LLMにエラーが発生したことを明示的に伝える
};
}
}
throw new Error("Tool not found");
});
エラーハンドリングを適切に行い、isError: true を返すことで、LLMは「実行に失敗した」ことを認識し、引数を修正して再試行するなどのリカバリー行動をとることが可能になります。
ステップ4:認証とセキュリティ:安全な中継層の設計
社内APIをLLMから呼び出す場合、セキュリティの確保は最優先課題です。MCPサーバーは、LLMと社内システムの間の中継層(プロキシ)として機能するため、ここで適切なアクセス制御を行う必要があります。
APIキー・OAuthトークンの秘匿管理
LLMのプロンプトやクライアント側に社内APIの認証情報を持たせることは避けるべきです。認証情報はMCPサーバー側で環境変数として保持し、サーバーがAPIを呼び出す際にヘッダーに付与する設計とします。
// 環境変数からAPIキーを取得
const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY;
if (!INTERNAL_API_KEY) {
console.error("環境変数 INTERNAL_API_KEY が設定されていません。");
process.exit(1);
}
// API呼び出し時のヘッダー設定例
/*
const response = await fetch("https://api.example.com/v1/tickets", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${INTERNAL_API_KEY}`
},
body: JSON.stringify(payload)
});
*/
ローカル環境と本番環境での接続設定
MCPサーバーをClaude Desktopなどのクライアントアプリケーションと接続するには、設定ファイル(claude_desktop_config.json)を編集します。この設定ファイル内で、MCPサーバーの起動コマンドと、必要な環境変数を渡します。
{
"mcpServers": {
"internal-api-server": {
"command": "node",
"args": ["/path/to/mcp-api-server/dist/index.js"],
"env": {
"INTERNAL_API_KEY": "your_secure_api_key_here"
}
}
}
}
この設計により、LLM自体はAPIキーの存在を知ることなく、安全に社内システムと対話することができます。本番環境へのデプロイにおいては、AWS Secrets ManagerやHashiCorp Vaultなどのシークレット管理サービスと連携することで、さらなるセキュリティ強化が図れます。
ステップ5:デバッグと検証:LLMとの疎通確認
実装したMCPサーバーを本番投入する前に、意図した通りに動作するかを検証するプロセスが不可欠です。
MCP Inspectorを用いた単体テスト
Anthropicは、MCPサーバーの開発とデバッグを支援する公式ツールとして「MCP Inspector」を提供しています。これを使用することで、LLMを介さずにMCPサーバーのResourcesやToolsをブラウザ上から直接テストすることができます。
# サーバーのビルド
npx tsc
# MCP Inspectorの起動
npx @modelcontextprotocol/inspector node dist/index.js
Inspector上で、定義したツール(create_support_ticket など)を選択し、手動でJSON形式の引数を入力して実行結果を確認します。ここで型エラーや通信エラーが発生しないことを確認することが、最初の関門となります。
実際のLLMクライアントでの動作確認
Inspectorでの単体テストに合格したら、Claude Desktopなどの対応クライアントから実際のLLMモデルと対話して疎通確認を行います。
プロンプト例:
「社内システムでエラーが発生しました。優先度を高(high)にして、サポートチケットを起票してください。タイトルは『DB接続エラー』、詳細は『タイムアウトが頻発しています』としてください。」
LLMがMCPサーバーのツールを呼び出し、成功のレスポンスを受け取って「チケット(TICKET-XXXX)を起票しました」と自然言語で回答できれば、API連携は成功です。もしLLMが意図しない引数を生成した場合は、ステップ3で解説したJSON Schemaの description を見直し、より明確な指示を記述するチューニングを行います。
さらなる応用:プロンプトテンプレートの活用と将来の拡張性
基本となるResourcesとToolsの実装が完了したら、MCPのもう一つの強力な機能である「Prompts」を活用することで、さらなるUXの向上が期待できます。
Prompts機能による定型タスクの自動化
Promptsは、再利用可能なプロンプトのテンプレートをMCPサーバー側で定義する機能です。例えば「コードレビュー依頼」や「日報の自動生成」など、頻繁に行うタスクのプロンプト構造をサーバー側で管理することで、ユーザーは複雑な指示を毎回入力する手間から解放されます。
マルチサーバー構成への発展と継続的な学習
実際の企業環境では、人事システム、CRM、開発ツールなど、複数の独立したMCPサーバーを立ち上げ、LLMがそれらを横断的に利用するマルチサーバー構成への発展が考えられます。これにより、LLMは「人事データを確認し、該当者のCRMアカウントを更新する」といった、部門を跨いだ自律的なエージェントとして機能するようになります。
AIと外部システムの統合技術は、日々急速な進化を遂げています。MCPのような新しいプロトコルや、LLMのエージェント化に関する最新動向をキャッチアップするには、技術ブログや公式ドキュメントを定期的に確認するだけでなく、X(旧Twitter)やLinkedInなどのSNSで専門家の発信を継続的にフォローし、情報収集の仕組みを整えることをおすすめします。自社のインフラにAIを安全かつ効果的に組み込むための第一歩として、ぜひ本チュートリアルの実装に挑戦してみてください。
コメント