opensearch-mcp-server 2.0.9__py3-none-any.whl → 2.0.11__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensearch-mcp-server
3
- Version: 2.0.9
3
+ Version: 2.0.11
4
4
  Summary: MCP Server for interacting with Elasticsearch and OpenSearch
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -218,10 +218,10 @@ Description-Content-Type: text/markdown
218
218
 
219
219
  # Elasticsearch/OpenSearch MCP Server
220
220
 
221
- [![smithery badge](https://smithery.ai/badge/elasticsearch-mcp-server)](https://smithery.ai/server/elasticsearch-mcp-server)
222
-
223
221
  [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/cr7258-elasticsearch-mcp-server-badge.png)](https://mseep.ai/app/cr7258-elasticsearch-mcp-server)
224
222
 
223
+ [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/cr7258/elasticsearch-mcp-server)](https://archestra.ai/mcp-catalog/cr7258__elasticsearch-mcp-server)
224
+
225
225
  ## Overview
226
226
 
227
227
  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.
@@ -267,7 +267,20 @@ https://github.com/user-attachments/assets/f7409e31-fac4-4321-9c94-b0ff2ea7ff15
267
267
 
268
268
  ## Configure Environment Variables
269
269
 
270
- Copy the `.env.example` file to `.env` and update the values accordingly.
270
+ The MCP server supports the following environment variables for authentication:
271
+
272
+ ### Basic Authentication (Username/Password)
273
+ - `ELASTICSEARCH_USERNAME`: Username for basic authentication
274
+ - `ELASTICSEARCH_PASSWORD`: Password for basic authentication
275
+ - `OPENSEARCH_USERNAME`: Username for OpenSearch basic authentication
276
+ - `OPENSEARCH_PASSWORD`: Password for OpenSearch basic authentication
277
+
278
+ ### API Key Authentication (Elasticsearch only) - Recommended
279
+ - `ELASTICSEARCH_API_KEY`: API key for [Elasticsearch](https://www.elastic.co/docs/deploy-manage/api-keys/elasticsearch-api-keys) or [Elastic Cloud](https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys) Authentication.
280
+
281
+ ### Other Configuration
282
+ - `ELASTICSEARCH_HOSTS` / `OPENSEARCH_HOSTS`: Comma-separated list of hosts (default: `https://localhost:9200`)
283
+ - `ELASTICSEARCH_VERIFY_CERTS` / `OPENSEARCH_VERIFY_CERTS`: Whether to verify SSL certificates (default: `false`)
271
284
 
272
285
  ## Start Elasticsearch/OpenSearch Cluster
273
286
 
@@ -292,7 +305,7 @@ You can access Kibana/OpenSearch Dashboards from http://localhost:5601.
292
305
  Using `uvx` will automatically install the package from PyPI, no need to clone the repository locally. Add the following configuration to 's config file `claude_desktop_config.json`.
293
306
 
294
307
  ```json
295
- // For Elasticsearch
308
+ // For Elasticsearch with username/password
296
309
  {
297
310
  "mcpServers": {
298
311
  "elasticsearch-mcp-server": {
@@ -309,6 +322,22 @@ Using `uvx` will automatically install the package from PyPI, no need to clone t
309
322
  }
310
323
  }
311
324
 
325
+ // For Elasticsearch with API key
326
+ {
327
+ "mcpServers": {
328
+ "elasticsearch-mcp-server": {
329
+ "command": "uvx",
330
+ "args": [
331
+ "elasticsearch-mcp-server"
332
+ ],
333
+ "env": {
334
+ "ELASTICSEARCH_HOSTS": "https://localhost:9200",
335
+ "ELASTICSEARCH_API_KEY": "<YOUR_ELASTICSEARCH_API_KEY>"
336
+ }
337
+ }
338
+ }
339
+ }
340
+
312
341
  // For OpenSearch
313
342
  {
314
343
  "mcpServers": {
@@ -332,7 +361,7 @@ Using `uvx` will automatically install the package from PyPI, no need to clone t
332
361
  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`.
333
362
 
334
363
  ```json
335
- // For Elasticsearch
364
+ // For Elasticsearch with username/password
336
365
  {
337
366
  "mcpServers": {
338
367
  "elasticsearch-mcp-server": {
@@ -352,6 +381,25 @@ Using `uv` requires cloning the repository locally and specifying the path to th
352
381
  }
353
382
  }
354
383
 
384
+ // For Elasticsearch with API key
385
+ {
386
+ "mcpServers": {
387
+ "elasticsearch-mcp-server": {
388
+ "command": "uv",
389
+ "args": [
390
+ "--directory",
391
+ "path/to/src/elasticsearch_mcp_server",
392
+ "run",
393
+ "elasticsearch-mcp-server"
394
+ ],
395
+ "env": {
396
+ "ELASTICSEARCH_HOSTS": "https://localhost:9200",
397
+ "ELASTICSEARCH_API_KEY": "<YOUR_ELASTICSEARCH_API_KEY>"
398
+ }
399
+ }
400
+ }
401
+ }
402
+
355
403
  // For OpenSearch
356
404
  {
357
405
  "mcpServers": {
@@ -378,11 +426,15 @@ Using `uv` requires cloning the repository locally and specifying the path to th
378
426
  ### Option 1: Using uvx
379
427
 
380
428
  ```bash
381
- # export environment variables
429
+ # export environment variables (with username/password)
382
430
  export ELASTICSEARCH_HOSTS="https://localhost:9200"
383
431
  export ELASTICSEARCH_USERNAME="elastic"
384
432
  export ELASTICSEARCH_PASSWORD="test123"
385
433
 
434
+ # OR export environment variables (with API key)
435
+ export ELASTICSEARCH_HOSTS="https://localhost:9200"
436
+ export ELASTICSEARCH_API_KEY="<YOUR_ELASTICSEARCH_API_KEY>"
437
+
386
438
  # By default, the SSE MCP server will serve on http://127.0.0.1:8000/sse
387
439
  uvx elasticsearch-mcp-server --transport sse
388
440
 
@@ -405,11 +457,15 @@ uv run src/server.py elasticsearch-mcp-server --transport sse --host 0.0.0.0 --p
405
457
  ### Option 1: Using uvx
406
458
 
407
459
  ```bash
408
- # export environment variables
460
+ # export environment variables (with username/password)
409
461
  export ELASTICSEARCH_HOSTS="https://localhost:9200"
410
462
  export ELASTICSEARCH_USERNAME="elastic"
411
463
  export ELASTICSEARCH_PASSWORD="test123"
412
464
 
465
+ # OR export environment variables (with API key)
466
+ export ELASTICSEARCH_HOSTS="https://localhost:9200"
467
+ export ELASTICSEARCH_API_KEY="<YOUR_ELASTICSEARCH_API_KEY>"
468
+
413
469
  # By default, the Streamable HTTP MCP server will serve on http://127.0.0.1:8000/mcp
414
470
  uvx elasticsearch-mcp-server --transport streamable-http
415
471
 
@@ -427,6 +483,21 @@ uv run src/server.py elasticsearch-mcp-server --transport streamable-http
427
483
  uv run src/server.py elasticsearch-mcp-server --transport streamable-http --host 0.0.0.0 --port 8000 --path /mcp
428
484
  ```
429
485
 
486
+ ## Compatibility
487
+
488
+ The MCP server is compatible with Elasticsearch 7.x, 8.x, and 9.x. By default, it uses the Elasticsearch 8.x client (without a suffix). To use the Elasticsearch 7.x client, run the `elasticsearch-mcp-server-es7` variant. For Elasticsearch 9.x, use `elasticsearch-mcp-server-es9`. For example:
489
+
490
+ ```bash
491
+ uvx elasticsearch-mcp-server-es7
492
+ ```
493
+
494
+ | MCP Server | Elasticsearch |
495
+ | --- | --- |
496
+ | elasticsearch-mcp-server-es7 | Elasticsearch 7.x |
497
+ | elasticsearch-mcp-server | Elasticsearch 8.x |
498
+ | elasticsearch-mcp-server-es9 | Elasticsearch 9.x |
499
+ | opensearch-mcp-server | OpenSearch 1.x, 2.x, 3.x |
500
+
430
501
  ## License
431
502
 
432
503
  This project is licensed under the Apache License Version 2.0 - see the [LICENSE](LICENSE) file for details.
@@ -1,8 +1,8 @@
1
1
  src/__init__.py,sha256=aNKcThftSLh9IjOTA-UUpoRzIm4R0WwXKGAzykwecmU,211
2
2
  src/server.py,sha256=BfMAgXTFV9C4waCWRro8Sfl6yXEXr2vJ7YoGY7HhMPY,5072
3
- src/version.py,sha256=pZib55qStLeBfZabni1F1OCNxdT02xP-5e34LjIBswQ,22
4
- src/clients/__init__.py,sha256=3UezAt9422S-7BvMiCo2Y9pmATVutorwsIQXP_g_CkA,1221
5
- src/clients/base.py,sha256=dKO18Wcq4gBc3j5Q1KbQ63heIir7jcX2Fl58uG7abyE,4674
3
+ src/version.py,sha256=T-q0gGRSS4Se3uygy0WDtGhPp5plXECGZSjUouo_yUU,23
4
+ src/clients/__init__.py,sha256=MUKeCxvAFzzVfQr4x1seB7cRVLtWAk7pb1-5tLG0T8g,1299
5
+ src/clients/base.py,sha256=tMNdKRsAG1dPHj7famwPvCXCONuT9jML-7FmWV9vXbs,5348
6
6
  src/clients/exceptions.py,sha256=NYF3KVw-9aAgCViin5OuBI6miMEPS5QsdHx6bcis1wc,2493
7
7
  src/clients/common/__init__.py,sha256=VgvgxFpESn2wAuJmH0XM_Ej2izI7dxK7QJe9wG4fmW0,211
8
8
  src/clients/common/alias.py,sha256=rB53TSld5x2vZyDNAwyEdnh1KWUXMSD7h5fSv_ubR2Q,759
@@ -18,8 +18,8 @@ src/tools/document.py,sha256=XZTVuk4di9VEwWMZN7jyDVnzoOiXkb4FBrXF44sVXTs,2052
18
18
  src/tools/general.py,sha256=whj1spjIb8SS75h843X6c3RTsrZcSm-66KVLlY7OEh0,817
19
19
  src/tools/index.py,sha256=7KNPtElTFelkjRSvdMqPBx9nx_9Uk01OnTMeVo7YeCs,1345
20
20
  src/tools/register.py,sha256=wrG2P6-YPW77bTg1j_ELp8omWRYsJjFeOHUy_unHe6Y,1344
21
- opensearch_mcp_server-2.0.9.dist-info/METADATA,sha256=bin99eLiAa5zNKjqcBjokOclRhJj9VMVjX7YZPrrqXA,19863
22
- opensearch_mcp_server-2.0.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- opensearch_mcp_server-2.0.9.dist-info/entry_points.txt,sha256=ImfJnUwMpQUBmu-1MeBG_P0dwamfXKQh82mBKW7tWNY,138
24
- opensearch_mcp_server-2.0.9.dist-info/licenses/LICENSE,sha256=DBsjuP5FR51d9kaUdXlVBuBv3cQ_I_adq9gefYQ9FcY,11339
25
- opensearch_mcp_server-2.0.9.dist-info/RECORD,,
21
+ opensearch_mcp_server-2.0.11.dist-info/METADATA,sha256=A_35ajQsEfqCYqo5hBp2jYX757DlbYUj4N_CO4utE_s,22552
22
+ opensearch_mcp_server-2.0.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ opensearch_mcp_server-2.0.11.dist-info/entry_points.txt,sha256=ImfJnUwMpQUBmu-1MeBG_P0dwamfXKQh82mBKW7tWNY,138
24
+ opensearch_mcp_server-2.0.11.dist-info/licenses/LICENSE,sha256=DBsjuP5FR51d9kaUdXlVBuBv3cQ_I_adq9gefYQ9FcY,11339
25
+ opensearch_mcp_server-2.0.11.dist-info/RECORD,,
src/clients/__init__.py CHANGED
@@ -24,12 +24,14 @@ def create_search_client(engine_type: str) -> SearchClient:
24
24
  hosts = [host.strip() for host in hosts_str.split(",")]
25
25
  username = os.environ.get(f"{prefix}_USERNAME")
26
26
  password = os.environ.get(f"{prefix}_PASSWORD")
27
+ api_key = os.environ.get(f"{prefix}_API_KEY")
27
28
  verify_certs = os.environ.get(f"{prefix}_VERIFY_CERTS", "false").lower() == "true"
28
29
 
29
30
  config = {
30
31
  "hosts": hosts,
31
32
  "username": username,
32
33
  "password": password,
34
+ "api_key": api_key,
33
35
  "verify_certs": verify_certs
34
36
  }
35
37
 
src/clients/base.py CHANGED
@@ -24,6 +24,7 @@ class SearchClientBase(ABC):
24
24
  hosts = config.get("hosts")
25
25
  username = config.get("username")
26
26
  password = config.get("password")
27
+ api_key = config.get("api_key")
27
28
  verify_certs = config.get("verify_certs", False)
28
29
 
29
30
  # Disable insecure request warnings if verify_certs is False
@@ -39,8 +40,8 @@ class SearchClientBase(ABC):
39
40
 
40
41
  # Initialize client based on engine type
41
42
  if engine_type == "elasticsearch":
42
- # Get auth parameters based on elasticsearch package version
43
- auth_params = self._get_elasticsearch_auth_params(username, password)
43
+ # Get auth parameters based on elasticsearch package version and authentication method
44
+ auth_params = self._get_elasticsearch_auth_params(username, password, api_key)
44
45
 
45
46
  self.client = Elasticsearch(
46
47
  hosts=hosts,
@@ -64,53 +65,67 @@ class SearchClientBase(ABC):
64
65
  base_url=base_url,
65
66
  username=username,
66
67
  password=password,
68
+ api_key=api_key,
67
69
  verify_certs=verify_certs,
68
70
  )
69
71
 
70
- def _get_elasticsearch_auth_params(self, username: Optional[str], password: Optional[str]) -> Dict:
72
+ def _get_elasticsearch_auth_params(self, username: Optional[str], password: Optional[str], api_key: Optional[str]) -> Dict:
71
73
  """
72
74
  Get authentication parameters for Elasticsearch client based on package version.
73
75
 
74
76
  Args:
75
77
  username: Username for authentication
76
78
  password: Password for authentication
79
+ api_key: API key for authentication
77
80
 
78
81
  Returns:
79
82
  Dictionary with appropriate auth parameters for the ES version
80
83
  """
84
+ # API key takes precedence over username/password
85
+ if api_key:
86
+ return {"api_key": api_key}
87
+
81
88
  if not username or not password:
82
89
  return {}
83
90
 
84
91
  # Check Elasticsearch package version to determine auth parameter name
85
92
  try:
86
93
  from elasticsearch import __version__ as es_version
87
- major_version = int(es_version.split('.')[0])
88
-
94
+ major_version = es_version[0]
89
95
  if major_version >= 8:
90
96
  # ES 8+ uses basic_auth
91
97
  return {"basic_auth": (username, password)}
92
98
  else:
93
99
  # ES 7 and below use http_auth
94
100
  return {"http_auth": (username, password)}
95
- except (ImportError, ValueError, AttributeError):
101
+ except Exception as e:
102
+ self.logger.error(f"Failed to detect Elasticsearch version: {e}")
96
103
  # If we can't detect version, try basic_auth first (ES 8+ default)
97
104
  return {"basic_auth": (username, password)}
98
105
 
99
106
  class GeneralRestClient:
100
- def __init__(self, base_url: Optional[str], username: Optional[str], password: Optional[str], verify_certs: bool):
107
+ def __init__(self, base_url: Optional[str], username: Optional[str], password: Optional[str], api_key: Optional[str], verify_certs: bool):
101
108
  self.base_url = base_url.rstrip("/") if base_url else ""
102
109
  self.auth = (username, password) if username and password else None
110
+ self.api_key = api_key
103
111
  self.verify_certs = verify_certs
104
112
 
105
113
  def request(self, method, path, params=None, body=None):
106
114
  url = f"{self.base_url}/{path.lstrip('/')}"
115
+ headers = {}
116
+
117
+ # Add API key to Authorization header if provided
118
+ if self.api_key:
119
+ headers["Authorization"] = f"ApiKey {self.api_key}"
120
+
107
121
  with httpx.Client(verify=self.verify_certs) as client:
108
122
  resp = client.request(
109
123
  method=method.upper(),
110
124
  url=url,
111
125
  params=params,
112
126
  json=body,
113
- auth=self.auth
127
+ auth=self.auth if not self.api_key else None, # Use basic auth only if no API key
128
+ headers=headers
114
129
  )
115
130
  resp.raise_for_status()
116
131
  ct = resp.headers.get("content-type", "")
src/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "2.0.9"
1
+ __version__ = "2.0.11"