elasticsearch-mcp
Elasticsearch-MCP is a read-only-by-default MCP server exposing 34 safety-gated tools to query, diagnose, map, ingest, and operate Elasticsearch/OpenSearch clusters over REST.
Query & Analyze: Run Query DSL (
run_query), count docs, generate/validate DSL, explain queries with profiling and per-doc scoring, and translate SQL to DSL.SQL: Execute SELECT-only Elasticsearch SQL queries with paging cursors and translate SQL to native DSL.
Pagination: Use point-in-time (
open_pit/close_pit) andpaged_searchto page beyond the 10k deep-paging limit.Diagnostics: Check cluster/index health, shard allocation (incl. unassigned reasons), slow queries, node stats, field capabilities across indices.
Mapping & Ingest: List/get mappings, analyze text tokenization, add mapping fields (gated), index/bulk-index documents.
Operations: Manage snapshots (list/create/restore/status), reindex, delete/update by query, update index settings, atomic alias swaps, poll/cancel tasks.
Cluster Info: View version, distribution, and active safety policy.
Safety: All write tools require
READ_ONLY=falseandconfirm=true; destructive ones additionally requireALLOW_DESTRUCTIVE=true. Index allow/deny policies and query limits enforce access.
Provides tools for interacting with Elasticsearch clusters, including query execution, DSL generation and explanation, index and cluster health, mapping analysis, snapshot management, and reindexing operations.
Supports OpenSearch clusters via the same REST API tools, enabling querying, diagnostics, mapping, and cluster operations in OpenSearch environments.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@elasticsearch-mcp Check cluster health and list any unassigned shards"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
elasticsearch-mcp
MCP server for Elasticsearch, built directly on the REST API (no elasticsearch-py dependency, so it also works against OpenSearch and managed clusters that only expose HTTP).
Any MCP agent — Claude, Cursor, a custom LLM agent — can query, diagnose, map, ingest, and run gated write operations. 34 tools. Read-only by default; every write path is gated.

Live demo: an agent lists indices, reads the mapping, runs a query and SQL, and gets blocked when it tries an unsafe write. See demo/demo.py.
Install
git clone https://github.com/baburajr/elasticsearch_mcp.git && cd elasticsearch_mcp
uv sync # or: pip install -e ".[dev]"
cp .env.example .env
pytest -qRelated MCP server: OpenSearch MCP Server
Register with Claude Code / Claude Desktop
{
"mcpServers": {
"elasticsearch": {
"command": "uv",
"args": ["--directory", "/abs/path/elasticsearch-mcp", "run", "elasticsearch-mcp"],
"env": {
"ES_MCP_HOSTS": "https://es.internal:9200",
"ES_MCP_API_KEY": "base64-encoded-api-key",
"ES_MCP_INDEX_ALLOW": "logs-*,metrics-*",
"ES_MCP_READ_ONLY": "true"
}
}
}
}Remote / shared deployment instead of stdio:
elasticsearch-mcp --transport streamable-httpTools
34 tools. Write tools (marked ✎) need ES_MCP_READ_ONLY=false and confirm=true in the call. Destructive ones (marked ⚠) additionally need ES_MCP_ALLOW_DESTRUCTIVE=true.
Query
Tool | What it does |
| Execute Query DSL. Size capped, timeout injected, deep paging rejected, |
| Structured spec to valid DSL, validated against the index, returns a field catalog |
|
|
| Match count without fetching hits |
SQL
Tool | What it does |
| Run Elasticsearch SQL via |
| Translate an SQL SELECT into native Query DSL via |
Paging (point-in-time)
Tool | What it does |
| Open/close a point-in-time to page a large result set consistently |
| Page past the 10000 deep-paging limit using the PIT + |
Diagnostics
Tool | What it does |
| Status, shard counts, pending tasks, optional per-node heap/disk/CPU/rejections |
| Per index: docs, store, segments, avg query and index latency, merges |
|
|
| Indices ranked by avg query latency, running search tasks, thread-pool rejections, slowlog thresholds |
| Nodes with role, version, heap/RAM/CPU/load, and master flag |
| Field types across indices via |
Mapping
Tool | What it does |
| Indices and aliases with doc counts and size |
| Flattened |
| Token output of an analyzer, for debugging zero-hit match queries |
| Additive mapping changes |
Ingest
Tool | What it does |
| Index a single document (create or overwrite) |
| Bulk index many documents in one |
Operations
Tool | What it does |
| Snapshot inventory and progress |
| Snapshot selected indices, async by default |
| Restore, with pre-flight check for existing open indices and rename support |
| Async reindex with query, pipeline, script, slicing, throttling; returns a task id |
| Delete matching docs (match_all refused); async, returns a task id |
| Painless script update of matching docs (match_all refused); async, returns a task id |
| Update dynamic index settings; static settings refused |
| Atomic alias add/remove, for zero-downtime reindex cutover |
| Poll or kill long-running tasks |
| Version, distribution, and the active safety policy |
Configuration
All variables use the ES_MCP_ prefix, read from the environment or .env.
Variable | Default | Purpose |
|
| Comma separated; failover across them |
| – | Pick one auth mode |
|
| TLS |
|
| Retries cover 429/502/503/504 and connect errors, with jittered backoff and |
|
| Master switch for all write tools |
|
| Second gate for restore_snapshot, reindex, put_mapping, update_settings, delete_by_query, update_by_query |
|
| Glob allow-list |
|
| Glob deny-list; deny wins |
|
| Hit caps |
|
| Rejects bucket explosions |
|
| Per-query guards |
|
| Token control |
| unset | JSONL record of every tool call and outcome |
|
| Logs go to stderr, never stdout (stdio transport) |
Safety model
Four independent layers, all failing closed:
Index policy — allow-list and deny-list checked on every call; deny wins; wildcard writes across the whole cluster refused.
Read-only — every write tool refuses unless
READ_ONLY=false, and each also requires an explicitconfirm=trueargument in the call itself.Destructive gate — restore_snapshot, reindex, put_mapping, update_settings, delete_by_query and update_by_query additionally need
ALLOW_DESTRUCTIVE=true.Query limits — size clamp, agg bucket cap, agg nesting depth cap, deep-paging rejection, injected search timeout.
Tools return errors as readable text (ERROR (run_query): ...) rather than raising, so the model can correct itself instead of stalling.
Recommended production posture: a dedicated ES API key with read on exactly the allowed indices, READ_ONLY=true, and a separate write-enabled instance only if you actually need reindex/restore from the assistant.
Docker
docker build -t elasticsearch-mcp .
docker run --rm -p 8000:8000 \
-e ES_MCP_HOSTS=https://es.internal:9200 \
-e ES_MCP_API_KEY=base64-encoded-api-key \
elasticsearch-mcp # runs the streamable-http transport, read-onlyThe image runs as a non-root user and defaults to ES_MCP_READ_ONLY=true.
Extending
Add a module under src/es_mcp/tools/, expose register(server, ctx), wire it in server.build_server. Use ctx.guard for policy checks and ctx.render for output so limits apply automatically.
License
Apache-2.0. See LICENSE.
Available Tools
34 toolsalias_actionsA
Add or remove index aliases atomically in one request. Pass actions as a JSON array, e.g. [{"add": {"index": "logs-2026", "alias": "logs"}}, {"remove": {"index": "logs-2025", "alias": "logs"}}]. Use this to swap an alias from an old index to a new one with zero downtime after a reindex. Only add/remove are allowed. Every index touched is checked against the write policy. Requires writes enabled and confirm=true.
Inputs: actions (required), confirm. Returns: result
analyze_textARead-only
Run text through an analyzer and see the resulting tokens. Use it when a match query returns nothing: it shows exactly how the field's analyzer tokenizes the indexed text vs your search term.
Inputs: text (required), field, index, analyzer. Returns: result
bulk_indexA
Bulk index many documents into one index in a single request. Pass documents as a JSON array of objects; each becomes an index action. Optional id_field names a field to use as the document id. Reports how many succeeded and the first few errors. Much faster than index_document in a loop. Requires writes enabled and confirm=true.
Inputs: index (required), documents (required), confirm, refresh, +1 more. Returns: result
cancel_taskAIdempotent
Cancel a running task by id. Use for a runaway reindex or a search eating the cluster.
Inputs: task_id (required), confirm. Returns: result
cat_nodesARead-only
List cluster nodes with role, version, heap/RAM/CPU/load, and master flag. Use it to spot a hot node, a version mismatch across nodes, or which node is master.
Inputs: no named parameters. Returns: result
close_pitBRead-only
Close a point-in-time by its pit_id to free cluster resources. Call after paging finishes.
Inputs: pit_id (required). Returns: result
cluster_healthARead-only
Cluster health overview: status, node counts, active/relocating/initializing/unassigned shards, pending tasks, and (optionally) per-index health. Start troubleshooting here.
Inputs: level, include_nodes. Returns: result
cluster_infoARead-only
Cluster name, version, distribution, and the safety policy this server is running under.
Inputs: no named parameters. Returns: result
count_documentsARead-only
Count matching documents without returning hits. Cheap way to size a query before running it.
Inputs: index (required), query. Returns: result
create_snapshotA
Create a snapshot of selected indices into a repository. Non-blocking by default: returns immediately, poll with snapshot_status. Requires writes enabled and confirm=true.
Inputs: snapshot (required), repository (required), confirm, indices, +2 more. Returns: result
delete_by_queryADestructive
Delete documents matching a query from an index. DESTRUCTIVE and irreversible. A query is mandatory: match_all is refused so you cannot wipe an index by accident. Runs asynchronously (wait_for_completion=false) and returns a task_id to poll with get_task. Reports how many documents match before deleting. Requires writes enabled, ES_MCP_ALLOW_DESTRUCTIVE=true, and confirm=true.
Inputs: index (required), query (required), slices, confirm, +2 more. Returns: result
explain_queryARead-only
Explain a query three ways: (1) _validate/query?rewrite=true shows how ES rewrites it and why it may be invalid, (2) profile=true gives per-component timing to find the slow clause, (3) if doc_id is given, _explain shows why that document matched or did not, with scoring detail.
Inputs: index (required), query (required), doc_id, profile. Returns: result
field_capsARead-only
Show the capabilities of one or more fields across indices via _field_caps: the type(s) each field has, whether it is searchable and aggregatable. The key use is catching a field mapped as different types in different indices (e.g. long in one, keyword in another), which breaks queries across an index pattern. Pass fields as a comma list or *.
Inputs: index (required), fields. Returns: result
find_slow_queriesARead-only
Find what is slow. Combines: indices ranked by average query latency (_stats), currently running search tasks with elapsed time (_tasks), search thread-pool queue/rejection counts per node, and the configured search slowlog thresholds per index (so you know whether slowlog is even on). Use top_n to control how many indices come back.
Inputs: index, top_n, min_avg_ms. Returns: result
generate_dslARead-only
Build Elasticsearch Query DSL from a structured spec, then validate it against the real index. Supply spec as JSON with any of: text, text_fields, filters [{field, op, value}], must_not, should, time_field/time_from/time_to, sort, size, aggs [{name, type, field, size, interval}], highlight_fields, source_includes. Ops: eq, neq, in, not_in, gt, gte, lt, lte, exists, missing, prefix, wildcard, match, match_phrase. Returns the generated DSL plus validation result and a field catalog so you can correct field names before running it.
Inputs: spec (required), index (required), validate. Returns: result
get_mappingARead-only
Get the mapping for an index. Returns a flattened field catalog (field path -> type, including multi-fields like .keyword) which is what you need to write correct queries, plus dynamic templates and total field count. Set raw=true for the untouched mapping JSON.
Inputs: index (required), raw, field_filter. Returns: result
get_taskARead-only
Poll a long-running task (reindex, update_by_query, delete_by_query) for progress and errors.
Inputs: task_id (required), wait_for_completion. Returns: result
index_documentA
Index (create or overwrite) a single document into an index. Pass doc as a JSON object. Optional doc_id: given, it overwrites that id; omitted, ES assigns one. Set refresh=true to make it searchable immediately (slower). Requires writes enabled and confirm=true.
Inputs: index (required), document (required), doc_id, confirm, +1 more. Returns: result
index_healthBRead-only
Per-index health and size: status, docs, store size, primaries/replicas, segment count, refresh and merge stats, plus search/index throughput. Pass an index pattern to narrow it down.
Inputs: index, include_settings. Returns: result
list_indicesARead-only
List indices, data streams and aliases matching a pattern, with doc counts and store size.
Inputs: pattern, include_aliases. Returns: result
list_snapshot_repositoriesARead-only
List registered snapshot repositories with their type and settings (bucket, base_path, ...).
Inputs: no named parameters. Returns: result
list_snapshotsARead-only
List snapshots in a repository with state, start/end time, duration, indices count, shard failures and size. Use this before restoring to pick the right snapshot.
Inputs: repository (required), verbose, snapshot. Returns: result
open_pitARead-only
Open a point-in-time (PIT) against an index and return its pit_id. A PIT freezes the data view so you can page through a large result set consistently with paged_search, past the 10000 deep-paging limit. Always close it with close_pit when done; keep_alive controls how long ES holds it open.
Inputs: index (required), keep_alive. Returns: result
paged_searchARead-only
Page through a large result set beyond the 10000 deep-paging limit using a point-in-time and search_after. First call: open_pit, then call this with the pit_id and a query; it returns one page plus next_search_after and the (possibly refreshed) pit_id. Pass both back on the next call to get the following page. A sort is required and an implicit _shard_doc tiebreak is added for stable ordering. No index argument: the PIT already binds the index. Close with close_pit at the end.
Inputs: pit_id (required), size, sort, query, +3 more. Returns: result
put_mappingADestructive
Add new fields to an existing index mapping. Only additive changes are possible in Elasticsearch; changing an existing field type requires reindex. Blocked unless the server runs with writes enabled, and requires confirm=true.
Inputs: index (required), properties (required), confirm. Returns: result
reindexADestructive
Copy documents from a source index to a destination, optionally filtered by a query and transformed by a pipeline or script. Runs asynchronously (wait_for_completion=false) and returns a task_id to poll with get_task. Use this to change a field type, reshard, or migrate data. Requires writes enabled, ES_MCP_ALLOW_DESTRUCTIVE=true, and confirm=true.
Inputs: dest_index (required), source_index (required), query, slices, +6 more. Returns: result
restore_snapshotADestructive
Restore indices from a snapshot. DESTRUCTIVE: an index that already exists must be closed or renamed via rename_pattern/rename_replacement, otherwise the restore fails. Requires writes enabled, ES_MCP_ALLOW_DESTRUCTIVE=true, and confirm=true.
Inputs: indices (required), snapshot (required), repository (required), confirm, +4 more. Returns: result
run_queryARead-only
Execute an Elasticsearch search against an index or alias. Accepts a full Query DSL body (JSON string or object). Size is capped by server policy, a search timeout is injected, and deep paging is rejected. Returns hits, aggregations, timing and shard stats. Use search_after for pagination beyond 10k.
Inputs: index (required), size, sort, from_, +6 more. Returns: result
shard_allocationARead-only
Shard placement and why shards are unassigned. Returns _cat/shards, per-node disk usage, and for any UNASSIGNED shard the cluster allocation explanation (decider-level reasons: disk watermark, awareness, filtering, max_retries). This is the tool for a yellow/red cluster.
Inputs: index, only_problems. Returns: result
snapshot_statusARead-only
Detailed progress of running or recent snapshots: per-shard stage, bytes done vs total.
Inputs: snapshot, repository. Returns: result
sql_queryARead-only
Run an Elasticsearch SQL query via the _sql API. Good for quick aggregate/filter questions without hand-writing Query DSL, e.g. SELECT status, COUNT() FROM "logs-" WHERE code >= 500 GROUP BY status. Only SELECT is allowed. The index in FROM is checked against the allow/deny policy. Row count is capped by fetch_size (server result-size limit applies). Returns columns and rows; if there are more rows a cursor is returned to pass back as the cursor argument.
Inputs: query, cursor, fetch_size. Returns: result
sql_translateARead-only
Translate an Elasticsearch SQL SELECT into the equivalent native Query DSL via _sql/translate, without running it. Use this to learn the DSL for a query, then hand the DSL to run_query for full control (search_after, routing, profiling).
Inputs: query (required). Returns: result
update_by_queryADestructive
Update documents in place by query, using a painless script. DESTRUCTIVE and irreversible. A query is mandatory: match_all is refused so you cannot rewrite a whole index by accident. Runs asynchronously; returns a task_id to poll with get_task. Reports how many documents match before updating. Requires writes enabled, ES_MCP_ALLOW_DESTRUCTIVE=true, and confirm=true.
Inputs: index (required), query (required), script_source (required), slices, +4 more. Returns: result
update_settingsADestructive
Update dynamic index settings such as number_of_replicas, refresh_interval, max_result_window, or blocks.*. Static settings (e.g. number_of_shards) cannot be changed on a live index and are refused. Requires writes enabled, ES_MCP_ALLOW_DESTRUCTIVE=true, and confirm=true.
Inputs: index (required), settings (required), confirm. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 34 tools
Each tool addresses a distinct concern: query execution, DSL generation, SQL translation, pagination, health, snapshots, async tasks, and writes. Even the seemingly similar search tools are clearly separated by their input format and use case, so an agent can select the right one with confidence.
Many names follow a verb_noun pattern like list_indices, create_snapshot, and run_query, but there are many noun-first or adjective-noun exceptions such as cluster_health, shard_allocation, field_caps, snapshot_status, sql_query, and bulk_index. The naming is readable and snake_case throughout, but the conventions are mixed enough that the pattern is not predictable.
34 tools is a heavy surface for an agent to navigate, especially since several areas such as cluster diagnostics, query generation, and snapshot management could be consolidated. While the breadth is understandable for Elasticsearch, the count exceeds what is typically needed for a coherent, easy-to-use tool set.
The server covers a wide range of operations: search, query generation, SQL, mappings, cluster health, slow-query investigation, snapshots, reindexing, async tasks, settings, aliases, and document writes. However, some core lifecycle operations are missing: no create_index, delete_index, close/open index, get document by id, delete snapshot, or single-document delete, which can create dead ends in common workflows.
Related MCP Connectors
Read-only MCP access to a documented IT fleet: state, changes, posture. 15 tools.
MCP access to ELSHWORK agents, repositories, isolated runs, discovery and tasks.
Host any HTTP API as a standard MCP server; auto-distribute to 12+ mainstream agent platforms.
Remote MCP for 1,500+ APIs. Vault-managed credentials; OAuth or API key. Search, load, and execute.
Related MCP Servers
- AlicenseBqualityDmaintenanceAn MCP server that enables interaction with Elasticsearch and OpenSearch clusters for searching documents and managing indices. It provides tools for cluster health monitoring, index configuration, and general API requests.16Apache 2.0
- FlicenseNot gradedqualityDmaintenanceEnables LLMs to interact with OpenSearch clusters to monitor cluster health, manage indices, and perform data searches. It provides a standardized interface for real-time OpenSearch operations within MCP-compatible environments like Open WebUI.-
- AlicenseNot gradedqualityAmaintenanceEnables AI assistants to interact with OpenSearch clusters for searching indices, retrieving mappings, and managing shards through the MCP protocol.Apache 2.0
- FlicenseNot gradedqualityDmaintenanceEnables read-only interaction with Elasticsearch, supporting listing indices, retrieving mappings, and executing search/aggregation queries via MCP.-