MCP: serve tools from the API process over Streamable HTTP at /mcp

- netx_api/mcp_remote: ops / topology / bizMonitor tools, Bearer auth,
  per-tool RBAC filtering, ?groups= filter, readOnlyHint and group meta.
- Mount the official MCP Streamable HTTP app at /mcp (stateless);
  AuthAuditMiddleware is pure ASGI and skips /mcp.
- Let uvicorn chunk MCP responses; some Windows LSPs rewrite
  Content-Length replies on */mcp* paths into broken chunked framing.
- NETX_MCP_ALLOWED_HOSTS opts into DNS-rebinding checks (off by default
  so LAN and reverse-proxy hosts work; Bearer is the gate).
- Remove the three stdio Python MCP packages; tests moved to /mcp,
  including real-TCP framing regressions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
oliver 2026-10-16 21:52:08 +08:00
parent 918ece86c3
commit 52679a7cfc
150 changed files with 2367 additions and 2812 deletions

View file

@ -1,249 +1,64 @@
# netx MCP — 安装与更新
# netx 远程 MCP(Streamable HTTP)
netx 通过 **stdio MCP** 把告警/网元能力暴露给 Cursor、Claude Desktop、oclaw 等宿主。MCP 进程只做 HTTP 客户端,**不直连数据库**;数据来自已运行的 netx REST API。
netx API 在同一端口(默认 `8890`)提供 `/mcp` 端点,使用 MCP **Streamable HTTP** 传输。客户端(Claude Code、Codex、Cursor、DSH netxops)只需配置 URL 与 Bearer token,**无需安装 Python MCP 包**。
## 架构
```
MCP 宿主 (Cursor / oclaw) → stdio netx_mcp → HTTP NETX_API_URL → netx API
MCP 宿主 → HTTP http://127.0.0.1:8890/mcp → netx_api.mcp_remote(进程内工具)
```
| 组件 | 部署位置 | 说明 |
|------|----------|------|
| **netx API** | 本机或远端服务器 | `python -m netx_api.main`,默认 `http://127.0.0.1:8890` |
| **netx-mcp** | 与 MCP 宿主同机 | `pip install` 轻量包,仅需 Python + httpx |
工具分组(查询参数 `groups`):
---
| groups | 工具数 | 说明 |
|--------|--------|------|
| `ops` | 15 | 告警 / 网元 / 只读 CLI |
| `topology` | 16 | 拓扑树 / 画布 / Fabric / 布局 |
| `bizMonitor` | 8 | 割接与业务状态只读 |
| (省略) | 39 | 当前 token 有权限的全部工具 |
## 前置条件
示例:`/mcp?groups=ops,bizMonitor`
1. **netx API 已启动**且可访问:
## 鉴权
```powershell
curl http://127.0.0.1:8890/health
```
- Header:`Authorization: Bearer <token>`
- 接受 MCP 令牌(`NETX_AUTH_MCP_TOKEN_FILE`,bootstrap 写入)或用户 JWT
- 无 token / 无效 → HTTP **401**
- `tools/list` 只列出当前 token 有 scope 的工具;`tools/call` 再按 RBAC 校验
2. **Python 3.11+**(与 MCP 宿主使用的 `python` 一致)。
## 客户端配置
3. 远端 API 时:记下 base URL(无尾部 `/`),例如 `http://10.0.0.5:8890`。
见根目录 [`mcp.all.json`](../mcp.all.json)。Claude Code 示例:
---
## 1. 安装 `netx-mcp` 包
在 **运行 MCP 的那台机器**上执行(不必安装整套 netx 服务依赖):
```powershell
cd D:\project\chatgpt\netx
pip install -e ./packages/netx-mcp
```bash
claude mcp add --transport http netx http://127.0.0.1:8890/mcp \
--header "Authorization: Bearer <token>"
```
验证(**必须用 oclaw 启动时会调用的同一个 `python`**,见下方说明):
仅 ops:
```powershell
where python
python -c "import netx_mcp; print('ok')"
python -m netx_mcp
```text
http://127.0.0.1:8890/mcp?groups=ops
```
若你在 A 终端里 `import netx_mcp` 成功,但 oclaw Admin 安装/Health 无反应或失败,多半是 **pip 装到了另一个 Python**。请用 oclaw 实际用的解释器安装:
## 语言
```powershell
# 把路径换成 where python 的第一条,或 oclaw 服务 venv 里的 python.exe
C:\Path\To\Same\python.exe -m pip install "git+https://github.com/hansjone/netx.git#subdirectory=packages/netx-mcp"
C:\Path\To\Same\python.exe -c "import netx_mcp; print('ok')"
```
- 环境变量 `NETX_LANG=zh|en`,或请求头 `Accept-Language`
- 工具 `description` / `inputSchema` 与历史 Python 包一致;`en` 时 API 查询附加 `lang=en`
从 GitHub 安装(无本地仓库时,**子目录必须是 `packages/netx-mcp`**):
## 安全
```powershell
pip install "git+https://github.com/hansjone/netx.git#subdirectory=packages/netx-mcp"
```
- `execManagedNe` 等写工具带 `annotations.readOnlyHint=false`
- CLI 硬门(`cli_guard`):仅单行 `show`/`display`,拒绝 shell 与破坏性命令
- 异步 exec / layout job:先返回 job id,再用轮询工具查询
开发者在 netx 仓库根目录也可 `pip install -e .`(含 API);MCP 仍推荐只装 `packages/netx-mcp`。
## 版本
---
`initialize.serverInfo.version` 使用 netx 包版本(`pyproject.toml` / `netx-ops`)。
## 2. 环境变量
无状态模式下不主动推送 `notifications/tools/list_changed`;客户端应在需要时重新 `tools/list`。
| 变量 | 必填 | 默认 | 说明 |
|------|------|------|------|
| `NETX_API_URL` | 否 | `http://127.0.0.1:8890` | netx REST 根地址,可指向远端 |
| `NETX_API_TOKEN` | 否 | 空 | Bearer;空则自动读 `data/auth/mcp_token`(API 首次启动生成) |
| `NETX_MCP_TOKEN_FILE` | 否 | `data/auth/mcp_token` | 默认 token 文件路径 |
| `NETX_LANG` | 否 | `zh` | `zh` / `en`,影响 API 文案 |
| `NETX_NE_EXEC_MAX_COMMANDS` | 否 | `5` | `execManagedNe` 单次最多命令数(硬上限 50);API 与 MCP 需同设 |
| `NETX_NE_EXEC_ASYNC_MIN_NES` | 否 | `4` | 批量 NE 数 ≥ 此值时自动走后台 job(`0`=仅 `async=true`) |
| `NETX_NE_EXEC_JOB_DIR` | 否 | `data/ne_exec_jobs` | 后台 exec job 状态目录 |
| `NETX_NE_EXEC_JOB_MAX_CONCURRENT` | 否 | `3` | 同时运行的 exec job 上限 |
## 相关文档
本机默认端口时 **可不设任何变量**。启用登录后,先启动一次 netx API,会生成 `data/auth/mcp_token`;MCP 会自动带上该 token。若要把 token 写进 Cursor 配置:
```json
"NETX_API_TOKEN": "nxt_从文件复制的内容"
```
---
## 3. 在 Cursor / Claude Desktop 中配置
复制仓库中的 [`mcp.json`](../mcp.json)(或 [`packages/netx-mcp/mcp.json`](../packages/netx-mcp/mcp.json))到客户端 MCP 配置,例如 Cursor:`.cursor/mcp.json`。
```json
{
"mcpServers": {
"netx": {
"command": "python",
"args": ["-m", "netx_mcp"],
"env": {
"NETX_API_URL": "http://127.0.0.1:8890",
"NETX_LANG": "zh",
"PYTHONIOENCODING": "utf-8",
"PYTHONUTF8": "1"
}
}
}
}
```
(可选)显式设置 `"NETX_API_TOKEN": "nxt_..."`;不设则读仓库/`cwd` 下的 `data/auth/mcp_token`。
保存后 **重启 Cursor/客户端**,使 MCP 子进程重新拉起。
---
## 4. 在 oclaw Admin 中安装
与 Cursor **同一份** `mcpServers` JSON 即可,无需转成别的格式。
1. 完成上文 **§1**(用 **oclaw 同机同一个 `python`** 安装 `netx-mcp`)。
2. Admin → MCP → 粘贴 `mcp.json` 全文 → 点击 **Install from JSON**(安装状态在下方一行小字)。
3. **Health** → **Sync Tools**(完整 scope 下应看到 **15** 个工具)。
4. 在 **MCP 专家绑定** 中为 ops 专家勾选 `server_id=netx`。
更细的 oclaw 说明(双轨内置工具、锚点注入等)见 oclaw 仓库:
`oclaw/docs/NETX_MCP_INTEGRATION.md`。
可选:oclaw 专用字段展开版 [`mcp_install_payload.json`](../mcp_install_payload.json)(与 `mcp.json` 等价)。
**Skills(真源)**:仓库根 [`skills/`](../skills/README.md) — `netx-ops` / `netx-topology` (ops = NMS + managed CLI)。MCP 包不再带 skills 镜像;Cursor 直接指 `netx/skills/`。dsh-netxops 发版前可 `sync-skills-from-netx.ps1`。oclaw 旧 playbook 不再维护。
**拓扑画布 MCP**(独立安装/绑定)见 [`MCP_TOPOLOGY.md`](./MCP_TOPOLOGY.md)(`server_id=netx-topology`)。
---
## 5. 暴露的工具
模型面使用通用 **Nms** 命名(当前适配器仍为 zte-ume,HTTP 仍 `/v1/ume/*`)。**0.3.0** 起不再暴露 `*Ume*` 工具名。
| 类别 | 工具名 |
|------|--------|
| NMS 告警 | `queryNmsAlarms`, `aggregateNmsAlarms`, `runNmsDiagnostics` |
| NMS 网元 | `queryNmsNeInventory`, `getNmsNe` |
| NMS 原始/SQL | `queryNmsAlarmsRaw`, `aggregateNmsAlarmsRaw`, `listNmsAlarmFields`, `sqlQueryNms` |
| common(CLI + 路径) | `listManagedNe`, `getManagedNe`, `execManagedNe`, `getNeExecJob`, `listCliTargets`, `findTopologyPaths` |
参数使用 `nms_ne_id` / `nms_ne_ids`,旧 `ume_*` 模型参数已删除;`getManagedNe` 只接受 `ne_id`。拓扑 Fabric / 画布工具在 **[`netx-topology-mcp`](./MCP_TOPOLOGY.md)**。oclaw 中名称带前缀:`mcp__netx__<toolName>`;DSH:`netx__<toolName>`。
**业务监控 / 割接分析**:独立 [`netx-bizmonitor-mcp`](./MCP_BIZMONITOR.md) 提供 8 个工具,需 `biz-monitor:read`;netxops 的 bizMonitor 组使用相同 stem。三组统一配置见根 [`mcp.all.json`](../mcp.all.json),不需要安装 netxops。
**CLI 安全边界**:独立 MCP 强制单行只读 `show` / `display`,可用只读输出过滤;配置、shell 操作符、重定向和多行脚本在 HTTP 前拒绝。后端开放 exec_policy 或 capability 的脚本提示也不能解除这个 MCP 门禁,Linux/RouterOS 原生 CLI 当前未暴露。异步 job_id 仅表示开始,按 poll_after_ms 有界轮询;terminal 后检查 result.ok,批量还须检查 summary.failed 和每条 results[].ok,不能只凭作业 succeeded 宣称全部成功。
---
## 6. 更新 MCP
MCP 代码在 **`packages/netx-mcp`**(版本见 `packages/netx-mcp/pyproject.toml`)。更新步骤:
### 6.1 重装 `netx-mcp` 包
**远程 MCP 机 / 无本地 netx 仓库**(不必 `git pull`,每次更新执行一条即可):
```powershell
pip install --upgrade "git+https://github.com/hansjone/netx.git#subdirectory=packages/netx-mcp"
```
**本机有 netx 仓库(开发)**:
```powershell
cd D:\project\chatgpt\netx
git pull
pip install -e ./packages/netx-mcp
```
确认版本(可选):
```powershell
pip show netx-mcp
```
### 6.2 重启 MCP 宿主
| 宿主 | 操作 |
|------|------|
| **Cursor / Claude** | 完全退出客户端后重开,或重载 MCP |
| **oclaw** | 重启 gateway/主进程;Admin 对 `netx` 再点 **Health** → **Sync Tools** |
仅改 `NETX_API_URL` 等 env 时,同样需重启 MCP 子进程(或 oclaw 整进程)。
### 6.3 无需改配置的情况
- 只改 **netx API** 业务逻辑、未改 MCP 工具名/参数:重装 `netx-mcp` 可选;API 部署后 MCP 自动走新 API。
- 改了 **工具列表或 JSON schema**:必须重装 `netx-mcp` 并在宿主 **Sync Tools**。
### 6.4 从旧入口迁移
| 旧方式 | 新方式 |
|--------|--------|
| `python -m netx_api.mcp` | `python -m netx_mcp`(唯一 Python 模块入口) |
| `NETX_MCP_MODE=db` 直连库 | 已废弃;使用 HTTP 模式 |
旧入口 `netx_api.mcp`、`netx_api/mcp_server.py` 和 `NETX_MCP_MODE=db` 已删除。请安装独立 `netx-mcp` 包并迁移配置;不再保留兼容委托。旧 DB 模式独有的导入告警查询/聚合/批次/诊断、同步触发、历史告警查询仍有 HTTP API,但不属于当前 ops 15 工具。
---
## 7. 自检
**API:**
```powershell
curl http://127.0.0.1:8890/health
```
**MCP 工具列表(需已 `pip install -e packages/netx-mcp`):**
```powershell
cd D:\project\chatgpt\netx
python -m pytest packages/netx-mcp/tests/test_mcp_http.py -q
```
**手动 stdio(PowerShell 示例):**
```powershell
$env:NETX_API_URL = "http://127.0.0.1:8890"
$p = Start-Process python -ArgumentList "-m","netx_mcp" -RedirectStandardInput pipe -RedirectStandardOutput pipe -NoNewWindow -PassThru
# 向 stdin 写入一行 JSON-RPC initialize / tools/list(见 packages/netx-mcp/tests)
```
---
## 8. 常见问题
| 现象 | 处理 |
|------|------|
| `ModuleNotFoundError: netx_mcp` | 用 **oclaw 同机同一个 `python.exe`** 执行 pip 安装(见 §1 验证) |
| **oclaw Admin 点安装「没反应」** | 1) 必须点 **Install from JSON**(只粘贴不点按钮无效)<br>2) 看安装区下方 **install status** 一行字(应出现 `[json] installing...` 或错误)<br>3) 浏览器 F12 → Console 是否有红色报错<br>4) 确认 JSON 为完整 `mcpServers` 或 `mcp_install_payload.json` 格式 |
| oclaw preflight `mcp_python_module_missing` | `python` 在 PATH 里但无 `netx_mcp`;对该 python 执行 git+pip 安装 |
| 工具调用连不上 API | 检查 `NETX_API_URL`、防火墙、远端 API 是否启动 |
| Windows 乱码 / JSON 解析失败 | 配置里保留 `PYTHONIOENCODING=utf-8`、`PYTHONUTF8=1`(见 `mcp.json`) |
| oclaw 工具数为 0 | Admin **Health → Sync Tools**;确认 `server_id=netx` 已绑定 ops 专家 |
| 旧脚本路径或模块不存在 | 安装 `packages/netx-mcp`,配置改为 `"args": ["-m", "netx_mcp"]` |
---
## 相关文件
| 文件 | 用途 |
|------|------|
| [`packages/netx-mcp/`](../packages/netx-mcp/) | MCP 实现与 `pyproject.toml` |
| [`mcp.json`](../mcp.json) | Cursor / oclaw 粘贴用配置 |
| [`mcp_install_payload.json`](../mcp_install_payload.json) | oclaw 字段展开版(可选) |
| [`packages/netx-mcp/README.md`](../packages/netx-mcp/README.md) | 子包速查 |
- [MCP_TOPOLOGY.md](./MCP_TOPOLOGY.md) — topology 组工具说明
- [MCP_BIZMONITOR.md](./MCP_BIZMONITOR.md) — bizMonitor 组工具说明

View file

@ -1,41 +1,9 @@
# 割接与业务监控 MCP
# netx MCP · bizMonitor 组
`netx-bizmonitor-mcp` 独立提供 bizMonitor 的 8 个只读工具。不安装 netxops 也能读取已有项目、批次、看板、红票、差异和采集原文。它只依赖 Python 3.11+ 与 httpx,通过 HTTP 使用正在运行的 netx。
割接与业务状态只读工具(8 个),通过同一远程端点提供:
## 三组一起配置
在运行 MCP 客户端的 Python 环境分别安装:
```powershell
python -m pip install -e ./packages/netx-mcp
python -m pip install -e ./packages/netx-topology-mcp
python -m pip install -e ./packages/netx-bizmonitor-mcp
```text
http://127.0.0.1:8890/mcp?groups=bizMonitor
```
将根 [mcp.all.json](../mcp.all.json) 的三个 server 放入客户端 MCP 配置。也可以不安装包,分别将该包的 `src` 绝对路径设为对应 server 的 `PYTHONPATH`。三个模块分别为 `netx_mcp`、`netx_topology_mcp`、`netx_bizmonitor_mcp`;console scripts 属于各独立包,安装 API 本体不会提供一个缺少依赖的 MCP 假入口。
统一设置 `NETX_API_URL`。凭据优先用 `NETX_API_TOKEN`,否则读取 `NETX_MCP_TOKEN_FILE`,再尝试工作目录 `data/auth/mcp_token`。客户端工作目录不确定时,给 `NETX_MCP_TOKEN_FILE` 配置绝对路径。不要把真实令牌提交到仓库。
业务组需 `biz-monitor:read`;ops 需 `alarms:read`、`ne:read`、`ne:exec`,SQL 另需 `sql:query`;topology 读取需 `ne:read`,建图/布局需 `ne:write`。默认 bootstrap token 不含 SQL 和拓扑写权限,因此工具数量会按 scope 缩减;有完整权限时分别列出 **15、16、8** 个工具。
## 入口与证据链
MCP 使用裸名;netxops 使用 `netx__` 加相同名称。
1. `listBizMonitors`:无 ID 的入口,取得 project_id/task_id。
2. `getBizMonitorContext`:读取模板、归一化、端口映射、两侧任务和基线。
3. `listBizMonitorBatches(project_id,include_runs=true)`:取得割接批次与 run_id;也可 `kind=runs,batch_id=...`。
4. `getBizMonitorBoard(batch_id)`:检查完成状态、缺失数据和统计。
5. `listBizMonitorReds(project_id)` / `getBizMonitorDiffs(run_id,color=red)`:逐页取得红项和证据。`q` 自动映射后端 `kw`。
6. `getBizCollectBatch`:采集批次的命令目录;需要时由 `listBizMonitorBatches(task_id)` 找采集批次。
7. `getBizCollectCommandRaw(batch_id,command_id)`:两侧原文取证。这里使用采集 batch_id,不能传割接 batch_id。
`old_key/new_key` 保留设备原始 A/B;`match_*` 和归一化映射用于内部配对。原文按需获取,可能很大;凭据会脱敏,不把内部匹配键冒充设备原始端口。
本组不会创建任务/模板、启动采集、执行 evaluate、关闭红票。没有批次时不能得出“割接通过”。完整行为和参数见 [包 README](../packages/netx-bizmonitor-mcp/README.md) 与 [skill](../skills/biz-monitor/netx-biz-monitor/SKILL.md)。
## 可复现验收
`scripts/mcp_acceptance.py` 启动当前 worktree 的真实 stdio MCP,记录去敏 JSONL;仅允许本机 8890 或隔离样例 8891。工具源目录可用 `--source-root` 显式指定,不会静默导入主仓库的 editable 包。
没有现场割接数据时,可用 `scripts/seed_mcp_acceptance.py` 在 `agent-wt/netx-mcp-lead/artifacts/` 的新 acceptance SQLite 文件生成明确标注的离线样例,真实运行业务评价,再通过本地 API/MCP 验证读取链路。样例不代表现场故障;不要将样例库替换应用库。脚本拒绝覆盖、外部路径、网络连接和监听端口。
鉴权与总览见 [MCP.md](./MCP.md)。需要 `biz-monitor:read`。全部工具 `readOnlyHint=true`。

View file

@ -1,141 +1,11 @@
# netx Topology MCP — 安装与更新
# netx MCP · topology 组
与告警/CLI 的 [`netx-mcp`](./MCP.md) **分开**的 stdio MCP,只提供 **拓扑树、画布(views)、Fabric 边/邻接**。Agent 可只装本包,或与 `netx` 并存。
拓扑树、画布(views)、Fabric 与布局工具,通过同一远程端点提供:
```
MCP 宿主 → stdio netx_topology_mcp → HTTP NETX_API_URL → /v1/topology/*
```text
http://127.0.0.1:8890/mcp?groups=topology
```
| 组件 | 说明 |
|------|------|
| **netx API** | 需已启动,拓扑数据在服务端 |
| **netx-topology-mcp** | 轻量 HTTP 客户端,与宿主同机 |
鉴权与总览见 [MCP.md](./MCP.md)。需要 `ne:read` / `ne:write` 等 scope(写工具含新建文件夹、改节点、布局写入等)。
---
## 1. 安装
```powershell
cd D:\project\chatgpt\netx
pip install -e ./packages/netx-topology-mcp
python -c "import netx_topology_mcp; print('ok')"
python -m netx_topology_mcp
```
从 GitHub:
```powershell
pip install "git+https://github.com/hansjone/netx.git#subdirectory=packages/netx-topology-mcp"
```
环境变量与 [`MCP.md`](./MCP.md) 相同:`NETX_API_URL`、`NETX_API_TOKEN` / `data/auth/mcp_token`、`NETX_LANG`。
---
## 2. Cursor / oclaw 配置
独立服务器 id:`netx-topology`(不要与 `netx` 混在同一个 command 里)。
```json
{
"mcpServers": {
"netx-topology": {
"command": "python",
"args": ["-m", "netx_topology_mcp"],
"env": {
"NETX_API_URL": "http://127.0.0.1:8890",
"NETX_API_TOKEN": "nxt_your_key_with_ne_write",
"NETX_LANG": "zh",
"PYTHONIOENCODING": "utf-8",
"PYTHONUTF8": "1"
}
}
}
}
```
画图必须设 `NETX_API_TOKEN`(网页新建 Key,默认含 `ne:write`)。未设置时回退读 `data/auth/mcp_token`(只读+CLI,**无写工具**)。改 Key 权限后 Sync Tools / 约 45s 内 scopes 会刷新。
样本:[`packages/netx-topology-mcp/mcp.json`](../packages/netx-topology-mcp/mcp.json)。
与告警 MCP 并存时,把两个 server 都放进 `mcpServers` 即可;未勾选/未安装的不会加载工具。
oclaw:Install from JSON → Health → Sync Tools(完整 scope 下应看到 **16** 个工具,含 `layoutTopologyView` / `sinkTopologyDualUnits` / `queryTopologyFabricNodes`)→ 专家绑定勾选 `server_id=netx-topology`。
Cursor:改包后若 Sync 工具数不对,请 **禁用/启用** `netx-topology` 或重载窗口(须重启 stdio 进程)。可选在 env 加 `PYTHONPATH=.../packages/netx-topology-mcp/src` 强制最新源码。
配套 Agent Skill(dual_unit 排水 → 扫角/polish → 手拖):[`skills/topology/netx-topology/SKILL.md`](../skills/topology/netx-topology/SKILL.md)。Cursor / oclaw 读 skill 后再调 MCP。
---
## 3. 工具一览
### 目录模型
- 顶级为导航「根」;其下唯一「根图」/ `Root map` 为画布(physical view)。
- 子区域即画布(创建时自动 physical view)。
- 文件夹 `ne_count`:子树去重 Fabric 网元数(来源无关)。
### 读
| 工具 | 作用 |
|------|------|
| `getTopologyTree` | 「根 / 根图 / 子区域」树 + views + `ne_count`(找画布用这个,勿再 listViews) |
| `getTopologyView` | 单图(节点+边+坐标);`detail=summary\|full` |
| `queryTopologyFabricNodes` | Fabric 库存:`mode=summary\|list\|search`(有 `q` 默认 search;list 支持 `region_folder_id`) |
| `classifyTopologyFabricNodes` | **分级打标**:`match`→`tag(level\|role, dry_run)`→`tag`;`level` 为 major.minor(0 外部…3 接入);`role` 预设仍可用。再 `addTopologyViewNodes` 上区域画布。**勿用切片建图** |
| `queryTopologyNeighborhood` | 邻域:compact nodes + `links[]`(NE 对,非端口) |
| `queryTopologyEdges` | 默认 **adjacency**:`links[{a,b,link_count}]`;画布一对网元一条线。`detail=ports` 才给端口行 |
| `analyzeTopologyViewLayout` | 布图验收(只读):`verdict` + score;`score_profile=auto\|default\|eye`(大稀疏斜边→eye);贴边=节点命中∪hits/link;util 甜区下限 0.08;`detail=structure` / `hotspots\|blocks\|both` |
### 写(只动画布,不污染 Fabric)
| 工具 | 作用 |
|------|------|
| `createTopologyFolder` | **唯一建图入口**:顶级→「根」+「根图」;根图下→**子区域**;返回 `view_id` |
| `addTopologyViewNodes` | **优先**传 `keyword`/`role`/`vendor`/`link_status` + `limit`/`offset`,由 API 筛选落点;也可 `fabric_node_ids`。拒绝 managed/UME。返回摘要。 |
| `removeTopologyViewNodes` | 筛选或 id 从画布移除(不删 Fabric),摘要 |
| `sinkTopologyDualUnits` | 根图 dual_units 分批沉入子区域;默认 `layout_batch`→`layout_dual_unit`→块扫挂载;日常禁 `until_empty` |
| `copyTopologyViewNodes` | 一键克隆画布成员+坐标到另一画布(`clear_target` 可选);源画布不动,测沙箱用 |
| `layoutTopologyView` `move_nodes` | 指定 `fabric_node_ids` 从 `source_view_id`→`view_id`(默认同移出源);对调两 view 回迁;`park=true` 扫角停靠 |
| `updateTopologyViewPositions` | **优先** `layout=grid\|offset\|stack` + 筛选,API 自己挪点;`positions[]` 仅少量微调 |
| `layoutTopologyView` | **布图/局部修**:公开 `action=layout\|layout_dual_unit\|orbit_sweep\|clear_edge_hits\|pull_far_chains\|compact_bbox\|polish_crossings\|fix_overlaps\|untangle\|straighten_channels\|level_bands\|move_nodes`;recipe 仅 `compact\|corridor\|rings\|unstick`。眼 sink 主路径:`until_limit(crossing→total)` → `clear_edge_hits` → `pull_far_chains` → `compact_bbox` → 手拖;禁对眼跑 polish/fix_overlaps/untangle/round。stall 且 moved≈0 即算法到头。巨图 apply 可能返回 `job_id` → 轮询 `job_status` / `job_cancel`。Job=**子进程+`data/runtime/layout_jobs` 落盘**。`mode=preview\|apply` |
| `projectTopologyNeighbors` | 投影**已有** LLDP 邻居到画布;区域画布务必传 `region_folder_id`,读 `out_of_region_skipped` |
**推荐流水线:** `getTopologyTree` →(可选)`createTopologyFolder` 取 `view_id` → `addTopologyViewNodes` →(可选)邻居投影 / 布局。
**硬顶 2000:目标 ≤2000 画一张图即可**;physical 根图/区域默认 `max_nodes=2000`,custom 仍按角色软顶。Agent 默认 `links[]` 邻接;布图按邻接与 Skill 中的经验摆点。
**刻意不提供:** 手工建链、`populate`、删 Fabric / 删整图。
写操作需要 token 具备 `ne:write`;只读为 `ne:read`。
**权限怎么开:** 网页 **系统 → API Key**(`/api-keys`)。新建默认已含 `ne:write`(可关掉);已有 Key 在 **操作 → 改权限**。把明文配到 `NETX_API_TOKEN` 后重启 MCP / Sync Tools。仓库自动生成的 `data/auth/mcp_token` 仍是只读+CLI(无写),需要画图请另建 Key 或改权限。
**前端能否看着画:** 在拓扑页左侧树或右侧浏览区点 **「实时同步」**(默认关闭;**不需要先打开某张图**)。开启后树约每 5 秒、已打开的图约每 3 秒拉取,可看到 MCP 新建区域/画布并往上加点。有未保存本地拖动时不会覆盖你的编辑。
---
## 4. 与 netx-mcp 的关系
| 包 | server_id | 职责 |
|----|-----------|------|
| `netx-mcp` | `netx` | NMS 告警、网元和只读 CLI(**15** 工具) |
| `netx-topology-mcp` | `netx-topology` | topology 组 15 工具及额外 sinkTopologyDualUnits(共 **16**) |
| `netx-bizmonitor-mcp` | `netx-bizmonitor` | 割接 / 业务状态只读分析(**8** 工具) |
显式后台保存:`layoutTopologyView(view_id=...,action=layout,recipe=compact,mode=apply,params={background:true})`。用 `layoutTopologyView(action=job_status,params={job_id:...})` 轮询,成功后 getTopologyView 回读。任务是 MCP 本地子进程与落盘 JSON,没有 HTTP layout job API;跨目录或机器不能轮询同一 job。失败后可能已有部分写入,先回读,不盲重试。布局保存成功与评分诊断分别判断;评分校准另行安排,本轮不改算法/阈值。
`queryTopologyEdges` 已从 `netx-mcp` **迁出**到本包,避免重复。
---
## 5. 更新
```powershell
cd <netx 仓库>
git pull
pip install -e ./packages/netx-topology-mcp
```
然后重启 MCP 宿主,并 Sync Tools。
布局算法在 netx 进程内执行(原 `netx-topology-mcp` 逻辑已迁入 `netx_api/mcp_remote/topology/`)。异步布局 job 仍返回 job id 后轮询。