|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +comments: true |
| 4 | +title: Integrating Elasticsearch with AI agents through Model Context Protocol |
| 5 | +excerpt: Building an Elasticsearch MCP Server for AI agents like Claude Desktop |
| 6 | +categories: genai |
| 7 | +tags: [elasticsearch,mcp] |
| 8 | +toc: true |
| 9 | +img_excerpt: |
| 10 | +--- |
| 11 | + |
| 12 | +Large Language Models (LLMs) are getting better every day at understanding all sorts of data, but they still suffer from knowledge cut-off when dealing with data that were not part of their pre-training. By integrating LLMs with external systems and knowledge bases, LLMs could achieve their true potential as they allow users to query and analyze complex data with natural conversations. [Model Context Protol (MCP)](https://modelcontextprotocol.io/) is one way of enable LLMs and AI assistants like Claude Desktop to perform actions, query data, and leverage the capabilities of other applications. |
| 13 | + |
| 14 | +In this blog post, we explore how to implement in Python using the `fastmcp` library an MCP server for Elasticsearch that enables Claude Desktop (or any AI Agent) to directly query and analyze data from an Elasticsearch cluster. We'll then see how to configure Claude Desktop to use this server. |
| 15 | + |
| 16 | +## What is MCP? |
| 17 | + |
| 18 | +The Model Context Protocol (MCP) is a standardized interface that allows LLMs like Claude to interact with external systems. It creates a bridge between AI assistants and various data stores, tools, and services while maintaining a consistent communication pattern. |
| 19 | + |
| 20 | +As shown in the diagram, MCP enables a standardized way for AI applications to interact with: |
| 21 | +- Data stores (databases, NoSQL databases) |
| 22 | +- CRM systems |
| 23 | +- Version control software |
| 24 | +- And potentially any external service with an appropriate MCP server |
| 25 | + |
| 26 | +{: .center-image } |
| 27 | + |
| 28 | +Furthermore, using the MCP Approach to integreate AI agents with externalo systems has the following benefits: |
| 29 | + |
| 30 | +1. **Standardization**: MCP provides a consistent interface for LLMs and AI agent to interact with external systems. |
| 31 | +2. **Separation of Concerns**: The LLM focuses on understanding and generating responses, while the MCP server handles the specifics of interacting with the target system (in this case, Elasticsearch). |
| 32 | +3. **Security**: Authentication details are managed by the MCP server, not exposed to the model. |
| 33 | +4. **Extensibility**: New tools can be easily added to the MCP server without changing the core integration. |
| 34 | + |
| 35 | + |
| 36 | +## Building an MCP Server |
| 37 | + |
| 38 | +In this section we will build an MCP server for Elasticsearch |
| 39 | + |
| 40 | +### Prerequisites |
| 41 | + |
| 42 | +Before you start, make sure you have: |
| 43 | + |
| 44 | +1. An Elasticsearch cluster accessible (either locally or remotely). |
| 45 | +2. Python installed (`3.8+ recommended`). |
| 46 | +3. `uv` for installing Python packages. |
| 47 | +4. Claude Desktop installed. |
| 48 | + |
| 49 | +### Setup |
| 50 | + |
| 51 | +We need to set up the environment that we will use to run our MCP server. We will use the `uv` tool, which helps manage Python environments, it automatically sets up the project files and manages the package dependencies. |
| 52 | + |
| 53 | +In the project directory (e.g. `elasticsearch_mcp`), initialize a `uv` project: |
| 54 | + |
| 55 | +```shell |
| 56 | +uv init |
| 57 | +uv venv |
| 58 | +source .venv/bin/activate |
| 59 | +``` |
| 60 | + |
| 61 | +The add the needed Python dependencies: |
| 62 | + |
| 63 | +```shell |
| 64 | +uv add elasticsearch |
| 65 | +uv add python-dotenv |
| 66 | +uv add mcp |
| 67 | +``` |
| 68 | + |
| 69 | +### Connecting to Elasticsearch |
| 70 | + |
| 71 | +Our MCP server needs to connect to the Elasticsearch cluster. We'll use environment variables to pass connection information and store them in a `.env` file. Here are few examples of content with different authentication methods: |
| 72 | + |
| 73 | +Environment variables for API Key authentication |
| 74 | + |
| 75 | +```shell |
| 76 | +# .env |
| 77 | +ES_URL=http://localhost:9200 |
| 78 | +ES_API_KEY=your_base64_encoded_api_key |
| 79 | +ES_CA_CERT=/path/to/http_ca.crt # Optional, for HTTPS verification |
| 80 | +``` |
| 81 | + |
| 82 | +Environment variables for Basic Auth authentication |
| 83 | + |
| 84 | +```shell |
| 85 | +# .env |
| 86 | +ES_URL=http://localhost:9200 |
| 87 | +ES_USERNAME=your_username |
| 88 | +ES_PASSWORD=your_password |
| 89 | +ES_CA_CERT=/path/to/http_ca.crt # Optional, for HTTPS verification |
| 90 | +``` |
| 91 | + |
| 92 | +Next, in our python code we laod environment variables from `.env` and then create an Elasticsearch client instance with one of the following authentication methods: |
| 93 | +1. API key authentication |
| 94 | +2. Username/password authentication |
| 95 | +3. No authentication for local development |
| 96 | + |
| 97 | +```python |
| 98 | +# server.py |
| 99 | +from elasticsearch import Elasticsearch |
| 100 | +from dotenv import load_dotenv |
| 101 | + |
| 102 | +load_dotenv() |
| 103 | + |
| 104 | +def createElasticsearchClient(): |
| 105 | + # Access environment variables |
| 106 | + ES_URL = os.getenv('ES_URL') |
| 107 | + ES_API_KEY = os.getenv('ES_API_KEY') # base64 encoded api key |
| 108 | + ES_USERNAME = os.getenv('ES_USERNAME') |
| 109 | + ES_PASSWORD = os.getenv('ES_PASSWORD') |
| 110 | + ES_CA_CERT = os.getenv('ES_CA_CERT') # path to http_ca.crt file # Optional, for HTTPS verification |
| 111 | + |
| 112 | + if ES_API_KEY: |
| 113 | + return Elasticsearch(hosts=[ES_URL], api_key=ES_API_KEY, ca_certs=ES_CA_CERT) |
| 114 | + if ES_USERNAME and ES_PASSWORD: |
| 115 | + return Elasticsearch(hosts=[ES_URL], basic_auth=(ES_USERNAME, ES_PASSWORD), ca_certs=ES_CA_CERT) |
| 116 | + return Elasticsearch(hosts=[ES_URL]) |
| 117 | + |
| 118 | +es = createElasticsearchClient() |
| 119 | +``` |
| 120 | + |
| 121 | +### Exposing Tools |
| 122 | + |
| 123 | +Now, let's define the functions that will interact with the Elasticsearch and serve as our MCP tools. |
| 124 | + |
| 125 | +First, we create an MCP server instance that allows Claude Desktop to interact with an Elasticsearch cluster: |
| 126 | + |
| 127 | +```python |
| 128 | +# server.py |
| 129 | +from mcp.server.fastmcp import FastMCP |
| 130 | + |
| 131 | +mcp = FastMCP("elasticsearch-mcp-server") |
| 132 | +``` |
| 133 | + |
| 134 | +Next, we define our main functions to list indices, get mappings, and perform searches. And annotate them with the `@mcp.tool()` decorator to register these Python functions as available tools for the MCP server. |
| 135 | + |
| 136 | +#### 1. List Indices |
| 137 | +A function allowing Claude to retrieve all available indices in the Elasticsearch cluster. |
| 138 | + |
| 139 | +```python |
| 140 | +# server.py |
| 141 | +@mcp.tool() |
| 142 | +def list_indices() -> List[str]: |
| 143 | + """ |
| 144 | + List all available Elasticsearch indices. |
| 145 | + |
| 146 | + Returns: |
| 147 | + List of indices |
| 148 | + """ |
| 149 | + indices = es.indices.get_alias(index="*") |
| 150 | + index_names = list(indices.keys()) |
| 151 | + return index_names |
| 152 | +``` |
| 153 | + |
| 154 | + |
| 155 | + |
| 156 | +#### 2. Get Mappings |
| 157 | +A function to enable Claude to understand the structure of data within a specific index by retrieving its mapping schema. |
| 158 | + |
| 159 | +```python |
| 160 | +# server.py |
| 161 | +@mcp.tool() |
| 162 | +def get_mappings(index: str) -> dict: |
| 163 | + """ |
| 164 | + Get field mappings for a specific Elasticsearch index. |
| 165 | +
|
| 166 | + Args: |
| 167 | + index: Name of the Elasticsearch index to get mappings for |
| 168 | +
|
| 169 | + Returns: |
| 170 | + Mapping schema for the specified index |
| 171 | + """ |
| 172 | + mappings = es.indices.get_mapping(index=index) |
| 173 | + return mappings |
| 174 | +``` |
| 175 | + |
| 176 | + |
| 177 | +#### 3. Search |
| 178 | +A function to allow Claude to execute Elasticsearch queries using the full power of the Elasticsearch Query DSL. |
| 179 | + |
| 180 | +```python |
| 181 | +# server.py |
| 182 | +@mcp.tool() |
| 183 | +def search(index: str, queryBody: dict) -> dict: |
| 184 | + """ |
| 185 | + Perform an Elasticsearch search with the provided query DSL. Highlights are always enabled. |
| 186 | +
|
| 187 | + Args: |
| 188 | + index: Name of the Elasticsearch index to search |
| 189 | + queryBody: Complete Elasticsearch query DSL object that can include query, size, from, sort, etc. |
| 190 | +
|
| 191 | + Returns: |
| 192 | + Search result |
| 193 | + """ |
| 194 | + response = es.search(index=index, body=queryBody) |
| 195 | + return response |
| 196 | +``` |
| 197 | + |
| 198 | +## Using our MCP server |
| 199 | + |
| 200 | +Our MCP server can be used by any AI assistant/IDE that supports MCP, including Claude Desktop. |
| 201 | + |
| 202 | +### Configuring Claude Desktop |
| 203 | + |
| 204 | +For Claude Deskto to connect to our Elasticsearch MCP server, we need to register the MCP server by adding it to Claude configuration file named `claude_desktop_config.json`. In MacOS, it should be available at `~/Library/Application\ Support/Claude/`. |
| 205 | + |
| 206 | +The configuration for our Elasticsearch MCP server should look like this: |
| 207 | + |
| 208 | +```json |
| 209 | +{ |
| 210 | + "mcpServers": { |
| 211 | + "ElasticsearchServer": { |
| 212 | + "command": "/path/to/uv", |
| 213 | + "args": [ |
| 214 | + "--directory", |
| 215 | + "/path/to/elasticsearch_mcp", |
| 216 | + "run", |
| 217 | + "server.py" |
| 218 | + ] |
| 219 | + } |
| 220 | + } |
| 221 | +} |
| 222 | +``` |
| 223 | + |
| 224 | +This configuration tells Claude Desktop how to launch the Elasticsearch MCP server: |
| 225 | +1. It uses `uv` (a Python package manager/runner) to execute the server |
| 226 | +2. It specifies the working directory where the server code is located |
| 227 | +3. It runs the `server.py` script |
| 228 | + |
| 229 | + |
| 230 | +### Using the Elasticsearch MCP Server with Claude |
| 231 | + |
| 232 | +With our MCP server implemented and Claude Desktop configured, we can now interact with our Elasticsearch cluster directly from Claude. For this, simply launch Claude Desktop and start a new conversation, that's it. |
| 233 | + |
| 234 | +We ask Claude questions about our Elasticsearch cluster and it will use the `list_indices`, `get_mappings`, or `search` tools accordingly to fulfill the requests. |
| 235 | + |
| 236 | +Here are few questions and answers: |
| 237 | + |
| 238 | +**1. What are the indices available in my Elasticsearch cluster?** |
| 239 | + |
| 240 | +```markdown |
| 241 | +I'll check what Elasticsearch indices are available in your cluster right now. |
| 242 | + |
| 243 | +I can see that there is one index available in your Elasticsearch cluster: |
| 244 | + |
| 245 | +- `hacker_news_posts` |
| 246 | + |
| 247 | +Would you like me to explore this index further, such as checking its mapping schema or running some sample queries? |
| 248 | +``` |
| 249 | + |
| 250 | +**2. what are the mappings for the 'hacker_news_posts' index?** |
| 251 | + |
| 252 | +```markdown |
| 253 | +I'll retrieve the mappings for the 'hacker_news_posts' index to show you its structure. |
| 254 | + |
| 255 | +Here are the mappings for the 'hacker_news_posts' index: |
| 256 | + |
| 257 | +### Top-level fields: |
| 258 | +- `by`: text field with keyword sub-field (for exact matching) |
| 259 | +- `id`: long (numeric) field |
| 260 | +- `score`: long (numeric) field |
| 261 | +- `time`: long (numeric) field, likely represents timestamps |
| 262 | +- `title`: text field with keyword sub-field |
| 263 | +- `type`: text field with keyword sub-field |
| 264 | +- `url`: text field with keyword sub-field |
| 265 | + |
| 266 | +### Nested comments structure: |
| 267 | +- `comments`: object with the following properties: |
| 268 | + - `id`: text field with keyword sub-field |
| 269 | + - `level`: long (numeric) field |
| 270 | + - `parent_id`: text field with keyword sub-field |
| 271 | + - `text`: text field with keyword sub-field |
| 272 | + - `time_ago`: text field with keyword sub-field |
| 273 | + - `user`: text field with keyword sub-field |
| 274 | + |
| 275 | +This appears to be an index storing Hacker News posts along with their associated comments. Each post has standard metadata like author (`by`), post ID, score, timestamp, title, type, and URL. The comments are stored as nested objects with their own structure including comment ID, nesting level, parent comment ID, comment text, relative time posted, and user who posted the comment. |
| 276 | + |
| 277 | +Would you like me to show you how to query this data in a specific way? |
| 278 | +``` |
| 279 | + |
| 280 | + |
| 281 | +## Conclusion |
| 282 | + |
| 283 | +We have successfully built a basic Elasticsearch MCP server using `fastmcp` and integrated it with Claude Desktop. This allowed us to query and explore data from Elasticsearch using natural language within Claude. |
| 284 | + |
| 285 | +Also note that Claude already provides a plethora of MCP servers that it can combine to let the user perform various tasks with simple natural language: |
| 286 | + |
| 287 | +- Data exploration and analysis |
| 288 | +- Generating insights from large datasets |
| 289 | +- Creating visualizations based on query results |
| 290 | +- Answering complex questions about data stored in Elasticsearch |
| 291 | + |
| 292 | +The standardization that MCP brings to AI development promises to make integrations more consistent, reliable, and easier to maintain as the AI ecosystem continues to evolve. By following the MCP pattern, you can create similar integrations for various data sources and services, expanding the capabilities of AI assistants like Claude without needing to modify the core LLM itself. |
| 293 | + |
| 294 | +## That's all folks |
| 295 | + |
| 296 | +I hope you enjoyed this article, feel free to leave a comment or reach out on twitter [@bachiirc](https://twitter.com/bachiirc). |
0 commit comments