Skip to main content
Glama
hpower2

OpenSearch MCP Server

by hpower2
README.md
# OpenSearch MCP Server

A Model Context Protocol (MCP) server built with FastMCP for retrieving error stack traces by transaction ID (TID) from OpenSearch logs.

## Features

- **Error stack trace retrieval**: Get complete error stack traces by transaction ID (TID)
- **Docker-based setup**: Easy local development with Docker Compose
- **Environment variable configuration**: Flexible connection settings via environment variables

## Prerequisites

- Python 3.10 or higher
- Docker and Docker Compose (for running the MCP server in a container)
- An accessible OpenSearch instance (you provide your own)
- pip or uv for package management

## Installation

1. Clone the repository:
```bash
git clone <repository-url>
cd opensearch-mcp
```

2. Install dependencies:
```bash
pip install -r requirements.txt
```

Or using uv:
```bash
uv pip install -r requirements.txt
```

3. Set up environment variables (optional):
```bash
cp .env.example .env
# Edit .env with your OpenSearch connection settings
```

## Quick Start

**Prerequisites**: You need your own OpenSearch instance running and accessible. Configure the connection details in the `.env` file.

### Option 1: Using Docker Compose (Recommended)

1. Copy the environment file and configure your OpenSearch connection:
```bash
cp .env.example .env
# Edit .env and set OPENSEARCH_HOST and OPENSEARCH_PORT to your OpenSearch instance
```

2. Start the MCP server:
```bash
docker-compose up
```

This will start:
- MCP server on port 48081 (HTTP endpoint: `http://localhost:48081/mcp`)

The MCP server will connect to your OpenSearch instance using the configuration from `.env`.

To run in detached mode:
```bash
docker-compose up -d
```

### Option 2: Run MCP Server directly with Python

1. Set up environment variables:
```bash
cp .env.example .env
# Edit .env and configure OPENSEARCH_HOST and OPENSEARCH_PORT to your OpenSearch instance
```

2. Run the MCP server:
```bash
python server.py
```

The server will start on `http://localhost:48081/mcp`.

### Option 3: Run MCP Server with Docker (standalone)

1. Build the Docker image:
```bash
docker build -t opensearch-mcp .
```

2. Run the container with your OpenSearch connection details:
```bash
docker run --rm -p 48081:48081 \
  -e OPENSEARCH_HOST=your-opensearch-host \
  -e OPENSEARCH_PORT=9200 \
  opensearch-mcp
```

Or use an env file:
```bash
docker run --rm -p 48081:48081 --env-file .env opensearch-mcp
```

The MCP server will be available at `http://localhost:48081/mcp`.

**Note**: 
- Replace `your-opensearch-host` with your actual OpenSearch hostname or IP
- Use `host.docker.internal` (macOS/Windows) or your host IP (Linux) if OpenSearch is running on the host machine
- For remote instances, use the actual hostname or IP address

## Configuration

The MCP server reads connection settings from environment variables:

| Variable | Default | Description |
|----------|---------|-------------|
| `OPENSEARCH_HOST` | `localhost` | OpenSearch host |
| `OPENSEARCH_PORT` | `9200` | OpenSearch port |
| `OPENSEARCH_USE_SSL` | `false` | Enable SSL/TLS |
| `OPENSEARCH_VERIFY_CERTS` | `false` | Verify SSL certificates |
| `OPENSEARCH_USERNAME` | - | Username for authentication (optional) |
| `OPENSEARCH_PASSWORD` | - | Password for authentication (optional) |

When using Docker Compose, these variables are automatically available from the container environment.

## MCP Tools

### `stack_trace_tid`

Retrieve full error stack trace by transaction ID (TID).

**Parameters:**
- `tid` (string, required): Transaction ID to search for
- `index_pattern` (string, optional): Index pattern to search (default: `servicelogs-*`)
- `repo_name` (string, optional): Filter by repository/application name (maps to `application_name` field)

**Example:**
```python
{
    "tid": "e1020584c0a74074a930c4e90e953912.139.17702793574644017",
    "repo_name": "xxx"
}
```

**Returns:**
Formatted text with one log entry per line. Each line follows the format:
```
timestamp - level - message - application_name - project
```

If a separate `stack_trace` field exists in the log document, the format is:
```
timestamp - level - message - stack_trace - application_name - project
```

**Note:** Log entries are sorted chronologically by `@timestamp` in ascending order. Newlines in messages and stack traces are normalized to ` | ` (pipe characters) for single-line format.

**Example Output:**
```
2026-02-05T08:15:57.566Z - INFO - channel GOPAY got phone A0:1ec614bbcb328b7c66436a318d7b77e0 from token - xxx - xxx
2026-02-05T08:15:57.570Z - INFO - register user for A0:44b351780fec875b97ad01d9bb946f3e , system : SYSTEM_TYPE_ATOME, channel: GOPAY - user-service-provider - pintar-user
2026-02-05T08:15:58.263Z - ERROR - POST http://:8080/user/info -d [{"module":"RESIDENTIAL_INFO","module":"RESIDENTIAL_INFO",...}] ***RESPONSE*** 400 {"timestamp":1770279358262,"status":400,"error":"Bad Request","path":"/user/info"} - xxx - xxx
2026-02-05T08:15:58.264Z - ERROR - [Gopay Linking] Failed to save user info for userId : [400 ] during [POST] to [http://:8080/user/info] [PintarUserInfoFeignClient#saveUserInfo(List)]: [{"timestamp":1770279358262,"status":400,"error":"Bad Request","path":"/user/info"}] - feign.FeignException$BadRequest: [400 ] during [POST] to [http://:8080/user/info] [PintarUserInfoFeignClient#saveUserInfo(List)]: [{"timestamp":1770279358262,"status":400,"error":"Bad Request","path":"/user/info"}] | at feign.FeignException.clientErrorStatus(FeignException.java:243) | at feign.FeignException.errorStatus(FeignException.java:223) | at feign.FeignException.errorStatus(FeignException.java:213) | ... - xxx - xxx
2026-02-05T08:15:58.267Z - INFO - [GopayServiceImpl] linking account failed for userId: , error: INVALID_KYC - xxx - xxx
```

**Note:** Log entries are sorted by timestamp in ascending order. Stack traces (when present) are included as part of the message field, with stack frames separated by ` | ` (pipe characters).

## Usage with MCP Clients

The MCP server uses **streamableHttp** transport on port **48081**. This enables HTTP-based communication instead of stdio, making it easier to deploy and scale.

### Starting the Server

**Important**: Ensure your OpenSearch instance is running and accessible before starting the MCP server.

#### Option 1: Using Docker Compose (Recommended)

1. Configure your OpenSearch connection in `.env`:
```bash
cp .env.example .env
# Edit .env with your OpenSearch host and port
```

2. Start the MCP server:
```bash
docker-compose up
```

The MCP server will be available at `http://localhost:48081/mcp`.

#### Option 2: Running Locally

1. Set up environment variables:
```bash
cp .env.example .env
# Edit .env with your OpenSearch host and port
```

2. Run the MCP server:
```bash
python server.py
```

The server will start on `http://localhost:48081/mcp`.

#### Option 3: Using Docker Run

Build the image:
```bash
docker build -t opensearch-mcp .
```

Run the container with your OpenSearch connection:
```bash
docker run --rm -p 48081:48081 \
  -e OPENSEARCH_HOST=your-opensearch-host \
  -e OPENSEARCH_PORT=9200 \
  opensearch-mcp
```

Or use an env file:
```bash
docker run --rm -p 48081:48081 --env-file .env opensearch-mcp
```

### Configuring MCP Clients

#### Cursor IDE

Add to your Cursor MCP settings (`mcp.json`):

```json
{
    "mcpServers": {
        "opensearch": {
            "url": "http://localhost:48081/mcp"
        }
    }
}
```

#### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
    "mcpServers": {
        "opensearch": {
            "url": "http://localhost:48081/mcp"
        }
    }
}
```

#### Remote Access

If the MCP server is running on a remote machine, use the remote URL:

```json
{
    "mcpServers": {
        "opensearch": {
            "url": "http://your-server-ip:48081/mcp"
        }
    }
}
```

### Configuration

The MCP server port and host can be configured via environment variables:

- `MCP_PORT`: Port for the HTTP server (default: 48081)
- `MCP_HOST`: Host to bind to (default: 0.0.0.0)

Example:
```bash
MCP_PORT=8080 MCP_HOST=127.0.0.1 python server.py
```

### Docker Networking Notes

- **Local development**: Use `http://localhost:48081/mcp`
- **VPN Access**: The docker-compose.yml uses `network_mode: host` to allow the container to access VPN resources through your host's network
- **Docker Compose**: When using host network mode, the container shares your host's network stack, including VPN connections
- **Remote access**: Ensure port 48081 is accessible from your MCP client

**Note**: With `network_mode: host`, the container uses your host's network directly, so:
- VPN resources are accessible from the container
- No port mapping is needed (ports are directly accessible on the host)
- The container can access resources that require VPN connection

## Development

### Project Structure

```
opensearch-mcp/
├── src/
│   └── opensearch_mcp/
│       ├── __init__.py
│       ├── server.py              # FastMCP server with tool definitions
│       └── opensearch_client.py    # OpenSearch client wrapper
├── docker-compose.yml              # Docker Compose setup (OpenSearch + MCP server)
├── Dockerfile                      # MCP server Docker image
├── .dockerignore                   # Docker ignore file
├── .env.example                    # Example environment variables
├── mcp.json.example                # Example MCP client configuration
├── requirements.txt                # Python dependencies
├── pyproject.toml                  # Python project configuration
└── README.md                       # This file
```

### Running Tests

```bash
# Ensure your OpenSearch instance is running and accessible

# Start MCP server with Docker Compose
docker-compose up

# Or run MCP server locally
python server.py
```

### Docker Compose Services

The `docker-compose.yml` includes only the MCP server service:

1. **opensearch-mcp**: MCP server (uses `.env` file for configuration)

The MCP server service:
- Builds from the local Dockerfile
- Loads environment variables from `.env` file
- Connects to your OpenSearch instance using the configuration from `.env`
- Exposes HTTP endpoint on port 48081 at `/mcp` path
- Uses streamableHttp transport for MCP communication

**Note**: You need to provide your own OpenSearch instance. Configure the connection details in the `.env` file.

### Log Data Format

The MCP server expects log documents with the following structure:

```json
{
    "@timestamp": "2024-01-01T10:00:00Z",
    "level": "ERROR",
    "msg": "Error occurred...",
    "application_name": "xxx",
    "project": "xxx",
    "stack_trace": "feign.FeignException$BadRequest: [...] | at ...",
    "TID": "e1020584c0a74074a930c4e90e953912.139.17702793574644017"
}
```

Key fields:
- `@timestamp`: Log timestamp (ISO 8601 format)
- `level`: Log level (INFO, WARN, ERROR, etc.)
- `msg`: Log message content
- `application_name`: Application/service name (used for filtering via `repo_name` parameter)
- `project`: Project/repository name
- `stack_trace`: Stack trace content (optional, formatted as pipe-separated lines)
- `TID`: Transaction ID field (searched using phrase matching across all fields)

## Troubleshooting

### Connection Errors

If you see connection errors:
1. Ensure your OpenSearch instance is running and accessible
2. Check OpenSearch health: `curl http://your-opensearch-host:9200/_cluster/health`
3. Verify environment variables in `.env` are set correctly (OPENSEARCH_HOST and OPENSEARCH_PORT)
4. If running in Docker, ensure the container can reach your OpenSearch instance (network configuration)

### No Results Found

- Check that your index pattern matches existing indices (default: `servicelogs-*`)
- Verify that the TID exists in your log documents (searched using phrase matching across all fields)
- If using `repo_name` filter, verify that `application_name` field exists and matches the filter value
- Ensure the TID format matches what's stored in your logs

### SSL/TLS Issues

If using SSL:
- Set `OPENSEARCH_USE_SSL=true`
- Configure `OPENSEARCH_VERIFY_CERTS` appropriately
- Provide CA certificates if needed

## License

MIT

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.