Skip to main content
Glama
zhengziha

ELK Log Query MCP

by zhengziha
README.md
# ELK Log Query MCP

通过 Playwright 登录 Kibana,用 FastMCP 暴露日志查询工具,按项目(Data View / 索引)检索日志。

## 功能

| Tool | 说明 |
|------|------|
| `list_projects` | 列出 `config.yaml` 中的项目(含 aliases);`include_remote=true` 时合并 Kibana 全部 Data View |
| `list_index_patterns` | 拉取 Kibana 侧全部 Data View / Index Pattern |
| `search_logs` | 按时间范围 + query_string 查日志;默认 `mode=summary`;可选 `trace_id` |
| `search_by_trace_id` | 按 TraceId / TID 关联查询(默认根 ID,覆盖同链路各 span) |
| `expand_log_hit` | 用摘要命中的 `_index` + `_id` 二次展开完整日志 |

## 安装

```bash
cd ELK-mcp-server
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
playwright install chromium
```

## 配置

```bash
cp config.example.yaml config.yaml
# 编辑 config.yaml:填写 base_url、账号密码、项目与 data_view_id 映射
```

示例(完整 8 个常用候选见 `config.example.yaml`):

```yaml
kibana:
  base_url: "https://elk.dev.jk.com"
  username: "elastic"
  password: "YOUR_PASSWORD"
  headless: true

# search_logs 指定项目三选一:
#   1) project=完整 ELK 名,如 saas-dev-zs-saas-crm-admin
#   2) project=服务名(无环境前缀,如 zs-saas-crm-admin) + env=test
#   3) project=aliases 里的叫法
# 不指定(或 project=default)→ 跨全部 candidate 项目聚合查询
projects:
  - name: saas-test-zs-saas-crm-admin
    service: zs-saas-crm-admin   # 不带环境前缀的服务名
    env: test                    # dev / test / pre ...
    candidate: true              # 参与未指定项目时的候选聚合
    data_view_id: "20e20590-6fa0-11f1-adae-c9851401c97f"
    index_pattern: "*saas-test-zs-saas-crm-admin*"   # 兜底 / 跨候选聚合用
```

`data_view_id` 来自 Discover URL 中的 `index='...'`。`service` + `env` 拼出完整 ELK 名 `saas-{env}-{service}`;`aliases` 用于把其它叫法(如 `aas-dev-zs-saas-crm-admin`)映射到同一项目。

> **`env=` 必填**:`search_logs` / `search_by_trace_id` / `list_index_patterns` 以及
> `list_projects(include_remote=true)` 都会拒绝缺失 `env=` 的调用(返回
> `error: ... REQUIRED ...`),**没有隐式默认环境**——不会在不指定环境时悄悄查
> dev/test/pre。写全名项目(如 `project=saas-dev-zs-api`)也请同时传匹配的
> `env=`;`env=` 把调用引向另一套集群(如 dev 项目 + env=prod)会被拒绝。

### 时间与时区

`search_logs` 的 `from_time` / `to_time` 中**无时区**的时间串(如 `2026-09-07 23:38`、`2026-09-07`)
默认按东八区 `Asia/Shanghai` 解释,发送给 ES 前自动附加 `+08:00` 显式偏移;`meta` 会给出
`timezone` 与 `timezone_note`(换算后的 UTC 窗口),0 命中时 `hints` 也会提醒——避免“日志明明在
却查不到”的静默 UTC 问题。`now-1h` 这类相对式、带 `Z`/`+08:00` 的绝对时间原样透传。
可通过 config.yaml 顶层 `timezone:` 改成其它 IANA 时区。

补充行为:

- **TraceId 0 命中兜底**:`search_by_trace_id`(含 `search_logs(trace_id=...)`)首查 0 命中时,
  自动用 `*根ID*` 子串通配再查一次(`meta.trace_fallback=true`),覆盖 message/content 字段
  分词不按点号切、TID 整串存储的场景。
- **宽窗口超时优雅降级**:内部异步 ese 长时间不完成时自动切到 console proxy + ES 侧短超时,
  返回**已有部分结果**并带 `partial: true` 与收窄窗口提示,不再让 30s+ 白白失败。
- **summary 可带自定义字段**:`fields=` 同时作用于 `search_logs` 的 summary 模式,
  命中的每条会带 `context`(如 Pod/镜像元数据),省去逐条 expand。

### 多集群单 server(按环境路由)

**只启动一个 MCP server**,同一份 `config.yaml` 可声明多套 Kibana/ES;工具用 `env=` 参数决定查哪套,Agent 无需感知 server 数量:

```yaml
kibana:                    # default 集群:dev/test/pre 共用 ES
  base_url: "https://elk.dev.jk.com"
  ...
clusters:                  # 其它集群(如生产 os-elk)
  prod:
    label: "os-elk 生产"
    kibana:
      base_url: "https://os-elk.jztjk.cn"
      ...
      message_field: "content"        # 该集群日志正文所在字段
      cookie_file: ".elk_session/prod-cookies.json"
    envs: [prod, stg]                 # 这些环境路由到此集群
    container_field: "_container_name_"
```

- **环境路由**:`env=dev/test/pre` → default 集群(索引模型 `saas-{env}-{service}`);`env=prod/stg` → prod 集群(独立 ES)。项目归属由其 `env` 决定,也可用 `cluster:` 显式覆盖。
- **共享索引区分业务**:os-elk 的 `jk-saas.jk-saas` 一个索引承载多个容器业务,项目配置 `container: <容器名>` 后,查询自动附加 `_container_name_=<容器名>` 过滤,无需手动传 service 文本。示例见 `config.yaml` 的 prod 段。
- **每个集群独立的账号 / cookie / message_field**,登录自动适配(OpenResty 网关表单或标准 Kibana 登录均可)。
- 生产 os-elk 全部可查索引(18 个 Data View)已注释在 `config.yaml`,可用 `list_projects(env="prod", include_remote=true)` 核对并把关心项补成 project。

> 注:生产 os-elk 依赖本机 hosts 绑定(`10.4.9.210 os-elk.jztjk.cn`)。直连 IP 也能到达同一网关,但证书、Cookie 域名、服务端审计都按域名设计,集成时请统一用 `https://os-elk.jztjk.cn` 作为 `base_url`。

## 本地启动

```bash
source .venv/bin/activate
python -m elk_mcp.server        # 单个 server,内部按 env 路由多集群
# 或
elk-mcp
```

## 接入 Cursor

在 Cursor MCP 配置中加入 **一个** server(路径按本机修改):

```json
{
  "mcpServers": {
    "elk-logs": {
      "command": "/Users/zhengzihang/Documents/my-mcp/elk-mcp-server/.venv/bin/python",
      "args": ["-m", "elk_mcp.server"],
      "cwd": "/Users/zhengzihang/Documents/my-mcp/elk-mcp-server",
      "env": {
        "PYTHONPATH": "/Users/zhengzihang/Documents/my-mcp/elk-mcp-server/src"
      }
    }
  }
}
```

各集群的 cookie 独立持久化,首次使用或过期后自动用 Playwright 登录。

若已 `pip install -e .`,可省略 `PYTHONPATH`,`command` 使用 `.venv/bin/elk-mcp` 亦可。

## Agent Skill

项目内 Cursor Skill:[.cursor/skills/elk-logs/SKILL.md](.cursor/skills/elk-logs/SKILL.md)  
指导 Agent 按「摘要 → Trace 关联 → 展开」工作流使用本 MCP(查日志 / TraceId / 排障时自动适用)。

## 使用示例

1. `list_projects` — 查看已配置项目(name / service / env / aliases / candidate);`list_projects(env="prod", include_remote=true)` 查看 os-elk 全部 Data View
2. 非生产(default 集群,dev/test/pre 共用 ES):
   `search_logs(project="zs-saas-crm-admin", env="test", service="zs-saas-crm-admin", query='"queryReceptionDocumentDetail"', from_time="now-1d", size=20)`  
   — 也可直接 `project="saas-test-zs-saas-crm-admin"`;默认摘要:`summary` + `message_preview` / `level` / `error_type` / `trace_id`
3. 生产(os-elk,`env=prod` 自动路由到另一套 ES,按容器 scope):
   `search_logs(project="zs-saas-api", env="prod", query='"something"', from_time="now-1h", size=20)`  
   — 项目带 `container` 时 meta 会显示 `scope: _container_name_=zs-saas-api`
4. `search_by_trace_id(trace_id="18b3bac3....116....", project="zs-saas-crm-admin", env="test", from_time="now-1d")`  
   — `match=related`(默认)用 `.` 前的根 ID 拉整条链路;`match=exact` 只匹配完整 span TID
5. `expand_log_hit(index=hit._index, doc_id=hit._id)` — 展开完整 message/堆栈(集群按索引名自动识别,多集群重名时传 `env=`)
6. 不确定项目:仍传 `env=`(必填),可省略 `project`(或传 `default`)→ 跨该集群的 candidate 项目聚合查询,`meta.fallback_candidates` 会列出扫描范围
7. 需要列表内直接带 message:`mode="full"`;`total=0` 时阅读 `hints` / `meta`

## 说明

- 会话 Cookie 会持久化到 `.elk_session/cookies.json`(可用 `kibana.cookie_file` 覆盖);启动时优先复用,仅在缺失/探测失败/401/403 时用 Playwright 重新登录,登录后关闭浏览器。
- 查询走 Kibana HTTP API(Cookie + `kbn-xsrf`),不解析 Discover DOM。
- `search_logs` 默认 `mode=summary`,只返回预览与归类摘要;完整日志用 `expand_log_hit` 二次拉取。
- `search_by_trace_id` 用于链路关联:兼容 `[TID:...]` / `TID:` / 原始 ID,默认按根 TraceId 查询并返回 `trace_summary`。
- `mode=full` 时默认字段仍为 `@timestamp` + `message`,长字段头尾截断;`fields=["*"]` 可拿全文。
- `kuery` / `lucene` 目前都按 ES `query_string` 执行;驼峰方法名、接口路径请加引号。
- `config.yaml` 与 `.elk_session/` 含敏感信息,已加入 `.gitignore`,请勿提交。