内容
# rust-junosmcp
这是一个非官方/社区项目。该存储库是一个独立的、社区驱动的项目。它与 Hewlett Packard Enterprise 或 Juniper Networks 无关,不受其支持或赞助。"HPE"、"Juniper"、"SRX"、"JUNOS"、"Security Director" 和 "Juniper Mist" 是其各自所有者的商标,这里仅用于描述该软件的互操作性。请将有关这些产品的支持和许可问题直接提交给各自的供应商。
一个适用于 Juniper Junos 设备的 [Model Context Protocol](https://modelcontextprotocol.io/) 服务器,用 Rust 编写。与 [Juniper/junos-mcp-server](https://github.com/Juniper/junos-mcp-server) 在清单格式和工具表面上兼容,但构建在异步 Rust ([rustEZ](https://github.com/fastrevmd-lab/rustEZ) + [rustnetconf](https://github.com/fastrevmd-lab/rustnetconf)) 之上,而不是 PyEZ。
## 性能
与 [Juniper/junos-mcp-server](https://github.com/Juniper/junos-mcp-server) (Python/PyEZ)在相同的 vSRX 实验室设备和相同的网络路径上进行基准测试。
| 测试 | rust-junosmcp (v0.3.0) | junos-mcp (Python) | 速度提升 |
|------|------------------------|--------------------|---------|
| 5 个顺序命令 | 30.4s (6.1s/命令) | 52.2s (10.4s/命令) | **1.7x** |
| 5 个并行命令 | 8.1s (1.6s/命令) | 11.1s (2.2s/命令) | **1.4x** |
| 4 个路由器 x 3 个命令(批处理) | 16.1s (1.3s/命令) | 不适用 | 仅限 Rust |
会话池(`PooledDevice`)消除了对同一路由器连续命令的 SSH/NETCONF 握手开销。批处理工具并行运行路由器,并具有可配置的并发上限。
> ## v0.3.0 发布
>
> 会话池、可靠性修复和提交确认。NETCONF 会话现在每个路由器都有一个 `PooledDevice` RAII 守护进程(300 秒空闲超时,30 秒 SSH 保活,背景回收)。五个错误修复:`get_junos_config` / `junos_config_diff` 的 XML 包装器剥离,正确的 `show configuration | compare rollback` 命令,超时现在涵盖 SSH 握手,批处理为未知路由器返回部分结果。新的 `confirm_timeout_mins` 参数用于 `load_and_commit_config`,用于具有自动回滚的确认提交。 从路径依赖切换到 crates.io 0.10.1 的 `rustez` 依赖。
>
> 查看 [v0.3.0 发布说明](https://github.com/fastrevmd-lab/RustJunosMCP/releases/tag/v0.3.0)。
## 功能范围
### v0.1(已发布)
- 6 个工具:`get_router_list`、`gather_device_facts`、`execute_junos_command`、`get_junos_config`、`junos_config_diff`、`load_and_commit_config`。
- 仅限标准输入/输出传输。
- 兼容 `devices.json` 插入(`auth.type` ∈ {`password`, `ssh_key`})。
- Docker 镜像(无分发)和 LXC 发布 tarball,带有 systemd 单元。
### v0.2(已发布)
- 流式 HTTP 传输(带有可选的 rustls TLS)。
- 带有每个令牌路由器/工具作用域的 bearer-token 身份验证。
- SIGHUP 热重载令牌存储。
### v0.2 跟进:PFE + 批处理(已发布)
- `execute_junos_pfe_command` — 针对明确的 FPC 目标的单个 PFE-shell 调用。
- `execute_junos_command_batch` — N 个路由器 x M 个操作 CLI 命令,并行跨路由器,每个命令和可选的整个批处理超时。飞行前阻塞列表 + 未知路由器检查;继续错误后飞行。
### v0.2 跟进:模板(已发布)
- `render_and_apply_j2_template` — 使用 JSON `vars_content` 对象渲染 Jinja2 模板(内联 `template_content`)。支持单个(`router_name`)或多个路由器(`router_names`),干运行和完整提交。重复使用与 `load_and_commit_config` 相同的阻塞列表 + 格式门控。
- 变量必须是顶级 JSON 对象。**YAML 不再被接受**,自 v0.5.2(RJMCP-SEC-002)起:`serde_yml` / `libyml` 咨询链(RUSTSEC-2025-0067/-0068)可从 MCP 输入到达,因此删除了 YAML 分支。
- 大小上限:`template_content` 和 `vars_content` 各自限制在 64 KiB。
- 严格未定义:缺少变量会失败,并显示变量名称而不是渲染为空。
- 自动格式检测:前导 `<` → `xml`,任何 `set ` / `delete ` 行 → `set`,否则 `text`。通过 `config_format` 覆盖。
- 结果形状:每行路由器一个,带有 `rendered_template`、`config_format` 和 `diff`(干运行)、`commit_comment`(应用模式 - 所提供的注释的回声 - rustez 不返回服务器颁发的提交 ID),或 `error`。
### v0.2 跟进:库存突变(已发布)
- `add_device` — 将 Junos 设备添加到内存库存并持久化到 `devices.json`。原子写入(临时文件 + 重命名),保留 `_blocklist_defaults`、每个设备的 `blocklist` 和其他顶级字段。基于 SHA-256 的 TOCTOU 保护拒绝与外部编辑竞争的调用。
- `reload_devices` — 重新读取当前的 `--device-mapping`(无参数)或交换到新的库存文件(`file_name`)。报告添加/删除/更改的设备名称。
- 新的 CLI 标志:`--inventory-readonly`(无条件拒绝两个工具)和 `--allow-password-auth-add`(允许在 `add_device` 中使用 `auth.type=password`;与 `--inventory-readonly` 互斥)。
- SIGHUP 现在还重新读取库存,除了令牌存储。
**文档化的锐利边缘:** `add_device` 不会修改令牌存储。如果令牌有 `--routers 'edge-*'` 并且您为 `core-3` 添加设备,则现有的令牌将不会看到新路由器。颁发新令牌或在 `add_device` 后旋转范围。
### v0.3(已发布)
- **NETCONF 会话池** — `PooledDevice` RAII 守护进程,每个路由器有一个槽(300 秒空闲超时,30 秒 SSH 保活,背景回收)。消除了对连续命令的 SSH 握手开销。
- **工具可靠性修复** — `get_junos_config` 和 `junos_config_diff` 的 XML 包装器剥离,纠正的 `show configuration | compare rollback N` 命令,超时现在涵盖 SSH 连接 + NETCONF 握手(不仅仅是 CLI 执行)。
- **批处理部分结果** — `execute_junos_command_batch` 为未知路由器返回内联错误行,而不是中止整个批处理。阻止列表违规仍然严格。
- **确认提交** — `load_and_commit_config` 为 `commit confirmed N` 提供了 `confirm_timeout_mins` 参数,具有自动回滚安全网。
### v0.4(已发布)
- **`transfer_file`** — 将主机暂存文件以 `/var/tmp/<basename>` 推送到 Junos 设备,使用传统 SCP(`scp -O`,由于 Junos 禁用 OpenSSH SFTP 子系统)。预飞行空闲空间检查 `/var`(`local_size + 32 MiB` 缓冲区),SHA-256 验证,传输后校验和重新验证与不匹配删除。仅限 SSH 密钥身份验证 — 密码身份验证设备被拒绝,返回 `[code=unsupported_auth]`。
- **`list_staged_files`** — 始终列出主机暂存目录,以及当提供 `router_name` 时,列出设备 `/var/tmp/` 目录。
- **稳定错误代码** — 每个传输失败都带有 LLM 可读的 `[code=...]` 显示标签(`bad_source_path`、`insufficient_disk`、`unsupported_auth`、`dest_exists_differs`、`scp_failed`、`connect_timeout`、`verify_mismatch`、`outer_timeout`、`device_probe_failed`)。
- **新 CLI 标志** — `--staging-dir`(默认为 `/var/lib/jmcp/staging`)和 `--known-hosts-file`(默认为 `/etc/jmcp/known_hosts`)。
- **打包** — `install.sh` 准备了 `jmcp:jmcp` 所拥有的新磁盘表面。参见下面的文件传输部分。
### v0.5(已发布)
- **`upgrade_junos`** — 两步(阶段然后确认)Junos 软件升级。通过 `transfer_file` 语义上传软件包,运行 `request system software add`,并重启。仅限独立使用;如果目标路由器存在会话池条目,则拒绝。
- 工具数量:13 → 14。
### v0.6(已发布)
- **`fetch_file`** — 将文件从 `<device>:/var/tmp/<basename>` 下载到主机暂存目录。SHA-256 验证,本地副本如果已经匹配则跳过,每个路由器序列化。与 `transfer_file` 镜像。
- 工具数量:14 → 15。
## 阻塞列表防护(v0.2)
`devices.json` 可以携带可选的 `_blocklist_defaults` 块和每个设备条目的可选 `blocklist` 字段。规则使用简单的通配符(`*`、`?`)和 `"deny"` 或 `"allow"` 操作。最具体的匹配项获胜;设备特定的规则会破坏顶级默认值。参见 [`devices-template.json`](devices-template.json) 示例和 [`docs/superpowers/specs/2026-05-04-blocklist-guardrails-design.md`](docs/superpowers/specs/2026-05-04-blocklist-guardrails-design.md) 获取完整设计。
`pfe_commands` 规则列表是独立的:`commands` 上的拒绝不会阻止 `execute_junos_pfe_command`,反之亦然。使用它来限制 PFE 输入(例如 `set *`)而不影响操作 CLI。
阻止列表适用于 `execute_junos_command` 和 `load_and_commit_config`。对于 `load_and_commit_config`,当设备有任何有效的配置规则时,`config_format` 必须是 `set`;否则,`text` 和 `xml` 有效载荷会在预飞行阶段被拒绝。
> **兼容性注意:** 使用 `_blocklist_defaults` 或每个设备的 `blocklist` 的文件与 Juniper/junos-mcp-server 的库存格式不兼容。没有这些字段的文件仍然可以无缝替换。
## 确认提交(v0.3)
`load_and_commit_config` 支持通过 `confirm_timeout_mins` 参数支持 Junos `commit confirmed`。路由器在 N 分钟后自动回滚,除非后续提交确认更改 — 一个对可能破坏管理连接的远程配置推送的关键安全网。
```json
{
"router_name": "core-1",
"config_text": "set interfaces ge-0/0/0 description test",
"confirm_timeout_mins": 10,
"commit_comment": "safe change with rollback window"
}
```
响应:
```json
{
"success": true,
"diff": "[edit interfaces ge-0/0/0]\n+ description test;",
"confirmed": true,
"rollback_in_minutes": 10,
"message": "Commit confirmed: auto-rollback in 10 minutes unless confirmed. Send another commit to confirm."
}
```
要确认(防止回滚),请发送另一个 `load_and_commit_config`,使用相同的配置(或任何有效配置),而无需 `confirm_timeout_mins`。
## 文件传输(`transfer_file` / `fetch_file` / `list_staged_files`)
`transfer_file` 使用传统 SCP(`scp -O`,由于 Junos 禁用 OpenSSH SFTP 子系统)将主机暂存文件推送到 Junos 设备上的 `/var/tmp/<basename>`。**基于 SHA-256 的幂等**:如果远程文件已经存在并具有匹配的摘要,则调用返回 `status: "skipped"`。传递 `force: true` 以在摘要不同时覆盖。
`fetch_file` 是镜像操作:它使用相同的传统 SCP 路径将 Junos 设备上的 `/var/tmp/<basename>` 下载到主机暂存目录。**基于 SHA-256 的幂等** — 如果本地文件已经存在并具有匹配的摘要,则调用返回 `status: "skipped"`。每个路由器序列化和传输后 SHA-256 重新验证与 `transfer_file` 相同。
**身份验证:** 仅限 SSH 密钥。具有 `auth.type = "password"` 的设备被拒绝,返回 `[code=unsupported_auth]`。在 `devices.json` 中添加 SSH 密钥并通过 `auth.private_key_path` 引用。
**磁盘表面:
| 路径 | 目的 | 默认模式 | 所有者 |
| ----------------------------- | --------------------------------------------- | ------------ | ----------- |
| `/var/lib/jmcp/staging/` | 主机文件传输暂存 | `0755` | `jmcp:jmcp` |
| `/etc/jmcp/known_hosts` | 咨询每个 SSH 推送的 `known_hosts` | `0644` | `jmcp:jmcp` |
在启动时使用 `--staging-dir <path>` 和 `--known-hosts-file <path>` 覆盖。
**主机密钥策略(v0.5.2+)**:scp 默认运行 `StrictHostKeyChecking=yes` — 未知设备主机密钥被拒绝。 `known_hosts` 文件必须在第一个 `transfer_file` / `upgrade_junos` 调用之前存在,否则工具会报错 `[code=known_hosts_missing]`。使用捆绑的帮助程序预先填充:
```bash
scripts/scan-known-hosts.sh --inventory /etc/jmcp/devices.json \
--known-hosts /etc/jmcp/known_hosts
```
用于实验室/首次联系,请传递 `--ssh-accept-new-host-keys` 以回落到 OpenSSH 的 `accept-new`(TOFU)模式。
`list_staged_files` 返回主机暂存目录的内容。如果提供 `router_name`,它还会运行 `file list /var/tmp/ detail` 设备并在 `device_files` 下包含这些条目。
**源路径安全:** `source_path` 必须是仅限 basename(无 `/`,无 `\`,无 `..`,无前导点,≤ 255 字节);它相对于 `--staging-dir` 并且永远不会逃逸。
**预飞行检查:** `transfer_file` 运行 `show system storage no-forwarding` 并拒绝在 `/var` 的空闲空间低于 `local_size + 32 MiB` 时推送。
**后验证:** 除非传递 `verify: false`,否则设备端校验和通过 `file checksum sha-256 /var/tmp/<basename>` 进行重新计算,并在不匹配时删除文件。
## 长运行的操作命令
每个 MCP 工具都提供每个调用的 `timeout` 参数(默认为 360 秒)。这是 **用户可见的唯一边界** 操作持续 1 小时内部 RPC 超时时间,因此合法运行时间超过 1 小时(`request system software add`、`request support information`、`request system snapshot`等)不会被无声截断。
**注意事项:** 当长时间运行的 RPC 跟随设备重启时,NETCONF 会话当然会死亡。会话池在下一个调用时干净地重新连接。
## 安全警告
该服务器允许 LLM 运行命令并将配置更改推送到您的 Junos 设备。在部署前,请阅读 [Juniper/junos-mcp-server的安全公告](https://github.com/Juniper/junos-mcp-server#important-security-notice)。相同的警告适用。
- 优选 SSH 密钥身份验证而不是密码。
- 在允许提交工具运行之前,请查看配置。
- 限制对 MCP 服务器的网络访问。
- 不要部署到不受信任的网络。
- 将 `devices.json` 权限设置为 `0600` — 它包含 SSH 凭证。
- `get_junos_config` 返回完整的配置,包括 `## SECRET-DATA` 哈希密码行。限制此工具的作用域仅限受信任的令牌。
- `reload_devices` 需要 `file_name` 是相对于原始 `--device-mapping` 目录的**相对路径**(自 v0.5.2 起)。绝对路径、`..` 遍历和指向外部目录的符号链接都被拒绝。
- 文本输入字段(`command`、`config_text`、`template_content`、`pfe_command`)限制在 1 MB 以内。批处理列表限制在 100 个路由器和 50 个命令。
## 快速入门(本地)
```bash
git clone https://github.com/fastrevmd-lab/RustJunosMCP.git
cd RustJunosMCP
# 构建(rustez 从 crates.io 自动拉取)。
cargo build --release
# 配置设备。
cp devices-template.json devices.json
$EDITOR devices.json # 设置 ip / username / auth
# 作为 MCP stdio 服务器运行。
./target/release/rust-junosmcp -f devices.json
```
## Claude 桌面配置
```json
{
"mcpServers": {
"junos": {
"command": "/path/to/rust-junosmcp",
"args": ["-f", "/path/to/devices.json"]
}
}
}
```
## Docker
```bash
docker build -t rust-junosmcp:0.3 .
# 运行。
docker run --rm -i \
-v $PWD/devices.json:/etc/jmcp/devices.json:ro \
-v $PWD/keys:/etc/jmcp/keys:ro \
rust-junosmcp:0.3
```
## LXC(Proxmox)
```bash
# 构建 tarball。
./scripts/package-lxc.sh
# 推送并安装在 VM 115 上(Debian 12 / Ubuntu 24.04 LXC)。
pct push 115 dist/rust-junosmcp_0.3.0_amd64.tar.gz /tmp/jmcp.tar.gz
pct exec 115 -- bash -c "tar xzf /tmp/jmcp.tar.gz -C /tmp && /tmp/rust-junosmcp_0.3.0_amd64/install.sh"
# 编辑 LXC 上的 /etc/jmcp/devices.json,然后:
pct exec 115 -- systemctl enable --now rust-junosmcp
```
> **v0.1 systemd 单元的注意事项:** stdio 不适合长时间运行的守护进程。单元被提供以兼容 v0.2 的 HTTP 传输。对于 v0.1,实际模式是从外部运行 MCP 客户端的二进制文件。
## 远程传输 + 身份验证(v0.2)
### 颁发令牌
```bash
cargo run -- token add \
--tokens-file tokens.json \
--name ops \
--routers '*' \
--tools execute_junos_command,gather_device_facts
```
> **注意:** 参见 [`tokens-template.json`](tokens-template.json) 获取文件格式。使用 `token add` 而不是手动编辑文件 — 哈希字段必须是秘密的 SHA-256,而不是纯文本。
> **以服务用户身份运行令牌子命令。** 当 systemd 单元以专用用户运行服务器(例如单元中的 `User=jmcp`),文件 `token add`/`revoke`/`rotate` 写继承调用用户的所有权。如果您以 `root` 身份运行它们,生成的 `tokens.json` 将为 `root:root 0600`,服务用户无法读取 — 服务器在启动时 crash-loop,报错为 `Permission denied`。要么:
>
> ```bash
> # 优先:以服务用户身份运行子命令。
> sudo -u jmcp rust-junosmcp token add --tokens-file /etc/jmcp/tokens.json ...
>
> # 或修复以 root 身份运行后的所有权。
> rust-junosmcp token add --tokens-file /etc/jmcp/tokens.json ...
> chown jmcp:jmcp /etc/jmcp/tokens.json
> ```
>
> 如果服务器在启动时遇到这种情况,错误消息现在报告文件的 uid/mode 和调用者的 uid,因此修复方法很明显。
### 使用身份验证(流式 HTTP)运行
```bash
cargo run -- \
--device-mapping devices.json \
--transport streamable-http \
-H 127.0.0.1 \
-p 8765 \
--tokens-file tokens.json
```
### 跳出循环保护(无认证,仅限本地)
```bash
cargo run -- --device-mapping devices.json --transport streamable-http \
-H 127.0.0.1 -p 8765 --allow-no-auth
```
如果绑定地址不是回环地址,则 `--allow-no-auth` 被拒绝。
### 非回环地址需要 TLS
```bash
cargo run -- \
--device-mapping devices.json \
--transport streamable-http \
-H 0.0.0.0 \
-p 8765 \
--tokens-file tokens.json \
--tls-cert cert.pem \
--tls-key key.pem
```
要在纯 HTTP 上绑定非回环地址(例如,在同一主机上的 TLS 终止代理后面),请添加 `--allow-insecure-bind`。此标志覆盖 TLS 要求,应谨慎使用 — 只有在您有外部保证传输安全时才使用。
### 热重载
在撤销或轮换令牌后,服务器会在不重启的情况下重新加载令牌存储。 将 `--server-pid <pid>` 传递给任何写入子命令,并在写入文件后自动发送 SIGHUP:
```bash
# Revoke — 写入文件,然后发出信号给服务器。
cargo run -- token revoke --tokens-file tokens.json --name ops --server-pid <pid>
# Rotate(生成新的密钥,保留范围)— 相同的模式。
cargo run -- token rotate --tokens-file tokens.json --name ops --server-pid <pid>
# 添加新令牌并发出信号。
cargo run -- token add \
--tokens-file tokens.json \
--name ops2 \
--routers '*' \
--tools execute_junos_command,gather_device_facts \
--server-pid <pid>
```
如果您需要触发重新加载而无需更改令牌(例如,手动编辑文件后),请直接发送 SIGHUP:
```bash
kill -HUP <pid>
```
### 拒绝矩阵
| 标志 | 绑定地址 | 结果 |
|---|---|---|
| _(无)_ | 任意地址 | 拒绝 — `--tokens-file` 或 `--allow-no-auth` 需要用于 streamable-http |
| `--allow-no-auth` 仅 | 非回环地址 | 拒绝 — `--allow-no-auth` 仅限回环地址 |
| `--allow-no-auth` 仅 | 回环地址 | 确认 — 但注意:如果您还提供 `--tls-cert`/`--tls-key`,则身份验证仍然禁用;TLS 提供机密性,但任何可以访问端口的客户端都具有完全的工具访问权限(foot-gun) |
| `--tokens-file` 仅 | 非回环地址,无 TLS | 拒绝 — 添加 `--tls-cert`/`--tls-key` 或 `--allow-insecure-bind` |
| `--tokens-file --allow-insecure-bind` | 非回环地址,无 TLS | 确认 — 令牌被检查;您正在断言外部传输安全性 |
| `--tokens-file --tls-cert cert.pem --tls-key key.pem` | 任意地址 | 确认 |
## 命令行界面
```
Junos MCP 服务器(Rust)
用法:rust-junosmcp [选项] [命令]
命令:
token 管理 bearer-token 存储
help 打印此消息或给定子命令的帮助
选项:
-f, --device-mapping <DEVICE_MAPPING>
具有设备映射的 JSON 文件(Juniper junos-mcp-server 兼容) [默认:devices.json]
-t, --transport <TRANSPORT>
传输 [默认:stdio] [可能的值:stdio, streamable-http]
-H, --host <HOST>
绑定主机(仅 streamable-http) [默认:127.0.0.1]
-p, --port <PORT>
绑定端口(仅 streamable-http) [默认:30030]
--tokens-file <TOKENS_FILE>
Bearer-token 文件。 streamable-http 需要,除非 --allow-no-auth
--tls-cert <TLS_CERT>
PEM 编码的 TLS 证书(仅 streamable-http)。与 --tls-key 配对
--tls-key <TLS_KEY>
PEM 编码的 TLS 密钥(仅 streamable-http)。与 --tls-cert 配对
--allow-no-auth
禁用 bearer-token 身份验证。拒绝绑定到非回环地址
--allow-insecure-bind
绑定到非回环地址的纯 HTTP。非 127.0.0.1 主机在未配置 TLS 时需要
--inventory-readonly
无条件拒绝 add_device 和 reload_devices
--allow-password-auth-add
允许 add_device 接受 auth.type=password(与 --inventory-readonly 互斥)
-h, --help
打印帮助
-V, --version
打印版本
```
## 对真实设备的测试
```bash
JMCP_TEST_HOST=10.0.0.1 \
JMCP_TEST_USER=admin \
JMCP_TEST_PASS=secret \
cargo test -p rust-junosmcp-core --test integration_real_device -- --ignored --nocapture
```
## 许可证
在 [MIT](LICENSE-MIT) 或 [Apache-2.0](LICENSE-APACHE) 下双重许可。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器