MCP Scaffold
timothywarner-org/claude-code
Scaffold production-ready Python MCP servers using FastMCP. An agent skill from timothywarner-org/claude-code.
A skill your agent uses when building, automating, or debugging Cisco Packet Tracer networks through the packet-tracer MCP (the pt tools) — planning topologies from natural language, live-deploying…
$ npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install Mats2208/MCP-Packet-Tracer packet-tracer --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/Mats2208/MCP-Packet-Tracer.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skill .claude/skills/packet-tracer && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "packet-tracer" agent skill from https://github.com/Mats2208/MCP-Packet-Tracer/tree/main/skill into .claude/skills/packet-tracer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "packet-tracer", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/Mats2208/MCP-Packet-Tracer/tree/main/skillType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install Mats2208/MCP-Packet-Tracer packet-tracer --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Mats2208/MCP-Packet-Tracer.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skill .agents/skills/packet-tracer && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "packet-tracer" agent skill from https://github.com/Mats2208/MCP-Packet-Tracer/tree/main/skill into .agents/skills/packet-tracer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "packet-tracer", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install Mats2208/MCP-Packet-Tracer packet-tracer --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Mats2208/MCP-Packet-Tracer.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skill .cursor/skills/packet-tracer && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "packet-tracer" agent skill from https://github.com/Mats2208/MCP-Packet-Tracer/tree/main/skill into .cursor/skills/packet-tracer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "packet-tracer", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/Mats2208/MCP-Packet-Tracer.git --path skill--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install Mats2208/MCP-Packet-Tracer packet-tracer --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Mats2208/MCP-Packet-Tracer.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skill .gemini/skills/packet-tracer && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "packet-tracer" agent skill from https://github.com/Mats2208/MCP-Packet-Tracer/tree/main/skill into .gemini/skills/packet-tracer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "packet-tracer", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install Mats2208/MCP-Packet-Tracer packet-tracerInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/Mats2208/MCP-Packet-Tracer.git skills-src && mkdir -p .github/skills && cp -r skills-src/skill .github/skills/packet-tracer && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "packet-tracer" agent skill from https://github.com/Mats2208/MCP-Packet-Tracer/tree/main/skill into .github/skills/packet-tracer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "packet-tracer", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install Mats2208/MCP-Packet-Tracer packet-tracer --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Mats2208/MCP-Packet-Tracer.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skill .opencode/skills/packet-tracer && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "packet-tracer" agent skill from https://github.com/Mats2208/MCP-Packet-Tracer/tree/main/skill into .opencode/skills/packet-tracer/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "packet-tracer", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
packet-tracerA skill your agent uses when building, automating, or debugging Cisco Packet Tracer networks through the packet-tracer MCP (the pt tools) — planning topologies from natural language, live-deploying…
Packet Tracer is an agent skill from Mats2208/MCP-Packet-Tracer. Use when building, automating, or debugging Cisco Packet Tracer networks through the packet-tracer MCP (the pt tools) — planning topologies from natural language, live-deploying into a running Packet Tracer, adding devices / links / modules, applying ACL / NAT, exporting configs, or writing raw Script-Engine JS via ptsendraw. This is the authoritative reference for the tool catalog, the mandatory discover→plan→validate→deploy workflow, the EXACT PT Script-Engine API (so you never invent method names), and the…
Its SKILL.md is about 7.5k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Agent Workflows, covering MCP servers. It works with Model Context Protocol and Python. The repository describes itself as: AI-powered Cisco Packet Tracer automation over MCP. Describe a network in natural language and it generates, validates and deploys full topologies (OSPF, EIGRP, RIP, DHCP). The licence is MIT.
5 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 3c7c7a4. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are javascript and python).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
PT_MCP_BRIDGE_TOKENFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Packet Tracer loads about 7.5k tokens when it runs. Until then it costs about 158 tokens; SKILL.md has 3,452 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from Mats2208/MCP-Packet-Tracer at commit 3c7c7a4, republished under its MIT licence (© Mats2208). 3,452 words, ~7,451 tokens.
.claude/skills/packet-tracer/SKILL.md (or your agent's skills folder).A Model Context Protocol server (packet-tracer) that drives Cisco Packet Tracer: plan a
topology, validate it, generate IOS/PTBuilder artifacts, and live-deploy into a running PT
over an HTTP bridge. The pt_* tools are your only interface — plus raw JS via pt_send_raw.
The single biggest failure mode for a model driving this MCP is guessing — a model name, a port name, a slot id, a cable type, a module name, or a Script-Engine API method. Every one of these is discoverable, and a wrong guess either fails validation or (for raw JS) freezes Packet Tracer (see the modal-freeze rule below).
Rules:
pt_list_devices, pt_list_templates, pt_get_device_details,
and pt_list_modules (if installing modules).pt_query_topology / pt_export_topology and confirm.LLM ──▶ MCP server ──▶ HTTP bridge :54321 ──▶ MCP Control Center (PT extension) ──▶ PT Script Engine:54321; the PT extension polls /next every 500 ms and runs each command
via $se('runCode', …) in PT's Script Engine, posting results back to /result.XMLHttpRequest does NOT exist in the Script Engine (only in the extension webview). That's
why all I/O goes through the bridge.pt_bridge_status → must say "ACTIVE and CONNECTED". If "NOT
connected", the user opens Extensions → MCP BUILDER in PT (or dismisses a stuck error modal).pt_full_build.pt_list_devices → pt_plan_topology → pt_validate_plan → pt_live_deploy.pt_bridge_status → pt_query_topology → pt_add_*/pt_rename_device/….pt_query_topology → pt_list_modules(router_model=…) → pt_install_modules_batch.Discovery / read-only: pt_list_devices, pt_get_device_details(model|alias), pt_list_templates,
pt_list_modules(router_model, category), pt_list_projects, pt_load_project, pt_bridge_status,
pt_query_topology, pt_export_topology (*need bridge).
Pure planning/generation: pt_plan_topology, pt_estimate_plan, pt_validate_plan, pt_fix_plan,
pt_explain_plan, pt_generate_script(include_configs), pt_generate_configs, pt_full_build(deploy=…).
pt_plan_topology/pt_full_build accept vlans, dual_stack, ipv6_base, wireless_laptops.
Disk / clipboard: pt_export, pt_deploy.
PT project files: pt_save_project(filename) / pt_open_project(path) — these write and read the
real .pkt, which is NOT what pt_export does (that one dumps the plan and scripts to disk).
pt_open_project replaces the current topology.
Live deploy & edit: pt_live_deploy (auto-reconciles dropped devices), pt_add_device, pt_add_link,
pt_delete_link, pt_delete_device, pt_rename_device, pt_move_device, pt_set_port, pt_send_raw,
pt_add_module, pt_install_modules_batch.
ACL / NAT (live): pt_apply_acl, pt_apply_acl_object, pt_remove_acl, pt_remove_acl_object,
pt_apply_nat, pt_remove_nat — all accept dry_run=True to preview CLI without touching PT.
Config-driven (live, dry_run): pt_apply_vlan (VLAN/trunk/inter-VLAN subinterfaces),
pt_apply_stp, pt_apply_port_security, pt_apply_hardening (hostname/banner/enable-secret/users/SSH),
pt_apply_interface_tuning (serial clock-rate + OSPF/EIGRP per-interface knobs).
Verification (live): pt_diff (plan vs live), pt_health_check (down links, dup IPs, cabled-no-IP).
pt_verify_connectivity gives three verdicts, not two: CONECTIVIDAD OK (every packet returned),
CONECTIVIDAD PARCIAL (some loss — normal on a first ping because of ARP, so re-run once before
calling it a fault) and SIN CONECTIVIDAD. It works from routers and switches, not only hosts.
pt_health_check no longer lists layer-2 ports as "cabled without IP", so anything it does report
there is a real host that never got its DHCP lease.
Live-state inspection (read the device, not the plan): pt_audit_security(device="") (security
posture with severities; never returns passwords or hashes, only the algorithm label),
pt_inspect_ports(device, only_linked) (per-port line/protocol, MAC, duplex, bandwidth, MTU, CDP,
NAT mode, applied ACLs; flags cabled-but-down), pt_read_vlans(switch) (real VLAN database, separates
your VLANs from PT's factory ones), pt_device_power(device, on) (power-cycle with read-back).
Canvas (capture & annotate): pt_screenshot(filename, fmt, output_dir) writes the image to disk
and returns the path — never the bytes, they would flood the context. pt_add_note(x, y, text)
writes a label; font size is not settable. pt_clear_annotations(kind) removes annotations only,
never devices or links (drawings included — redraw circles after clearing notes).
There is no drawing tool, but PT's drawCircle does work from raw JS once you pass all
seven arguments — see "Drawing IS possible" below. Colours still don't take.
Canvas coordinates match pt_add_device: routers ~y=100, switches ~y=250, hosts ~y=400.
Recipe for a diagram worth showing: pt_full_build → pt_add_note per subnet and link →
drawCircle per LAN → pt_screenshot → adjust and repeat.
Backup / workspace: pt_backup_config(device, include_xml) (real startup-config + serial,
config-register, boot images), pt_project_metadata(description="") (saved filename, PT version,
description, device/link count; pass description to set it), pt_workspace_options(...) — tri-state
flags, -1 leaves a setting alone. Turn auto_cabling=0 before a scripted build if you need links on
exact interfaces, and check external_network_access before assuming traffic stays in the simulator.
DHCP on a Server-PT: pt_configure_dhcp_server(device, network, mask, gateway, dns, start_ip, max_users, pool_name="serverPool", port="FastEthernet0", enabled, drop_factory_pool, remove, dry_run)
creates/edits a pool on a server (GUI: Services > DHCP), turns the service on and reads every pool
back. Without network it only reads. Router DHCP is a different thing: IOS CLI ip dhcp pool, which
is what dhcp=True in the planner emits. Give the server a static IP inside the pool's subnet first.
Telemetry / QoS: pt_apply_netflow(device, name, destination_ip, udp_port, version, source_port, monitors, remove, dry_run) configures a NetFlow exporter directly (not via CLI) and reads it back to
confirm. pt_read_qos(device) is read-only: QoS cannot be created programmatically, so author
class-maps and policy-maps with IOS CLI and use this to verify.
Simulation: pt_simulation_mode(on) (Realtime ↔ Simulation), pt_simulation_step(action, times)
(forward/back/reset), pt_read_packet_trace(limit, device, include_decisions) — the event list
plus PT's own per-OSI-layer explanation of each decision (same text as the GUI's PDU Details pane).
Workflow: pt_simulation_mode(on=True) → generate traffic (pt_verify_connectivity) → pt_read_packet_trace.
There is no pt_send_pdu: PT does not let an extension originate a packet the way the GUI's
Add Simple PDU button does. Generate traffic with a real ping instead.
Resources: pt://catalog/devices, /cables, /aliases, /templates, pt://capabilities.
pt_full_build(template="router_on_a_stick", vlans=3) → N VLANs,
trunk uplink, router .1q subinterfaces, one /24 + DHCP pool per VLAN. 2960 omits trunk
encapsulation (dot1q-only); 3560 emits it.pt_plan_topology(dual_stack=True) → routers get ipv6 address via CLI +
ipv6 unicast-routing; hosts use SLAAC (configurePcIpv6 = enable + auto-config). Static host
IPv6 is NOT settable via the PT API (addIpv6Address fails on HostPort) — SLAAC is the path.wireless_laptops=True swaps each Laptop-PT NIC to PT-LAPTOP-NM-1W (slot "0")
→ Wireless0, and adds one AccessPoint-PT per LAN, each wired to that LAN's own switch.
⚠️ Wireless addressing is NOT deterministic. Laptops auto-associate on the default SSID and
PT exposes no SSID API (verified: neither the AP nor its port has setSsid), so with more
than one AP a laptop can associate to any of them and take a DHCP lease from another LAN's
pool. Measured on PT 9.0.1: LT10, sitting right next to its own LAN's AP, still associated to
the LAN-1 AP and got 192.168.0.13; only with that AP powered off did it take 192.168.4.25.
The planner now emits a WIRELESS_AMBIGUOUS_ASSOCIATION warning when ≥2 APs coexist.
Always confirm with pt_inspect_ports; for deterministic addressing use wireless_laptops=False.pt_send_raw / raw JS)These are the real signatures (verified against the MCP's runtime patches and live testing). If a method is not here, do not assume it exists.
Globals
getDevices(filter) → Array of NAME STRINGS, not device objects (filter "" = all,
"router" = routers). Calling .getName() / .getPorts() on an element throws
TypeError: Property 'getName' of object R1 is not a function — the element is "R1".
To get the object, feed each name to ipc.network().getDevice(name).allModuleTypes[name] → module-type handle (passed to addModule)reportResult(data) → exists only when wait_result=True; POSTs the result backgetDevice(...); ❌ no XMLHttpRequest in the Script EngineNetwork / device — var d = ipc.network().getDevice("R1"); // ← correct singular lookup, may be null
ipc.appWindow().getActiveWorkspace().getLogicalWorkspace() → lwlw.addDevice(type, model, x, y) → autoName · lw.createLink(d1,p1,d2,p2,cableEnumInt)d.getPorts() → Array of port-name strings — use .length, [i], .join(",").
❌ never .size(), .at(i), .getName() on it (TypeError → modal → freeze).d.getPort(name) → Port | null · d.getPower()/setPower(bool)/skipBoot()/setName(name)d.moveToLocation(x, y) → reposiciona en el canvas lógico. Es lo que usa pt_move_device;
en un solo pt_send_raw podés reacomodar decenas de dispositivos sin una llamada por cada uno.d.addModule(slot, allModuleTypes[model], modelName) → bool (slot is a STRING)d.getProcess("AclProcess") → AclProcess | null (routers) · d.enterCommand(cmd, mode)d.getCommandLine() → console handle with getOutput(), enterCommand(cmd), getPrompt().
Use this for console work: getCommandPrompt() exists ONLY on hosts and throws
TypeError on any router. PCs expose both, so getCommandLine() covers both worlds.
⚠️ A router deployed by the MCP was never touched by console, so it sits at
Would you like to enter the initial configuration dialog? [yes/no]: — a ping sent
there is eaten as the yes/no answer. Prime it first: answer no, then send an empty
command to clear Press RETURN to get started.d.setDhcpFlag(bool), d.setDefaultGateway(ip)Port — var p = d.getPort("GigabitEthernet0/0");
p.getLink() → link | null (null = free) · p.setIpSubnetMask(ip, mask) · p.setDefaultGateway(ip)p.setDnsServerIp(ip) · p.setAclInID(id)/p.setAclOutID(id) (pass "" to clear)AclProcess — var ap = d.getProcess("AclProcess");
ap.addAcl(name) · ap.getAcl(name) → acl|null · ap.removeAcl(name)acl.addStatement(str) → bool · acl.getCommandCount() → intDHCP server (Server-PT only) — verified PT 9.0 + IpcAPI reference (class_dhcp_server_process)
var s = d.getProcess("DhcpServerMain").getDhcpServerProcessByPortName("FastEthernet0");
getProcess("DhcpServerMain") is null on routers, switches and PCs; a wrong port name → null.
⚠️ DhcpServerMain itself has no pool methods (getPoolCount etc. are undefined there) —
that is why issue #23 concluded it was impossible. They live one level down, on s.s.addPool(name) (a repeated name does not duplicate) · s.getPool(name) → pool|null ·
s.removePool(name) · s.getPoolCount() / s.getPoolAt(i) · s.setEnable(bool) / s.isEnable() ·
s.addExcludedAddress(ip, ip). Avoid addNewPool(...): 8 unnamed arguments in the reference.setNetworkAddress(ip) · setNetworkMask(network, mask) — TWO args, one arg throws
Invalid arguments for IPC call · setDefaultRouter(ip) · setDnsServerIp(ip) · setStartIp(ip) ·
setMaxUsers(n) (recomputes the end from the start, so set start first) · setEndIp(ip).
Getters: getDhcpPoolName/getNetworkAddress/getSubnetMask/getDefaultRouter/getDnsServerIp/ getStartIp/getEndIp/getMaxUsers. There is no setter for TFTP, WLC or domain name.serverPool re-fits itself to the server's subnet with
start = network address and no gateway, so next to a named pool it hands out .1.
After changing a pool, clients keep their old lease — even one outside the new range. Toggling setDhcpFlag does not renew; run ipconfig /release then ipconfig /renew on the host console.Cable enum ints (for lw.createLink): straight 8100 · cross 8101 · roll 8102 · fiber 8103 ·
phone 8104 · cable 8105 · serial 8106 · auto 8107 · console 8108 · wireless 8109 · coaxial 8110 ·
octal 8111 · cellular 8112 · usb 8113 · custom_io 8114.
try { var d = ipc.network().getDevice("R1"); reportResult("ports="+d.getPorts().join(",")); }
catch (e) { reportResult("ERR: " + e); }An uncaught error pops a modal QMessageBox in PT ("An error occurred… ReferenceError…") that is
modal and freezes the webview polling loop — the bridge goes "NOT connected" until a human clicks
OK. A caught error never does. (The server also guards commands now, but wrap anyway — it's
free insurance and keeps results clean.)
plan.errors before you deploypt_plan_topology / pt_full_build return errors[] and warnings[];
pt_validate_plan returns the same as typed codes. An empty errors[] is now
meaningful — it did not used to be. The validator only checked per-device facts,
so a topology split into islands passed with valid: true, deployed, and failed
silently. It now walks the graph.
| Code | What it means | What to do |
|---|---|---|
TOPOLOGY_DISCONNECTED | The cabled devices form more than one component — something has no path to the rest | Usually the hub ran out of ports (a 2911 has 3 Gigabit). Use a bigger model, add a module, or fewer routers. Do not deploy: the isolated part cannot route |
OSPF_NO_NETWORKS | An OSPF process with zero network statements | That router has no addressed, linked interface. Fix the links first |
OSPF_INVALID_ROUTER_ID | router-id 0.0.0.0, which IOS rejects | Same root cause as above |
WIRELESS_AMBIGUOUS_ASSOCIATION | warning — two or more APs share the default SSID | Not a blocker. Confirm with pt_inspect_ports which subnet each wireless host actually landed in |
IP_CONFLICT / INVALID_IP_ADDRESS | Duplicate or malformed address | Re-address |
DHCP_GATEWAY_MISMATCH | warning — the pool gateway is on no interface of that router | Check the pool against the router's interfaces |
Wireless hosts are excluded from the connectivity graph on purpose: they carry no cable by design, so counting them as islands would flag every wireless topology as broken.
pt_live_deploy takes the plan as a string argument, so a plan with ~90
devices (thousands of lines of JSON) cannot practically be passed to it, and
pt_load_project only hands the JSON back to you.
For anything past roughly 60 devices, or for shapes the templates do not cover (mixed IGPs per region, custom cores), build it locally and push it straight to the mailbox — the plan never has to travel through a tool call:
from src.packet_tracer_mcp.domain.models.plans import TopologyPlan, DevicePlan, LinkPlan
from src.packet_tracer_mcp.domain.services.validator import validate_plan
from src.packet_tracer_mcp.infrastructure.generator.ptbuilder_generator import (
generate_executable_script,
)
from src.packet_tracer_mcp.infrastructure.execution.file_bridge import FileBridge
plan = TopologyPlan(...) # devices, links, ospf/eigrp/rip_configs, vlans
result = validate_plan(plan) # same rules the MCP uses — check it first
script = generate_executable_script(plan)
bridge = FileBridge()
assert bridge.pt_alive() # Script Engine heartbeat
for batch in chunks(script.split("
"), 25):
body = "".join(f"try{{{line}}}catch(e){{}}" for line in batch)
bridge.send_and_wait(body + "reportResult('ok');", timeout=90.0)Verified: 88 devices, 88 links, 282 JS statements in 12 batches over the file
bridge. One try/catch per statement so a single failure does not take the batch
down. A plan can carry ospf_configs, eigrp_configs and rip_configs at the
same time — and one router can appear in two of them, which is how you build a
redistribution boundary. redistribute itself is not in the plan model: push
it as extra CLI with configureIosDevice(name, cli).
| ❌ Wrong | ✅ Right | Why |
|---|---|---|
getDevice("R1") | ipc.network().getDevice("R1") (or getDevices("") to list) | no global getDevice → ReferenceError → freeze |
d.getPorts().size() / .at(i) / .getName() | d.getPorts()[i] (string array) | getPorts returns strings |
getProcess("DhcpServerMain").getPoolCount() | getProcess("DhcpServerMain").getDhcpServerProcessByPortName("FastEthernet0").getPoolCount() (or pt_configure_dhcp_server) | the pools live on the per-port process |
pt_add_module(slot=0) | slot="0" (string) | int slot silently fails (=== compare) |
cable "crossover" | "cross" | alias works but cross is canonical |
invent model "Cisco4500" | pt_list_devices / pt_get_device_details first | use real catalog names |
invent module "NM-4T" | pt_list_modules(router_model=…) first | use real module names |
| raw JS unwrapped | wrap in try/catch + reportResult | uncaught error freezes the bridge |
trust add_module "timeout" = failure | verify with pt_query_topology | it often succeeds despite timeout |
straight, cross, roll, serial, fiber, console, phone, cable, coaxial, auto, wireless, octal, cellular, usb, custom_io. Omit cable_type in pt_add_link to infer (router↔router &
switch↔switch → cross; router↔switch, switch↔host → straight).GigabitEthernet0/0..0/2 but 1941/2901 only 0/0..0/1;
ISR4321/4331 GigabitEthernet0/0/0..;
2960/3560 FastEthernet0/1..0/24 + GigabitEthernet0/1..0/2; PC/Laptop/Server FastEthernet0;
HWIC-2T in "0/x" → Serial0/x/0,Serial0/x/1;
Cloud-PT has 8 ports: Serial0..3, Modem4, Modem5, Ethernet6, Coaxial7.
⚠️ A cloud only forwards over its serial ports once frame relay is configured, and the MCP has no
tool for that. For a WAN core that actually passes traffic, link routers to each other with /30s and
hang the cloud off Ethernet6 as an external stub.pt_plan_topology): LANs /24 from 192.168.0.0, gateway .1, hosts from .2;
router↔router /30 from 10.0.0.0; DHCP pool per LAN with .1 excluded; routing
static|ospf|eigrp|rip|none (+ floating_routes, ospf_process_id, eigrp_as).slot is a STRING. Pick a module whose compatible_with includes the model — the MCP rejects an
incompatible module up front when the module declares compatible_with (HWIC/NIM/built-ins); generic
PT-* modules have no declared constraint, so choose sensibly there. Always confirm with
pt_query_topology after install; a single pt_add_module may report a timeout yet still succeed.
| Router family | Module type | Slot (string) | Ports added | Status |
|---|---|---|---|---|
| ISR G2 — 2911/2901 | HWIC (HWIC-2T, HWIC-1GE-SFP) | "0/0".."0/3" | Serial0/x/0,Serial0/x/1 | ✅ verified 2911 |
| ISR G2 — 1941 | HWIC | only "0/0", "0/1" — it has 2 slots, not 4 | Serial0/x/0,Serial0/x/1 | ✅ verified 1941 (PT 9.0.1) |
| ISR 4000 — ISR4321/4331 | NIM (NIM-2T, NIM-ES2-4) | "0/1", "0/2" | Serial0/1/0,Serial0/1/1 | ✅ verified ISR4321 & ISR4331 |
| 2811 / 2620XM / 2621XM | NM (NM-4A/S, NM-2FE2W,…) | "1" | Serial1/0..1/3 | ✅ verified 2811 |
| Router-PT (generic) | NM (NM-*, PT-ROUTER-NM-*) | "1" | ⚠️ non-standard ids (e.g. Serial2/0) | installs, odd port names |
⚠️ The
pt_add_moduledocstring/SERVER_INSTRUCTIONS say NIM slots are"0"/"1"— that is wrong; the working slot is"0/1"(chassis/subslot). HWIC ="0/x", NM ="1", NIM ="0/1". The install mechanism (addModule) is identical across routers — only the slot string differs by family. Always confirm the result withpt_query_topology.
Prefer pt_install_modules_batch for multiple modules (one power-cycle); individual installs
power-cycle the device and can exceed the wait window (and often report a timeout despite succeeding).
compatible_with (HWIC/NIM/built-ins
reject a wrong model); generic PT-* modules carry no constraint, so still pick sensibly.pt_add_module (single) can report a timeout but still succeed — verify ports, don't blindly retry.
(pt_install_modules_batch no longer guesses: it reports installed per module, see round 2 below.)three_router_triangle closes the ring (R3↔R1) and hub_spoke wires R1→every spoke — the
orchestrator honors the template shape (was a flat chain before). ⚠️ hub_spoke is limited by the
hub's port count: a 2911 has 3 Gigabit ports, so it cannot serve 5 spokes plus its own LAN. Asking
for more no longer fails silently — the plan comes back with TOPOLOGY_DISCONNECTED. Pick a router
with more ports, add a module, or use multi_lan (a chain needs only 3 ports per router).pt://capabilities is derived from the live tool registry (supported_live.nat/acl/modules/…) — it
can no longer drift; trust it and the tools.pt_live_deploy "N/N verified" checks device/link existence only — host IPs may lag a few seconds.routing= raises a raw exception; invalid router_model returns a degenerate plan with an
embedded errors[] — always check plan.errors before deploying.Power Distribution Device can appear after deploy (off-canvas).for...invar d = lw.getEllipseItemData(id);
for (var k in d) { ... } // ← CRASHES Packet Tracer. The whole app dies.try/catch does not save you: this is not a JS exception, it is the script engine
blowing up and taking the process with it. Verified the hard way against PT 9.0.1 — the
app closed with "Cisco Packet Tracer quit unexpectedly".
Inspect native objects by calling known getters and stringifying the result, never by enumerating keys. If you don't know the shape, probe one accessor at a time.
drawCircle takes SEVEN argumentslw.drawCircle(x, y, ignored, diameter, 0, 0, 0) // x,y = TOP-LEFT cornerReturns an ellipse id and really draws. With 3–6 arguments it throws, which is why it was
believed unusable — same failure mode as setHideDevLabel (needs 2, not 1): the call was
fine, the arity was wrong. The old note that "the size argument controls stacking order"
came from passing the size in slot 3; slot 4 is the diameter and slot 3 is ignored.
Calibrated on PT 9.0.1 at default zoom: the drawn diameter is roughly 0.71 × the argument,
so to circle a LAN centred at cx use drawCircle(cx - 173, y, 0, 480, 0,0,0). Don't compute
it blind — draw, pt_screenshot, adjust. Clear with pt_clear_annotations (it removes
drawings too, so redraw circles after clearing notes).
Colours still don't work: the last three arguments accept values but the ellipse comes out with the default outline.
/mcp reconnect), nothing is
listening on :54321 until the first pt_* call — importing the server deliberately opens no
socket. So "not connected" right after a reconnect usually means nobody has called a tool yet,
not that the extension died: just call pt_bridge_status and PT resumes polling in seconds.
Don't ask the user to reopen MCP BUILDER before trying that.getClassName() is useless for telling a switch from a router. PT classifies by behaviour:
a 3560 answers "Router" (it is multilayer) and a 2960 answers "CiscoDevice" — neither ever
says "switch". Use getModel() and resolve it against the catalog.addModule() returns false instead of throwing when the slot does not exist on that model.
Check the return value; a silent false used to be reported as a successful install. pt_add_module
and pt_install_modules_batch now verify it and report installed / failed per module.getDevice(name) resolving only to one — the other became unreachable by name. Now rejected.Would you like to enter the initial configuration dialog? [yes/no]:. A ping sent there is eaten as
the yes/no answer and never runs. Prime it first: send no, then an empty command to clear
Press RETURN to get started. (pt_verify_connectivity now does this for you.)no shutdown
for addressed interfaces, so a link you cabled but never addressed shows up as a red triangle on the
canvas and as a down link in pt_health_check. Give it an address, even a throwaway /30.setSsid). If you need deterministic
addressing, use wireless_laptops=False and cable the laptops.PT_MCP_BRIDGE_TOKEN is validated. If the server refuses to start with BridgeTokenError, the
variable is set to something shorter than 32 chars or outside [A-Za-z0-9_-]. Fix it or unset it so
the on-disk token is used. It fails loudly on purpose: a bad value there would disable the only real
defence the bridge has.pt_full_build — only if you want a
specific arrangement.pt_full_build(routers=2, switches_per_router=1, pcs_per_lan=2, dhcp=True, routing="static", deploy=True).pt_install_modules_batch([{device:"R1",slot:"0/0",module:"HWIC-2T"}, {device:"R1",slot:"0/1",module:"HWIC-2T"}]).pt_apply_acl(router="R1", name_or_number="101", acl_type="extended", entries=[{action:"deny",protocol:"tcp",source:"any",destination:"host 192.168.0.10",dest_port_op:"eq", dest_port:23},{action:"permit",protocol:"ip",source:"any",destination:"any"}], binding_interface="GigabitEthernet0/0", binding_direction="in", dry_run=True) — preview first.pt_send_raw('try{var d=ipc.network().getDevice("R1");reportResult(d?d.getPorts().join(","):"missing")}catch(e){reportResult("ERR:"+e)}', wait_result=True).© Mats2208, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skill of Mats2208/MCP-Packet-Tracer.
Open the folder on GitHubat commit 3c7c7a4
Packet Tracer next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Packet Tracer this skillMats2208/MCP-Packet-Tracer | 247 | — | ~7.5k | Automated safety check: Pass | MIT | |
| MCP Scaffoldtimothywarner-org/claude-code | 224 | — | ~940 | Automated safety check: Pass | MIT | |
| Open PRArcadeAI/arcade-mcp | 1k | — | ~2.9k | Automated safety check: Pass | MIT | |
| MCP DeveloperJeffallan/claude-skills | 12k | — | ~1.5k | Automated safety check: Pass | MIT | |
| MCP Server Builderanthropics/skills | 180k | 63 repos | ~2.3k | Automated safety check: Pass | Apache-2.0 | |
| MCP Server BuildershareAI-lab/learn-claude-code | 78k | 4 repos | ~1.2k | Automated safety check: Pass | MIT |
timothywarner-org/claude-code
Scaffold production-ready Python MCP servers using FastMCP. An agent skill from timothywarner-org/claude-code.
ArcadeAI/arcade-mcp
Prepare arcade-mcp changes for review by verifying intended behavior, filling the repository PR template, and creating or updating the PR.
Jeffallan/claude-skills
Builds and debugs MCP servers and clients in TypeScript or Python, from tool and resource definitions to transport setup and inspector-based protocol checks.
anthropics/skills
Guides the design and implementation of Model Context Protocol servers in TypeScript or Python, from tool naming and error messages to evaluation.
shareAI-lab/learn-claude-code
Walks through building MCP servers in Python or TypeScript that expose tools, resources and prompts to Claude, with templates, registration and testing.
MemPalace/mempalace
Installs and configures MemPalace as a private local palace, a shared-brain hub or a client of an existing hub, including MCP registration and version-correct initialization.
Works with
Categories
A skill your agent uses when building, automating, or debugging Cisco Packet Tracer networks through the packet-tracer MCP (the pt tools) — planning topologies from natural language, live-deploying…. Packet Tracer is an agent skill from Mats2208/MCP-Packet-Tracer. Use when building, automating, or debugging Cisco Packet Tracer networks through the packet-tracer MCP (the pt tools) — planning topologies from natural language, live-deploying into a running Packet Tracer, adding devices / links / modules, applying ACL / NAT, exporting configs, or writing raw Script-Engine JS via ptsendraw.
Packet Tracer fits situations like: debugging Cisco Packet Tracer networks through the packet-tracer MCP (the pt tools) — planning topologies from natural language; live-deploying into a running Packet Tracer; adding devices / links / modules; applying ACL / NAT.
Run `npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a claude-code`. Or copy the skill folder (skill in Mats2208/MCP-Packet-Tracer) into .claude/skills/packet-tracer in your project. Claude Code loads it when a task matches its description.
Run `npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a codex`. Or copy the skill folder (skill in Mats2208/MCP-Packet-Tracer) into .agents/skills/packet-tracer in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add Mats2208/MCP-Packet-Tracer --skill packet-tracer -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/packet-tracer, .gemini/skills/packet-tracer, .github/skills/packet-tracer and .opencode/skills/packet-tracer in your project.
Going by SKILL.md and its folder, Packet Tracer needs credentials named PT_MCP_BRIDGE_TOKEN.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Packet Tracer is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.5k tokens (SKILL.md is roughly 30k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Packet Tracer: MCP Scaffold (timothywarner-org/claude-code, 224 stars), Open PR (ArcadeAI/arcade-mcp, 1k stars), MCP Developer (Jeffallan/claude-skills, 12k stars) and MCP Server Builder (anthropics/skills, 180k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
Mats2208 (a GitHub user) maintains it in Mats2208/MCP-Packet-Tracer, which has 247 GitHub stars. The repository was last updated on October 7, 2026.
Source: Mats2208/MCP-Packet-Tracer on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.