Content
# Elasticsearch Log MCP
This is a read-only, constrained Elasticsearch log query MCP service implemented in Go, supporting both Codex local `stdio` and remote intelligent agent concurrent access via HTTP MCP.
## Capabilities
- `search_logs`: Query logs by time, keyword, level, service, and Trace ID
- `list_index_categories`: List current available log indexes by date suffix
- `get_log_context`: Query the context before and after a specified log timestamp
- `aggregate_logs`: Aggregate statistics by level, service, or Trace ID
- `read_log_detail_chunk`: Read large log fields in chunks after confirmation
The service only accepts structured parameters, not raw Elasticsearch DSL, and does not provide write, delete index, or update document capabilities.
## Security Boundaries
The HTTP server listens on `0.0.0.0:8080` by default, and the current version does not have authentication. In a production environment, it must be placed in a controlled network and use Kubernetes NetworkPolicy, firewall, security group, or authenticated reverse proxy to restrict access sources.
Server-side limitations include:
- Single query limited to 12 hours, with UTC+8 time semantics; start time included, end time excluded
- Query default page size is 200, maximum page size is 500
- Query matching more than 10,000 logs stops returning logs, requiring a narrowed time range or increased filtering conditions
- Maximum 20 keywords, single keyword limited to 1024 bytes, Trace ID limited to 20
- Aggregation returns up to 100 data buckets
- Context defaults to 20 logs before and after, single side limited to 100 logs, default half-window 10 minutes, maximum 1 hour
- Log fields exceeding 64 KiB must be confirmed and read in chunks of up to 64 KiB
- HTTP global concurrency limit 32, single source concurrency limit 4
`TOKEN_SECRET` is used to sign pagination cursors and large log confirmation tokens, and must be a random long string, configured separately in different environments. Do not submit real keys.
## Configuration
Required environment variables:
| Variable | Description | Example |
| --- | --- | --- |
| `ES_URLS` | Elasticsearch addresses, comma-separated | `http://es-0:9200,http://es-1:9200` |
| `LOG_INDEX_PATTERN` | Allowable index pattern | `logging-production-toprouter-sub2api-*` |
| `TOKEN_SECRET` | Server-side signature key | `replace-with-random-secret` |
Optional environment variables:
| Variable | Default value | Description |
| --- | --- | --- |
| `HTTP_ADDRESS` | `0.0.0.0:8080` | HTTP listen address; not used in stdio mode |
| `LOG_TIME_FIELD` | `@timestamp` | Log time field |
| `LOG_LEVEL_FIELD` | `level` | Log level field |
| `LOG_MESSAGE_FIELD` | `message` | Log message field |
| `LOG_EXCEPTION_FIELD` | `exception` | Exception field |
| `LOG_SERVICE_FIELD` | `name` | Service field |
| `LOG_TRACE_ID_FIELD` | `requestId` | Trace/request ID field |
Log index naming can use, for example, `logging-production-service-name-date`, corresponding configuration is `logging-production-service-name-*`. Indexes containing hint words or user content, such as `banyuewan_prompt_log-*`, will be rejected.
`.env.example` provides a complete configuration template. Environment variables can be injected through shell, Docker `--env-file`, or Kubernetes.
## Local Running
```bash
cp .env.example .env
set -a
. ./.env
set +a
go run ./cmd/log-mcp --transport http
```
Build binary:
```bash
go build -o log-mcp ./cmd/log-mcp
./log-mcp --transport http
```
The HTTP MCP address is `http://127.0.0.1:8080/`. The service will output an unauthenticated warning to stderr during startup.
## Docker Running
Build image:
```bash
docker build -t elk-log-mcp:local .
```
Run with environment file:
```bash
docker run --rm \
--name elk-log-mcp \
--env-file .env \
-p 8080:8080 \
elk-log-mcp:local
```
The container starts the HTTP MCP by default, and the container port is `8080`. Do not submit `.env` or real `TOKEN_SECRET` to Git.
## Kubernetes Deployment
`deploy/k8s/deployment.yaml` and `deploy/k8s/service.yaml` are renderable deployment templates. The templates use `${...}` placeholders, prepare environment variables first, and then use `envsubst` to render:
```bash
export IMAGE=registry.example.com/sre/elk-log-mcp:v1
export ES_URLS='http://elasticsearch:9200'
export LOG_INDEX_PATTERN='logging-production-toprouter-sub2api-*'
export TOKEN_SECRET='replace-with-a-long-random-secret'
export HTTP_ADDRESS='0.0.0.0:8080'
export LOG_TIME_FIELD='timestamp'
export LOG_LEVEL_FIELD='level'
export LOG_MESSAGE_FIELD='message'
export LOG_EXCEPTION_FIELD='exception'
export LOG_SERVICE_FIELD='service'
export LOG_TRACE_ID_FIELD='traceId'
envsubst < deploy/k8s/deployment.yaml | kubectl apply -f -
kubectl apply -f deploy/k8s/service.yaml
kubectl -n logging rollout status deployment/elk-log-mcp
```
The default resource is `ClusterIP`, and the service port is `8080`. The deployment template includes non-root operation, read-only root file system, probe, resource request/limit, graceful termination, and rolling update configuration. `TOKEN_SECRET` is also injected through container environment variables; in a production environment, it is recommended to use an external key management system or CI/CD security variables instead of writing to YAML or command history.
View and rollback:
```bash
kubectl -n logging get pods,svc -l app=elk-log-mcp
kubectl -n logging logs deployment/elk-log-mcp
kubectl -n logging rollout undo deployment/elk-log-mcp
```
If you need to access the HTTP MCP from outside the cluster, you should additionally configure Ingress or LoadBalancer and provide authentication and source restrictions at the ingress layer. The current service itself does not verify the authentication key.
## Accessing Codex (stdio)
Modify the `command` in `codex.mcp.json` to the local binary absolute path, and replace `env` with Elasticsearch, index, and signature key:
```json
{
"mcpServers": {
"elasticsearch-log": {
"command": "/absolute/path/log-mcp",
"args": ["--transport", "stdio"],
"env": {
"ES_URLS": "http://es-node-1:9200,http://es-node-2:9200",
"LOG_INDEX_PATTERN": "logging-production-toprouter-sub2api-*",
"TOKEN_SECRET": "replace-with-a-long-random-secret"
}
}
}
}
```
The MCP protocol data in stdio mode is written to stdout, and the running log is written to stderr.
## Remote Intelligent Agent Access
The remote intelligent agent uses the HTTP MCP address:
```text
http://<service-address>:8080/
```
Within a Kubernetes cluster, you can use `http://elk-log-mcp.logging.svc.cluster.local:8080/`. Before accessing across networks, you must add authentication, TLS, and access control at the reverse proxy or gateway layer.
## Development and Verification
```bash
gofmt -w cmd internal
go test ./...
go test -race ./...
go vet ./...
openspec validate elasticsearch-log-mcp --strict --no-interactive
```