MCPとは? から、LLMの能力を最大限引き出す共通ルールMCPをPythonで自作し、Elasticsearchと連携させる具体的な手順を説明します。
LLMは万能じゃない?AI導入の壁
ChatGPTやClaudeなどの大規模言語モデル(LLM)は、私たちの日常業務に革命をもたらしています。しかし、こんな疑問を持ったことはありませんか?
- 「メールを送って」と頼んだのに、実際には送ってくれない
- 検索はできるけど、社内データベースとのやり取りは難しい
そう、LLMはテキストを“予測”するだけのモデルに過ぎず、自分で“行動”する力は持っていません。
MCP(Model Context Protocol)とは?
MCPとは、LLMがさまざまな外部ツールと統一的に連携できるようにするための「共通ルール(プロトコル)」です。
ソフトウェア開発の世界では「標準」がとても重要です。たとえば「REST API」は、異なるシステム同士がスムーズに連携できる共通ルールとして広く使われています。
この“標準の考え方”をLLMにも適用したものが、MCPになります。
なぜ今、MCPが注目されているのか?
従来のようにツールごとにバラバラのAPIを組み合わせる必要はなく、MCPを通すことで統一された方法でさまざまなタスクを実行できます。
- ツール連携の仕組みがシンプルになり、開発効率が向上
- コードの再利用性や保守性がアップ
- セキュリティの統一管理が可能
- LLMを実際の業務・サービスに活用しやすくなる
MCPエコシステム
MCPエコシステムは以下の要素で構成されています。
- MCPクライアント/ホスト:
- 例:Claude, Wind surf, Cursorなど。
- 機能:エコシステムのクライアント側、すなわちLLM側となる部分です。
- プロトコル:
- 機能:クライアントとサーバー間の双方向の接続を担います。
- 場所:MCPクライアントとMCPサーバーの間に存在します。
- MCPサーバー:
- 機能:外部サービス(その機能や実行可能なこと)をクライアントに翻訳して伝えます。
- サービス:
- 機能:LLMがアクセスしたい外部の機能やデータソースそのものです(例:Elastic)。
MCPの仕組みを理解する:シーケンス図で見る処理の流れ
MCPを利用したLLMと外部サービスの連携は、以下のシーケンス図のように進行します。

【実践】MCPサーバーをPythonで構築しElasticsearchと連携する手順
ここからは、実際にMCPサーバーを構築し、Elasticsearchと連携する手順を示します。これにより、自然言語を用いたElasticsearchの操作が可能になります。
前提条件
- Macbook Pro M4チップ
- VS code、Python が使える好みのIDE
- Claude Desktop アプリをインストール済み(今回は無料枠)
- Elasticsearch 環境
※MCPサーバー作成の公式クイックスタートを参考に進みますhttps://github.com/modelcontextprotocol/python-sdk
ステップ1: UVパッケージマネージャーの導入
uvをインストール。
※https://docs.astral.sh/uv/getting-started/installation/
curl -LsSf https://astral.sh/uv/install.sh | sh
インストール後、以下で確認:
which uv
/usr/local/bin/uv などが出れば成功です。
ステップ2: MCPサーバーの作成
ターミナルを開き、以下のコマンドで新しいプロジェクト(mcp-elasticsearch)を初期化します。
uv init mcp-elasticsearch
作成されたプロジェクトディレクトリに移動します。
cd mcp-elasticsearch
MCP の CLI パッケージをプロジェクトの依存関係に追加します。
uv add "mcp[cli]"
これで、MCP Python SDK がインストールされ、プロジェクトの準備が完了しました。
ステップ3: MCPサーバーのコードを書く
Python用のMCP公式SDKを参考に進みます。https://github.com/modelcontextprotocol/python-sdk
- mcp-elasticsearch プロジェクトを Visual Studio Code で開きます。
- env ファイルを作成し、以下を記述。
ELASTIC_USER=elastic
ELASTIC_PASSWORD=****
ELASTIC_CLOUD_URL=https://your-deployment.aws.found.io
- プロジェクトフォルダにあるmain.py の中身を以下のコードに置き換えます。
# =========================================
# Elasticsearch MCP Server for Claude
# =========================================
from mcp.server.fastmcp import FastMCP
from elasticsearch import Elasticsearch
from dotenv import load_dotenv
import os
# ----------------------
# 🔧 Setup
# ----------------------
print("Loading environment variables...")
load_dotenv()
# Start MCP server
mcp = FastMCP("ElasticsearchMCP")
# Connect to Elasticsearch
try:
es = Elasticsearch(
os.getenv("ELASTIC_CLOUD_URL"),
basic_auth=(os.getenv("ELASTIC_USER"), os.getenv("ELASTIC_PASSWORD"))
)
print("🔗 Connected to Elasticsearch!")
except Exception as e:
print("❌ Failed to connect:", e)
es = None
# ----------------------
# 🛠️ MCP Tools
# ----------------------
@mcp.tool()
def list_indices() -> list:
"""Get all index names in the cluster."""
# ES Query: GET /_cat/indices
return list(es.indices.get_alias().keys()) if es else ["ES not initialized"]
@mcp.tool()
def get_cluster_health() -> dict:
"""Check the health status of the Elasticsearch cluster."""
# ES Query: GET /_cluster/health
return es.cluster.health() if es else {"error": "ES not initialized"}
@mcp.tool()
def count_documents(index_name: str) -> int:
"""Count how many documents exist in a given index."""
# ES Query: GET /{index}/_count
try:
return es.count(index=index_name)['count'] if es else -1
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
def search_index(index_name: str, keyword: str) -> list:
"""Search for documents in an index using a keyword."""
# ES Query:
# POST /{index}/_search
# {
# "query": { "match": { "_all": "keyword" } }
# }
try:
result = es.search(
index=index_name,
query={"match": {"_all": keyword}},
size=5
)
return [doc["_source"] for doc in result["hits"]["hits"]]
except Exception as e:
return [f"Error: {str(e)}"]
@mcp.tool()
def get_index_mapping(index_name: str) -> dict:
"""Retrieve the field definitions (mappings) of an index."""
# ES Query: GET /{index}/_mapping
try:
return es.indices.get_mapping(index=index_name)[index_name]["mappings"]
except Exception as e:
return {"error": str(e)}
@mcp.tool()
def delete_index(index_name: str) -> str:
"""Delete an entire index (irreversible)."""
# ES Query: DELETE /{index}
try:
es.indices.delete(index=index_name)
return f"✅ Deleted index '{index_name}'"
except Exception as e:
return f"❌ Error: {str(e)}"
@mcp.tool()
def get_recent_docs(index_name: str, size: int = 5) -> list:
"""Fetch the most recent documents from an index."""
# ES Query:
# POST /{index}/_search
# {
# "size": 5,
# "sort": [{ "@timestamp": "desc" }]
# }
try:
result = es.search(
index=index_name,
size=size,
sort=[{"@timestamp": "desc"}]
)
return [doc["_source"] for doc in result["hits"]["hits"]]
except Exception as e:
return [f"Error: {str(e)}"]
# ----------------------
# Start MCP Server
# ----------------------
if __name__ == "__main__":
print("🚀 Starting MCP server...")
if os.environ.get("RUN_MCP_MANUALLY"):
print("Running in manual mode (not Claude Desktop).")
else:
print("Waiting for Claude Desktop input (stdio mode).")
try:
mcp.run()
print("✅ MCP server exited cleanly.")
except Exception as e:
print("❌ MCP server crashed:", e)
このコードでは、
- fast_mcp モジュールから FastMCP クラスをインポートしています。
- ElasticsearchMCP という名前で MCP サーバーのインスタンスを作成しています。
- @mcp.tool() デコレータを使って、七つの ツールを定義しています。ツールの説明(”””ここに入っている文章 “””)は AI がツールの中身を理解するために重要です。
list_indices()
インデックス一覧の取得get_cluster_health()
クラスタの状態確認count_documents(index)
ドキュメント件数のカウント- search_index(index, keyword) キーワード検索
get_index_mapping(index)
マッピング取得delete_index(index)
インデックス削除- get_recent_docs(index, size) 最新のドキュメント取得
ステップ4: Claude Desktop アプリのインストール
まだインストールしていない場合は、Claude の公式サイトから Claude Desktop アプリをダウンロードしてインストールしてください。
※Claude Desktop Download: https://claude.ai/download
MCPサーバーと連携させる前の画面↓
ステップ5: MCP サーバーの起動
- ターミナルで、mcp-elasticsearch ディレクトリにいることを確認します。
- 以下のコマンドを実行して、MCP サーバーを起動します。
uv run mcp install main.py
サーバー追加されたメッセージを確認できます。
Claudeに設定項目が追加されました。Claude Desktopアプリを開いている場合は、一度終了し、20秒後に再度起動するとMCPサーバーが画面に表示されるようになります。問題が生じた際は、トラブルシューティングのセッションをご確認ください。
ステップ6: Claude Desktopとの連携設定
Claude Desktop アプリで、MCP サーバーが正しく認識されているか確認します。
Claudeの設定
- Claude Desktop アプリを開きます。
- 「Claude」>「設定」>「開発者」に進みます。
- 「構成を編集」をクリックし、設定ファイル
- ディレクトリが開きます。(claude-desktop-configuration.json) を好きなIDEで開きます。
- 設定ファイル内に、以下のような MCP サーバーの設定が記述されているか確認します。
{
"mcpServers": {
"ElasticsearchMCP": {
"command": "/Users/sam-koyama/.local/bin/uv",
"args": [
"--directory",
"/Users/sam-koyama/MCP/second/mcp-elasticsearch",
"run",
"main.py"
],
"env": {
"ELASTIC_USER": "elastic",
"ELASTIC_PASSWORD": "3fik3ua3kXnbOlp0ThX41be1",
"ELASTIC_CLOUD_URL": "https://my-deployment-a17327.es.ap-northeast-1.aws.found.io"
}
}
}
}
違う場合、トラブルシューティングの項を参照してください。
ツールの実行テスト
作成したツールを実際に Claude から実行してみましょう。
- Claude Desktop アプリを開始します。
- チャットウィンドウ下部に表示されているハンマーアイコン(利用可能な MCP ツールを示します)をクリックします。
- 先ほど作成したMCPから添付ツール(プラグのアイコン)を選択すると、「連携サービス選択」で追加したプロジェクトが確認できます。
ステップ7: Claudeからの動作確認
Claudeのチャットウィンドウで下記の様なクエリーを投げます。
「blogsインデックスにあるドキュメントの数を教えて」
初回利用時には、アクセス許可を求められることがあるので、「Allow for this chat」を選択します。
その前に正解をKibanaのコンソールで確認しましょう。
ということで、応答は「7つ」であれば、MCP サーバーと Claude の連携は成功です!
トラブルシューティング:困ったときのヒント
もし MCP サーバーが Claude Desktop に表示されない、またはエラーが表示された場合は、以下の点を確認してみてください。
- 設定ファイルのパス確認: claude-desktop-configuration.json 内の command フィールドが、UV の絶対パス(例: /usr/local/bin/uv)になっているか確認してください。修正後はファイルを保存し、Claude Desktop を再起動します。
- Claude Desktop の再起動: アプリを一度完全に終了し、再起動してみてください。
- デスクトップフォルダへのアクセス許可: Claude Desktop がデスクトップフォルダへのアクセスを求めてきた場合は、必ず許可してください。
最後に
今回は、MCPの基本動作確認のため、Elasticsearch連携のシンプルなコードを使用しました。
現状のMCPには、サーバーセットアップの手作業やローカル設定の煩雑さといった技術的課題が見られますが、新しい技術であるため今後の発展が期待されます。