Elasticsearch MCP Server
by da1y
- src
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { Client } from '@elastic/elasticsearch';
interface IndexInfo {
index: string;
[key: string]: unknown;
}
const server = new Server(
{
name: "example-servers/elasticsearch",
version: "0.1.0",
},
{
capabilities: {
resources: {},
tools: {},
},
}
);
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Please provide an Elasticsearch URL as a command-line argument");
process.exit(1);
}
const esUrl = args[0];
const resourceBaseUrl = new URL(esUrl);
resourceBaseUrl.protocol = "elasticsearch:";
resourceBaseUrl.password = "";
const client = new Client({
node: esUrl,
});
const SCHEMA_PATH = "schema";
server.setRequestHandler(ListResourcesRequestSchema, async () => {
try {
const indices = await client.cat.indices({ format: 'json' }) as IndexInfo[];
const resources = indices.map(index => ({
uri: new URL(`${index.index}/${SCHEMA_PATH}`, resourceBaseUrl).href,
mimeType: "application/json",
name: `"${index.index}" index schema`,
}));
return { resources };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new Error(`Failed to list indices: ${errorMessage}`);
}
});
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const resourceUrl = new URL(request.params.uri);
const pathComponents = resourceUrl.pathname.split("/");
const schema = pathComponents.pop();
const indexName = pathComponents.pop();
if (!indexName || schema !== SCHEMA_PATH) {
throw new Error("Invalid resource URI");
}
try {
const response = await client.indices.getMapping({
index: indexName,
});
const mappingData = (response as Record<string, any>)[indexName];
return {
contents: [{
uri: request.params.uri,
mimeType: "application/json",
text: JSON.stringify(mappingData, null, 2),
}],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new Error(`Failed to get mapping: ${errorMessage}`);
}
});
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "search",
description: "Run an Elasticsearch query",
inputSchema: {
type: "object",
properties: {
index: { type: "string" },
query: { type: "object" },
},
required: ["index", "query"],
},
},
{
name: "create_index",
description: "Create a new Elasticsearch index",
inputSchema: {
type: "object",
properties: {
index: { type: "string" },
mappings: {
type: "object",
description: "Index mappings configuration",
default: {}
},
settings: {
type: "object",
description: "Index settings configuration",
default: {}
}
},
required: ["index"],
},
},
{
name: "list_indices",
description: "List all Elasticsearch indices",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
{
name: "index_document",
description: "Index a document in Elasticsearch",
inputSchema: {
type: "object",
properties: {
index: { type: "string" },
id: {
type: "string",
description: "Optional document ID. If not provided, Elasticsearch will generate one"
},
document: {
type: "object",
description: "Document to index"
},
},
required: ["index", "document"],
},
}
],
};
});
type SearchArguments = {
index: string;
query: Record<string, unknown>;
}
type CreateIndexArguments = {
index: string;
mappings?: Record<string, unknown>;
settings?: Record<string, unknown>;
}
type IndexDocumentArguments = {
index: string;
id?: string;
document: Record<string, unknown>;
}
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = request.params.arguments as unknown;
switch (request.params.name) {
case "search": {
const { index, query } = args as SearchArguments;
try {
const result = await client.search({
index,
body: query,
});
return {
content: [{ type: "text", text: JSON.stringify(result.hits, null, 2) }],
isError: false,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new Error(`Search failed: ${errorMessage}`);
}
}
case "create_index": {
const { index, mappings = {}, settings = {} } = args as CreateIndexArguments;
try {
const result = await client.indices.create({
index,
body: {
mappings,
settings
}
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
isError: false,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new Error(`Failed to create index: ${errorMessage}`);
}
}
case "list_indices": {
try {
const indices = await client.cat.indices({ format: 'json' });
return {
content: [{ type: "text", text: JSON.stringify(indices, null, 2) }],
isError: false,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new Error(`Failed to list indices: ${errorMessage}`);
}
}
case "index_document": {
const { index, id, document } = args as IndexDocumentArguments;
try {
const result = await client.index({
index,
id,
document,
refresh: true // Make document immediately available for search
});
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
isError: false,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new Error(`Failed to index document: ${errorMessage}`);
}
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
runServer().catch(console.error);