nagameTW/mcp-server-malcolm
GitHub: nagameTW/mcp-server-malcolm
为 Malcolm 网络流量分析平台提供 MCP 接口,使兼容的 AI agent 能够以结构化方式安全地查询网络流量、警报和资产信息。
Stars: 3 | Forks: 0
# mcp-server-malcolm
[](https://pypi.org/project/mcp-server-malcolm/)
[](https://pypi.org/project/mcp-server-malcolm/)
[](LICENSE)
**English** | [繁體中文](README.zh-TW.md)
首个为 [Malcolm](https://malcolm.fyi) 提供的 MCP server,Malcolm 是一个开源的网络流量分析平台(包含 Zeek + Suricata + Arkime + OpenSearch,以及可选的 NetBox)。
它为任何兼容 MCP 的 AI agent 提供了对 Malcolm 的结构化访问:搜索和聚合网络流量、发现字段名称、查询 Suricata 警报、浏览 Arkime 会话、解析 NetBox 资产以及检查系统健康状况。开启写入类别后,它还可以创建警报、为会话添加 tag、启动 hunt 以及上传 PCAP。
## 在你主动选择前为只读
在没有任何配置的情况下,此 server 仅暴露读取工具。它的行为就像一个只读客户端,其所有操作都不会改变 Malcolm 中的数据。
该 server 将写入权限分为四个类别,每个类别都由独立的环境变量控制,且默认全部关闭。它不会注册被禁用的类别,因此该类别的工具永远不会出现在 `list_tools()` 中,也无法被调用。在启动时,它会打印出哪些类别处于开启状态:
```
[mcp-server-malcolm] write classes: alerting=off arkime-tag=off hunt-job=off pcap-upload=off
```
每次写入都是附加的。版本 1 中没有任何工具可以删除数据、移除 tag 或触及用户账户。这是刻意为之的设计(参见 [非目标](#non-goals))。
## 为什么需要 MCP 层
Malcolm 将所有网络元数据保存在一个 OpenSearch 索引(`arkime_sessions3-*`)中,其包含非标准的字段名称和特有的过滤语法。如果让 LLM 直接针对该索引编写原始的 OpenSearch DSL,往往会出错。此 server 代替模型承担了这项工作:
- 它暴露的是 Malcolm 的过滤语法,而不是原始的 DSL。
- 它提供字段发现功能,以便模型在查询前检查字段名称。
- 它提供值枚举功能,让模型看到字段实际包含的值。
- 它封装了 Suricata 警报查询并处理字段映射(`suricata.alert.*` 对比 `rule.*`)。
- 它添加了 NetBox 资产上下文(IP 到设备的映射,网段信息)。
写入端也遵循相同的理念。该 server 并非直接将 Malcolm 已经向任何已认证用户开放的原始 OpenSearch 和 NetBox 透传接口交给 agent,而是暴露了一组经过精心挑选、命名且可审计的写入操作。有关此内容的更多信息,请参阅 [安全模型](#security-model)。
## 读取工具
这些工具始终会被注册。
### DSL 核心(与后端无关)
针对配置好的 endpoint 执行普通的 OpenSearch DSL(Malcolm 的 `/mapi/opensearch` 代理)。不包含特定于 Malcolm 的查询结构:只要将 base URL 指向任何兼容 OpenSearch 的后端,它们就能正常工作。
| 工具 | 描述 |
|------|-------------|
| `search_dsl` | 执行原始的 OpenSearch DSL 查询(命中结果 + 聚合,无隐藏时间窗口) |
| `count` | 统计匹配 DSL 查询子句的文档数量 |
| `list_indices` | 列出索引(名称/健康状态/状态/文档计数) |
| `index_mapping` | 索引的字段映射/schema |
| `cluster_health` | OpenSearch 集群健康状态 |
### 核心查询
| 工具 | 描述 |
|------|-------------|
| `malcolm_search` | 使用 Malcolm 过滤语法搜索网络流量 |
| `malcolm_aggregate` | 按一个或多个字段聚合流量(带有计数的 Top-N) |
| `malcolm_alerts` | 按特征、严重程度、IP 搜索 Suricata 警报 |
### 字段发现(防幻觉)
| 工具 | 描述 |
|------|-------------|
| `malcolm_field_search` | 按关键字、前缀或类型搜索可用的字段名 |
| `malcolm_field_values` | 列出某个字段的不同值 |
| `malcolm_field_profile` | 显示哪些 `event.dataset` 类型包含该字段 |
### 系统健康状态
| 工具 | 描述 |
|------|-------------|
| `malcolm_service_status` | 所有 Malcolm 服务的就绪状态以及版本信息 |
| `malcolm_data_coverage` | 每个 sensor 的数据新鲜度、每个 dataset 的文档计数、索引信息 |
| `malcolm_ping` | 对 Malcolm API 的快速存活检测 |
### 资产上下文
| 工具 | 描述 |
|------|-------------|
| `malcolm_netbox_lookup` | 在 NetBox 中查找 IP、设备或网络前缀 |
| `malcolm_netbox_sites` | 列出 NetBox 站点目录(id、名称、元数据) |
### Arkime
| 工具 | 描述 |
|------|-------------|
| `arkime_sessions` | 使用 Arkime 表达式语法搜索 Arkime 会话 |
| `arkime_session_detail` | 获取单个会话的所有字段(完整的 SPI 文档) |
| `arkime_session_pcap` | 获取会话的 PCAP 并报告其大小和文件魔数有效性(仅元数据,不向磁盘写入任何内容) |
| `arkime_unique` | 列出某个字段的所有不同值,可选择包含计数 |
| `arkime_spigraph` | 某个字段的 Top 值及其时间序列图 |
| `arkime_spiview` | 在一次调用中跨多个字段获取值概况 |
| `arkime_connections` | 源/目的连接图(节点和链接) |
### 关联与导出
| 工具 | 描述 |
|------|-------------|
| `malcolm_related_sessions` | 查找与 Zeek UID 相关的所有会话 |
| `malcolm_dashboard_export` | 将 OpenSearch Dashboards 保存的对象导出为 JSON |
## 写入工具(可选开启)
每个类别通过将其标志设置为 `true` 来启用。除非你主动要求,否则不会执行任何写入操作。
| 类别 | 标志 | 工具 | Endpoint |
|-------|------|-------|----------|
| alerting | `MALCOLM_MCP_ENABLE_ALERTING` | `malcolm_create_alert` | `POST /mapi/event` |
| arkime-tag | `MALCOLM_MCP_ENABLE_ARKIME_TAGS` | `arkime_add_tags` | `POST /arkime/api/sessions/addtags` |
| hunt-job | `MALCOLM_MCP_ENABLE_HUNT_JOBS` | `arkime_create_hunt`, `arkime_hunt_status` | `POST /arkime/api/hunt` |
| pcap-upload | `MALCOLM_MCP_ENABLE_PCAP_UPLOAD` | `malcolm_upload_pcap` | `POST /server/php/submit.php` |
- **alerting**:`malcolm_create_alert` 将分析师或 agent 生成的发现索引为一个警报文档,你可以在 Malcolm 的仪表板中看到它。它使用 `/mapi/event`,这是 Malcolm 自身内置的专用写入 endpoint,也是其他类别遵循的模板。
- **arkime-tag**:`arkime_add_tags` 用于为会话添加 tag。它仅用于添加;移除 tag 需要更高的 Arkime 角色权限及其自身的安全设计,因此被推迟实现。
- **hunt-job**:`arkime_create_hunt` 启动跨 PCAP 的数据包搜索(开销很大,因此请先确定查询范围)。`arkime_hunt_status` 用于读取任务进度,并随该类别一起提供。
- **pcap-upload**:`malcolm_upload_pcap` 将本地抓包文件发送给 Malcolm 进行摄取(ingest),并带有客户端的大小限制。
每个写入工具都带有 MCP 注解 `readOnlyHint: false` 和 `destructiveHint: false`,因此 MCP 客户端可以在调用运行前执行其自身的确认步骤。
## 安全模型
Malcolm 的默认部署已经赋予任何已认证用户对原始 OpenSearch(`/mapi/opensearch/*`)不受限制的写入权限,以及对 NetBox 完整的 CRUD(`/mapi/netbox/*`)权限。两者都是没有进行 HTTP 动词过滤的纯粹反向代理;Malcolm 自身的只读模式是移除它们,而不是尝试对它们进行过滤。在常见的认证模式下,“已登录”即意味着具有等同管理员的权限。
在此开启某个写入类别并不会打开一扇原本关闭的门。这扇门在平台层面已经是敞开的。此 server 只是提供了一种经过精心设计的通过方式:
- 提供少量、命名的写入操作集合,而不是原始的透传。
- 默认关闭,每次只能启用一个类别。
- 每次写入尝试都会有一行审计记录。
- 包含 MCP 注解,以便客户端可以要求进行确认。
此 server **不会**暴露原始的 OpenSearch 和 NetBox 写入透传,无论是否通过标志隐藏。管理(Curating)这一攻击面正是它的职责所在。
## 审计
每次写入尝试都会输出一行 JSON,无论成功还是失败:
```
{"ts": "2026-07-06T09:12:44Z", "tool": "arkime_add_tags", "class": "arkime-tag", "target": "ids=240601-abc", "params": {"tags": "suspicious"}, "outcome": "ok"}
```
`outcome` 的值可以是 `ok`、`http_4xx`、`http_5xx` 或 `error:` 中的一种。过长的参数值会被截断,并且 PCAP 的字节流永远不会被记录。默认的输出目标是 stderr;设置 `MALCOLM_MCP_AUDIT_FILE` 可以改为将其追加到文件中。读取工具不会被审计。
## 快速开始
### 安装
```
pip install mcp-server-malcolm
```
或者从源码安装:
```
git clone https://github.com/nagameTW/mcp-server-malcolm.git
cd mcp-server-malcolm
pip install -e .
```
### 配置
为你的 Malcolm 实例设置连接变量:
```
export MALCOLM_URL="https://malcolm.example"
export MALCOLM_USERNAME="admin"
export MALCOLM_PASSWORD="admin"
export MALCOLM_SSL_VERIFY="false" # Malcolm ships self-signed certs by default
export MALCOLM_TIMEOUT="30"
```
保持写入标志未设置以在只读模式下运行。要启用某个类别,请设置其标志:
```
export MALCOLM_MCP_ENABLE_ALERTING="true"
export MALCOLM_MCP_AUDIT_FILE="/var/log/malcolm-mcp-audit.jsonl"
```
### 运行
```
# 作为 MCP server (stdio transport)
mcp-server-malcolm
# 或通过 Python module
python -m mcp_server_malcolm
```
## 用法
### MCP 客户端(配置文件)
将此 server 添加到你的 MCP 客户端配置中:
```
{
"mcpServers": {
"malcolm": {
"command": "mcp-server-malcolm",
"env": {
"MALCOLM_URL": "https://malcolm.example",
"MALCOLM_USERNAME": "admin",
"MALCOLM_PASSWORD": "admin",
"MALCOLM_SSL_VERIFY": "false"
}
}
}
}
```
确切的配置文件位置,请查阅你的 MCP 客户端文档。许多客户端使用项目级别的 `.mcp.json` 或全局配置文件。
### Python(直接导入)
脱离 MCP 层直接使用 `MalcolmClient`:
```
import asyncio
from mcp_server_malcolm import MalcolmClient
async def main():
client = MalcolmClient(
base_url="https://malcolm.example",
username="admin",
password="admin",
)
# Search network traffic
results = await client.search(
filters={"event.dataset": "conn", "source.ip": "192.0.2.77"},
limit=10,
)
# Aggregate by protocol
agg = await client.aggregate(
fields="network.protocol",
filters={"network.direction": ["inbound", "outbound"]},
)
# Discover field names
fields = await client.search_fields(keyword="useragent")
# Get distinct values
datasets = await client.field_values(field="event.dataset")
# Look up a NetBox asset
asset = await client.netbox_get(
"api/ipam/ip-addresses/",
params={"address": "192.0.2.77"},
)
await client.close()
asyncio.run(main())
```
写入原语位于 `_write_*` 方法之后。只有受控的写入工具才能调用它们,直接导入的路径无法访问。
## Malcolm 过滤语法
Malcolm 使用简单的 JSON 过滤语法,而不是 OpenSearch DSL:
```
# 精确匹配
{"event.dataset": "conn"}
# 多个值 (OR)
{"network.direction": ["inbound", "outbound"]}
# 否定
{"!network.transport": "icmp"}
# Field 必须存在 (not null)
{"!related.password": null}
# 通配符
{"suricata.alert.signature": "*MALWARE*"}
# 组合 (AND)
{"event.dataset": "dns", "source.ip": "192.0.2.77"}
```
## 示例
### 搜索发往可疑域名的 DNS 查询
```
malcolm_search(
filters='{"event.dataset": "dns", "zeek.dns.query": "*example.com*"}',
limit=20,
time_from="7 days ago"
)
```
### 按协议聚合顶级会话者
```
malcolm_aggregate(
fields="source.ip,destination.ip,network.protocol",
filters='{"network.direction": ["inbound", "outbound"]}',
limit=20
)
```
### 在查询前验证字段名
```
malcolm_field_search(prefix="zeek.dns")
malcolm_field_values(field="event.dataset")
malcolm_field_profile(field="zeek.ssl.server_name")
```
### 创建警报(需启用 alerting 类别)
```
malcolm_create_alert(
title="Periodic beacon to 192.0.2.77",
severity=2,
description="60s-interval C2 candidate",
source_ip="192.0.2.10",
dest_ip="192.0.2.77"
)
```
### 为会话添加待审查 tag(需启用 arkime-tag 类别)
```
arkime_add_tags(session_ids="240601-abc,240601-def", tags="review,beacon")
```
### 启动 hunt(需启用 hunt-job 类别)
```
arkime_create_hunt(
name="beacon-bytes",
search="deadbeef",
search_type="hex",
total_sessions=42,
start_time=1717200000,
stop_time=1717203600,
expression="ip==192.0.2.77"
)
```
## 配置参考
| 变量 | 默认值 | 描述 |
|----------|---------|-------------|
| `MALCOLM_URL` | `https://localhost` | Malcolm 的 base URL |
| `MALCOLM_USERNAME` | `admin` | Basic auth 用户名 |
| `MALCOLM_PASSWORD` | `admin` | Basic auth 密码 |
| `MALCOLM_SSL_VERIFY` | `false` | 验证 TLS 证书(接受 CA 路径) |
| `MALCOLM_TIMEOUT` | `30` | HTTP 请求超时时间(秒) |
| `MALCOLM_MCP_ENABLE_ALERTING` | `false` | 启用 alerting 写入类别 |
| `MALCOLM_MCP_ENABLE_ARKIME_TAGS` | `false` | 启用可追加的会话 tag 标记 |
| `MALCOLM_MCP_ENABLE_HUNT_JOBS` | `false` | 启用 Arkime hunt 创建和状态查询 |
| `MALCOLM_MCP_ENABLE_PCAP_UPLOAD` | `false` | 启用 PCAP 上传 |
| `MALCOLM_MCP_AUDIT_FILE` | 未设置 | 写入审计文件(未设置时输出到 stderr) |
## 使用到的 Malcolm API endpoint
| Endpoint | 方法 | 使用者 |
|----------|--------|---------|
| `/mapi/document` | POST | `malcolm_search`, `malcolm_alerts`, `malcolm_related_sessions` |
| `/mapi/agg/` | POST | `malcolm_aggregate`, `malcolm_field_values`, `malcolm_field_profile`, `malcolm_data_coverage` |
| `/mapi/fields` | GET | `malcolm_field_search`, `malcolm_field_profile` |
| `/mapi/ready`, `/mapi/version` | GET | `malcolm_service_status` |
| `/mapi/ping` | GET | `malcolm_ping` |
| `/mapi/ingest-stats`, `/mapi/indices` | GET | `malcolm_data_coverage` |
| `/mapi/dashboard-export/` | GET | `colm_dashboard_export` |
| `/mapi/opensearch//_search` | POST | `search_dsl` |
| `/mapi/opensearch//_count` | POST | `count` |
| `/mapi/opensearch/_cat/indices` | GET | `list_indices` |
| `/mapi/opensearch//_mapping` | GET | `index_mapping` |
| `/mapi/opensearch/_cluster/health` | GET | `cluster_health` |
| `/mapi/netbox/*` | GET | `malcolm_netbox_lookup` |
| `/mapi/netbox-sites` | GET | `malcolm_netbox_sites` |
| `/mapi/event` | POST | `malcolm_create_alert` (写入) |
| `/arkime/api/sessions` | GET | `arkime_sessions` |
| `/arkime/api/session/` | GET | `arkime_session_detail` |
| `/arkime/api/sessions.pcap` | GET | `arkime_session_pcap` |
| `/arkime/api/unique` | GET | `arkime_unique` |
| `/arkime/api/spigraph` | GET | `arkime_spigraph` |
| `/arkime/api/spiview` | GET | `arkime_spiview` |
| `/arkime/api/connections` | GET | `arkime_connections` |
| `/arkime/api/sessions/addtags` | POST | `arkime_add_tags` (写入) |
| `/arkime/api/hunt`, `/arkime/api/hunts` | POST, GET | `arkime_create_hunt`, `arkime_hunt_status` (写入 + 读取) |
| `/server/php/submit.php` | POST | `malcolm_upload_pcap` (写入) |
这些 endpoint 路径和请求体结构匹配 Malcolm `26.06.1` 和 Arkime `v6.5.0`。两者在不同版本间会发生变动,因此如果写入工具返回意外错误,请针对你自己的版本重新检查。
## 非目标
版本 1 刻意排除了以下内容:
- 破坏性写入(删除 Arkime 会话、移除 tag、管理用户)。
- 原始的 OpenSearch 写入或原始的 NetBox CRUD 透传,无论是否通过标志启用。
- `streamable-http` 传输(仅支持 stdio)。
## 环境要求
- Python 3.11+
- 拥有 API 访问权限的 Malcolm 实例
- 与 Malcolm 的网络连通性
## 许可证
MIT © nagameTW
标签:MCP服务, Metaprompt, Rootkit, Suricata, Zeek, 现代安全运营, 网络安全, 逆向工具, 隐私保护