| name | fortigate-ops |
| description | 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. |
| version | 1.0.0 |
| license | Apache-2.0 |
| tags | ["fortinet","fortigate","fortios","firewall","vpn","ipsec","vdom","ha","security","multi-vendor"] |
| user-invocable | true |
| metadata | {"openclaw":{"requires":{"bins":"[Truncated]","env":"[Truncated]"}}} |
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
{ "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?"
fgt_vpn_tunnels — read both phases.
- Phase 1 down → IKE/peer/PSK/routing problem. Check
fgt_get_routes for a path
to the remote gateway.
- Phase 1 up, phase 2 down → selector/proxy-ID mismatch. Inspect
phase2_selectors[] for which pair failed.
- 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
fgt_compare_with_manager with the ADOM and package.
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.
only_in_manager → package not installed since those rules were added.
- 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.