Skip to main content
Glama

ptmcp — 后台操控 Cisco Packet Tracer 的 MCP(面向 agent)

在不抢焦点、不弹任何对话框的前提下,让 agent 建拓扑、把链路画成绿的、跑 CLI、存 .pkt。 在 PT 8.0.0.0212 上实测通过;设计目标是一台陌生电脑上装好 PT 后,agent 拿到本仓库就能干活。

pip install -e .
python -m ptmcp bootstrap      # 安装 PT 侧脚本模块并自检
python -m ptmcp status

MCP 客户端接入:

claude mcp add --scope user --transport stdio ptmcp -- python -m ptmcp serve

它解决的问题

症状

原因

本仓库的做法

链路一直是红的

交换机↔交换机用直通线,PT 判 MDI/MDX 错

catalog.pick_cable() 自动选交叉线(8101),主机/路由接交换机用直通(8100)

脚本引擎卡死、请求全超时

fileSaveAs() 覆盖已存在文件 → "Overwrite File?" 模态框阻塞引擎

pt.save() 先删目标、存到唯一临时名、再 os.replace

fileNew() 弹"是否保存"

默认带提示

统一用 fileNew(false)

addDevice 后链路/端口对象失效

fileNew() 会让缓存的工作区句柄失效

先 fileNew(false),再取工作区句柄

拿不到 CLI 输出

PT 的 IPC 不暴露终端回显(getCurrentHistory().history 只有命令串)

pt_ping() 在设备 CLI 里真跑 ping(人打开 CLI 能看到 Reply),可达性用 pt_verify_l2() 做机器判定

Related MCP server: Packet Tracer MCP Server

工具(MCP)

tool

作用

pt_status()

画布上的设备/链路 + 引擎是否在轮询

pt_new()

清空画布(无对话框)

pt_build(spec_json, save_path)

建图:设备 / 链路(自动选线)/ IP / CLI,并逐条报链路 up/down

pt_wait_links(spec_json, timeout_s)

轮询到全部链路 up(STP 收敛需要几秒到几十秒)

pt_cli(device, commands)

在 IOS 设备上执行 CLI

pt_ping(source, target_ip, count)

在设备 CLI 里执行 ping

pt_save(path)

存 .pkt,永不弹覆盖对话框

pt_run_js(code)

逃生口:直接跑 JS(结尾 throw new Error(text) 回传)

拓扑规格:

{
  "devices": [{"name": "S1", "model": "2950T-24", "x": 260, "y": 140},
              {"name": "PC0", "model": "PC-PT", "x": 150, "y": 400}],
  "links":   [{"a": "S1", "a_port": "FastEthernet0/1",
               "b": "PC0", "b_port": "FastEthernet0"}],
  "ips":     [{"device": "PC0", "port": "FastEthernet0",
               "ip": "192.168.2.1", "mask": "255.255.255.0"}],
  "cli":     {"S1": ["vlan 2", "name VLAN2", "exit"]},
  "save":    "C:\\pt\\lab.pkt"
}

links[].cable 可省略(auto):交换机组之间自动交叉、其余直通。

通道原理

PT 的 Script Module 是唯一能在 PT 进程内跑代码的正规入口。本仓库复用 MCP-Packet-Tracer 的 V5.2.pts (MIT,bootstrap 自动下载并校验 SHA-256)作为 PT 侧的执行器,Python 侧只往文件邮箱写命令:

%LOCALAPPDATA%\packet-tracer-mcp\bridge\req_<id>.js   -> PT 执行
%LOCALAPPDATA%\packet-tracer-mcp\bridge\res_<id>.txt  <- 结果
%LOCALAPPDATA%\packet-tracer-mcp\bridge\alive.txt     <- 心跳

因此 PT 可以最小化、被遮挡、甚至窗口关掉,自动化照跑。

一次性 GUI 步骤

PT 8 没有"静默注册脚本模块"的接口,模块只能通过界面登记一次:

Extensions → Scripting → Configure PT Script Modules → 选中 MCP BUILDER → Settings → On Startup → Start

登记过一次、设为 On Startup 后,之后每次启动 PT 都会自动轮询,agent 全程无需人介入。

已验证 / 未验证

实测(PT 8.0.0.0212):

  • 案例27/28/29 三张拓扑由 JSON 生成并保存;案例28 的 S1–S2 干道自动选交叉线后 allUp: true

  • pt_verify_l2() 读回:S1:FastEthernet0/13 up=true mode=2(trunk) / S2 同

  • pt_save() 全程无对话框(含覆盖场景)

  • PC(PC-PT)没有 getIpcTerminalLine,PC 的 ping 输出读不到;需要机器判定时用 pt_verify_l2(), 需要"看得见的 ping"时用 pt_ping() 把命令打在 IOS 设备 CLI 上

未验证:

  • PT 9.x(API 可能有差异)

  • 无人值守下自动登记脚本模块(PT 无此接口)

致谢

MIT License.

1.1:像 CLI 一样用它

新增的读回层让 agent 不再"盲配"——PT 会把每台设备序列化成 XML,我们从中还原出 CLI 的输出:

python -m ptmcp show S1 run         # show running-config(真配置行)
python -m ptmcp show S1 vlan        # show vlan(VLAN 库:id/name)
python -m ptmcp show S1 vtp         # show vtp status(mode/domain/revision)
python -m ptmcp show S1 interfaces  # 每端口 up/protocol/mode/vlan/native/duplex
python -m ptmcp show S1 mac         # MAC 列表

批量执行(一次往返,复杂脚本也不费时):

python -m ptmcp batch ops.json
[{"op":"cli","device":"S1","commands":["vlan 30","name VOICE","exit"]},
 {"op":"port","device":"S1","port":"GigabitEthernet0/2","mode":1,"access_vlan":30},
 {"op":"ip","device":"PC1","port":"FastEthernet0","ip":"10.0.0.1","mask":"255.255.255.0"},
 {"op":"power","device":"R1","on":true},
 {"op":"rename","device":"S1","to":"CORE"},
 {"op":"module","device":"R1","slot":0,"model":"HWIC-2T"}]

设备与 UI 操作:

python -m ptmcp power R1 off | on       # 物理电源
python -m ptmcp module R1 0 HWIC-2T     # 插模块(自动断电/上电 + skipBoot)
python -m ptmcp rename S1 CORE
python -m ptmcp mode simulation         # logical/physical/realtime/simulation
python -m ptmcp panels --left 0 --bottom 1
python -m ptmcp window maximize
python -m ptmcp clip                    # 读写 PT 剪贴板
python -m ptmcp ui                      # 一次看全 UI 可控项 + 当前状态

MCP 工具同步新增:pt_show / pt_batch / pt_power / pt_rename / pt_add_module / pt_ui_controls / pt_view_state / pt_switch_mode / pt_panels / pt_window / pt_clipboard / pt_status_line。

边界(实测,不猜)

  • 菜单项枚举不到:Qt 的 QMenu 是懒构建,PT 8 里未展开时 count() 返回 0。菜单能做到的事都有等价的脚本入口(面板显隐、模式切换、窗口几何、剪贴板、writeToPT);只有必须"真点一下"的,才交给 computer-use 层。

  • CLI 屏幕文本读不到:getCurrentHistory().history 只有命令串,没有回显;所以读配置走 serializeToXml,读端口状态走 IPC 对象。

  • PC 没有 IOS 控制台,其 ping 只能靠 GUI 的 Command Prompt;机器判定用 pt_verify_l2。

1.2:动手前先预检(把"红链路"挡在建图之前)

python -m ptmcp precheck spec.json          # 只体检,不改画布
python -m ptmcp precheck spec.json --fix    # 顺手把线缆/端口修好

检查项(每一条都来自我们实际踩过的坑):

code

含义

后果

bad-model

型号不在 PT 目录里

addDevice 直接失败

dup-name

设备重名

PT 静默改名,后续按名字全查不到

overlap

两台设备同一个坐标

图标叠在一起

no-device

画布上没这台(live 模式)

连线失败

no-port

端口名不存在

createLink 返回 false,链路凭空消失

port-busy

端口已被占用

同上

bad-cable

交换机↔交换机用了直通线

两端永远 down,图上是红线

--fix 会做两件确定性修复:按设备类型重算线缆,并把冲突/不存在的端口换到同设备下一个空闲端口(修复时会预留端口,避免两条链路又撞到一起)。

实测:

before = 8 issues
after  --fix → 剩余 2 issues(bad-model / dup-name / overlap 属于人必须决策的)
links after fix: S1:FastEthernet0/5 <-> S2:FastEthernet0/5  cable=cross
                 S1:FastEthernet0/6 <-> S2:FastEthernet0/6  cable=cross

pt.build() 现在会自动先跑 precheck(新建画布时跳过 live 检查,否则会误报),报告里带 precheck.ok。

1.3:路由配置读回(配置级)

python -m ptmcp routing R1
{"protocols":[{"proto":"ospf 1","lines":["log-adjacency-changes",
  "network 10.1.0.0 0.0.255.255 area 0","network 10.0.0.0 0.0.0.3 area 0"]}],
 "static_routes":[], "interfaces":["10.1.2.1 255.255.255.0", "..."]}

这里有一条硬边界(本轮实测确认)

把路由器 XML 全量 dump 出来查过:里面只有 ENABLED_ROUTER,没有路由表、没有 OSPF 邻居、没有 trunk allowed-vlan、没有 STP 状态。 这些是 IOS 进程里的动态状态,PT 只把它们送到 CLI 屏幕,而屏幕文本读不到。所以:

  • 能读:running-config、VLAN 库、VTP 状态、端口 up/proto/mode/vlan/native、MAC、路由配置

  • 读不到:show ip route 的学到的路由、show ip ospf neighbor、show interfaces trunk 的 allowed 列表、STP 端口角色

  • 需要这些时:pt_cli 把命令打进设备,人/computer-use 看屏幕;机器判定仍用 pt_verify_l2 + pt_ping

1.4:自动修复闭环(assert → repair → re-assert)

python -m ptmcp fixloop spec.json --plan   # 先看会改什么,不动手
python -m ptmcp fixloop spec.json          # 直接闭环修复(默认 3 轮)

它读 spec 里的 asserts,和实时设备状态比对,把能确定修复的偏差翻译成 pt_batch 的 ops,一次往返执行,然后重新断言;直到通过 / 没有可自动修的 / 轮数用完。

能自动修:access vlan、trunk 模式与 native、两端 trunk 一致性、缺失 VLAN、VTP mode/domain、管理性 down 的接口(no shutdown)。 不自动修(列为 manual):same_vlan/diff_vlan 与布线矛盾(这是设计问题)、线缆类型错(要重建)、缺硬件。

实测(故意把 S1 改坏:native 1、f0/2 落回 VLAN1):

改动前 asserts:failed = 3
  - S1:FastEthernet0/2 vlan=1 mode=1, expected access 3
  - S1:FastEthernet0/13 trunk native=1 (need 2)
  - trunk mismatch S1:FastEthernet0/13-S2:FastEthernet0/13: native 1 vs 2

fixloop:
  round 1  7 ops(S1/S2 的 port 属性 + IOS CLI)
  round 2  already correct
  ok=True  applied=1  final: 11 passed / 0 failed

一轮就能收敛,因为修复同时改运行态属性(立即生效,链路判定用)和下发 CLI(重启后仍然成立)。

1.5:全新复杂拓扑实测(园区双核心)

specs/campus.json:15 设备 / 14 链路 —— 2×2911(单臂路由,VLAN10+30 各两条子接口 + OSPF)、4×2960 接入(trunk 上行)、8 台 PC + 1 台服务器。

python -m ptmcp build specs\campus.json
→ precheck: True | allUp: True | asserts: 41 passed / 0 failed | 7.2s
→ saved C:\pt\campus_v1.pkt (70,377 bytes)

用到的断言:4 个 trunk 检查、3 个 access VLAN、3 组同 VLAN 可达、1 组跨 VLAN 隔离、2 个 VLAN 库存在性。

过程中真实遇到并修掉的坑:

  1. 3560-24PS 没有 Gi0/3-4(只有 24×Fa + 2×Gi),我第一版把上行接在不存在的端口上 → 4 条链路凭空消失。precheck 的 live 检查在建新画布时是跳过的,所以没拦住——这是已知盲点,改进方向是给每个型号内置端口表。

  2. PT 8 的 3560 不接受 switchport 系列命令(no switchport 可以,反向不行),trunk 配不上。最后改用 2911 单臂路由(成熟可靠),3560 这类设备在 PT 8 上的 L3 交换配置仍属未打通项。

  3. 3560 的 FastEthernet 端口对象没有 getAdminOpMode(返回 -1),导致断言误判。已加运行配置回退:API 拿不到就用 show run 里的 switchport mode trunk 判定,并修了 _long_if 把 FastEthernet0/1 误转成 FastEthernetstEthernet0/1 的 bug。

已知小瑕疵

pt_save 为规避覆盖弹窗会先写临时名再改名,因此 PT 标题栏显示的是临时文件名(...tmp.pkt),磁盘上的 campus_v1.pkt 是正确的;重开该文件标题即正常。

1.6:完全不依赖 computer use(实测)

之前的说法是"注册模块要手动点一次界面"。这个前提被推翻了:把 V5.2.pts 放进 <PT 安装目录>\extensions\MCPBuilder\ 之后,PT 启动时会自动加载并启动脚本模块, 不需要任何点击。

因此整条链路现在可以零 GUI 自动化跑完:

python -m ptmcp bootstrap        # 找 PT、校验并安装 .pts(纯文件操作)
python -m ptmcp start            # 若未运行则拉起 PT,并等待脚本引擎上线
python tests/headless.py         # 冷启动 → 建图 → 全绿 → 断言 → 存 .pkt → 截图

冷启动实测(先 taskkill 掉 PT,再全自动跑,两次结果一致):

[1/6] killing any running Packet Tracer...
[2/6] launching Packet Tracer (no GUI automation)... engine alive after 4.4s
[3/6] building the topology...        15 devices / 14 links in 72.2s
[4/6] waiting for every link to come up... all_up=True after 0.3s
[5/6] assertions...                   41 passed / 0 failed
[6/6] saving .pkt + screenshot (PrintWindow, no input)...
       C:\pt\campus_v1.pkt (70,313 bytes)  +  C:\pt\campus_v1.png

截图不再是 computer-use:ptmcp/shot.py 用 Win32 PrintWindow(PW_RENDERFULLCONTENT) 把窗口渲染到位图再转 PNG —— 不移动鼠标、不发送按键、不用 UIA,PT 被遮挡也能拍。

这一轮修掉的真实缺陷:冷启动丢配置

冷启动时 vlan 10 / name USERS 会静默丢失:交换机还在 boot,CLI 送进去被丢弃。 现在 build() 分两阶段:先建设备/链路/IP,再 wait_ready() 轮询 isBooting() 直到设备就绪, 最后才下发 CLI。代价是冷启动建图时间从 7s 变成 ~72s(等设备启动),热启动仍是 8s 级。

1.7:同机横评(三个都装了)与一个硬结论

按"把他们全部安装"实测,结论不是"都装上就完事":

ptmcp(本仓库)

cisco-pt-mcp 0.1.6

packet-tracer-mcp 0.9.0

安装

复制 .pts + pip install -e .

复制 .pts + uvx/pip

pip + 它自己的 V5.2.pts

工具数

26

18

61

冷启动无人值守

✅ 4.3s 起来并自动轮询

❌ getNetwork 30s 超时:Packet Tracer plugin did not connect within 30s(要先手点 Extensions 菜单打开它的窗口)

❌ PT 8.0 上 addDevice 报 ReferenceError

PT 8.0 建图

✅ 15 设备/14 链路全绿

未验证

❌

断言 / 自动修复

✅ 8 类 + fix loop

❌

❌

两个 PT 侧扩展不能共存(本项目实测):把 cisco-pt-mcp.pts 放进 extensions\ 后,PT 启动时我们的模块不再轮询(alive.txt 停更、请求超时); 移除它的 .pts 后,我们的引擎 4 秒内恢复。所以在 PT 8.0 上只能二选一。

从他们的开源代码里学到、并已并入本项目的接口

cisco-pt-mcp 的扩展源码是公开的(MIT),里面给出了我们之前没找到的两个入口:

ipc.simulation()                          // setSimulationMode / forward / resetSimulation
                                          // getFrameInstanceCount / getFrameInstanceAt
ipc.appWindow().getUserCreatedPDU().addSimplePdu(src, dst)   // 真·ICMP PDU
ipc.commandLog()                          // getEntryCount / getEntryAt / entry.getCommand()

ptmcp/pdu.py 已实现 ping(source, destination)(建 PDU → 步进仿真 → 判定) 和 command_log(device)。状态:实验性——PDU 能建、能步进、能读到逐帧状态, 但"成功"判定还不稳(PT 的 PDU 列表会累积旧 PDU,resetSimulation() 不清空它们, 导致帧序列混入上一次的流量)。这条要真正可用,还得先解决 PDU 清理。

1.8:把"终端"接进来(并整合竞品的强项)

终端操作(已实测)

python -m ptmcp term S1                       # 提示符/模式/待输入行
python -m ptmcp run  S1 "enable" "conf t" "hostname CORE1" "exit"
python -m ptmcp type S1 "show vlan brief"     # 逐字符输入(等于人打字)

run() 会把每条命令执行后的提示符回传,这就是"命令真的在终端里跑了"的证据:

enable                    :: S1(config)#
configure terminal        :: S1(config)#
vlan 40                   :: CORE1(config-vlan)#
interface FastEthernet0/3 :: CORE1(config-if)#
switchport mode trunk     :: CORE1(config-if)#

同一台设备随后读回:FastEthernet0/3 = access vlan 40、GigabitEthernet0/1 = trunk。 pt_term_println(device, "文本") 还能往终端显示区写一行,人打开 CLI 就能看到。

读不到的仍然是"屏幕回显":TerminalLine 只给 prompt/mode/pending input, 它的 history 对象里不是文本。所以判定一律走设备状态(show run/VLAN/端口)或 PDU。

整合竞品的强项(MIT,直接调用,不重写)

ptmcp/borrow.py 调用 packet-tracer-mcp 的生成器,把它们多年调过的 IOS 配置生成逻辑 接到我们自己的终端执行器上:

from ptmcp.borrow import vlan_cli, subinterface_cli, acl_cli, nat_cli, hardening_cli, port_security_cli
lines = vlan_cli([{"vlan_id": 40, "name": "CAMPUS"}],
                 [{"switch": "S1", "port": "FastEthernet0/3", "vlan_id": 40}],
                 [{"switch": "S1", "port": "GigabitEthernet0/1"}], "2960-24TT")
# -> vlan 40 / name CAMPUS / exit / interface ... / switchport mode access / ...
term.run("S1", ["enable", "configure terminal"] + lines)

没装 packet-tracer-mcp 时会明确报错并给出安装命令,ptmcp 其余功能不受影响。

PDU ping:仍未定论(不吹)

pt_pdu_ping(PC0, PC1) 能建 PDU、能步进、能读到逐帧状态(PC0:buffered/PC0:dropped), 但正例也没通过:把 PC1 也划到 VLAN 2 后仍然 ok=false。所以这条现在是"能下发、能观察、 不能作准",README 里标记实验性,判定仍以 pt_asserts + pt_verify_l2 为准。

1.9:PDU ping 跑通了(并抓到我自己的两个错)

经过三处修正,pt_pdu_ping 现在能给可信结论:

  1. 判定规则:PT 在目的主机上把帧标成 sent,回程才在源主机出现 accepted。 所以成功 = "目的设备上出现过帧" 且 "其后源设备上有 accepted 帧"。

  2. realtime 等待:主机启动与 ARP 都发生在实时时钟里;一进仿真模式时钟就冻结。 必须先在实时模式等到 isBooting()=false,再切仿真步进(否则首帧必丢)。

  3. 模式卫生:ping() 结束一定切回 realtime,否则后续建图/断言全乱。

实测(干净的两主机小实验):

H1 -> H2: ok=true  reached_destination=true got_reply=true
trace: H1:buffered → H1:sent → SW:sent → H2:sent → SW:sent → H1:accepted

它立刻指出了我真实存在的配置问题(这正是它的价值):

PC11 -> PC12 : ok=true   ✅ 同 VLAN
PC21 -> SRV1 : ok=false  ❌ R1:dropped   (服务器在 A2 后面,走 R1 Gi0/2.30)
PC11 -> PC41 : ok=false  ❌ R2:dropped   (跨路由器,OSPF 路径)

SRV1 原本被我配在 10.30.0.0/24,但它实际挂在 R1 Gi0/2 这条链路的 VLAN30 (10.31.0.0/24)——是 spec 写错了,已改成 10.31.0.10/24。跨路由器那条 (PC41)仍未通,说明 R1↔R2 的 OSPF 没有真正起效,这是下一个要查的点。

一个我自己造出来的坑(已回滚)

为了让 CLI 更稳,我曾加过"读终端模式,不是 config 就补 enable/configure terminal"。 结果 getIpcTerminalLine().getMode() 会把还没初始化的控制台弄坏:之后所有设备的 getPrompt() 都变空,enterCommand 变成静默 no-op,配置全部不生效。 已回滚成 PTBuilder 的 enterCommand("!", "global"),配置立刻恢复(hostname applied: True, prompt 变回 S1(config)#)。教训:没初始化前不要碰终端对象。

1.10:陌生电脑装什么、能不能"一句话搞定"

结论:装完之后可以一句话建图并全绿,但全新机器上有 4 个前置,其中 3 个可自动、1 个只能点一次。

install.ps1 把能自动的都做了(本机实测通过):

== 1/5 Packet Tracer ==   found C:\Program Files\Cisco Packet Tracer 8.0  version 8.0.0.0212
== 2/5 Python package ==   package installed
== 3/5 PT script module == copied to ...\extensions\MCPBuilder
== 4/5 start Packet Tracer ==  already running and polling
== 5/5 smoke test ==       allUp: true

前置条件:

#

条件

谁来做

1

已装 Packet Tracer(仅 8.x 验证过,PT 9 的脚本 API 不同)

人工

2

Python 3.10+、能访问 PyPI

install.ps1 自动

3

能写 C:\Program Files\...\extensions\

必须管理员 PowerShell,脚本会检测并提示

4

PT 首次启动的 NetAcad 登录,点一次 "Continue as a Guest"

只能人工(之后 PT 记住,不再问)

5

Windows(PrintWindow/tasklist/路径都是 Win32)

—

6

可选:pip install packet-tracer-mcp(借用它的 ACL/NAT 生成器)

install.ps1 之外单独装

已知功能缺口(会影响"跟本机一样"这句话):跨 VLAN / 跨路由的 pt_pdu_ping 还没跑通 (R1:dropped / R2:dropped),3560 的三层交换配置在 PT 8 上配不上;这两条见 HANDOFF.md。

Related MCP Connectors

Related MCP Servers