From 360331ca10eca622cb5333757784a7d3590e82d6 Mon Sep 17 00:00:00 2001 From: getsolaris Date: Fri, 4 Apr 2025 16:17:52 +0900 Subject: [PATCH 1/5] feat: add tool to retrieve shard information for indices --- index.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/index.ts b/index.ts index c895718..c706dac 100644 --- a/index.ts +++ b/index.ts @@ -315,6 +315,68 @@ export async function createElasticsearchMcpServer( } ); + // Tool 4: Get shard information + server.tool( + "get_shards", + "Get shard information for all or specific indices", + { + index: z + .string() + .optional() + .describe("Optional index name to get shard information for"), + }, + async ({ index }) => { + try { + const response = await esClient.cat.shards({ + index, + format: "json", + }); + + const shardsInfo = response.map((shard) => ({ + index: shard.index, + shard: shard.shard, + prirep: shard.prirep, + state: shard.state, + docs: shard.docs, + store: shard.store, + ip: shard.ip, + node: shard.node, + })); + + const metadataFragment = { + type: "text" as const, + text: `Found ${shardsInfo.length} shards${index ? ` for index ${index}` : ""}`, + }; + + return { + content: [ + metadataFragment, + { + type: "text" as const, + text: JSON.stringify(shardsInfo, null, 2), + }, + ], + }; + } catch (error) { + console.error( + `Failed to get shard information: ${ + error instanceof Error ? error.message : String(error) + }` + ); + return { + content: [ + { + type: "text" as const, + text: `Error: ${ + error instanceof Error ? error.message : String(error) + }`, + }, + ], + }; + } + } + ); + return server; } From c3b6981eb6c2df7eb2630e7d9821c0e3c7c80c40 Mon Sep 17 00:00:00 2001 From: getsolaris Date: Sun, 6 Apr 2025 21:46:40 +0900 Subject: [PATCH 2/5] docs: add get_shards tool --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f8b689d..c9c00c8 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This server connects agents to your Elasticsearch data using the Model Context P * `list_indices`: List all available Elasticsearch indices * `get_mappings`: Get field mappings for a specific Elasticsearch index * `search`: Perform an Elasticsearch search with the provided query DSL +* `get_shards`: Get shard information for all or specific indices ## Prerequisites From 4bfc39d2157510865f0056bdfe55a4937b670c23 Mon Sep 17 00:00:00 2001 From: getsolaris Date: Tue, 8 Apr 2025 22:30:18 +0900 Subject: [PATCH 3/5] feat: add create_index tool --- index.ts | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) diff --git a/index.ts b/index.ts index 4c1ad7a..d3ab21e 100644 --- a/index.ts +++ b/index.ts @@ -399,6 +399,133 @@ export async function createElasticsearchMcpServer( } ); + // Tool 5: Create Index + server.tool( + "create_index", + "Create a new Elasticsearch index with mappings. Accepts natural language descriptions or DDL statements.", + { + index: z + .string() + .trim() + .min(1, "Index name is required") + .describe("Name of the index to create"), + + mappings: z + .record(z.any()) + .describe(` +Elasticsearch mappings in one of the following formats: + +1. Natural Language Description: +"Create a user table with: +- id: integer field for user identification +- name: text field searchable in Korean +- email: keyword field for exact matching +- created_at: date field for creation timestamp" + +2. MySQL DDL: +"CREATE TABLE users ( + id INT PRIMARY KEY, + name VARCHAR(255), + email VARCHAR(255), + created_at TIMESTAMP +);" + +3. Elasticsearch Mapping (Final Format): +{ + "properties": { + "id": { "type": "integer" }, + "name": { "type": "text", "analyzer": "korean" }, + "email": { "type": "keyword" }, + "created_at": { "type": "date" } + } +} + +Note: If you provide format 1 or 2, the LLM will automatically convert it to format 3. +`), + + settings: z + .record(z.any()) + .optional() + .describe( + "Optional index settings (e.g., number_of_shards, number_of_replicas)" + ), + + analyzer: z + .string() + .optional() + .describe("Optional custom analyzer configuration for text fields"), + }, + async ({ index, mappings, settings, analyzer }) => { + try { + // Check if index already exists + const exists = await esClient.indices.exists({ index }); + if (exists) { + return { + content: [ + { + type: "text" as const, + text: `Index ${index} already exists. Please choose a different name or delete the existing index first.`, + }, + ], + }; + } + + // Create index with mappings + const createResponse = await esClient.indices.create({ + index, + body: { + settings: { + ...settings, + analysis: analyzer + ? { + analyzer: { + custom_analyzer: { + type: "custom", + tokenizer: "standard", + filter: ["lowercase", "stop"], + }, + }, + } + : undefined, + }, + mappings: mappings + }, + }); + + const metadataFragment = { + type: "text" as const, + text: `Index ${index} created successfully with the following configuration:`, + }; + + return { + content: [ + metadataFragment, + { + type: "text" as const, + text: JSON.stringify(createResponse, null, 2), + }, + ], + }; + } catch (error) { + console.error( + `Failed to create index: ${ + error instanceof Error ? error.message : String(error) + }` + ); + return { + content: [ + { + type: "text" as const, + text: `Error: ${ + error instanceof Error ? error.message : String(error) + }`, + }, + ], + }; + } + } + ); + return server; } From 63c42f68db1f7fec5e471c923c1056a048643cbb Mon Sep 17 00:00:00 2001 From: getsolaris Date: Tue, 8 Apr 2025 22:30:34 +0900 Subject: [PATCH 4/5] docs: add create_index tools and tip --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3e60549..fccaa82 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This server connects agents to your Elasticsearch data using the Model Context P * `get_mappings`: Get field mappings for a specific Elasticsearch index * `search`: Perform an Elasticsearch search with the provided query DSL * `get_shards`: Get shard information for all or specific indices +* `create_index`: Create a new Elasticsearch index with mappings and settings ## Prerequisites @@ -146,6 +147,7 @@ We welcome contributions from the community! For details on how to contribute, p * "Show me the field mappings for the 'products' index." * "Find all orders over $500 from last month." * "Which products received the most 5-star reviews?" +* "Create a new index for users with fields for id, name, email, and created_at." ## How It Works From 112314d872af8549e8c22b2310fd54b0e741c089 Mon Sep 17 00:00:00 2001 From: getsolaris Date: Tue, 8 Apr 2025 22:58:20 +0900 Subject: [PATCH 5/5] feat: analyzer --- index.ts | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/index.ts b/index.ts index d3ab21e..3ed0534 100644 --- a/index.ts +++ b/index.ts @@ -451,9 +451,37 @@ Note: If you provide format 1 or 2, the LLM will automatically convert it to for ), analyzer: z - .string() + .record(z.any()) .optional() - .describe("Optional custom analyzer configuration for text fields"), + .describe(` +Elasticsearch analyzer configuration in one of the following formats: + +1. Natural Language Description: +"Create a Korean text analyzer with: +- type: standard +- stopwords: Korean stopwords +- tokenizer: standard" + +2. JSON Format: +{ + "korean": { + "type": "standard", + "stopwords": "_korean_" + } +} + +3. Elasticsearch Analyzer (Final Format): +{ + "analyzer": { + "korean": { + "type": "standard", + "stopwords": "_korean_" + } + } +} + +Note: If you provide format 1 or 2, the LLM will automatically convert it to format 3. +`), }, async ({ index, mappings, settings, analyzer }) => { try { @@ -479,12 +507,8 @@ Note: If you provide format 1 or 2, the LLM will automatically convert it to for analysis: analyzer ? { analyzer: { - custom_analyzer: { - type: "custom", - tokenizer: "standard", - filter: ["lowercase", "stop"], - }, - }, + ...analyzer + } } : undefined, },