Skip to main content
Glama
elasticsearch-mcp

elasticsearch-mcp

by baburajr

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.

elasticsearch-mcp demo

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 -q

Related 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-http

Tools

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

run_query

Execute Query DSL. Size capped, timeout injected, deep paging rejected, search_after supported

generate_dsl

Structured spec to valid DSL, validated against the index, returns a field catalog

explain_query

_validate?rewrite=true + profile timing breakdown + per-document _explain

count_documents

Match count without fetching hits

SQL

Tool

What it does

sql_query

Run Elasticsearch SQL via _sql (SELECT only); the FROM target is checked against the index policy. Returns columns, rows, and a paging cursor

sql_translate

Translate an SQL SELECT into native Query DSL via _sql/translate, without running it

Paging (point-in-time)

Tool

What it does

open_pit / close_pit

Open/close a point-in-time to page a large result set consistently

paged_search

Page past the 10000 deep-paging limit using the PIT + search_after, with an automatic _shard_doc tiebreak

Diagnostics

Tool

What it does

cluster_health

Status, shard counts, pending tasks, optional per-node heap/disk/CPU/rejections

index_health

Per index: docs, store, segments, avg query and index latency, merges

shard_allocation

_cat/shards plus _cluster/allocation/explain decider reasons for unassigned shards

find_slow_queries

Indices ranked by avg query latency, running search tasks, thread-pool rejections, slowlog thresholds

cat_nodes

Nodes with role, version, heap/RAM/CPU/load, and master flag

field_caps

Field types across indices via _field_caps; flags fields mapped as different types in different indices

Mapping

Tool

What it does

list_indices

Indices and aliases with doc counts and size

get_mapping

Flattened field path -> type catalog including multi-fields

analyze_text

Token output of an analyzer, for debugging zero-hit match queries

put_mapping ✎⚠

Additive mapping changes

Ingest

Tool

What it does

index_document ✎

Index a single document (create or overwrite)

bulk_index ✎

Bulk index many documents in one _bulk request, with error summary

Operations

Tool

What it does

list_snapshot_repositories, list_snapshots, snapshot_status

Snapshot inventory and progress

create_snapshot ✎

Snapshot selected indices, async by default

restore_snapshot ✎⚠

Restore, with pre-flight check for existing open indices and rename support

reindex ✎⚠

Async reindex with query, pipeline, script, slicing, throttling; returns a task id

delete_by_query ✎⚠

Delete matching docs (match_all refused); async, returns a task id

update_by_query ✎⚠

Painless script update of matching docs (match_all refused); async, returns a task id

update_settings ✎⚠

Update dynamic index settings; static settings refused

alias_actions ✎

Atomic alias add/remove, for zero-downtime reindex cutover

get_task, cancel_task

Poll or kill long-running tasks

cluster_info

Version, distribution, and the active safety policy

Configuration

All variables use the ES_MCP_ prefix, read from the environment or .env.

Variable

Default

Purpose

HOSTS

http://localhost:9200

Comma separated; failover across them

API_KEY / USERNAME+PASSWORD / BEARER_TOKEN

–

Pick one auth mode

VERIFY_CERTS, CA_CERTS, CLIENT_CERT, CLIENT_KEY

true

TLS

REQUEST_TIMEOUT, CONNECT_TIMEOUT, MAX_RETRIES

30, 10, 3

Retries cover 429/502/503/504 and connect errors, with jittered backoff and Retry-After

READ_ONLY

true

Master switch for all write tools

ALLOW_DESTRUCTIVE

false

Second gate for restore_snapshot, reindex, put_mapping, update_settings, delete_by_query, update_by_query

INDEX_ALLOW

*

Glob allow-list

INDEX_DENY

.*,security-*

Glob deny-list; deny wins

DEFAULT_SIZE, MAX_RESULT_SIZE

10, 200

Hit caps

MAX_AGG_BUCKETS

1000

Rejects bucket explosions

SEARCH_TIMEOUT, TERMINATE_AFTER

30s, unset

Per-query guards

MAX_RESPONSE_CHARS, MAX_SOURCE_CHARS

60000, 2000

Token control

AUDIT_LOG_PATH

unset

JSONL record of every tool call and outcome

LOG_LEVEL

INFO

Logs go to stderr, never stdout (stdio transport)

Safety model

Four independent layers, all failing closed:

  1. Index policy — allow-list and deny-list checked on every call; deny wins; wildcard writes across the whole cluster refused.

  2. Read-only — every write tool refuses unless READ_ONLY=false, and each also requires an explicit confirm=true argument in the call itself.

  3. Destructive gate — restore_snapshot, reindex, put_mapping, update_settings, delete_by_query and update_by_query additionally need ALLOW_DESTRUCTIVE=true.

  4. 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-only

The 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 tools
alias_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_textA
Read-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_taskA
Idempotent

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_nodesA
Read-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_pitB
Read-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_healthA
Read-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_infoA
Read-only

Cluster name, version, distribution, and the safety policy this server is running under.

Inputs: no named parameters. Returns: result

count_documentsA
Read-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_queryA
Destructive

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_queryA
Read-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_capsA
Read-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_queriesA
Read-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_dslA
Read-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_mappingA
Read-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_taskA
Read-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_healthB
Read-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_indicesA
Read-only

List indices, data streams and aliases matching a pattern, with doc counts and store size.

Inputs: pattern, include_aliases. Returns: result

list_snapshot_repositoriesA
Read-only

List registered snapshot repositories with their type and settings (bucket, base_path, ...).

Inputs: no named parameters. Returns: result

list_snapshotsA
Read-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_pitA
Read-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

put_mappingA
Destructive

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

reindexA
Destructive

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_snapshotA
Destructive

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_queryA
Read-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_allocationA
Read-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_statusA
Read-only

Detailed progress of running or recent snapshots: per-shard stage, bytes done vs total.

Inputs: snapshot, repository. Returns: result

sql_queryA
Read-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_translateA
Read-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_queryA
Destructive

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_settingsA
Destructive

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

    A3.6/5.0

    Scored across 34 tools

    Disambiguation5/5

    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.

    Naming Consistency3/5

    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.

    Tool Count2/5

    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.

    Completeness3/5

    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

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      An 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.
      16
      Apache 2.0
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables 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.
      -
    • A
      license
      Not graded
      quality
      A
      maintenance
      Enables AI assistants to interact with OpenSearch clusters for searching indices, retrieving mappings, and managing shards through the MCP protocol.
      Apache 2.0
    • F
      license
      Not graded
      quality
      D
      maintenance
      Enables read-only interaction with Elasticsearch, supporting listing indices, retrieving mappings, and executing search/aggregation queries via MCP.
      -