Skip to main content
Glama
M0-AR

Custom Elasticsearch MCP Server

by M0-AR
README.md
# Custom Elasticsearch MCP Server

A simple MCP (Model Context Protocol) server for Elasticsearch designed for cloud environments where your public key is already authorized on the server.

## Why This Custom Version?

**No API Key Required** - Unlike the official Elasticsearch MCP server that requires both `ES_URL` and `ES_API_KEY`, this version only needs the URL since your public key is already trusted on the cloud server.

**Enhanced Tools** - Better usability with optional parameters and improved defaults compared to the official version.

## What This Does

This MCP server connects Cursor to your Elasticsearch cluster with 4 powerful tools:
- **`list_indices`** - List all indices (optional pattern filter)
- **`search`** - Full Elasticsearch Query DSL support
- **`get_mappings`** - Get field mappings for any index
- **`get_shards`** - View cluster shard information

## Quick Start

### Build from Source
```bash
git clone https://github.com/M0-AR/Custom-Elasticsearch-MCP-Server.git
cd Custom-Elasticsearch-MCP-Server
docker build -t elasticsearch-mcp:latest .
```

### 2. Add to Cursor MCP Configuration
Add this to your `.cursor/mcp.json` file:

**Configuration:**
```json
{
    "mcpServers": {
        "elasticsearch-custom": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "--add-host=host.docker.internal:host-gateway",
                "-e",
                "ES_URL=http://host.docker.internal:9400",
                "elasticsearch-mcp:latest"
            ]
        }
    }
}
```

### 3. Restart Cursor
Close and reopen Cursor. You should see the elasticsearch-custom server with 4 tools enabled.

## Configuration

**Environment Variables:**
- `ES_URL` - Your Elasticsearch URL (default: `http://localhost:9400`)
- `MAX_CONNECTIONS` - Maximum concurrent connections (default: `100`)
- `MAX_KEEPALIVE_CONNECTIONS` - Maximum keepalive connections (default: `20`)
- `CONNECTION_TIMEOUT` - Connection timeout in seconds (default: `30`)
- `REQUEST_TIMEOUT` - Request timeout in seconds (default: `30`)

**For different Elasticsearch ports:**
```json
"ES_URL=http://host.docker.internal:9200"
```

**For high-traffic environments:**
```json
"MAX_CONNECTIONS=200",
"MAX_KEEPALIVE_CONNECTIONS=50",
"CONNECTION_TIMEOUT=60",
"REQUEST_TIMEOUT=60"
```

## Example Usage

Once connected in Cursor, you can:

- **List all indices:** "Show me all elasticsearch indices"
- **Search data:** "Search for sales data in hq.sales index"  
- **Get mappings:** "What fields are in the hq.menuitems index?"
- **Check cluster:** "Show me the elasticsearch cluster status"

## Comparison with Official Server

| Feature | Official Server | This Custom Server |
|---------|----------------|-------------------|
| **Authentication** | Requires `ES_URL` + `ES_API_KEY` | Only needs `ES_URL` (public key authorized) |
| **list_indices** | Requires `indexPattern` parameter | Optional parameter with "*" default |
| **Tools Available** | 4 tools (same functions) | 4 tools (enhanced usability) |
| **Security** | API key based | Public key authorization |
| **Concurrency** | Synchronous blocking | **Async with connection pooling** |
| **Performance** | Single request at a time | **100+ concurrent requests** |

## Concurrent Request Handling

This MCP server is designed to handle multiple parallel requests from multiple applications simultaneously using industry best practices:

### **Key Features:**

✅ **Async/Await Architecture** - Non-blocking I/O for parallel request processing
✅ **Connection Pooling** - Reuses HTTP connections (up to 100 concurrent)
✅ **HTTP/2 Support** - Multiplexes multiple requests over single connection
✅ **Configurable Limits** - Adjust connection limits for your workload
✅ **Thread-Safe** - FastMCP handles concurrent tool execution safely

### **Performance Characteristics:**

- **Default:** 100 concurrent connections, 20 keepalive connections
- **Scalable:** Configure up to 1000+ concurrent connections
- **Efficient:** Connection reuse reduces latency by ~50%
- **Reliable:** Proper timeout handling prevents connection exhaustion

### **Configuration for High Traffic:**

```json
{
    "mcpServers": {
        "elasticsearch-custom": {
            "command": "docker",
            "args": [
                "run", "-i", "--rm",
                "--add-host=host.docker.internal:host-gateway",
                "-e", "ES_URL=http://host.docker.internal:9400",
                "-e", "MAX_CONNECTIONS=200",
                "-e", "MAX_KEEPALIVE_CONNECTIONS=50",
                "-e", "CONNECTION_TIMEOUT=60",
                "-e", "REQUEST_TIMEOUT=60",
                "elasticsearch-mcp:latest"
            ]
        }
    }
}
```

### **Testing Concurrent Requests:**

```bash
# Test 10 parallel requests
for i in {1..10}; do
    echo '{"jsonrpc": "2.0", "id": '$i', "method": "tools/call", "params": {"name": "list_indices", "arguments": {}}}' | \
    python3 simple_elasticsearch_mcp.py &
done
wait
```

## Files

- `simple_elasticsearch_mcp.py` - Main MCP server
- `Dockerfile` - Container build instructions  
- `requirements.txt` - Python dependencies

## Manual Testing

### Test the server directly:
```bash
python3 simple_elasticsearch_mcp.py
```

### Test with JSON-RPC commands:

**1. List all tools:**
```bash
echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' | python3 simple_elasticsearch_mcp.py
```

**2. List all indices:**
```bash
echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "list_indices", "arguments": {}}}' | python3 simple_elasticsearch_mcp.py
```

**3. Search for data:**
```bash
echo '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "search", "arguments": {"index": "hq.sales", "queryBody": {"query": {"match_all": {}}, "size": 3}}}}' | python3 simple_elasticsearch_mcp.py
```

**4. Get index mappings:**
```bash
echo '{"jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": {"name": "get_mappings", "arguments": {"index": "hq.menuitems"}}}' | python3 simple_elasticsearch_mcp.py
```

**5. Check cluster shards:**
```bash
echo '{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "get_shards", "arguments": {}}}' | python3 simple_elasticsearch_mcp.py
```

### Set custom Elasticsearch URL:
```bash
ES_URL="http://your-es-host:9200" python3 simple_elasticsearch_mcp.py
```

## Troubleshooting

### **❌ "Connection refused" or "timed out" errors**

**Root Cause:** The most common issue is Docker container networking when Elasticsearch is accessible via SSH tunnel.

**Solution:** Ensure these requirements are met:

#### **1. SSH Tunnel Must Be Active**
If your Elasticsearch is behind SSH tunnel (common for cloud deployments):
```bash
# Start SSH tunnel to forward port 9400
ssh -L 9400:localhost:9400 -N -f -l username your-server-ip

# Verify tunnel is working
curl -X GET "localhost:9400/_cluster/health?pretty"
```

#### **2. Correct Docker Configuration**
Your `mcp.json` should use **exactly** this configuration:
```json
"elasticsearch-custom": {
    "command": "docker",
    "args": [
        "run",
        "-i",
        "--rm",
        "--add-host=host.docker.internal:host-gateway",
        "-e",
        "ES_URL=http://host.docker.internal:9400",
        "elasticsearch-mcp:latest"
    ]
}
```

**Key Points:**
- ✅ Use `--add-host=host.docker.internal:host-gateway` (not IP addresses)
- ✅ Use `ES_URL=http://host.docker.internal:9400` (not localhost)
- ✅ SSH tunnel must be running before starting Cursor

#### **3. Test Docker Connectivity**
```bash
# Test if Docker can reach your Elasticsearch
docker run --rm --add-host=host.docker.internal:host-gateway alpine/curl \
  curl -s http://host.docker.internal:9400/_cluster/health
```

#### **4. Complete MCP Docker Test**
Test the full MCP workflow with this comprehensive command:
```bash
# Full MCP server test with proper initialization
{
    echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test-client", "version": "1.0.0"}}}';
    echo '{"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}}';
    echo '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "list_indices", "arguments": {}}}';
} | docker run -i --rm --add-host=host.docker.internal:host-gateway -e ES_URL="http://host.docker.internal:9400" elasticsearch-mcp:latest
```

**Expected Output:**
- Initialization response with server info
- List of all Elasticsearch indices in JSON format
- No error messages

#### **5. Alternative: Network Host Mode**
If `host-gateway` doesn't work, try network host mode:
```json
"args": [
    "run", "-i", "--rm", "--network=host",
    "-e", "ES_URL=http://localhost:9400",
    "elasticsearch-mcp:latest"
]
```

### **❌ "Received request before initialization was complete"**

**Root Cause:** MCP protocol requires proper initialization sequence.

**Solution:** Always initialize before calling tools:
```bash
# Correct sequence:
echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0"}}}'
echo '{"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}}'
echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "list_indices", "arguments": {}}}'
```

## That's It! 

Build → Add to config → Restart Cursor → Done! 🚀