From 6d03ff7b66e1960e477aa0e1fc3b99620d9955a0 Mon Sep 17 00:00:00 2001 From: Jedr Blaszyk Date: Mon, 24 Mar 2025 13:27:50 +0100 Subject: [PATCH] [MCP Server] Add notice about moving to own repo --- .../elasticsearch-mcp-server/.gitignore | 2 - example-apps/elasticsearch-mcp-server/.nvmrc | 1 - .../elasticsearch-mcp-server/README.md | 149 +-------- .../elasticsearch-mcp-server/index.ts | 297 ------------------ .../elasticsearch-mcp-server/package.json | 44 --- .../elasticsearch-mcp-server/tsconfig.json | 21 -- 6 files changed, 1 insertion(+), 513 deletions(-) delete mode 100644 example-apps/elasticsearch-mcp-server/.gitignore delete mode 100644 example-apps/elasticsearch-mcp-server/.nvmrc delete mode 100644 example-apps/elasticsearch-mcp-server/index.ts delete mode 100644 example-apps/elasticsearch-mcp-server/package.json delete mode 100644 example-apps/elasticsearch-mcp-server/tsconfig.json diff --git a/example-apps/elasticsearch-mcp-server/.gitignore b/example-apps/elasticsearch-mcp-server/.gitignore deleted file mode 100644 index de4d1f00..00000000 --- a/example-apps/elasticsearch-mcp-server/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -dist -node_modules diff --git a/example-apps/elasticsearch-mcp-server/.nvmrc b/example-apps/elasticsearch-mcp-server/.nvmrc deleted file mode 100644 index dc0bb0f4..00000000 --- a/example-apps/elasticsearch-mcp-server/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v22.12.0 diff --git a/example-apps/elasticsearch-mcp-server/README.md b/example-apps/elasticsearch-mcp-server/README.md index 161e2fc7..f7f4f475 100644 --- a/example-apps/elasticsearch-mcp-server/README.md +++ b/example-apps/elasticsearch-mcp-server/README.md @@ -1,150 +1,3 @@ # Elasticsearch MCP Server -Connect to your Elasticsearch data directly from any MCP Client (like Claude Desktop) using the Model Context Protocol (MCP). - -This server connects agents to your Elasticsearch data using the Model Context Protocol (MCP). It allows you to interact with your Elasticsearch indices through natural language conversations. - -## Features - -* **List Indices**: View all available Elasticsearch indices -* **Get Mappings**: Inspect field mappings for specific indices -* **Search**: Execute Elasticsearch queries using full Query DSL capabilities with automatic highlighting - -## Prerequisites - -* Node.js (v22+) -* An Elasticsearch instance -* Elasticsearch API key with appropriate permissions -* Claude Desktop App (free version is sufficient) - -## Installation & Setup - -### Using the Published NPM Package - -The easiest way to use Elasticsearch MCP Server is through the published npm package: - -1. **Configure Claude Desktop App** - - Open Claude Desktop App - - Go to Settings > Developer > MCP Servers - - Click `Edit Config` and add a new MCP Server with the following configuration to your `claude_desktop_config.json`: - -```json -{ - "mcpServers": { - "elasticsearch-mcp-server": { - "command": "npx", - "args": [ - "-y", - "mcp-server-elasticsearch" - ], - "env": { - "ES_URL": "your-elasticsearch-url", - "ES_API_KEY": "your-api-key" - } - } - } -} -``` - -2. **Start a Conversation** - - Open a new conversation in Claude Desktop App - - The MCP server should connect automatically - - You can now ask Claude questions about your Elasticsearch data - -### Developing Locally - -If you want to develop or modify the server locally: - -1. **Use the correct Node.js version** -```bash -nvm use -``` - -2. **Install Dependencies** -```bash -npm install -``` - -3. **Build the Project** -```bash -npm run build -``` - -4. **Configure Claude Desktop for local development** - - Open Claude Desktop App - - Go to Settings > Developer > MCP Servers - - Click `Edit Config` and add a new MCP Server with the following configuration: - -```json -{ - "mcpServers": { - "Elasticsearch MCP Server (Local)": { - "command": "node", - "args": [ - "/path/to/your/project/dist/index.js" - ], - "env": { - "ES_URL": "your-elasticsearch-url", - "ES_API_KEY": "your-api-key" - } - } - } -} -``` - -5. **Debugging** -```bash -npm run inspector -``` - -## Example Questions - -* "What indices do I have in my Elasticsearch cluster?" -* "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?" - -## How It Works - -When you ask Claude a question about your data: -1. Claude analyzes your request and determines which Elasticsearch operations are needed -2. The MCP server carries out these operations (listing indices, fetching mappings, performing searches) -3. Claude processes the results and presents them in a user-friendly format - -## Security Best Practices - -You can create a dedicated Elasticsearch API key with minimal permissions to control access to your data: - -``` -POST /_security/api_key -{ - "name": "es-mcp-server-access", - "role_descriptors": { - "claude_role": { - "cluster": [ - "monitor" - ], - "indices": [ - { - "names": [ - "index-1", - "index-2", - "index-pattern-*" - ], - "privileges": [ - "read", - "view_index_metadata" - ] - } - ] - } - } -} -``` - -## Troubleshooting - -* If the server isn't connecting, check that your MCP configuration is correct -* Ensure your Elasticsearch URL is accessible from your machine -* Verify that your API key has the necessary permissions -* Check the terminal output for any error messages +Moved to: https://github.com/elastic/mcp-server-elasticsearch diff --git a/example-apps/elasticsearch-mcp-server/index.ts b/example-apps/elasticsearch-mcp-server/index.ts deleted file mode 100644 index 4129708d..00000000 --- a/example-apps/elasticsearch-mcp-server/index.ts +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env node - -import { z } from "zod"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { Client, estypes } from "@elastic/elasticsearch"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; - -// Simplified configuration schema with only URL and API key -const ConfigSchema = z.object({ - url: z - .string() - .trim() - .min(1, "Elasticsearch URL cannot be empty") - .url("Invalid Elasticsearch URL format") - .describe("Elasticsearch server URL"), - - apiKey: z - .string() - .trim() - .min(1, "API key is required") - .describe("API key for Elasticsearch authentication"), -}); - -type ElasticsearchConfig = z.infer; - -export async function createElasticsearchMcpServer( - config: ElasticsearchConfig -) { - const validatedConfig = ConfigSchema.parse(config); - const { url, apiKey } = validatedConfig; - - const esClient = new Client({ - node: url, - auth: { - apiKey: apiKey, - }, - }); - - const server = new McpServer({ - name: "elasticsearch-mcp-server", - version: "0.1.3", - }); - - // Tool 1: List indices - server.tool( - "list_indices", - "List all available Elasticsearch indices", - {}, - async () => { - try { - const response = await esClient.cat.indices({ format: "json" }); - - const indicesInfo = response.map((index) => ({ - index: index.index, - health: index.health, - status: index.status, - docsCount: index.docsCount, - })); - - return { - content: [ - { - type: "text" as const, - text: `Found ${indicesInfo.length} indices`, - }, - { - type: "text" as const, - text: JSON.stringify(indicesInfo, null, 2), - }, - ], - }; - } catch (error) { - console.error( - `Failed to list indices: ${ - error instanceof Error ? error.message : String(error) - }` - ); - return { - content: [ - { - type: "text" as const, - text: `Error: ${ - error instanceof Error ? error.message : String(error) - }`, - }, - ], - }; - } - } - ); - - // Tool 2: Get mappings for an index - server.tool( - "get_mappings", - "Get field mappings for a specific Elasticsearch index", - { - index: z - .string() - .trim() - .min(1, "Index name is required") - .describe("Name of the Elasticsearch index to get mappings for"), - }, - async ({ index }) => { - try { - const mappingResponse = await esClient.indices.getMapping({ - index, - }); - - return { - content: [ - { - type: "text" as const, - text: `Mappings for index: ${index}`, - }, - { - type: "text" as const, - text: `Mappings for index ${index}: ${JSON.stringify( - mappingResponse[index]?.mappings || {}, - null, - 2 - )}`, - }, - ], - }; - } catch (error) { - console.error( - `Failed to get mappings: ${ - error instanceof Error ? error.message : String(error) - }` - ); - return { - content: [ - { - type: "text" as const, - text: `Error: ${ - error instanceof Error ? error.message : String(error) - }`, - }, - ], - }; - } - } - ); - - // Tool 3: Search an index with simplified parameters - server.tool( - "search", - "Perform an Elasticsearch search with the provided query DSL. Highlights are always enabled.", - { - index: z - .string() - .trim() - .min(1, "Index name is required") - .describe("Name of the Elasticsearch index to search"), - - queryBody: z - .record(z.any()) - .refine( - (val) => { - try { - JSON.parse(JSON.stringify(val)); - return true; - } catch (e) { - return false; - } - }, - { - message: "queryBody must be a valid Elasticsearch query DSL object", - } - ) - .describe( - "Complete Elasticsearch query DSL object that can include query, size, from, sort, etc." - ), - }, - async ({ index, queryBody }) => { - try { - // Get mappings to identify text fields for highlighting - const mappingResponse = await esClient.indices.getMapping({ - index, - }); - - const indexMappings = mappingResponse[index]?.mappings || {}; - - const searchRequest: estypes.SearchRequest = { - index, - ...queryBody, - }; - - // Always do highlighting - if (indexMappings.properties) { - const textFields: Record = {}; - - for (const [fieldName, fieldData] of Object.entries( - indexMappings.properties - )) { - if (fieldData.type === "text" || "dense_vector" in fieldData) { - textFields[fieldName] = {}; - } - } - - searchRequest.highlight = { - fields: textFields, - pre_tags: [""], - post_tags: [""], - }; - } - - const result = await esClient.search(searchRequest); - - // Extract the 'from' parameter from queryBody, defaulting to 0 if not provided - const from = queryBody.from || 0; - - const contentFragments = result.hits.hits.map((hit) => { - const highlightedFields = hit.highlight || {}; - const sourceData = hit._source || {}; - - let content = ""; - - for (const [field, highlights] of Object.entries(highlightedFields)) { - if (highlights && highlights.length > 0) { - content += `${field} (highlighted): ${highlights.join( - " ... " - )}\n`; - } - } - - for (const [field, value] of Object.entries(sourceData)) { - if (!(field in highlightedFields)) { - content += `${field}: ${JSON.stringify(value)}\n`; - } - } - - return { - type: "text" as const, - text: content.trim(), - }; - }); - - const metadataFragment = { - type: "text" as const, - text: `Total results: ${ - typeof result.hits.total === "number" - ? result.hits.total - : result.hits.total?.value || 0 - }, showing ${result.hits.hits.length} from position ${from}`, - }; - - return { - content: [metadataFragment, ...contentFragments], - }; - } catch (error) { - console.error( - `Search failed: ${ - error instanceof Error ? error.message : String(error) - }` - ); - return { - content: [ - { - type: "text" as const, - text: `Error: ${ - error instanceof Error ? error.message : String(error) - }`, - }, - ], - }; - } - } - ); - - return server; -} - -const config: ElasticsearchConfig = { - url: process.env.ES_URL || "", - apiKey: process.env.ES_API_KEY || "", -}; - -async function main() { - const transport = new StdioServerTransport(); - const server = await createElasticsearchMcpServer(config); - - await server.connect(transport); - - process.on("SIGINT", async () => { - await server.close(); - process.exit(0); - }); -} - -main().catch((error) => { - console.error( - "Server error:", - error instanceof Error ? error.message : String(error) - ); - process.exit(1); -}); diff --git a/example-apps/elasticsearch-mcp-server/package.json b/example-apps/elasticsearch-mcp-server/package.json deleted file mode 100644 index 465ac49d..00000000 --- a/example-apps/elasticsearch-mcp-server/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "mcp-server-elasticsearch", - "description": "Elasticsearch MCP Server", - "version": "0.1.3", - "license": "MIT", - "author": "Elastic", - "type": "module", - "main": "dist/index.js", - "module": "dist/index.js", - "bin": { - "mcp-server-elasticsearch": "./dist/index.js" - }, - "repository": "https://github.com/elastic/elasticsearch-labs", - "bugs": "https://github.com/elastic/elasticsearch-labs/issues", - "homepage": "https://www.elastic.co/", - "keywords": [ - "elasticsearch", - "search", - "mcp", - "mcp-server" - ], - "files": [ - "dist" - ], - "scripts": { - "build": "tsc && shx chmod +x dist/*.js", - "prepare": "npm run build", - "watch": "tsc --watch", - "start": "node dist/index.js", - "inspector": "npx @modelcontextprotocol/inspector node dist/index.js" - }, - "dependencies": { - "@elastic/elasticsearch": "^8.17.1", - "@modelcontextprotocol/sdk": "1.7.0" - }, - "devDependencies": { - "@types/node": "^22", - "shx": "^0.3.4", - "typescript": "^5.6.2" - }, - "engines": { - "node": "^18" - } -} diff --git a/example-apps/elasticsearch-mcp-server/tsconfig.json b/example-apps/elasticsearch-mcp-server/tsconfig.json deleted file mode 100644 index a76cdbe9..00000000 --- a/example-apps/elasticsearch-mcp-server/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", - "esModuleInterop": true, - "outDir": "./dist", - "rootDir": "./", - "strict": true, - "declaration": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": [ - "*.ts" - ], - "exclude": [ - "node_modules", - "dist" - ] -}