Building an Elasticsearch MCP Server
Expose full-text search over Elasticsearch as an MCP tool, restricted to search-only queries against a fixed set of indices.
Quick Answer / TL;DR
An Elasticsearch MCP server uses the official @elastic/elasticsearch client to run search queries via the Query DSL, restricted to a fixed list of indices and forbidding any query that would delete or modify documents.
Key Takeaways
- Restrict the tool to a fixed, server-side list of allowed indices - never let a model-supplied index name reach the client unchecked.
- Use the _search endpoint exclusively; the client can also delete indices and documents, so those methods should never be wired into the tool at all.
- A match query is usually the right default for natural-language search; reserve term queries for exact-value filters like status codes or IDs.
Why not just copy a reference implementation?
The official MCP servers repository says directly: "The servers in this repository are intended as reference implementations to demonstrate MCP features and SDK usage... not as production-ready solutions." Applied naively to Elasticsearch, that pattern would wire up the full client - including delete and index-management methods - rather than the search-only surface used below.
A restricted search tool
The official client exposes the full Elasticsearch API, including destructive operations, so the safety boundary here is which client methods the tool code calls - only .search() - combined with validating the requested index against an allowlist before the call is made.
import { Client } from "@elastic/elasticsearch";
const client = new Client({
node: process.env.ELASTICSEARCH_URL!,
auth: { apiKey: process.env.ELASTICSEARCH_API_KEY! },
});
const ALLOWED_INDICES = (process.env.ELASTICSEARCH_ALLOWED_INDICES ?? "").split(",").filter(Boolean);
async function searchTool(index: string, query: string, size = 20) {
if (!ALLOWED_INDICES.includes(index)) {
throw new Error(`Index "${index}" is not in the allowed list.`);
}
const result = await client.search({
index,
query: { match: { content: query } },
size,
});
return result.hits.hits.map((hit) => ({ score: hit._score, ...hit._source as object }));
}Building an Elasticsearch MCP Server FAQs
Direct answers for developers, operators, and Indian teams evaluating MCP.