Metadata-Version: 2.4
Name: artisan-es-reader-plugin
Version: 0.3.0
Summary: Elasticsearch MCP server — query logs with or without SSH tunnel
Requires-Python: >=3.11
Requires-Dist: elasticsearch<9.0.0,>=8.0.0
Requires-Dist: mcp>=1.0.0
Requires-Dist: paramiko>=3.0.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: sshtunnel>=0.4.0
Description-Content-Type: text/markdown

# Elasticsearch MCP Server

Query Elasticsearch logs directly from Claude Cowork — with or without an SSH tunnel.

---

## Installation

### 1. Install the plugin

Get **es-mcp** from the Cowork plugin marketplace and install it.

### 2. Install `uv`

The MCP server runs via `uvx`, which requires `uv` to be installed on your machine:

```powershell
# Windows
powershell -ExecutionPolicy Bypass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

```bash
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
```

### 3. Configure the MCP server

Open `claude_desktop_config.json`:

```
Windows:  %APPDATA%\Claude\claude_desktop_config.json
macOS:    ~/Library/Application Support/Claude/claude_desktop_config.json
```

Add the entry inside `"mcpServers": { }`. Define every Elasticsearch source you
want to reach as a **profile** under `ES_PROFILES`, and pick which one is used
when no profile is specified with `"default"`:

```json
{
  "mcpServers": {
    "elasticsearch-logs": {
      "command": "uvx",
      "args": ["artisan-es-reader-plugin@latest", "artisan-es-reader-plugin"],
      "env": {
        "ES_PROFILES": "{\"default\":\"tealive_staging\",\"profiles\":{\"tealive_staging\":{\"es_use_ssl\":true,\"es_verify_certs\":false,\"es_username\":\"your-es-username\",\"es_password\":\"your-es-password\",\"ssh_host\":\"staging-bastion-ip\",\"ssh_username\":\"ubuntu\",\"ssh_pem_file\":\"C:\\\\Users\\\\your-name\\\\staging.pem\",\"ssh_remote_es_host\":\"localhost\",\"ssh_remote_es_port\":9200},\"tealive_production\":{\"es_use_ssl\":true,\"es_verify_certs\":false,\"es_username\":\"your-es-username\",\"es_password\":\"your-es-password\",\"ssh_host\":\"prod-bastion-ip\",\"ssh_username\":\"ubuntu\",\"ssh_pem_file\":\"C:\\\\Users\\\\your-name\\\\prod.pem\",\"ssh_remote_es_host\":\"localhost\",\"ssh_remote_es_port\":9200},\"baskbear\":{\"es_use_ssl\":true,\"es_verify_certs\":false,\"es_username\":\"your-es-username\",\"es_password\":\"your-es-password\",\"ssh_host\":\"baskbear-bastion-ip\",\"ssh_username\":\"ubuntu\",\"ssh_pem_file\":\"C:\\\\Users\\\\your-name\\\\baskbear.pem\",\"ssh_remote_es_host\":\"localhost\",\"ssh_remote_es_port\":9200}}}"
      }
    }
  }
}
```

`ES_PROFILES` is a JSON object (as a string). Because it lives inside JSON, every
quote is escaped `\"` and Windows backslashes are doubled again to `\\\\`. The
de-escaped shape is just:

```json
{
  "default": "tealive_staging",
  "profiles": {
    "tealive_staging":     { "ssh_host": "...", "ssh_pem_file": "...", "es_username": "...", "es_password": "...", ... },
    "tealive_production":  { "ssh_host": "...", "ssh_pem_file": "...", ... },
    "baskbear":            { "ssh_host": "...", "ssh_pem_file": "...", ... }
  }
}
```

Per-profile keys (all optional; sensible defaults applied):

| Key | Description | Default |
|---|---|---|
| `es_host` / `es_port` | ES host/port for **direct** connections (ignored when `ssh_host` is set) | `localhost` / `9200` |
| `es_username` / `es_password` | Elasticsearch credentials | empty |
| `es_use_ssl` | `true` if ES runs HTTPS | `false` |
| `es_verify_certs` | `false` for self-signed certs | `false` |
| `ssh_host` | Bastion / jump host. Leave unset for a direct connection | empty |
| `ssh_port` / `ssh_username` | SSH port / user | `22` / `ubuntu` |
| `ssh_pem_file` | Absolute path to your local PEM key (Windows: `C:\\\\Users\\\\you\\\\key.pem`) | `~/.ssh/id_rsa` |
| `ssh_remote_es_host` / `ssh_remote_es_port` | ES host/port as seen from the bastion | `localhost` / `9200` |
| `ssh_local_port` | Local tunnel port (`0` = auto) | `0` |

### Selecting a profile

Every tool takes an optional `profile` argument. Just ask naturally — "search
**baskbear** logs for X", "recent errors in **tealive production**" — and the
matching profile is used. Omit it and the `default` profile is queried. Call
`list_profiles` to see what's configured, or `connection_info` to inspect one.

> **Backward compatible:** if `ES_PROFILES` is not set, the server falls back to
> the old flat `ES_*` / `SSH_*` env vars as a single profile named `default`.

### 4. Restart Cowork

The SSH tunnel starts automatically on first tool use.

---

## Notes

- `es_host` is only used for direct connections. When `ssh_host` is set, traffic routes through the tunnel and `es_host` is ignored.
- `es_use_ssl`: set `true` if your Elasticsearch runs HTTPS.
- `es_verify_certs`: set `false` for self-signed certificates.
- `ssh_local_port`: `0` = auto-pick a free local port.
- To connect without an SSH tunnel for a profile, leave its `ssh_host` unset.
- Each profile gets its own client + tunnel, created lazily on first use and reused afterwards.

---

## Updating

### For users

Updates are automatic — just restart Cowork and `uvx` will pull the latest version from PyPI.

### For maintainers

1. Make changes to `src/es_mcp/server.py`
2. Bump the version in `pyproject.toml`
3. Build and publish:
   ```bash
   python -m build
   twine upload dist/*
   ```
4. Users get the new version automatically on next Cowork restart — no action needed on their end

---

## Available tools

All tools accept an optional `profile` argument to choose the Elasticsearch source.

| Tool | What it does |
|---|---|
| `list_profiles` | List configured profiles and the default |
| `list_indices` | List indices (supports glob pattern) |
| `search_logs` | Full-text search with filters, sort, pagination |
| `get_recent_errors` | Error-level entries from the last N minutes |
| `get_index_mapping` | Field schema for an index |
| `run_aggregation` | Run a custom ES aggregation |
| `connection_info` | Show active connection / tunnel status |
