AI agent skill · by @automateyournetwork
Fortigate Ops
FortiGate device operations — system status, interfaces, routing, IPsec VPN tunnel state with phase 1 and phase 2 reported separately, HA member identification, per-VDOM scoping, and manager-vs-device drift detection. Use when asking what a FortiGate is ACTUALLY doing right now, whether a tunnel is up, or whether the device matches FortiManager's intent.
Archived 2026-10-23 · Source updated 2026-10-11 · License: Apache-2.0. Check the original repository for the latest version.
SKILL.md instructions
# FortiGate Operations — the device plane
## MCP Server
- **Server**: `fortinet-mcp` (NetClaw-authored, spec 080 / roadmap R3)
- **Command**: `$FORTINET_MCP_CMD`
- **Transport**: stdio · FortiOS REST API, bearer token
- **Requires**: `FORTIGATE_HOST`, `FORTIGATE_API_TOKEN`
- **Mode**: read-only — this skill has no write path at all
## The distinction this skill exists to protect
**A FortiGate knows what it is doing. It does not know what it was supposed to do.**
| Question | Plane | Skill |
|---|---|---|
| "Is the tunnel up? What's in the routing table?" | device | **this skill** |
| "What policy is *intended* across the estate?" | manager | `fortimanager-ops` |
| "Has anything ever *matched* this rule?" | analyzer | `fortianalyzer-ops` |
| "Run a raw FortiOS CLI command" | CLI | `multivendor-raw-cli` (spec 076) |
If a device does not answer, this skill reports that it did not answer. **It never
substitutes FortiManager's intended configuration as though it were observed
state** — that would turn "the box is unreachable" into a confident, wrong
description of a box nobody can see.
## Tools (6, all read-only)
| Tool | What it answers |
|---|---|
| `fgt_system_status` | Hostname, serial, version, HA mode, **which member answered** |
| `fgt_list_interfaces` | Interfaces: link, addressing, speed, error counters, per VDOM |
| `fgt_get_routes` | Routing table as observed, optional protocol filter |
| `fgt_vpn_tunnels` | IPsec tunnels — **phase 1 and phase 2 separately** |
| `fgt_get_policies` | Firewall policy as running on the device |
| `fgt_compare_with_manager` | Divergence between intent and observed state |
## Phase 1 and phase 2 are reported separately, always
This is the single most important behaviour here.
A tunnel with **phase 1 up and phase 2 down** is neither "up" nor "down". It is a
specific and common fault — usually a proxy-ID or selector mismatch — where IKE
negotiated fine and no traffic can actually pass. Collapsing the two into one
`status` field destroys the only signal that distinguishes it from a healthy tunnel
or a dead one.
`fgt_vpn_tunnels` therefore returns `phase1_status`, `phase2_status`, and
`phase2_selectors[]` per selector pair, because one down selector out of five is
still a fault worth naming.
## Every response carries its plane and scope
```jsonc
{ "plane": "device", "scope": {"device": "FGVMEVS9GWUAOMBD", "vdom": "root"},
"source": "...", "outcome": "ok", "data": {...}, "notes": [] }
```
**Scope is mandatory.** A figure without its VDOM is ambiguous on a multi-VDOM unit,
so a response that cannot name its scope is returned as an error rather than as an
unqualified result.
## Workflow: "is the tunnel up?"
1. `fgt_vpn_tunnels` — read **both** phases.
2. Phase 1 down → IKE/peer/PSK/routing problem. Check `fgt_get_routes` for a path
to the remote gateway.
3. Phase 1 up, phase 2 down → selector/proxy-ID mismatch. Inspect
`phase2_selectors[]` for which pair failed.
4. Both up but no traffic → not a tunnel problem. Move to policy
(`fgt_get_policies`) or to the analyzer plane for whether anything matched.
## Workflow: out-of-band change detection
1. `fgt_compare_with_manager` with the ADOM and package.
2. **`only_in_device`** → rules on the box that are absent from the policy package.
Someone changed the firewall directly. This is the highest-value finding this
skill produces and it is invisible from either plane alone.
3. **`only_in_manager`** → package not installed since those rules were added.
4. If either plane is unreachable, the tool refuses to compare and names the plane
that failed. A half-comparison would be worse than none.
## Notes on evaluation-licensed labs
A FortiGate-VM evaluation licence caps the unit at **1 vCPU, 2 GB RAM, 3 interfaces,
3 routes and 3 firewall policies**. A small ruleset on such a device is a lab limit,
not the estate's real posture — `fgt_get_policies` says so in its notes.
An **unlicensed** FortiGate refuses REST authentication entirely: every request
returns 401 regardless of token validity, trusthost, or admin profile. If every call
fails with `auth_expired`, check `get system status` for `License Status: Valid`
before suspecting the token.
## Integration with other skills
| Skill | How they compose |
|---|---|
| `fortimanager-ops` | The intent this device is measured against |
| `fortianalyzer-ops` | Whether traffic actually matched what is configured here |
| `fwrule-analyzer` | Feed `fgt_get_policies` output to its FortiOS parser |
| `multivendor-raw-cli` | Raw CLI (spec 076). Use when you need command output, not structure |
| `pyats-troubleshoot` | Correlate firewall path findings with routing/device state elsewhere |
| `gait-session-tracking` | Every operation here is GAIT-audited automatically |
## Important rules
- **Read-only.** This skill cannot change a FortiGate. Policy changes go through
`fortimanager-ops` and its two gates.
- **Never report phase 1 and phase 2 as one status.**
- **Never fill an unreachable device's silence with manager data.**
- **Always carry the VDOM.**