elasticsearch-mcp-server 1.0.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of elasticsearch-mcp-server might be problematic. Click here for more details.
- {elasticsearch_mcp_server-1.0.0.dist-info → elasticsearch_mcp_server-2.0.0.dist-info}/METADATA +105 -29
- elasticsearch_mcp_server-2.0.0.dist-info/RECORD +22 -0
- elasticsearch_mcp_server-2.0.0.dist-info/entry_points.txt +3 -0
- src/__init__.py +6 -0
- src/clients/__init__.py +42 -0
- src/clients/base.py +54 -0
- src/clients/common/__init__.py +6 -0
- src/clients/common/alias.py +20 -0
- src/clients/common/client.py +25 -0
- src/clients/common/cluster.py +12 -0
- src/clients/common/document.py +37 -0
- src/clients/common/index.py +20 -0
- src/clients/exceptions.py +69 -0
- src/server.py +71 -0
- src/tools/__init__.py +13 -0
- src/tools/alias.py +46 -0
- src/tools/cluster.py +17 -0
- src/tools/document.py +64 -0
- src/tools/index.py +44 -0
- src/tools/register.py +41 -0
- elasticsearch_mcp_server/__init__.py +0 -10
- elasticsearch_mcp_server/es_client.py +0 -40
- elasticsearch_mcp_server/server.py +0 -41
- elasticsearch_mcp_server/tools/cluster.py +0 -38
- elasticsearch_mcp_server/tools/document.py +0 -25
- elasticsearch_mcp_server/tools/index.py +0 -51
- elasticsearch_mcp_server-1.0.0.dist-info/RECORD +0 -11
- elasticsearch_mcp_server-1.0.0.dist-info/entry_points.txt +0 -2
- {elasticsearch_mcp_server-1.0.0.dist-info → elasticsearch_mcp_server-2.0.0.dist-info}/WHEEL +0 -0
- {elasticsearch_mcp_server-1.0.0.dist-info → elasticsearch_mcp_server-2.0.0.dist-info}/licenses/LICENSE +0 -0
{elasticsearch_mcp_server-1.0.0.dist-info → elasticsearch_mcp_server-2.0.0.dist-info}/METADATA
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: elasticsearch-mcp-server
|
|
3
|
-
Version:
|
|
4
|
-
Summary: MCP Server for interacting with Elasticsearch
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: MCP Server for interacting with Elasticsearch and OpenSearch
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
7
7
|
http://www.apache.org/licenses/
|
|
@@ -205,17 +205,23 @@ License: Apache License
|
|
|
205
205
|
limitations under the License.
|
|
206
206
|
License-File: LICENSE
|
|
207
207
|
Requires-Python: >=3.10
|
|
208
|
+
Requires-Dist: anthropic>=0.49.0
|
|
208
209
|
Requires-Dist: elasticsearch>=8.0.0
|
|
209
210
|
Requires-Dist: fastmcp>=0.4.0
|
|
210
211
|
Requires-Dist: mcp>=1.0.0
|
|
212
|
+
Requires-Dist: opensearch-py>=2.0.0
|
|
211
213
|
Requires-Dist: python-dotenv>=1.0.0
|
|
214
|
+
Requires-Dist: tomli-w>=1.2.0
|
|
215
|
+
Requires-Dist: tomli>=2.2.1
|
|
212
216
|
Description-Content-Type: text/markdown
|
|
213
217
|
|
|
214
|
-
# Elasticsearch MCP Server
|
|
218
|
+
# Elasticsearch/OpenSearch MCP Server
|
|
219
|
+
|
|
220
|
+
[](https://smithery.ai/server/elasticsearch-mcp-server)
|
|
215
221
|
|
|
216
222
|
## Overview
|
|
217
223
|
|
|
218
|
-
A Model Context Protocol (MCP) server implementation that provides Elasticsearch interaction. This server enables searching documents, analyzing indices, and managing cluster through a set of tools.
|
|
224
|
+
A Model Context Protocol (MCP) server implementation that provides Elasticsearch and OpenSearch interaction. This server enables searching documents, analyzing indices, and managing cluster through a set of tools.
|
|
219
225
|
|
|
220
226
|
<a href="https://glama.ai/mcp/servers/b3po3delex"><img width="380" height="200" src="https://glama.ai/mcp/servers/b3po3delex/badge" alt="Elasticsearch MCP Server" /></a>
|
|
221
227
|
|
|
@@ -227,41 +233,67 @@ https://github.com/user-attachments/assets/f7409e31-fac4-4321-9c94-b0ff2ea7ff15
|
|
|
227
233
|
|
|
228
234
|
### Index Operations
|
|
229
235
|
|
|
230
|
-
- `list_indices`: List all indices
|
|
231
|
-
- `
|
|
232
|
-
- `
|
|
236
|
+
- `list_indices`: List all indices.
|
|
237
|
+
- `get_index`: Returns information (mappings, settings, aliases) about one or more indices.
|
|
238
|
+
- `create_index`: Create a new index.
|
|
239
|
+
- `delete_index`: Delete an index.
|
|
233
240
|
|
|
234
241
|
### Document Operations
|
|
235
242
|
|
|
236
|
-
- `search_documents`: Search documents
|
|
243
|
+
- `search_documents`: Search for documents.
|
|
244
|
+
- `index_document`: Creates or updates a document in the index.
|
|
245
|
+
- `get_document`: Get a document by ID.
|
|
246
|
+
- `delete_document`: Delete a document by ID.
|
|
247
|
+
- `delete_by_query`: Deletes documents matching the provided query.
|
|
237
248
|
|
|
238
249
|
### Cluster Operations
|
|
239
250
|
|
|
240
|
-
- `get_cluster_health`:
|
|
241
|
-
- `get_cluster_stats`:
|
|
251
|
+
- `get_cluster_health`: Returns basic information about the health of the cluster.
|
|
252
|
+
- `get_cluster_stats`: Returns high-level overview of cluster statistics.
|
|
253
|
+
|
|
254
|
+
### Alias Operations
|
|
255
|
+
|
|
256
|
+
- `list_aliases`: List all aliases.
|
|
257
|
+
- `get_alias`: Get alias information for a specific index.
|
|
258
|
+
- `put_alias`: Create or update an alias for a specific index.
|
|
259
|
+
- `delete_alias`: Delete an alias for a specific index.
|
|
242
260
|
|
|
261
|
+
## Configure Environment Variables
|
|
243
262
|
|
|
244
|
-
|
|
263
|
+
Copy the `.env.example` file to `.env` and update the values accordingly.
|
|
245
264
|
|
|
246
|
-
Start
|
|
265
|
+
## Start Elasticsearch/OpenSearch Cluster
|
|
266
|
+
|
|
267
|
+
Start the Elasticsearch/OpenSearch cluster using Docker Compose:
|
|
247
268
|
|
|
248
269
|
```bash
|
|
249
|
-
|
|
270
|
+
# For Elasticsearch
|
|
271
|
+
docker-compose -f docker-compose-elasticsearch.yml up -d
|
|
272
|
+
|
|
273
|
+
# For OpenSearch
|
|
274
|
+
docker-compose -f docker-compose-opensearch.yml up -d
|
|
250
275
|
```
|
|
251
276
|
|
|
252
|
-
|
|
277
|
+
The default Elasticsearch username is `elastic` and password is `test123`. The default OpenSearch username is `admin` and password is `admin`.
|
|
253
278
|
|
|
254
|
-
You can access Kibana from http://localhost:5601.
|
|
279
|
+
You can access Kibana/OpenSearch Dashboards from http://localhost:5601.
|
|
255
280
|
|
|
256
281
|
## Usage with Claude Desktop
|
|
257
282
|
|
|
258
|
-
|
|
283
|
+
### Option 1: Installing via Smithery
|
|
259
284
|
|
|
260
|
-
|
|
285
|
+
To install Elasticsearch Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/elasticsearch-mcp-server):
|
|
261
286
|
|
|
262
|
-
|
|
287
|
+
```bash
|
|
288
|
+
npx -y @smithery/cli install elasticsearch-mcp-server --client claude
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Option 2: Using uvx
|
|
292
|
+
|
|
293
|
+
Using `uvx` will automatically install the package from PyPI, no need to clone the repository locally. Add the following configuration to Claude Desktop's config file `claude_desktop_config.json`.
|
|
263
294
|
|
|
264
295
|
```json
|
|
296
|
+
// For Elasticsearch
|
|
265
297
|
{
|
|
266
298
|
"mcpServers": {
|
|
267
299
|
"elasticsearch-mcp-server": {
|
|
@@ -270,23 +302,41 @@ Using `uvx` will automatically install the package from PyPI, no need to clone t
|
|
|
270
302
|
"elasticsearch-mcp-server"
|
|
271
303
|
],
|
|
272
304
|
"env": {
|
|
273
|
-
"
|
|
274
|
-
"
|
|
275
|
-
"
|
|
305
|
+
"ELASTICSEARCH_HOST": "https://localhost:9200",
|
|
306
|
+
"ELASTICSEARCH_USERNAME": "elastic",
|
|
307
|
+
"ELASTICSEARCH_PASSWORD": "test123"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// For OpenSearch
|
|
314
|
+
{
|
|
315
|
+
"mcpServers": {
|
|
316
|
+
"opensearch-mcp-server": {
|
|
317
|
+
"command": "uvx",
|
|
318
|
+
"args": [
|
|
319
|
+
"opensearch-mcp-server"
|
|
320
|
+
],
|
|
321
|
+
"env": {
|
|
322
|
+
"OPENSEARCH_HOST": "https://localhost:9200",
|
|
323
|
+
"OPENSEARCH_USERNAME": "admin",
|
|
324
|
+
"OPENSEARCH_PASSWORD": "admin"
|
|
276
325
|
}
|
|
277
326
|
}
|
|
278
327
|
}
|
|
279
328
|
}
|
|
280
329
|
```
|
|
281
330
|
|
|
282
|
-
### Option
|
|
331
|
+
### Option 3: Using uv with local development
|
|
283
332
|
|
|
284
|
-
Using `uv` requires cloning the repository locally and specifying the path to the source code.
|
|
333
|
+
Using `uv` requires cloning the repository locally and specifying the path to the source code. Add the following configuration to Claude Desktop's config file `claude_desktop_config.json`.
|
|
285
334
|
|
|
286
335
|
```json
|
|
336
|
+
// For Elasticsearch
|
|
287
337
|
{
|
|
288
338
|
"mcpServers": {
|
|
289
|
-
"elasticsearch": {
|
|
339
|
+
"elasticsearch-mcp-server": {
|
|
290
340
|
"command": "uv",
|
|
291
341
|
"args": [
|
|
292
342
|
"--directory",
|
|
@@ -295,9 +345,29 @@ Using `uv` requires cloning the repository locally and specifying the path to th
|
|
|
295
345
|
"elasticsearch-mcp-server"
|
|
296
346
|
],
|
|
297
347
|
"env": {
|
|
298
|
-
"
|
|
299
|
-
"
|
|
300
|
-
"
|
|
348
|
+
"ELASTICSEARCH_HOST": "https://localhost:9200",
|
|
349
|
+
"ELASTICSEARCH_USERNAME": "elastic",
|
|
350
|
+
"ELASTICSEARCH_PASSWORD": "test123"
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// For OpenSearch
|
|
357
|
+
{
|
|
358
|
+
"mcpServers": {
|
|
359
|
+
"opensearch-mcp-server": {
|
|
360
|
+
"command": "uv",
|
|
361
|
+
"args": [
|
|
362
|
+
"--directory",
|
|
363
|
+
"path/to/src/elasticsearch_mcp_server",
|
|
364
|
+
"run",
|
|
365
|
+
"opensearch-mcp-server"
|
|
366
|
+
],
|
|
367
|
+
"env": {
|
|
368
|
+
"OPENSEARCH_HOST": "https://localhost:9200",
|
|
369
|
+
"OPENSEARCH_USERNAME": "admin",
|
|
370
|
+
"OPENSEARCH_PASSWORD": "admin"
|
|
301
371
|
}
|
|
302
372
|
}
|
|
303
373
|
}
|
|
@@ -309,11 +379,17 @@ Using `uv` requires cloning the repository locally and specifying the path to th
|
|
|
309
379
|
|
|
310
380
|
Restart Claude Desktop to load the new MCP server.
|
|
311
381
|
|
|
312
|
-
Now you can interact with your Elasticsearch cluster through Claude using natural language commands like:
|
|
382
|
+
Now you can interact with your Elasticsearch/OpenSearch cluster through Claude using natural language commands like:
|
|
313
383
|
- "List all indices in the cluster"
|
|
314
384
|
- "How old is the student Bob?"
|
|
315
385
|
- "Show me the cluster health status"
|
|
316
386
|
|
|
387
|
+
## Usage with Anthropic MCP Client
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
uv run mcp_client/client.py src/server.py
|
|
391
|
+
```
|
|
392
|
+
|
|
317
393
|
## License
|
|
318
394
|
|
|
319
|
-
This project is licensed under the Apache License Version 2.0 - see the [LICENSE](LICENSE) file for details.
|
|
395
|
+
This project is licensed under the Apache License Version 2.0 - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
src/__init__.py,sha256=aNKcThftSLh9IjOTA-UUpoRzIm4R0WwXKGAzykwecmU,211
|
|
2
|
+
src/server.py,sha256=BtNr3oKiRK1rbnocFIKzwpukY87_AylzKh_ffwP6Zr8,2153
|
|
3
|
+
src/clients/__init__.py,sha256=3UezAt9422S-7BvMiCo2Y9pmATVutorwsIQXP_g_CkA,1221
|
|
4
|
+
src/clients/base.py,sha256=vTe4I62ruO2bEeSAag-2B5fJOWJDXllKbo8qA0h4VkM,2160
|
|
5
|
+
src/clients/exceptions.py,sha256=NYF3KVw-9aAgCViin5OuBI6miMEPS5QsdHx6bcis1wc,2493
|
|
6
|
+
src/clients/common/__init__.py,sha256=VgvgxFpESn2wAuJmH0XM_Ej2izI7dxK7QJe9wG4fmW0,211
|
|
7
|
+
src/clients/common/alias.py,sha256=rB53TSld5x2vZyDNAwyEdnh1KWUXMSD7h5fSv_ubR2Q,759
|
|
8
|
+
src/clients/common/client.py,sha256=GD-V97Dj8c2XGUYt58vqCggkk-k8WJePPgngBDWO5aM,973
|
|
9
|
+
src/clients/common/cluster.py,sha256=pd5BVpqqDU6Lck3K704eEdhgFgzt9NstotWQLyG9zFM,401
|
|
10
|
+
src/clients/common/document.py,sha256=ZzZiXDf_UhlN2FCmqW3drVjIZ07kGYP9yth_sgsJGPc,1623
|
|
11
|
+
src/clients/common/index.py,sha256=vyH5iXlJe5JLcDK6fhCCEPN-tgGm6zP5ilwGPdWCXbY,776
|
|
12
|
+
src/tools/__init__.py,sha256=qiMRNUKlpNGnqbrkgBHGa7El1Pw_0a2qUJnuk_Q8LtY,324
|
|
13
|
+
src/tools/alias.py,sha256=p9TD4gXkGRGWHTYfvCg7G2hj-Uch9jwDXJNUY1hSD0Y,1376
|
|
14
|
+
src/tools/cluster.py,sha256=XRAG-uxdfrieYX1ov_cBb66IYXaa8OoSWCUfXvNauy0,587
|
|
15
|
+
src/tools/document.py,sha256=XZTVuk4di9VEwWMZN7jyDVnzoOiXkb4FBrXF44sVXTs,2052
|
|
16
|
+
src/tools/index.py,sha256=7KNPtElTFelkjRSvdMqPBx9nx_9Uk01OnTMeVo7YeCs,1345
|
|
17
|
+
src/tools/register.py,sha256=wrG2P6-YPW77bTg1j_ELp8omWRYsJjFeOHUy_unHe6Y,1344
|
|
18
|
+
elasticsearch_mcp_server-2.0.0.dist-info/METADATA,sha256=ro27Cz5Px51wVUv8lz-ASLObuMlWXQlbcw-RWT6SonI,18512
|
|
19
|
+
elasticsearch_mcp_server-2.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
20
|
+
elasticsearch_mcp_server-2.0.0.dist-info/entry_points.txt,sha256=ImfJnUwMpQUBmu-1MeBG_P0dwamfXKQh82mBKW7tWNY,138
|
|
21
|
+
elasticsearch_mcp_server-2.0.0.dist-info/licenses/LICENSE,sha256=DBsjuP5FR51d9kaUdXlVBuBv3cQ_I_adq9gefYQ9FcY,11339
|
|
22
|
+
elasticsearch_mcp_server-2.0.0.dist-info/RECORD,,
|
src/__init__.py
ADDED
src/clients/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
|
|
5
|
+
from src.clients.common.client import SearchClient
|
|
6
|
+
from src.clients.exceptions import handle_search_exceptions
|
|
7
|
+
|
|
8
|
+
def create_search_client(engine_type: str) -> SearchClient:
|
|
9
|
+
"""
|
|
10
|
+
Create a search client for the specified engine type.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
engine_type: Type of search engine to use ("elasticsearch" or "opensearch")
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
A search client instance
|
|
17
|
+
"""
|
|
18
|
+
# Load configuration from environment variables
|
|
19
|
+
load_dotenv()
|
|
20
|
+
|
|
21
|
+
# Get configuration from environment variables
|
|
22
|
+
prefix = engine_type.upper()
|
|
23
|
+
hosts_str = os.environ.get(f"{prefix}_HOSTS", "https://localhost:9200")
|
|
24
|
+
hosts = [host.strip() for host in hosts_str.split(",")]
|
|
25
|
+
username = os.environ.get(f"{prefix}_USERNAME")
|
|
26
|
+
password = os.environ.get(f"{prefix}_PASSWORD")
|
|
27
|
+
verify_certs = os.environ.get(f"{prefix}_VERIFY_CERTS", "false").lower() == "true"
|
|
28
|
+
|
|
29
|
+
config = {
|
|
30
|
+
"hosts": hosts,
|
|
31
|
+
"username": username,
|
|
32
|
+
"password": password,
|
|
33
|
+
"verify_certs": verify_certs
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return SearchClient(config, engine_type)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
'create_search_client',
|
|
40
|
+
'handle_search_exceptions',
|
|
41
|
+
'SearchClient',
|
|
42
|
+
]
|
src/clients/base.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
import logging
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
class SearchClientBase(ABC):
|
|
7
|
+
def __init__(self, config: Dict, engine_type: str):
|
|
8
|
+
"""
|
|
9
|
+
Initialize the search client.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
config: Configuration dictionary with connection parameters
|
|
13
|
+
engine_type: Type of search engine to use ("elasticsearch" or "opensearch")
|
|
14
|
+
"""
|
|
15
|
+
self.logger = logging.getLogger()
|
|
16
|
+
self.config = config
|
|
17
|
+
self.engine_type = engine_type
|
|
18
|
+
|
|
19
|
+
# Extract common configuration
|
|
20
|
+
hosts = config.get("hosts")
|
|
21
|
+
username = config.get("username")
|
|
22
|
+
password = config.get("password")
|
|
23
|
+
verify_certs = config.get("verify_certs", False)
|
|
24
|
+
|
|
25
|
+
# Disable insecure request warnings if verify_certs is False
|
|
26
|
+
if not verify_certs:
|
|
27
|
+
warnings.filterwarnings("ignore", message=".*verify_certs=False is insecure.*")
|
|
28
|
+
warnings.filterwarnings("ignore", message=".*Unverified HTTPS request is being made to host.*")
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
import urllib3
|
|
32
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
33
|
+
except ImportError:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
# Initialize client based on engine type
|
|
37
|
+
if engine_type == "elasticsearch":
|
|
38
|
+
from elasticsearch import Elasticsearch
|
|
39
|
+
self.client = Elasticsearch(
|
|
40
|
+
hosts=hosts,
|
|
41
|
+
basic_auth=(username, password) if username and password else None,
|
|
42
|
+
verify_certs=verify_certs
|
|
43
|
+
)
|
|
44
|
+
self.logger.info(f"Elasticsearch client initialized with hosts: {hosts}")
|
|
45
|
+
elif engine_type == "opensearch":
|
|
46
|
+
from opensearchpy import OpenSearch
|
|
47
|
+
self.client = OpenSearch(
|
|
48
|
+
hosts=hosts,
|
|
49
|
+
http_auth=(username, password) if username and password else None,
|
|
50
|
+
verify_certs=verify_certs
|
|
51
|
+
)
|
|
52
|
+
self.logger.info(f"OpenSearch client initialized with hosts: {hosts}")
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(f"Unsupported engine type: {engine_type}")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from src.clients.base import SearchClientBase
|
|
4
|
+
|
|
5
|
+
class AliasClient(SearchClientBase):
|
|
6
|
+
def list_aliases(self) -> Dict:
|
|
7
|
+
"""Get all aliases."""
|
|
8
|
+
return self.client.cat.aliases()
|
|
9
|
+
|
|
10
|
+
def get_alias(self, index: str) -> Dict:
|
|
11
|
+
"""Get aliases for the specified index."""
|
|
12
|
+
return self.client.indices.get_alias(index=index)
|
|
13
|
+
|
|
14
|
+
def put_alias(self, index: str, name: str, body: Dict) -> Dict:
|
|
15
|
+
"""Creates or updates an alias."""
|
|
16
|
+
return self.client.indices.put_alias(index=index, name=name, body=body)
|
|
17
|
+
|
|
18
|
+
def delete_alias(self, index: str, name: str) -> Dict:
|
|
19
|
+
"""Delete an alias for the specified index."""
|
|
20
|
+
return self.client.indices.delete_alias(index=index, name=name)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from src.clients.common.alias import AliasClient
|
|
4
|
+
from src.clients.common.cluster import ClusterClient
|
|
5
|
+
from src.clients.common.document import DocumentClient
|
|
6
|
+
from src.clients.common.index import IndexClient
|
|
7
|
+
|
|
8
|
+
class SearchClient(IndexClient, DocumentClient, ClusterClient, AliasClient):
|
|
9
|
+
"""
|
|
10
|
+
Unified search client that combines all search functionality.
|
|
11
|
+
|
|
12
|
+
This class uses multiple inheritance to combine all specialized client implementations
|
|
13
|
+
(index, document, cluster, alias) into a single unified client.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, config: Dict, engine_type: str):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the search client.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
config: Configuration dictionary with connection parameters
|
|
22
|
+
engine_type: Type of search engine to use ("elasticsearch" or "opensearch")
|
|
23
|
+
"""
|
|
24
|
+
super().__init__(config, engine_type)
|
|
25
|
+
self.logger.info(f"Initialized the {engine_type} client")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from src.clients.base import SearchClientBase
|
|
4
|
+
|
|
5
|
+
class ClusterClient(SearchClientBase):
|
|
6
|
+
def get_cluster_health(self) -> Dict:
|
|
7
|
+
"""Get cluster health information from OpenSearch."""
|
|
8
|
+
return self.client.cluster.health()
|
|
9
|
+
|
|
10
|
+
def get_cluster_stats(self) -> Dict:
|
|
11
|
+
"""Get cluster statistics from OpenSearch."""
|
|
12
|
+
return self.client.cluster.stats()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import Dict, Optional
|
|
2
|
+
|
|
3
|
+
from src.clients.base import SearchClientBase
|
|
4
|
+
|
|
5
|
+
class DocumentClient(SearchClientBase):
|
|
6
|
+
def search_documents(self, index: str, body: Dict) -> Dict:
|
|
7
|
+
"""Search for documents in the index."""
|
|
8
|
+
return self.client.search(index=index, body=body)
|
|
9
|
+
|
|
10
|
+
def index_document(self, index: str, document: Dict, id: Optional[str] = None) -> Dict:
|
|
11
|
+
"""Creates a new document in the index."""
|
|
12
|
+
# Handle parameter name differences between Elasticsearch and OpenSearch
|
|
13
|
+
if self.engine_type == "elasticsearch":
|
|
14
|
+
# For Elasticsearch: index(index, document, id=None, ...)
|
|
15
|
+
if id is not None:
|
|
16
|
+
return self.client.index(index=index, document=document, id=id)
|
|
17
|
+
else:
|
|
18
|
+
return self.client.index(index=index, document=document)
|
|
19
|
+
else:
|
|
20
|
+
# For OpenSearch: index(index, body, id=None, ...)
|
|
21
|
+
if id is not None:
|
|
22
|
+
return self.client.index(index=index, body=document, id=id)
|
|
23
|
+
else:
|
|
24
|
+
return self.client.index(index=index, body=document)
|
|
25
|
+
|
|
26
|
+
def get_document(self, index: str, id: str) -> Dict:
|
|
27
|
+
"""Get a document by ID."""
|
|
28
|
+
return self.client.get(index=index, id=id)
|
|
29
|
+
|
|
30
|
+
def delete_document(self, index: str, id: str) -> Dict:
|
|
31
|
+
"""Removes a document from the index."""
|
|
32
|
+
return self.client.delete(index=index, id=id)
|
|
33
|
+
|
|
34
|
+
def delete_by_query(self, index: str, body: Dict) -> Dict:
|
|
35
|
+
"""Deletes documents matching the provided query."""
|
|
36
|
+
return self.client.delete_by_query(index=index, body=body)
|
|
37
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Dict, Optional
|
|
2
|
+
|
|
3
|
+
from src.clients.base import SearchClientBase
|
|
4
|
+
|
|
5
|
+
class IndexClient(SearchClientBase):
|
|
6
|
+
def list_indices(self) -> Dict:
|
|
7
|
+
"""List all indices."""
|
|
8
|
+
return self.client.cat.indices()
|
|
9
|
+
|
|
10
|
+
def get_index(self, index: str) -> Dict:
|
|
11
|
+
"""Returns information (mappings, settings, aliases) about one or more indices."""
|
|
12
|
+
return self.client.indices.get(index=index)
|
|
13
|
+
|
|
14
|
+
def create_index(self, index: str, body: Optional[Dict] = None) -> Dict:
|
|
15
|
+
"""Creates an index with optional settings and mappings."""
|
|
16
|
+
return self.client.indices.create(index=index, body=body)
|
|
17
|
+
|
|
18
|
+
def delete_index(self, index: str) -> Dict:
|
|
19
|
+
"""Delete an index."""
|
|
20
|
+
return self.client.indices.delete(index=index)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
from typing import TypeVar, Callable
|
|
4
|
+
|
|
5
|
+
from fastmcp import FastMCP
|
|
6
|
+
from mcp.types import TextContent
|
|
7
|
+
|
|
8
|
+
T = TypeVar('T')
|
|
9
|
+
|
|
10
|
+
def handle_search_exceptions(func: Callable[..., T]) -> Callable[..., list[TextContent]]:
|
|
11
|
+
"""
|
|
12
|
+
Decorator to handle exceptions in search client operations.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
func: The function to decorate
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Decorated function that handles exceptions
|
|
19
|
+
"""
|
|
20
|
+
@functools.wraps(func)
|
|
21
|
+
def wrapper(*args, **kwargs):
|
|
22
|
+
logger = logging.getLogger()
|
|
23
|
+
try:
|
|
24
|
+
return func(*args, **kwargs)
|
|
25
|
+
except Exception as e:
|
|
26
|
+
logger.error(f"Unexpected error in {func.__name__}: {e}")
|
|
27
|
+
return [TextContent(type="text", text=f"Unexpected error in {func.__name__}: {str(e)}")]
|
|
28
|
+
|
|
29
|
+
return wrapper
|
|
30
|
+
|
|
31
|
+
def with_exception_handling(tool_instance: object, mcp: FastMCP) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Register tools from a tool instance with automatic exception handling applied to all tools.
|
|
34
|
+
|
|
35
|
+
This function temporarily replaces mcp.tool with a wrapped version that automatically
|
|
36
|
+
applies the handle_search_exceptions decorator to all registered tool methods.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
tool_instance: The tool instance that has a register_tools method
|
|
40
|
+
mcp: The FastMCP instance used for tool registration
|
|
41
|
+
"""
|
|
42
|
+
# Save the original tool method
|
|
43
|
+
original_tool = mcp.tool
|
|
44
|
+
|
|
45
|
+
@functools.wraps(original_tool)
|
|
46
|
+
def wrapped_tool(*args, **kwargs):
|
|
47
|
+
# Get the original decorator
|
|
48
|
+
decorator = original_tool(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
# Return a new decorator that applies both the exception handler and original decorator
|
|
51
|
+
def combined_decorator(func):
|
|
52
|
+
# First apply the exception handling decorator
|
|
53
|
+
wrapped_func = handle_search_exceptions(func)
|
|
54
|
+
# Then apply the original mcp.tool decorator
|
|
55
|
+
return decorator(wrapped_func)
|
|
56
|
+
|
|
57
|
+
return combined_decorator
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
# Temporarily replace mcp.tool with our wrapped version
|
|
61
|
+
mcp.tool = wrapped_tool
|
|
62
|
+
|
|
63
|
+
# Call the registration method on the tool instance
|
|
64
|
+
tool_instance.register_tools(mcp)
|
|
65
|
+
finally:
|
|
66
|
+
# Restore the original mcp.tool to avoid affecting other code that might use mcp.tool
|
|
67
|
+
# This ensures that our modification is isolated to just this tool registration
|
|
68
|
+
# and prevents multiple nested decorators if register_all_tools is called multiple times
|
|
69
|
+
mcp.tool = original_tool
|
src/server.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from fastmcp import FastMCP
|
|
5
|
+
|
|
6
|
+
from src.clients import create_search_client
|
|
7
|
+
from src.tools.alias import AliasTools
|
|
8
|
+
from src.tools.cluster import ClusterTools
|
|
9
|
+
from src.tools.document import DocumentTools
|
|
10
|
+
from src.tools.index import IndexTools
|
|
11
|
+
from src.tools.register import ToolsRegister
|
|
12
|
+
|
|
13
|
+
class SearchMCPServer:
|
|
14
|
+
def __init__(self, engine_type):
|
|
15
|
+
# Set engine type
|
|
16
|
+
self.engine_type = engine_type
|
|
17
|
+
self.name = f"{self.engine_type}_mcp_server"
|
|
18
|
+
self.mcp = FastMCP(self.name)
|
|
19
|
+
self.logger = logging.getLogger()
|
|
20
|
+
self.logger.info(f"Initializing {self.name}...")
|
|
21
|
+
|
|
22
|
+
# Create the corresponding search client
|
|
23
|
+
self.search_client = create_search_client(self.engine_type)
|
|
24
|
+
|
|
25
|
+
# Initialize tools
|
|
26
|
+
self._register_tools()
|
|
27
|
+
|
|
28
|
+
def _register_tools(self):
|
|
29
|
+
"""Register all MCP tools."""
|
|
30
|
+
# Create a tools register
|
|
31
|
+
register = ToolsRegister(self.logger, self.search_client, self.mcp)
|
|
32
|
+
|
|
33
|
+
# Define all tool classes to register
|
|
34
|
+
tool_classes = [
|
|
35
|
+
IndexTools,
|
|
36
|
+
DocumentTools,
|
|
37
|
+
ClusterTools,
|
|
38
|
+
AliasTools
|
|
39
|
+
]
|
|
40
|
+
# Register all tools
|
|
41
|
+
register.register_all_tools(tool_classes)
|
|
42
|
+
|
|
43
|
+
def run(self):
|
|
44
|
+
"""Run the MCP server."""
|
|
45
|
+
self.mcp.run()
|
|
46
|
+
|
|
47
|
+
def run_search_server(engine_type):
|
|
48
|
+
"""Run search server with specified engine type."""
|
|
49
|
+
server = SearchMCPServer(engine_type=engine_type)
|
|
50
|
+
server.run()
|
|
51
|
+
|
|
52
|
+
def elasticsearch_mcp_server():
|
|
53
|
+
"""Entry point for Elasticsearch MCP server."""
|
|
54
|
+
run_search_server(engine_type="elasticsearch")
|
|
55
|
+
|
|
56
|
+
def opensearch_mcp_server():
|
|
57
|
+
"""Entry point for OpenSearch MCP server."""
|
|
58
|
+
run_search_server(engine_type="opensearch")
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
# Default to Elasticsearch
|
|
62
|
+
engine_type = "elasticsearch"
|
|
63
|
+
|
|
64
|
+
# If command line arguments are provided, use the first argument as the engine type
|
|
65
|
+
if len(sys.argv) > 1:
|
|
66
|
+
engine_type = sys.argv[1].lower()
|
|
67
|
+
|
|
68
|
+
if engine_type == "opensearch":
|
|
69
|
+
opensearch_mcp_server()
|
|
70
|
+
else:
|
|
71
|
+
elasticsearch_mcp_server()
|
src/tools/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from src.tools.index import IndexTools
|
|
2
|
+
from src.tools.document import DocumentTools
|
|
3
|
+
from src.tools.cluster import ClusterTools
|
|
4
|
+
from src.tools.alias import AliasTools
|
|
5
|
+
from src.tools.register import ToolsRegister
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
'IndexTools',
|
|
9
|
+
'DocumentTools',
|
|
10
|
+
'ClusterTools',
|
|
11
|
+
'AliasTools',
|
|
12
|
+
'ToolsRegister',
|
|
13
|
+
]
|
src/tools/alias.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
class AliasTools:
|
|
6
|
+
def __init__(self, search_client):
|
|
7
|
+
self.search_client = search_client
|
|
8
|
+
def register_tools(self, mcp: FastMCP):
|
|
9
|
+
@mcp.tool()
|
|
10
|
+
def list_aliases() -> List[Dict]:
|
|
11
|
+
"""List all aliases."""
|
|
12
|
+
return self.search_client.list_aliases()
|
|
13
|
+
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def get_alias(index: str) -> Dict:
|
|
16
|
+
"""
|
|
17
|
+
Get alias information for a specific index.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
index: Name of the index
|
|
21
|
+
"""
|
|
22
|
+
return self.search_client.get_alias(index=index)
|
|
23
|
+
|
|
24
|
+
@mcp.tool()
|
|
25
|
+
def put_alias(index: str, name: str, body: Dict) -> Dict:
|
|
26
|
+
"""
|
|
27
|
+
Create or update an alias for a specific index.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
index: Name of the index
|
|
31
|
+
name: Name of the alias
|
|
32
|
+
body: Alias configuration
|
|
33
|
+
"""
|
|
34
|
+
return self.search_client.put_alias(index=index, name=name, body=body)
|
|
35
|
+
|
|
36
|
+
@mcp.tool()
|
|
37
|
+
def delete_alias(index: str, name: str) -> Dict:
|
|
38
|
+
"""
|
|
39
|
+
Delete an alias for a specific index.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
index: Name of the index
|
|
43
|
+
name: Name of the alias
|
|
44
|
+
"""
|
|
45
|
+
return self.search_client.delete_alias(index=index, name=name)
|
|
46
|
+
|
src/tools/cluster.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
class ClusterTools:
|
|
6
|
+
def __init__(self, search_client):
|
|
7
|
+
self.search_client = search_client
|
|
8
|
+
def register_tools(self, mcp: FastMCP):
|
|
9
|
+
@mcp.tool()
|
|
10
|
+
def get_cluster_health() -> Dict:
|
|
11
|
+
"""Returns basic information about the health of the cluster."""
|
|
12
|
+
return self.search_client.get_cluster_health()
|
|
13
|
+
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def get_cluster_stats() -> Dict:
|
|
16
|
+
"""Returns high-level overview of cluster statistics."""
|
|
17
|
+
return self.search_client.get_cluster_stats()
|
src/tools/document.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Dict, Optional
|
|
2
|
+
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
class DocumentTools:
|
|
6
|
+
def __init__(self, search_client):
|
|
7
|
+
self.search_client = search_client
|
|
8
|
+
|
|
9
|
+
def register_tools(self, mcp: FastMCP):
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
def search_documents(index: str, body: Dict) -> Dict:
|
|
12
|
+
"""
|
|
13
|
+
Search for documents.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
index: Name of the index
|
|
17
|
+
body: Search query
|
|
18
|
+
"""
|
|
19
|
+
return self.search_client.search_documents(index=index, body=body)
|
|
20
|
+
|
|
21
|
+
@mcp.tool()
|
|
22
|
+
def index_document(index: str, document: Dict, id: Optional[str] = None) -> Dict:
|
|
23
|
+
"""
|
|
24
|
+
Creates or updates a document in the index.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
index: Name of the index
|
|
28
|
+
document: Document data
|
|
29
|
+
id: Optional document ID
|
|
30
|
+
"""
|
|
31
|
+
return self.search_client.index_document(index=index, id=id, document=document)
|
|
32
|
+
|
|
33
|
+
@mcp.tool()
|
|
34
|
+
def get_document(index: str, id: str) -> Dict:
|
|
35
|
+
"""
|
|
36
|
+
Get a document by ID.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
index: Name of the index
|
|
40
|
+
id: Document ID
|
|
41
|
+
"""
|
|
42
|
+
return self.search_client.get_document(index=index, id=id)
|
|
43
|
+
|
|
44
|
+
@mcp.tool()
|
|
45
|
+
def delete_document(index: str, id: str) -> Dict:
|
|
46
|
+
"""
|
|
47
|
+
Delete a document by ID.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
index: Name of the index
|
|
51
|
+
id: Document ID
|
|
52
|
+
"""
|
|
53
|
+
return self.search_client.delete_document(index=index, id=id)
|
|
54
|
+
|
|
55
|
+
@mcp.tool()
|
|
56
|
+
def delete_by_query(index: str, body: Dict) -> Dict:
|
|
57
|
+
"""
|
|
58
|
+
Deletes documents matching the provided query.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
index: Name of the index
|
|
62
|
+
body: Query to match documents for deletion
|
|
63
|
+
"""
|
|
64
|
+
return self.search_client.delete_by_query(index=index, body=body)
|
src/tools/index.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import Dict, Optional, List
|
|
2
|
+
|
|
3
|
+
from fastmcp import FastMCP
|
|
4
|
+
|
|
5
|
+
class IndexTools:
|
|
6
|
+
def __init__(self, search_client):
|
|
7
|
+
self.search_client = search_client
|
|
8
|
+
|
|
9
|
+
def register_tools(self, mcp: FastMCP):
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
def list_indices() -> List[Dict]:
|
|
12
|
+
"""List all indices."""
|
|
13
|
+
return self.search_client.list_indices()
|
|
14
|
+
|
|
15
|
+
@mcp.tool()
|
|
16
|
+
def get_index(index: str) -> Dict:
|
|
17
|
+
"""
|
|
18
|
+
Returns information (mappings, settings, aliases) about one or more indices.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
index: Name of the index
|
|
22
|
+
"""
|
|
23
|
+
return self.search_client.get_index(index=index)
|
|
24
|
+
|
|
25
|
+
@mcp.tool()
|
|
26
|
+
def create_index(index: str, body: Optional[Dict] = None) -> Dict:
|
|
27
|
+
"""
|
|
28
|
+
Create a new index.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
index: Name of the index
|
|
32
|
+
body: Optional index configuration including mappings and settings
|
|
33
|
+
"""
|
|
34
|
+
return self.search_client.create_index(index=index, body=body)
|
|
35
|
+
|
|
36
|
+
@mcp.tool()
|
|
37
|
+
def delete_index(index: str) -> Dict:
|
|
38
|
+
"""
|
|
39
|
+
Delete an index.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
index: Name of the index
|
|
43
|
+
"""
|
|
44
|
+
return self.search_client.delete_index(index=index)
|
src/tools/register.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List, Type
|
|
3
|
+
|
|
4
|
+
from fastmcp import FastMCP
|
|
5
|
+
|
|
6
|
+
from src.clients import SearchClient
|
|
7
|
+
from src.clients.exceptions import with_exception_handling
|
|
8
|
+
|
|
9
|
+
class ToolsRegister:
|
|
10
|
+
"""Class to handle registration of MCP tools."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, logger: logging.Logger, search_client: SearchClient, mcp: FastMCP):
|
|
13
|
+
"""
|
|
14
|
+
Initialize the tools register.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
logger: Logger instance
|
|
18
|
+
search_client: Search client instance
|
|
19
|
+
mcp: FastMCP instance
|
|
20
|
+
"""
|
|
21
|
+
self.logger = logger
|
|
22
|
+
self.search_client = search_client
|
|
23
|
+
self.mcp = mcp
|
|
24
|
+
|
|
25
|
+
def register_all_tools(self, tool_classes: List[Type]):
|
|
26
|
+
"""
|
|
27
|
+
Register all tools with the MCP server.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
tool_classes: List of tool classes to register
|
|
31
|
+
"""
|
|
32
|
+
for tool_class in tool_classes:
|
|
33
|
+
self.logger.info(f"Registering tools from {tool_class.__name__}")
|
|
34
|
+
tool_instance = tool_class(self.search_client)
|
|
35
|
+
|
|
36
|
+
# Set logger and client attributes
|
|
37
|
+
tool_instance.logger = self.logger
|
|
38
|
+
tool_instance.search_client = self.search_client
|
|
39
|
+
|
|
40
|
+
# Register tools with automatic exception handling
|
|
41
|
+
with_exception_handling(tool_instance, self.mcp)
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import os
|
|
3
|
-
from dotenv import load_dotenv
|
|
4
|
-
from elasticsearch import Elasticsearch
|
|
5
|
-
import warnings
|
|
6
|
-
|
|
7
|
-
class ElasticsearchClient:
|
|
8
|
-
def __init__(self, logger: logging.Logger):
|
|
9
|
-
self.logger = logger
|
|
10
|
-
self.es_client = self._create_elasticsearch_client()
|
|
11
|
-
|
|
12
|
-
def _get_es_config(self):
|
|
13
|
-
"""Get Elasticsearch configuration from environment variables."""
|
|
14
|
-
# Load environment variables from .env file
|
|
15
|
-
load_dotenv()
|
|
16
|
-
config = {
|
|
17
|
-
"host": os.getenv("ELASTIC_HOST"),
|
|
18
|
-
"username": os.getenv("ELASTIC_USERNAME"),
|
|
19
|
-
"password": os.getenv("ELASTIC_PASSWORD")
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if not all([config["username"], config["password"]]):
|
|
23
|
-
self.logger.error("Missing required Elasticsearch configuration. Please check environment variables:")
|
|
24
|
-
self.logger.error("ELASTIC_USERNAME and ELASTIC_PASSWORD are required")
|
|
25
|
-
raise ValueError("Missing required Elasticsearch configuration")
|
|
26
|
-
|
|
27
|
-
return config
|
|
28
|
-
|
|
29
|
-
def _create_elasticsearch_client(self) -> Elasticsearch:
|
|
30
|
-
"""Create and return an Elasticsearch client using configuration from environment."""
|
|
31
|
-
config = self._get_es_config()
|
|
32
|
-
|
|
33
|
-
# Disable SSL warnings
|
|
34
|
-
warnings.filterwarnings("ignore", message=".*TLS with verify_certs=False is insecure.*",)
|
|
35
|
-
|
|
36
|
-
return Elasticsearch(
|
|
37
|
-
config["host"],
|
|
38
|
-
basic_auth=(config["username"], config["password"]),
|
|
39
|
-
verify_certs=False
|
|
40
|
-
)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
import logging
|
|
3
|
-
from fastmcp import FastMCP
|
|
4
|
-
from .tools.index import IndexTools
|
|
5
|
-
from .tools.document import DocumentTools
|
|
6
|
-
from .tools.cluster import ClusterTools
|
|
7
|
-
|
|
8
|
-
class ElasticsearchMCPServer:
|
|
9
|
-
def __init__(self):
|
|
10
|
-
self.name = "elasticsearch_mcp_server"
|
|
11
|
-
self.mcp = FastMCP(self.name)
|
|
12
|
-
|
|
13
|
-
# Configure logging
|
|
14
|
-
logging.basicConfig(
|
|
15
|
-
level=logging.INFO,
|
|
16
|
-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
17
|
-
)
|
|
18
|
-
self.logger = logging.getLogger(self.name)
|
|
19
|
-
|
|
20
|
-
# Initialize tools
|
|
21
|
-
self._register_tools()
|
|
22
|
-
|
|
23
|
-
def _register_tools(self):
|
|
24
|
-
"""Register all MCP tools."""
|
|
25
|
-
# Initialize tool classes
|
|
26
|
-
index_tools = IndexTools(self.logger)
|
|
27
|
-
document_tools = DocumentTools(self.logger)
|
|
28
|
-
cluster_tools = ClusterTools(self.logger)
|
|
29
|
-
|
|
30
|
-
# Register tools from each module
|
|
31
|
-
index_tools.register_tools(self.mcp)
|
|
32
|
-
document_tools.register_tools(self.mcp)
|
|
33
|
-
cluster_tools.register_tools(self.mcp)
|
|
34
|
-
|
|
35
|
-
def run(self):
|
|
36
|
-
"""Run the MCP server."""
|
|
37
|
-
self.mcp.run()
|
|
38
|
-
|
|
39
|
-
def main():
|
|
40
|
-
server = ElasticsearchMCPServer()
|
|
41
|
-
server.run()
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Dict, Any
|
|
3
|
-
from ..es_client import ElasticsearchClient
|
|
4
|
-
from mcp.types import TextContent
|
|
5
|
-
|
|
6
|
-
class ClusterTools(ElasticsearchClient):
|
|
7
|
-
def register_tools(self, mcp: Any):
|
|
8
|
-
"""Register cluster-related tools."""
|
|
9
|
-
|
|
10
|
-
@mcp.tool(description="Get cluster health status")
|
|
11
|
-
async def get_cluster_health() -> list[TextContent]:
|
|
12
|
-
"""
|
|
13
|
-
Get health status of the Elasticsearch cluster.
|
|
14
|
-
Returns information about the number of nodes, shards, etc.
|
|
15
|
-
"""
|
|
16
|
-
self.logger.info("Getting cluster health")
|
|
17
|
-
try:
|
|
18
|
-
response = self.es_client.cluster.health()
|
|
19
|
-
return [TextContent(type="text", text=str(response))]
|
|
20
|
-
except Exception as e:
|
|
21
|
-
self.logger.error(f"Error getting cluster health: {e}")
|
|
22
|
-
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
|
23
|
-
|
|
24
|
-
@mcp.tool(description="Get cluster statistics")
|
|
25
|
-
async def get_cluster_stats() -> list[TextContent]:
|
|
26
|
-
"""
|
|
27
|
-
Get statistics from a cluster wide perspective.
|
|
28
|
-
The API returns basic index metrics (shard numbers, store size, memory usage) and information
|
|
29
|
-
about the current nodes that form the cluster (number, roles, os, jvm versions, memory usage, cpu and installed plugins).
|
|
30
|
-
https://www.elastic.co/guide/en/elasticsearch/reference/8.17/cluster-stats.html
|
|
31
|
-
"""
|
|
32
|
-
self.logger.info("Getting cluster stats")
|
|
33
|
-
try:
|
|
34
|
-
response = self.es_client.cluster.stats()
|
|
35
|
-
return [TextContent(type="text", text=str(response))]
|
|
36
|
-
except Exception as e:
|
|
37
|
-
self.logger.error(f"Error getting cluster stats: {e}")
|
|
38
|
-
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Dict, Any
|
|
3
|
-
from ..es_client import ElasticsearchClient
|
|
4
|
-
from mcp.types import TextContent
|
|
5
|
-
|
|
6
|
-
class DocumentTools(ElasticsearchClient):
|
|
7
|
-
def register_tools(self, mcp: Any):
|
|
8
|
-
"""Register document-related tools."""
|
|
9
|
-
|
|
10
|
-
@mcp.tool(description="Search documents in an index with a custom query")
|
|
11
|
-
async def search_documents(index: str, body: dict) -> list[TextContent]:
|
|
12
|
-
"""
|
|
13
|
-
Search documents in a specified index using a custom query.
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
index: Name of the index to search
|
|
17
|
-
body: Elasticsearch query DSL
|
|
18
|
-
"""
|
|
19
|
-
self.logger.info(f"Searching in index: {index} with query: {body}")
|
|
20
|
-
try:
|
|
21
|
-
response = self.es_client.search(index=index, body=body)
|
|
22
|
-
return [TextContent(type="text", text=str(response))]
|
|
23
|
-
except Exception as e:
|
|
24
|
-
self.logger.error(f"Error searching documents: {e}")
|
|
25
|
-
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Dict, Any
|
|
3
|
-
from ..es_client import ElasticsearchClient
|
|
4
|
-
from mcp.types import TextContent
|
|
5
|
-
|
|
6
|
-
class IndexTools(ElasticsearchClient):
|
|
7
|
-
def register_tools(self, mcp: Any):
|
|
8
|
-
"""Register index-related tools."""
|
|
9
|
-
|
|
10
|
-
@mcp.tool(description="List all indices in the Elasticsearch cluster")
|
|
11
|
-
async def list_indices() -> list[TextContent]:
|
|
12
|
-
"""List all indices in the Elasticsearch cluster."""
|
|
13
|
-
self.logger.info("Listing indices...")
|
|
14
|
-
try:
|
|
15
|
-
indices = self.es_client.cat.indices(format="json")
|
|
16
|
-
return [TextContent(type="text", text=str(indices))]
|
|
17
|
-
except Exception as e:
|
|
18
|
-
self.logger.error(f"Error listing indices: {e}")
|
|
19
|
-
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
|
20
|
-
|
|
21
|
-
@mcp.tool(description="Get index mapping")
|
|
22
|
-
async def get_mapping(index: str) -> list[TextContent]:
|
|
23
|
-
"""
|
|
24
|
-
Get the mapping for an index.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
index: Name of the index
|
|
28
|
-
"""
|
|
29
|
-
self.logger.info(f"Getting mapping for index: {index}")
|
|
30
|
-
try:
|
|
31
|
-
response = self.es_client.indices.get_mapping(index=index)
|
|
32
|
-
return [TextContent(type="text", text=str(response))]
|
|
33
|
-
except Exception as e:
|
|
34
|
-
self.logger.error(f"Error getting mapping: {e}")
|
|
35
|
-
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
|
36
|
-
|
|
37
|
-
@mcp.tool(description="Get index settings")
|
|
38
|
-
async def get_settings(index: str) -> list[TextContent]:
|
|
39
|
-
"""
|
|
40
|
-
Get the settings for an index.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
index: Name of the index
|
|
44
|
-
"""
|
|
45
|
-
self.logger.info(f"Getting settings for index: {index}")
|
|
46
|
-
try:
|
|
47
|
-
response = self.es_client.indices.get_settings(index=index)
|
|
48
|
-
return [TextContent(type="text", text=str(response))]
|
|
49
|
-
except Exception as e:
|
|
50
|
-
self.logger.error(f"Error getting settings: {e}")
|
|
51
|
-
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
elasticsearch_mcp_server/__init__.py,sha256=wGsW3VlozSA6CWKQSuUTaLZb31m4dNN8ACNG6xRwOyU,186
|
|
2
|
-
elasticsearch_mcp_server/es_client.py,sha256=n7RYcB97DgCIkrY1ryP4Ubq3IJxfnKqzvXMXRCbOXr8,1510
|
|
3
|
-
elasticsearch_mcp_server/server.py,sha256=RGKJKk6EpnM1etM8CYXoR_A4OjHqlXcIyRqgJfNJksg,1212
|
|
4
|
-
elasticsearch_mcp_server/tools/cluster.py,sha256=bGShKKDCgHW0GZoPd8ty__yX1Yn_dTwOH4hGGQsO5l8,1849
|
|
5
|
-
elasticsearch_mcp_server/tools/document.py,sha256=PDD63Etp1MS0G8N3IDg1j-hG41r5OFe4adlotnyuZHU,1075
|
|
6
|
-
elasticsearch_mcp_server/tools/index.py,sha256=Zgc90bVNH18LaHs4CXIRDAdcAdJyxx_RN788xZjLA-A,2174
|
|
7
|
-
elasticsearch_mcp_server-1.0.0.dist-info/METADATA,sha256=2aJ3yVM0e-zCowyEEOQYmZw78ii3qwLLdJ5RRzAv7qQ,16225
|
|
8
|
-
elasticsearch_mcp_server-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
-
elasticsearch_mcp_server-1.0.0.dist-info/entry_points.txt,sha256=sAW_y-4MkO9T00GbIb_tLQikrTPF94uHU1YkbWMj65g,75
|
|
10
|
-
elasticsearch_mcp_server-1.0.0.dist-info/licenses/LICENSE,sha256=DBsjuP5FR51d9kaUdXlVBuBv3cQ_I_adq9gefYQ9FcY,11339
|
|
11
|
-
elasticsearch_mcp_server-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|