自然言語でElasticsearchを操作!Python×Claude Desktop×MCP連携例

BLOG

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 がツールの中身を理解するために重要です。
  1. list_indices()        インデックス一覧の取得
  2. get_cluster_health()     クラスタの状態確認
  3. count_documents(index)    ドキュメント件数のカウント
  4. search_index(index, keyword) キーワード検索
  5. get_index_mapping(index)    マッピング取得
  6. delete_index(index)      インデックス削除
  7. 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には、サーバーセットアップの手作業やローカル設定の煩雑さといった技術的課題が見られますが、新しい技術であるため今後の発展が期待されます。