なぜ「MCP」なのか? 共通規格がもたらすLLM連携のパラダイムシフト
LLM(大規模言語モデル)の業務活用が次のフェーズへと進む中、「プロンプトに手動でコンテキストをコピペする」段階から、「LLMが自律的に社内システムへアクセスし、必要なデータを取得する」エージェント型の利用へと移行しています。Anthropic公式ドキュメント(2025年時点)に記載されている通り、Claude 3ファミリー(Opus、Sonnet、Haiku)は高度な推論能力とツール呼び出し(function calling)機能を備えており、外部システムとの連携において強力なポテンシャルを発揮します。しかし、システム連携の現場では「開発・保守コストの増大」という大きな壁が存在していました。
従来の独自API連携とMCPの違い
従来のシステム開発において、LLMに外部ツールを操作させる場合、各LLMプロバイダーが提供する独自の仕様に合わせてAPIやラッパー層を開発する必要がありました。例えば、特定のLLM向けにツール連携を実装し、その後別のLLMにも対応させようとした場合、インターフェースの差異を吸収するためのコードを都度書き直さなければなりません。この「1対1」の密結合なアーキテクチャは、APIのスキーマ変更があった際にすべてのLLM向け接続コードを修正する必要があり、連携するモデルやツールが増えるほど保守コストが指数関数的に増大します。
ここで状況を一変させるのが、Model Context Protocol(MCP)です。MCPは、データソース(サーバー)とLLMアプリケーション(クライアント)の間の通信を標準化するオープンなプロトコルです。JSON-RPCをベースとしたこの共通規格を採用することで、一度開発したMCPサーバーは、プロトコルに対応するあらゆるLLMクライアントから再利用可能になります。「一度書けば、どこでも動く(Write once, run anywhere)」というソフトウェア工学の理想を、LLM連携の分野に持ち込んだのがMCPの最大の功績と言えるでしょう。
サーバー・クライアント・ホストの三位一体構造
MCPのアーキテクチャの優位性を理解する上で重要なのが、「サーバー」「クライアント」「ホスト」という3つの役割の明確な分離です。
- MCPサーバー:社内データベース、API、ローカルファイルなどのデータソースに直接接続し、MCPの仕様に従って機能を公開する軽量なプログラムです。データの取得や加工のロジックはすべてここに集約されます。
- MCPクライアント:Claude Desktopなどのアプリケーション内部で動作し、サーバーとの通信(JSON-RPCメッセージの送受信)を担当します。ユーザーのインターフェースとサーバーを橋渡しする役割を担います。
- ホスト(LLM):Claude 3などの基盤モデルそのものです。クライアントから渡されたコンテキストやツールの定義を解釈し、「次にどのツールを、どのような引数で実行すべきか」を推論します。
この構造により、機密性の高い社内データへのアクセス権限や認証情報はMCPサーバー側に安全に留保され、LLM本体には「必要なタイミングで、必要なデータのみ」が渡されます。セキュリティとプライバシーの観点からも、エンタープライズ要件を満たす非常に理にかなった疎結合アーキテクチャです。
開発準備:MCPサーバー構築のためのスタックと環境セットアップ
ここからは、実際にTypeScriptを用いて独自のMCPサーバーを構築する手順をステップバイステップで解説します。JSON-RPCベースの厳格な通信仕様を持つMCPプロトコルを扱う上で、堅牢な型システムを持つTypeScriptは非常に相性が良く、実行時エラーを未然に防ぐことができます。
Node.jsとTypeScriptの導入
まずはプロジェクトのベースとなる環境を構築します。最新のLTSバージョンのNode.jsがインストールされていることを前提とします。
適当なディレクトリを作成し、プロジェクトを初期化します。MCPサーバーはモダンなJavaScript環境で動作させるため、ES Modulesを有効化することが推奨されます。
# プロジェクトディレクトリの作成と移動
mkdir my-mcp-server
cd my-mcp-server
# package.jsonの初期化(ES Modulesを有効化)
npm init -y
npm pkg set type="module"
# TypeScriptと実行・型定義関連のパッケージをインストール
npm install -D typescript @types/node tsx
続いて、TypeScriptの設定ファイル(tsconfig.json)を生成し、Node.js環境に適した設定を行います。以下の設定をプロジェクトルートに保存してください。
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
MCP SDK(@modelcontextprotocol/sdk)のインストール
次に、MCPサーバーの中核となる公式SDKと、LLMからの入力値を安全に検証するためのバリデーションライブラリ(Zodなど)をインストールします。
# MCP公式SDKとZod、およびJSON Schema変換ツールのインストール
npm install @modelcontextprotocol/sdk zod zod-to-json-schema
ソースコードを配置する src ディレクトリを作成し、エントリーポイントとなる index.ts を用意すれば、開発の準備は完了です。
mkdir src
touch src/index.ts
【実践】MCPサーバーの基本実装:3つの主要機能を定義する
MCPには、LLMに提供する機能として「Resources(リソース)」「Tools(ツール)」「Prompts(プロンプト)」という3つの主要な概念が存在します。これらを一つのサーバーインスタンスにどのように統合していくか、具体的なコードとともに解説します。
Resources:静的データの提供
Resourcesは、ファイルの内容、データベースのスキーマ、社内規定のドキュメントなど、LLMに読み取らせたい「静的なデータ」を提供する機能です。各リソースはURI(Uniform Resource Identifier)で一意に識別されます。
以下は、index.ts にサーバーを初期化し、リソースを定義する基本コードです。
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// 1. サーバーインスタンスの作成
// サーバー名とバージョンを指定して初期化します
const server = new Server(
{
name: "my-internal-data-server",
version: "1.0.0",
},
{
capabilities: {
resources: {}, // Resources機能を有効化
tools: {}, // Tools機能を有効化
prompts: {}, // Prompts機能を有効化
},
}
);
// 2. 利用可能なリソースのリストを定義
server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: "internal://database/customers/schema",
name: "Customer Database Schema",
description: "社内顧客データベースのテーブル構造とスキーマ情報",
mimeType: "application/json",
},
],
};
});
// 3. リソースの読み込み処理を定義
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
if (request.params.uri === "internal://database/customers/schema") {
// 実際にはここでデータベース等から動的にスキーマ情報を取得します
const schemaData = {
tables: ["users", "orders", "subscriptions"],
version: "v2.1",
};
return {
contents: [
{
uri: request.params.uri,
mimeType: "application/json",
text: JSON.stringify(schemaData, null, 2),
},
],
};
}
throw new Error(`Resource not found: ${request.params.uri}`);
});
Tools:LLMからのアクション実行
Toolsは、LLMが外部システムに対してアクション(データの検索、計算処理、APIを通じた状態の変更など)を実行するための機能です。引数のスキーマをJSON Schema形式で明確に定義することで、LLMが正しいフォーマットでリクエストを生成できるようになります。
import {
ListToolsRequestSchema,
CallToolRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
// ツールの引数スキーマをZodで厳格に定義
const SearchCustomerSchema = z.object({
query: z.string().describe("検索したい顧客の氏名またはメールアドレス"),
limit: z.number().optional().default(5).describe("取得する最大件数(1〜50)"),
});
// 4. 利用可能なツールのリストを定義
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "search_customer",
description: "社内データベースから顧客情報を検索し、詳細を取得します",
// ZodスキーマをJSON Schemaに変換してLLMに提示
inputSchema: zodToJsonSchema(SearchCustomerSchema),
},
],
};
});
// 5. ツールの実行処理を定義
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "search_customer") {
// LLMからの入力値をZodで安全にパース・検証
const args = SearchCustomerSchema.parse(request.params.arguments);
// 擬似的な検索処理(実際はここでSQLクエリや社内APIを発行)
// 注意: 実際の運用では引数をエスケープ処理し、SQLインジェクションを防ぐ必要があります
const results = `検索クエリ「${args.query}」に対する結果(最大${args.limit}件): 該当データなし`;
return {
content: [
{
type: "text",
text: results,
},
],
};
}
throw new Error(`Tool not found: ${request.params.name}`);
});
Prompts:テンプレートの管理
Promptsは、再利用可能なプロンプトテンプレートをサーバー側で一元管理する機能です。これにより、複雑な業務指示や定型的なタスクのコンテキストを、クライアント(ユーザー側)ではなくサーバー側でコントロールできます。
import {
ListPromptsRequestSchema,
GetPromptRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// 6. 利用可能なプロンプトのリストを定義
server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
prompts: [
{
name: "analyze_customer_feedback",
description: "顧客フィードバックの感情分析と主要課題の抽出を行う標準プロンプト",
arguments: [
{
name: "feedbackText",
description: "分析対象となるフィードバックの生テキスト",
required: true,
},
],
},
],
};
});
// 7. プロンプトの取得処理を定義
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
if (request.params.name === "analyze_customer_feedback") {
const feedback = request.params.arguments?.feedbackText;
return {
description: "顧客フィードバック分析テンプレート",
messages: [
{
role: "user",
content: {
type: "text",
text: `以下の顧客フィードバックを分析し、1. 感情(ポジティブ/ネガティブ/ニュートラル)、2. 主要な課題、3. 改善提案 の3点に構造化してまとめてください。\n\n対象テキスト:\n${feedback}`,
},
},
],
};
}
throw new Error(`Prompt not found: ${request.params.name}`);
});
最後に、標準入出力(stdio)を使用してサーバーを起動し、クライアントからのリクエストを待ち受ける処理を追加します。MCPは標準入出力やSSE(Server-Sent Events)をトランスポート層として使用します。
// 8. サーバーの起動処理
async function run() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Server is running on stdio transport");
}
run().catch((error) => {
console.error("Fatal error occurred during server startup:", error);
process.exit(1);
});
これで基本的なMCPサーバーの実装は完了です。npx tsx src/index.ts コマンドで直接実行できる状態になっています。
Claude Desktopへの統合とデバッグ:動作確認のベストプラクティス
サーバーの実装が完了したら、実際にLLMクライアントから呼び出せるかを確認します。ローカル開発環境でのテストには、Anthropic社が提供するClaude Desktopアプリを使用するのが最もスムーズです。
config.jsonへのサーバー登録
Claude Desktopに自作のMCPサーバーを認識させるには、専用の設定ファイル(claude_desktop_config.json)を編集し、サーバーの起動コマンドを登録します。
設定ファイルの配置場所はOSによって異なります:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
以下のように、作成したサーバーのパスを指定して設定を記述します。
{
"mcpServers": {
"my-internal-server": {
"command": "npx",
"args": [
"tsx",
"/絶対パス/my-mcp-server/src/index.ts"
]
}
}
}
設定ファイルを保存し、Claude Desktopを再起動してください。設定が正しく読み込まれると、チャット入力欄の近くにコンセントのアイコン(MCP連携マーク)が表示され、定義したツール(search_customerなど)が利用可能になります。チャットで「社内データベースから顧客情報を検索して」と指示を出すと、Claudeが自律的にツールを呼び出す様子が確認できるはずです。
MCP Inspectorを活用したデバッグ手法
本番環境にデプロイする前や、Claude Desktopとの接続がうまくいかない場合は、公式のデバッグツールである「MCP Inspector」を活用することが推奨されます。
# MCP Inspectorを使用して作成したサーバーを起動
npx @modelcontextprotocol/inspector npx tsx src/index.ts
このコマンドを実行すると、ローカル環境にブラウザベースのインタラクティブなデバッグUIが立ち上がります。Inspector上では、JSON-RPCの生のメッセージログ(初期化リクエスト、リソース一覧の取得、ツール実行のペイロードなど)を視覚的に確認でき、各エンドポイントを手動でテストすることが可能です。開発の初期段階では、いきなりClaudeに接続するのではなく、まずInspectorで単体テストを完了させるのがエンジニアとしてのベストプラクティスです。
一歩進んだ応用:セキュリティ設計と運用におけるトラブルシューティング
MCPサーバーは、社内の機密データと外部のLLMをつなぐ「関所」としての役割を果たします。そのため、単に動くものを作るだけでなく、本番運用を見据えた強固なセキュリティ設計が不可欠です。
入力値のバリデーションとサニタイズ
LLMは確率的なモデルであるため、常にスキーマ通りの完璧なJSONを出力するとは限りません。ハルシネーションによる意図しない形式のデータや、悪意のあるプロンプトインジェクションに起因する不正なパラメータが渡されるリスクを常に想定する必要があります。
前述の実装例で導入した Zod のようなスキーマバリデーションライブラリは、この問題に対する強力な防壁となります。
- 厳格な型チェック: LLMからの入力が定義した型や文字数制限に完全に一致するかを検証します。
- 未知のプロパティの排除:
z.object().strict()などを活用し、スキーマに定義されていない余分なパラメータが渡された場合は、即座にエラーとして弾く設計にします。 - Human in the loop(人間の介在)の設計: データの読み取り(Read)だけでなく、データの更新や削除(Write/Delete)を伴うToolを実装する場合は、LLMの判断だけで即座に実行するのではなく、一度ユーザーに確認を求める承認フローをシステム側に組み込むことが強く推奨されます。
よくあるエラー(接続タイムアウト、認可失敗)の解決策
運用中に直面しやすい技術的トラブルとその解決策を整理しておきましょう。
- 接続タイムアウト: LLMからのツール呼び出しに対し、サーバー側の処理(重いDB検索や外部API連携)が遅延するとタイムアウトが発生します。これを防ぐため、検索範囲を絞る必須パラメータを設けたり、処理に時間がかかる場合は「処理を受け付けました。ID: 123で非同期実行中です」といったステータスを即座に返す設計に変更することが有効です。
- 環境変数の読み込みエラー: データベースのパスワードや外部APIキーを扱う場合、MCPサーバーの起動コンテキストで環境変数が正しく読み込まれているか確認が必要です。Claude Desktopから起動される場合、通常のターミナルとは環境変数のスコープが異なることがあるため、
.envファイルを明示的に読み込む処理(dotenvパッケージの利用など)をコード内に組み込むことが推奨されます。 - 標準出力の汚染による通信エラー: MCPは
stdioトランスポートを使用する場合、標準出力(stdout)をJSON-RPCの通信経路として占有します。そのため、デバッグ目的でconsole.log()を使用して文字列を出力すると、JSON-RPCプロトコルが破壊され、通信エラーとなります。ログ出力には必ずconsole.error()(標準エラー出力)を使用するよう徹底してください。
まとめと次のステップ:MCPエコシステムを組織に広げるために
本記事では、MCPがもたらすパラダイムシフトの理論から、TypeScriptを用いたサーバーの構築、Claude Desktopへの統合、そして実践的なセキュリティ設計までを一貫して解説しました。
独自のMCPサーバーを社内で共有する方法
一度構築したMCPサーバーは、個人のローカル環境に留めておくのではなく、チーム内で共有することで真の価値を発揮します。ソースコードを社内のGitリポジトリで管理し、Dockerコンテナとしてパッケージ化することで、他のエンジニアも自身の環境で容易にサーバーを立ち上げ、LLM連携の恩恵を受けることができます。
また、社内ポータルやドキュメント管理システム(Wikiなど)と連携する共通のMCPサーバーを一つ構築すれば、全社的なナレッジ検索の効率が飛躍的に向上するでしょう。
コミュニティMCPサーバーの活用
すべてをゼロから自作する必要はありません。MCPはオープンな規格であるため、GitHubやSmitheryなどのリポジトリには、Google Drive、Slack、Notion、PostgreSQLなど、主要なSaaSやデータベースと連携するためのオープンソースのMCPサーバーが既に多数公開されています。これらを組み合わせることで、開発コストを最小限に抑えながら、強力なAIアシスタント環境を構築することが可能です。
MCPを用いた社内システムのAI化は、単なる技術的実験にとどまらず、全社的な業務効率化の基盤となります。しかし、実際のエンタープライズ環境での導入には、既存の認証基盤との連携や、アクセス権限の細やかな制御など、本記事で触れた以外にも考慮すべきアーキテクチャ設計のポイントが多数存在します。
より安全かつスケーラブルな導入を進めるためには、自社の環境に応じた最適な設計パターンを知ることが重要です。個別の状況に応じたアドバイスを得ることで、より効果的な導入が可能になります。自社への適用を検討する際は、専門的なフレームワークや導入チェックリストなどの詳細資料を手元に置き、チーム内で共通認識を形成しながらプロジェクトを推進していくことをおすすめします。
コメント