关于 routeros-command-tree
介绍 RouterOS 命令树及其 `/console/inspect` REST 端点的技术 Skill,面向构建 RouterOS 工具、模式或命令查询功能的开发者,并说明遍历、版本差异与谨慎处理风险请求的方法。
主要功能
- 01说明命令树节点类型
- 02查询 `/console/inspect` 请求
- 03遍历并缓存命令树
- 04映射 CLI 命令与 REST 动词
- 05生成 RAML 和 OpenAPI 模式
- 06保守处理风险路径
适用场景
- 01构建 RouterOS 命令查询工具
- 02生成 REST API 模式
- 03离线检查缓存的命令树数据
MCP 相关
该 Skill 说明 `rosetta` 如何使用命令树数据进行 MCP 命令查询,但未说明 MCP 服务的实现方式。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tikoci/ routeros-skills --skill routeros-command-tree安装后在 Claude Code 中输入/routeros-command-tree确认已加载。
详细介绍
RouterOS 命令树与 /console/inspect
概览
RouterOS 将所有配置和命令组织成一棵层次结构树。CLI 中的每个路径
(如 /ip/address/add)都对应树中的一个节点。/console/inspect REST 端点
可让你以编程方式探索整棵树——restraml(RAML/OpenAPI
架构生成器)和 rosetta(MCP 命令查询)等工具就是通过这种方式构建数据库的。
命令树结构
RouterOS 的命令层次结构包含四种节点类型:
| 节点类型 | 含义 | 示例 |
|---|---|---|
dir | 目录——包含子路径 | /ip、/system |
path | 路径——可导航的层级(通常包含命令) | /ip/address、/interface/bridge |
cmd | 命令——可执行的操作 | add、set、print、remove、get、export |
arg | 参数——命令的一个参数 | address=、interface=、disabled= |
树示例
/ (root dir)
├── ip/ (dir)
│ ├── address/ (path)
│ │ ├── add (cmd)
│ │ │ ├── address (arg) — "IP address"
│ │ │ ├── interface (arg) — "Interface name"
│ │ │ └── disabled (arg) — "yes | no"
│ │ ├── set (cmd)
│ │ ├── remove (cmd)
│ │ ├── get (cmd)
│ │ ├── print (cmd)
│ │ └── export (cmd)
│ ├── route/ (path)
│ │ └── ...
│ └── dns/ (path)
│ ├── set (cmd)
│ ├── cache/ (path)
│ │ ├── print (cmd)
│ │ └── flush (cmd)
│ └── ...
├── interface/ (dir)
│ └── ...
├── system/ (dir)
│ └── ...
└── ...
/console/inspect API
端点
POST /rest/console/inspect
需要基本身份验证。适用于所有 RouterOS 7.x 版本。
请求类型
| 请求 | 用途 | 返回内容 |
|---|---|---|
child | 列出某路径的子节点 | {type: "child", name, "node-type"} 数组 |
syntax | 获取节点的帮助文本 | {type: "syntax", text} 数组 |
highlight | 语法高亮数据 | 每字节的令牌类别——参见 routeros-syntax-inspection 技能 |
completion | Tab 补全建议 | 补全候选项——有效性检查请参见 routeros-syntax-inspection |
列出子节点
// List children of /ip
const children = await fetch(`${base}/console/inspect`, {
method: "POST",
headers: { ...authHeaders, "Content-Type": "application/json" },
body: JSON.stringify({
request: "child",
path: "ip",
}),
}).then(r => r.json());
// Response:
// [
// { "type": "child", "name": "address", "node-type": "path" },
// { "type": "child", "name": "arp", "node-type": "path" },
// { "type": "child", "name": "cloud", "node-type": "path" },
// { "type": "child", "name": "dhcp-client", "node-type": "path" },
// { "type": "child", "name": "dns", "node-type": "path" },
// { "type": "child", "name": "route", "node-type": "path" },
// ...
// ]
获取语法帮助
// Get description for /ip/address/add → address argument
const syntax = await fetch(`${base}/console/inspect`, {
method: "POST",
headers: { ...authHeaders, "Content-Type": "application/json" },
body: JSON.stringify({
request: "syntax",
path: "ip,address,add,address", // comma-separated path
}),
}).then(r => r.json());
// Response:
// [{ "type": "syntax", "text": "IP address" }]
路径格式
path 字段使用逗号分隔的片段(而非斜杠):
- 根路径:
""(空字符串) /ip:"ip"/ip/address:"ip,address"/ip/address/add:"ip,address,add"/ip/address/add → address 参数:"ip,address,add,address"
使用 JavaScript 的 Array.toString() 方法时,数组会自然生成这种逗号分隔格式:["ip", "address", "add"].toString() → "ip,address,add"。
树遍历模式
递归遍历整棵树:
async function walkTree(path = [], tree = {}) {
const children = await fetchInspect("child", path.toString());
for (const child of children) {
if (child.type !== "child") continue;
const childPath = [...path, child.name];
tree[child.name] = { _type: child["node-type"] };
// For args, fetch the syntax description — but NOT inside dangerous subtrees
if (child["node-type"] === "arg") {
if (DANGEROUS_PATHS.some(p => childPath.includes(p))) continue;
const syntax = await fetchInspect("syntax", childPath.toString());
if (syntax.length === 1 && syntax[0].text.length > 0) {
tree[child.name].desc = syntax[0].text;
}
}
// Recurse into this child (child enumeration is safe even in dangerous subtrees)
await walkTree(childPath, tree[child.name]);
}
return tree;
}
危险路径——保守跳过策略
在旧版 RouterOS 上,通过 /console/inspect 查询脚本关键字路径的语法或补全信息时,可能会导致 REST 服务死锁。爬虫会保守地跳过包含以下任一名称的子树中的语法查询:
where, do, else, rule, command, on-error
这些是 RouterOS 脚本结构。应将这六个路径视为保守的爬虫跳过策略,而非永恒不变的崩溃规律:在
tikoci/restraml 中进行的实时探测(MikroTik 支持案例 SUP-127641)
确认,只有对 RouterOS ≤7.20.8 上的裸 do 使用 request=syntax/completion 才会导致死锁——其余五个路径在该版本上都会立即返回 []——并发现此问题已在
7.21.4 中修复。(最初关于这六个路径的说法源自顺序探测:一旦
do 卡住服务,之后测试的每个路径看起来都会导致崩溃。)在所有已测试版本上,枚举子节点(child 请求)都是安全的,["do","command"] 这类嵌套路径也同样安全。
保守做法(示例中采用的做法)是在任一祖先路径匹配危险路径时跳过整个参数,并在旧版或未知版本上为每个请求设置较短的超时时间。实际的 rest2raml.js 实现采用了这种模式。
const DANGEROUS_PATHS = ["where", "do", "else", "rule", "command", "on-error"];
CLI 命令 → REST 动词映射
RouterOS CLI 命令在 REST API 中映射为 HTTP 动词:
| CLI 命令 | HTTP 动词 | REST URL 模式 | 说明 |
|---|---|---|---|
get(打印) | GET | /rest/ip/address | 返回所有条目的数组 |
get(单个) | GET | /rest/ip/address/*1 | 根据 ID 返回单个条目 |
add | PUT | /rest/ip/address | 创建新条目(不是 POST!) |
set | PATCH | /rest/ip/address/*1 | 更新现有条目 |
remove | DELETE | /rest/ip/address/*1 | 根据 ID 删除条目 |
print | POST | /rest/ip/address/print | 操作风格(也可通过 GET 使用) |
| 其他命令 | POST | /rest/path/command | 操作,例如重启、清空等 |
关键要点: REST 中的 PUT = 创建,PATCH = 更新。这与许多 REST API 约定相反,后者通常使用 PUT 进行幂等更新,使用 POST 创建。
RAML/OpenAPI 架构生成
根据命令树生成 API 架构时:
- 遍历命令树,收集所有路径、命令和参数
- 对于每个
cmd节点:get→ 生成GET /path(列表)和GET /path/{id}(单个)add→ 生成PUT /path,请求体根据参数生成set→ 生成PATCH /path/{id},请求体根据参数生成remove→ 生成DELETE /path/{id}- 其他命令 →
POST /path/command
- 对于命令下的每个
arg,生成请求体属性或查询参数 - 语法查询中的
desc字段会成为描述
.proplist 和 .query 参数
所有基于 POST 的命令端点都接受两个特殊参数:
.proplist— 选择要返回的属性(类似 SQL SELECT).query— 筛选表达式数组(类似 SQL WHERE)
这些是 RouterOS REST API 的约定,并非标准 REST 模式。
输出格式
inspect 树可转换为多种架构格式:
inspect.json(原始输出)
递归调用 /console/inspect 返回的原始树。每个节点包含:
{
"address": {
"_type": "path",
"add": {
"_type": "cmd",
"address": { "_type": "arg", "desc": "IP address" },
"interface": { "_type": "arg", "desc": "Interface name" }
},
"set": { "_type": "cmd", ... },
"print": { "_type": "cmd", ... }
}
}
RAML 1.0(schema.raml)
转换为 RAML 1.0 资源/方法表示法:
/ip:
/address:
get:
queryParameters: ...
responses: ...
put:
body:
application/json:
properties:
address: { type: any, description: "IP address" }
/{id}:
get: ...
patch: ...
delete: ...
OpenAPI 3.0(openapi.json)
根据同一 inspect 树生成的标准 OpenAPI 3.0 架构(7.21.1+)。
inspect.json 数据模型
每个版本的 inspect.json 都是该 RouterOS 版本命令树的权威事实来源。它包含:
- CLI 层次结构中的所有可导航路径
- 每个路径层级中所有可执行的命令
- 每个命令的所有参数
- 参数的语法描述
工具可以离线解析 inspect.json,无需连接到运行中的路由器 — 设置 INSPECTFILE 环境变量后,架构生成器会使用缓存文件,而不是查询路由器。
使用命令树的常见模式
查找路径中的命令
// Given an inspect.json node for /ip/address
const node = inspectData.ip.address;
// Commands are children with _type === "cmd"
const commands = Object.entries(node)
.filter(([key, val]) => val._type === "cmd")
.map(([key]) => key);
// → ["add", "set", "remove", "get", "print", "export", ...]
查找命令的参数
// Arguments of /ip/address/add
const addCmd = inspectData.ip.address.add;
const args = Object.entries(addCmd)
.filter(([key, val]) => val._type === "arg")
.map(([key, val]) => ({ name: key, description: val.desc }));
// → [{name: "address", description: "IP address"}, ...]
遍历目录
// Directories and paths (navigable children)
const children = Object.entries(node)
.filter(([key, val]) => val._type === "dir" || val._type === "path")
.map(([key]) => key);
性能说明
- 在运行中的路由器上遍历完整命令树需要数分钟(数千个 HTTP 请求,每个请求都是对
/console/inspect的单独 POST)。借助 KVM 加速,CHR 的响应很快,但大量顺序请求累积起来仍需不少时间。 - 每次
/console/inspect调用都是单独的 HTTP 请求 — 没有批处理 API - 开发/测试时使用
INSPECTFILE,避免重复进行实时查询 - 命令树与版本相关 — 不同 RouterOS 版本的命令集不同
- 额外的软件包(container、iot、zerotier 等)会增加其他命令树分支
附加资源
- MikroTik 的新版手册网站提供可浏览的 CLI 参考,网址为
https://manual.mikrotik.com/docs/CLI%20Reference/。它按软件包/路径展示命令菜单、
标志、参数名称和类型(例如,
/docs/CLI%20Reference/system/ip/address)。可将其视为/console/inspect的 人类可读版配套资料;如果它与运行中的路由器不一致,应以该路由器上的/console/inspect为准。 - 有关 REST API 的详情:请参阅
routeros-fundamentals技能 → REST API 模式 - 有关运行 CHR 以进行查询:请参阅
routeros-qemu-chr技能 - 有关 /app YAML 格式(这是 7.22+ 的树形结构中可见的一项功能):请参阅
routeros-app-yaml技能