内容
# Splunk SOAR MCP 服务器
[MCP(模型上下文协议)](https://modelcontextprotocol.io) 服务器,用于 **Splunk SOAR** (前身为 Phantom)。它允许像 Claude Code 这样的 AI 助手以无头方式操作 SOAR 实例 - 无需浏览器。每个调用都直接通过 `ph-auth-token` (或 HTTP 基本身份验证)使用 SOAR REST API。
**您可以执行以下操作:**
- 分类容器(案例):搜索、读取字段/工件、添加分析师笔记
- 以编程方式编写和部署剧本(`import_playbook` 包路径 - 实际适用于现代剧本的唯一写入路径)
- 运行剧本和单个应用操作,然后轮询状态、操作运行和调试日志
- 管理自定义列表和资产配置
- 通过 `rest` 出口阀门回退到任何原始 REST 调用
已针对 **Splunk SOAR 6.4.x** 进行测试。
---
## 要求
- Python 3.10+
- [`uv`](https://docs.astral.sh/uv/) (推荐)或 `pip install fastmcp`
- 到您的 SOAR 实例的网络访问权限
- 具有身份验证令牌的 SOAR **自动化用户** (请参见下文)
## 步骤 1 — 在 SOAR 中创建自动化用户和令牌
1. 以管理员身份登录到您的 SOAR 网络界面。
2. 进入 **管理 → 用户管理 → 用户 → + 用户**。
3. 设置 **用户类型 = 自动化**。赋予其一个名称,如 `mcp-auto`。
4. 授予具有所需权限的角色(至少:查看容器/剧本;添加 **编辑/导入剧本** 和 **执行操作** 如果您想要部署/运行功能)。
5. 保存用户,然后打开它 - **REST API 身份验证配置** 部分显示包含 `ph-auth-token` 值的 JSON。复制该令牌。
6. 记下用户的数字 **ID** (在查看用户时在 URL 中可见,或通过 `GET /rest/ph_user?_filter_type="automation"`)。您将使用它作为 `SOAR_OWNER_ID` - 在通过 `run_action` 运行单个操作时需要它。
> 或者,您可以跳过令牌并使用 `SOAR_USER` / `SOAR_PASS` 的 HTTP 基本身份验证,但强烈建议使用范围限定的自动化令牌。
## 步骤 2 — 配置环境变量
| 变量 | 必需 | 描述 |
|---|---|---|
| `SOAR_HOST` | ✅ | 您的 SOAR 实例的基本 URL,例如 `https://soar.example.com` (不需要尾部斜杠) |
| `SOAR_TOKEN` | ✅ (或用户/密码) | 来自步骤 1 的 `ph-auth-token` |
| `SOAR_USER` / `SOAR_PASS` | — | HTTP 基本备用,仅在未设置 `SOAR_TOKEN` 时使用 |
| `SOAR_OWNER_ID` | — | 自动化用户的数字 `ph_user` ID(默认为 `1`)。用作 `run_action` 的 `owner` |
| `SOAR_VERIFY_SSL` | — | `true` 以验证 TLS 证书。默认为 `false`,因为大多数本地 SOAR 实例使用自签名证书 |
## 步骤 3 — 使用您的 MCP 客户端注册
### Claude Code (项目范围)
在项目根目录中创建 `.mcp.json` (参见 [`.mcp.json.example`](.mcp.json.example)):
```json
{
"mcpServers": {
"soar": {
"command": "uv",
"args": ["run", "--with", "fastmcp", "python", "/absolute/path/to/splunk-soar-mcp/server.py"],
"env": {
"SOAR_HOST": "https://YOUR-SOAR-HOST",
"SOAR_TOKEN": "YOUR-PH-AUTH-TOKEN",
"SOAR_OWNER_ID": "1"
}
}
}
}
```
然后在该项目中启动 Claude Code 并在提示时批准服务器(或运行 `/mcp` 以检查连接)。
> **不要提交您的实际 `.mcp.json`** - 它包含您的令牌。将它放在 `.gitignore` 中或使用客户端支持的环境变量引用。
### Claude 桌面版
将相同的块添加到 `claude_desktop_config.json`:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
### 普通 pip 而不是 uv
```json
"command": "python3",
"args": ["/absolute/path/to/splunk-soar-mcp/server.py"]
```
之前已完成 `pip install fastmcp`。
## 步骤 4 — 验证连接
要求您的助手调用 `whoami` 工具。预期输出:
```json
{
"host": "https://YOUR-SOAR-HOST",
"auth_mode": "token",
"auth_ok": true,
"version": "6.4.1.361",
"run_as": {"id": 1, "username": "mcp-auto", "type": "automation"}
}
```
如果 `auth_ok` 为 `false`,则令牌错误或缺少 REST 访问权限。如果服务器启动失败并显示 `SOAR_HOST 未设置`,请修复您的 `env` 块。
---
## 工具参考
### 连接和通用
| 工具 | 用途 |
|---|---|
| `whoami()` | 检查凭证、SOAR 版本和哪些自动化用户操作运行 |
| `rest(method, path, json_body)` | 通用 REST 出口阀门(`GET`/`POST`/`DELETE`,路径如 `/rest/...`) |
### 容器(案例)
| 工具 | 用途 |
|---|---|
| `search_containers(name_contains, status, tenant, limit)` | 按名称子字符串/状态/租户查找容器 |
| `get_container(container_id)` | 关键字段 + 容器的 `custom_fields` |
| `get_artifacts(container_id, limit)` | 列出具有其完整 CEF 字典的工件 |
| `update_artifact(artifact_id, cef)` | **合并** 键到工件的 CEF(纯 POST 会静默删除其他键 - 此工具读取然后合并) |
| `update_container_fields(container_id, custom_fields, status, name)` | 更新容器的 `custom_fields` / `status` / `name`。容器 `custom_fields` **合并服务器端**,因此部分写入是安全的 |
| `add_note(container_id, title, content, note_type)` | 向容器添加注释 |
### 剧本 - 读取和作者
| 工具 | 用途 |
|---|---|
| `list_playbooks(name_contains, limit)` | 按名称查找剧本 |
| `get_playbook(playbook_id)` | 元数据:名称、`passed_validation`、类型、`input_spec`、活动 |
| `get_playbook_source(playbook_id)` | 生成的 Python + 输入/输出规范 - 读取转换剧本前的实际数据路径 |
| `deploy_playbook(out_dir, name)` | 从 `out_dir/{name}.py` + `{name}.json` 构建 tgz 包,验证(AST 解析),导入并验证。**每个导入都会创建新的剧本 ID** |
| `import_bundle_b64(b64, scm, force)` | 导入预构建的 base64 gzip-tarball 包 |
> **为什么是包?** 现代(`is_modern: true`)剧本 *不能* 通过纯 `POST /rest/playbook/{id}` 编写 - 该端点返回 `success` 但静默更改内容。`POST /rest/import_playbook` 带有 tar.gz 包是实际的写入路径。
### 剧本和操作 - 运行和调试
| 工具 | 用途 |
|---|---|
| `run_playbook(container_id, playbook_id, inputs, scope)` | 触发剧本运行(支持输入剧本) |
| `playbook_run_status(playbook_run_id)` | 轮询一次运行:状态 + 消息 |
| `wait_for_playbook_run(playbook_run_id, timeout_secs, poll_secs)` | 轮询直到运行完成,返回最终状态 + 其操作运行 |
| `action_runs(playbook_run_id)` | 列出剧本运行产生的操作运行 |
| `app_run_detail(action_run_id)` | 每个应用运行的参数 + 结果消息 - 显示发送到应用的实际参数 |
| `playbook_run_log(playbook_run_id, contains, limit)` | 运行的调试日志 - SOAR 报告实际失败的地方(例如“phantom.collect2() 返回空列表”) |
| `run_action(action, asset, app_id, container_id, parameters, ...)` | 在任何剧本之外运行单个应用操作并等待结果 |
### 自定义列表和资产
| 工具 | 用途 |
|---|---|
| `get_custom_list(name)` | 按名称读取自定义列表(`decided_list`) |
| `update_custom_list_rows(name, rows)` | 替换行(`{"0-based index": [full row]}`)。第 0 行是标题;始终发送每列 |
| `update_asset(asset_id, config_updates)` | **合并** 键到资产配置(例如旋转密码)。读取然后合并 - 部分 POST 将删除其他键。永远不会回显密钥 |
---
## 典型工作流程:构建 → 部署 → 测试剧本
```text
1. 生成 out_dir/{NAME}.py 和 out_dir/{NAME}.json(剧本源 + 图)
2. deploy_playbook("out_dir", "NAME") → 导入 ID + passed_validation
3. run_playbook(container_id, id, {..inputs..}) → playbook_run_id
4. wait_for_playbook_run(run_id) → 最终状态 + 操作运行
5. playbook_run_log(run_id) → 如果有故障则调试
```
## 经验教训
- `import_playbook` 始终创建 **新** 的剧本 ID - 它从不按名称覆盖。
- REST `DELETE /rest/playbook/{id}` 返回 **405** - 只能在 UI 中删除剧本。
- 裸 `POST /rest/artifact/{id} {"cef": {...}}` **替换** 整个 CEF 并销毁其他键。资产配置也是如此。使用此处的合并工具。
- 对象具有不同的写入语义:容器的 `custom_fields` **合并** (部分写入安全),工件顶级字段(名称/标签/严重性) **合并**,但 `artifact.cef` 和 `asset.configuration` **替换**。
- 容器的 `status` 值很挑剔 - `"in progress"` 被拒绝;有效值为 `in-progress` (带连字符)。
- `run_action` 需要 `owner` 和 `app_id` **同时** 在顶级和目标内,否则 SOAR 会以误导性错误拒绝请求。
- 一个 `phantom.collect2()` 调用会收集超过一个块 - 永远不要在单个数据路径列表中跨越两个 `playbook_input:*` 字段。
## 安全说明
- 使用具有最低角色的专用 **自动化用户** ,而非个人管理员帐户。
- 将令牌保持在版本控制之外(`.mcp.json` 在此存储库的 `.gitignore` 中)。
- 默认情况下,TLS 验证是 **关闭的** (自签名证书在本地是规范的)。如果您的实例具有有效的证书,请设置 `SOAR_VERIFY_SSL=true`。
- 如果令牌泄露,请从 **用户管理** 旋转它。
## 许可证
[MIT](LICENSE)