mcfortigate
Provides read-only access to FortiGate firewall configuration and live state, including address objects, policies, interfaces, routes, DHCP leases, ARP entries, wireless clients, reference lookup, configuration search, and device identification across one or more appliances.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcfortigateis the address object 10.10.5.22 safe to delete, or still referenced?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcfortigate
An MCP server that lets a language model read a FortiGate firewall and answer questions about it.
Read-only. Nothing in this server can change a configuration.
Full documentation: mcfortigate.warehack.ing
Why not just wrap the REST API
The obvious way to build this is one tool per FortiOS endpoint returning raw
JSON, and that turns out to work badly. A single FortiOS policy object carries
more than eighty fields, most of them empty strings, unused IPv6 arrays, and
internal UUIDs. Ask a model "which policies allow traffic into the DMZ" against
a raw wrapper and it spends most of its attention on "srcaddr6": [] before it
finds anything useful.
So the tools here are shaped around the questions operators actually ask rather than around the endpoints FortiOS happens to expose. Three examples.
"Is this address object safe to delete?" is find_references, and it is the
tool that shaped the rest of this server.
The obvious implementation scans the tables that obviously hold references: policies, groups, virtual IPs, routes. The appliance disagrees about what "obviously" covers. Asked directly, FortiOS 7.0.14 reports 74 tables that can reference a firewall address and 234 that can reference an interface. An object used only by a web-proxy profile comes back from the obvious implementation as unreferenced, which is a wrong answer in the direction that destroys data.
So the authority here is the appliance's own reference lookup, the one behind
the reference counter in its web UI. Two things about it are worth knowing.
Every row it returns carries reference_count: 0, genuine references included,
so the presence of a row is the signal and the count is a trap. And asked about
a key that is absent from the table you named, it answers success with an empty
list, so guessing the wrong table reports an in-use object as free. Querying
the address table about wan1 finds nothing while the interface table finds
two references, which is why this tool works out what kind of object a name
refers to before it asks.
It also walks past the direct referrer. The appliance's lookup is not
transitive: an address inside a group reports the group and stops, so the
policy that actually breaks never appears. On our lab, asking about the switch
port internal1 directly returns one row — a virtual switch. Walking two more
levels, through the switch and the interface that carries it, reaches the
firewall policy that a change to that port would disturb. Reached references
are reported separately from direct ones, each carrying the chain that found
it, and the walk is depth-capped and says so when it stops early.
The answer is five-valued rather than a boolean, because there are genuinely
five things that can be true. Beyond referenced and no_references there is
object_not_found for a name that matches nothing,
no_references_in_checked_scopes for when the authoritative lookup was
unavailable and only the partial scan ran, and indeterminate for when the
scan itself was incomplete. safe_to_delete appears only for the first two.
A token scoped to firewall objects gets 403 on the routing table, and the
client library turns every error status into an empty list, so doing least
privilege correctly makes a false clean bill of health more likely, not less.
"What is 198.51.100.47?" is find_device. It searches the wireless client
list, the DHCP lease table, and the ARP table, then merges what each one knows
about a MAC into a single record.
MAC addresses are normalized before that join, and before matching a query
against them. The appliance we test on spells every MAC the same way, so the
normalization is insurance rather than an observed fix on that firmware — but
the query side is a real defect it closes: a MAC pasted from anywhere else
arrives as 20-47-47-7D-DB-7B or 2047.477d.db7b, and a substring match
against the colon-separated table form finds nothing and reports the device
unknown. The matcher also refuses to read an IPv4 address as a MAC fragment,
since an address is nothing but hex digits and dots and a loose matcher would
let one match an unrelated device.
"Where does 203.0.113.0/24 appear?" is search_config, which looks through
addresses, groups, services, interfaces, routes, and policies at once, for when
you do not yet know which kind of object holds the answer.
Related MCP server: Fortigate MCP
Install
uvx mcfortigateAdd it to Claude Code. Read the token rather than typing it on the command line, so it does not land in your shell history:
printf 'FortiGate API token: '; read -rs FORTIGATE_TOKEN; echo
claude mcp add fortigate \
--env FORTIGATE_HOST=fgt.example.com \
--env FORTIGATE_TOKEN="$FORTIGATE_TOKEN" \
-- uvx mcfortigateHistory records the literal "$FORTIGATE_TOKEN", because the line is recorded
before the shell expands it.
That is worth doing and it is not sufficient. Claude Code stores MCP
environment values in ~/.claude.json in plaintext, and nothing on this side
changes that. Treat the token as readable at rest and put the real control
on the appliance: a read-only profile, and a trusted-host list naming only the
machine that runs this. Those hold even if the token leaks; keeping it out of
history only means it leaks from one fewer place.
Configuration
Create a read-only REST API admin on the FortiGate under System > Administrators > Create New > REST API Admin, give it a read-only profile, and restrict its trusted hosts to whatever runs this server. Then set two variables:
FORTIGATE_HOST=fgt.example.com
FORTIGATE_TOKEN=your-tokenA username and password pair works as a fallback on older firmware, via
FORTIGATE_USERNAME and FORTIGATE_PASSWORD, but a token is better because it
avoids a login round-trip on every call.
For several appliances, set FORTIGATE_TARGETS to a JSON object. The keys
become the target argument on every tool, so short aliases are worth it:
FORTIGATE_TARGETS='{
"edge": {"host": "fgt-edge1.example.com", "token": "..."},
"branch": {"host": "fgt-br2.example.com", "token": "...", "verify_ssl": false}
}'With one appliance configured the target argument can be omitted everywhere.
With several it is required, and omitting it produces an error listing the valid
aliases so the model can correct itself.
See .env.example for the optional settings, which are FORTIGATE_NAME,
FORTIGATE_VDOM, FORTIGATE_VERIFY_SSL, FORTIGATE_TIMEOUT, and
FORTIGATE_PORT.
Tools
Orientation
Tool | Answers |
| Which appliances can this server reach |
| Model, serial, firmware, hostname, CPU and memory |
| Where does this term appear, anywhere in the config |
Firewall
Tool | Answers |
| What named addresses exist, with readable values |
| What groups exist and what is in them |
| What services exist, with protocols and ports |
| What rules exist, in evaluation order, with filters |
| What destination NAT is configured |
| What points at this object, and is it safe to delete |
Network
Tool | Answers |
| What interfaces exist, with addresses and link state |
| What VLANs exist, with tags and parent interfaces |
| What routes were configured |
| What routes are actually in use right now |
Live state
Tool | Answers |
| Who is on the wireless right now, with hostnames |
| What leases are currently issued |
| What IP-to-MAC bindings the appliance sees |
| Who is this MAC, IP, or hostname |
Configuration tools read what the appliance was told to do. Live tools read what
it currently observes, and those answers are true only at the moment of the
call. The distinction matters more than it sounds: a DHCP-assigned default route
shows up in get_routing_table and never in list_static_routes, because it was
never configured.
Why the listing tools page and the others do not
Every listing tool takes limit and offset, defaults to 200 rows, and always
reports total_available. A partial page additionally carries truncated and
next_offset.
The problem this solves is not memory. A FortiGate with four thousand ARP entries produces a response Python handles without noticing; what breaks is an MCP client with a size limit truncating the payload on the way to the model, which then reads whatever survived as the whole answer. Nothing in the data says otherwise. Bounding the result here makes the same truncation visible instead of silent.
The analysis tools — find_references, search_config, find_device — take no
limit on purpose. They scan in order to reach a verdict, and a verdict from a
partial scan is not a shorter answer, it is a wrong one.
FortiOS behaviors handled here
These were found against real hardware rather than in documentation, and each one fails silently rather than loudly, which is what makes them worth writing down.
Boolean fields are strings. FortiOS writes "enable" and "disable" where
JSON would use true and false. Passing those through bool() reads every
disabled thing as enabled, since bool("disable") is True. That single
mistake once flipped every non-blackhole route to blackhole.
The same format means two things. A dotted-mask string like
203.0.113.0 255.255.255.0 is a network in firewall.address.subnet but the
interface's own address in system.interface.ip. Collapsing the second to its
network address invents IPs that do not exist.
Relational fields change shape by version. Fields such as dstaddr arrive
as [{"name": "X"}] on FortiOS 7.2 and later, but as a bare string on 7.0.x.
Both are handled.
dst is a placeholder when dstaddr is set. When a route points at a named
address object, FortiOS writes the all-zeros sentinel into dst. Reading dst
first turns every named-destination route into a bogus default route.
Identity lives in the envelope, not the body. The appliance serial, firmware
version, and build number are siblings of results on every cmdb response, not
inside it. Helpers that unwrap straight to results discard them, which makes
the serial look absent from the API entirely.
FortiOS creates its own interfaces. Names prefixed wqtn., vap., ssl.,
and naf. are bookkeeping, generated alongside VAPs and VPN tunnels. They are
hidden by default, since no operator made them and none can act on them. Pass
include_internal=true to see them.
Development
uv venv && uv pip install -e ".[dev]"
uv run pytest
uv run ruff check src/ tests/The reading layer in fortios.py and the configuration layer in config.py are
pure functions with no network or framework coupling, so the test suite runs in
under a second with no fixtures and no mock appliance.
Related
The FortiOS behaviors above were learned while building nautobot-ssot-fortinet, which syncs FortiGate configuration into Nautobot in both directions. This server reuses that knowledge for a different purpose.
License
Apache-2.0
Available Tools
17 toolsfind_deviceIdentify a device by MAC, IP, or hostnameARead-onlyIdempotent
Identify a device on the network by MAC, IP, or hostname fragment.
Searches the wireless client list, the DHCP lease table, and the ARP table together, then merges everything known about each matching device into one record. A device seen in several places produces one result rather than three partial ones.
This is the tool for questions like "what is 198.51.100.47", "is that laptop on the network", or "which SSID is this MAC on".
A MAC query matches whatever punctuation the appliance used, so
20-47-47-7d-db-7b, 2047.477d.db7b, and 20:47:47:7d:db:7b all find
the same device. An IP query is never treated as a MAC.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to search. Defaults to the one configured for this target. The `vdom` field in the response names the one actually searched, and a device in another vdom will not be found from here. | |
| query | Yes | A MAC address, an IP address, or part of a hostname. Matching is case-insensitive and substring-based, so a partial MAC or a bare hostname prefix works. Partial MACs must keep their separators to be recognized as MACs. | |
| target | No | Which FortiGate to query. Optional when only one is configured. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, openWorld, and non-destructive, so the safety profile is covered. The description adds genuinely non-obvious behavior: cross-source merging, one-result-per-device deduplication rather than three partial records, and MAC punctuation normalization. It does not discuss result ordering or ambiguity when many devices match, keeping it below 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short paragraphs, front-loaded with what the tool does, then behavior, then example questions, then query-format edge cases. No filler; each paragraph carries distinct information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be described, and annotations carry the safety profile. The description still supplies the merge/dedup behavioral model and query-format rules, leaving no material gap for calling this tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so baseline is 3. The description goes further by explaining that MAC matching ignores punctuation (three equivalent forms shown) and that an IP query is never treated as a MAC — semantics that the schema's 'partial MACs must keep their separators' text alone does not make fully clear.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Identify a device on the network by MAC, IP, or hostname fragment') and explains the mechanism: it searches the wireless client list, DHCP lease table, and ARP table together and merges matches. This implicitly and clearly distinguishes it from the sibling raw-table tools list_wifi_clients, list_dhcp_leases, and get_arp_table.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete triggering questions ('what is 198.51.100.47', 'is that laptop on the network', 'which SSID is this MAC on'), which makes the use case unambiguous. It does not explicitly name an alternative tool or state when not to use it (e.g., when raw per-table output is wanted), so it stops short of full routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
find_referencesFind what references an objectARead-onlyIdempotent
Find what references an address, service, or interface, before changing it.
This answers the question that precedes every firewall change, which is whether something is safe to touch.
The authority is the appliance itself. FortiOS exposes the same
reference lookup its web UI uses, which knows every table that can hold
a reference, seventy-four of them for a firewall address on 7.0.14. This
tool asks that endpoint and reports what it says in references. It
also scans policies, groups, virtual IPs, and static routes directly,
because those yield readable detail the endpoint does not, such as a
policy's name and action.
Read verdict rather than inferring from a count:
referenced, something points at itno_references, the appliance confirmed nothing doesno_references_in_checked_scopes, the authoritative lookup was unavailable and a partial scan found nothing, which is a fact about four tables rather than about the applianceobject_not_found, no address, group, service, virtual IP, or interface by this name exists, so the question is probably a typoindeterminate, something needed could not be read
safe_to_delete appears only for the first two, because a table this
tool could not read cannot support a claim that nothing references the
object. A denied read is the likely outcome for a correctly
least-privileged token, so an incomplete answer is normal rather than
exceptional, and sources_checked names what failed.
The two lists count different things, and will disagree without being
in conflict. total_references and references count reference
sites: a policy using one address as both its source and its
destination is two. The detail lists (policies, groups, vips,
routes) count objects, so the same policy appears once there, with
referenced_as naming both roles. Neither number is wrong; prefer
references when reporting what must be changed before a delete, and
the detail lists when naming the objects an operator has to open.
Containers are walked through, and the results are kept separate.
references holds what the appliance named directly and every row
carries depth: 0. transitive_references holds what was reached
through an address group, a service group, a zone, or a switch: each
row carries the depth it was found at and a via chain of the
container names that led to it. The distinction is the remedy. A direct
reference is removed from the object holding it; a transitive one is
removed by editing a container or a member list, and the policy that
stops matching is not the object you edit.
expansion reports how far the walk got. status is complete when
the chain ran out, depth_capped when it hit the ceiling with
containers still unopened, which are then named in unexpanded, and
incomplete when something along the way could not be read. Only
complete means the transitive list is the whole blast radius.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to search. Defaults to the one configured for this target. An object with the same name can exist in several vdoms, and this answer is about one of them. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| object_name | Yes | Exact name of the address, group, service, virtual IP, or interface. Matching is exact, not a search. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With annotations already declaring read-only, idempotent, and open-world behavior, the description adds rich operational context: the authoritative endpoint, supplementary direct scans, verdict semantics, when safe_to_delete is available, expected denied reads, the distinction between reference sites and objects, transitive reference handling, and expansion completeness. This is substantially more than the annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but well-structured with front-loaded purpose and bulleted verdict explanations. Every section appears to serve a distinct need for interpreting this tool's complex output, though some of the prose could be tightened without losing information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity of the reference lookup, the need to interpret verdicts and counts, and the existence of an output schema, the description is complete enough for an agent to call and interpret the tool correctly. It covers when to use it, how results are assembled, and how to read the return fields, leaving no critical gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all three parameters (object_name, vdom, target) are already documented in the schema. The description adds no further parameter-level detail such as matching syntax, vdom semantics, or target selection beyond what the schema states, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Find') and resource ('what references an address, service, or interface') and frames the tool's purpose as answering a pre-change safety question. It is clearly distinct from sibling list_* tools and search_config, which enumerate or search configurations rather than resolving references.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says when to use ('before changing it') and what question it answers ('whether something is safe to touch'). However, it does not name alternative tools (e.g., search_config) or state when not to use this tool, leaving some routing inference to the agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_arp_tableShow the ARP tableARead-onlyIdempotent
Show the ARP table, which is the IP-to-MAC bindings the appliance sees.
ARP catches devices DHCP does not, meaning anything with a static address, so it is the fallback when a device is present but holds no lease. MAC addresses are reported in one canonical form.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum rows to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| interface | No | Keep only entries learned on this interface, matched exactly. A name that matches no interface empties the list, so check `filtered_out` before reading an empty result as an empty ARP table. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds useful behavioral context by noting MAC addresses are reported in one canonical form, which is not in the structured fields. It does not mention pagination or rate limits, but the bar is lower due to annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences, front-loaded with the core purpose and immediately followed by the key usage distinction. Every sentence adds value: definition, comparison to DHCP, and a note on MAC address format.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only table listing tool with a rich output schema, full parameter coverage, and comprehensive annotations, the description covers purpose, usage context, and a behavioral detail (canonical MAC form). Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the schema already documents all five parameters thoroughly, including defaults, limits, and the interface filter caveat. The description adds no parameter-specific information beyond what the schema provides, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Show) and resource (ARP table), then immediately defines what the ARP table is (IP-to-MAC bindings). It is clearly distinguishable from siblings like list_dhcp_leases because the description explains ARP catches devices DHCP does not.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides a clear usage context: 'ARP catches devices DHCP does not... so it is the fallback when a device is present but holds no lease.' This implicitly tells the agent when to prefer this tool over list_dhcp_leases, though it does not explicitly name that sibling or state exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_routing_tableShow the active routing tableARead-onlyIdempotent
Show the active IPv4 routing table as the appliance is forwarding it.
This is live state rather than configuration, so it includes connected routes, dynamically learned routes, and routes handed over by DHCP, none of which appear in the static route configuration.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum rows to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| protocol | No | Filter by route type, such as static, connect, or dhcp. Matched exactly; `filtered_out` reports how many rows it removed. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, and openWorld, so the safety profile is covered. The description adds real behavioral content beyond that: it discloses that the result is live state including connected, dynamically learned, and DHCP-delivered routes that never appear in static config, which tells the agent what to expect in the payload.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no waste, with the core scope ('live state, not configuration') front-loaded immediately after the purpose statement. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and annotations covering safety and idempotency, the description only needs to frame what the tool returns and when it differs from config-oriented siblings, which it does. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with vdom, limit, offset, target, and protocol all documented in-schema, so the baseline is 3. The description adds no syntax or format detail beyond what the schema already supplies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Show the active IPv4 routing table') and pins down the scope as live forwarding state rather than config. It never names the obvious sibling list_static_routes, so the differentiation from that tool is left implicit rather than explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The contrast between 'live state' and 'configuration' implies when this tool applies versus the static-route siblings, but no alternative tool is named and no when-not guidance is given. Usage is inferable rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_system_statusShow appliance identity and healthARead-onlyIdempotent
Report appliance identity and health: model, serial, firmware, load.
The serial and firmware version come from the response envelope rather than the body. FortiOS puts them as siblings of the results object on every cmdb call, and helpers that unwrap straight to results discard them, which is why they appear missing from the API until you read the raw response.
Fields that a given firmware does not report are omitted rather than returned as null, so an absent key means the appliance did not offer the value. FortiOS 7.0.14, for instance, reports no uptime anywhere in the monitor tree.
| Name | Required | Description | Default |
|---|---|---|---|
| target | No | Which FortiGate to query. Optional when only one is configured. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive, open world), yet the description adds substantial non-obvious behavior: serial/firmware live in the response envelope not the body, and unreported fields are omitted rather than null so an absent key is meaningful. This is exactly the kind of quirk disclosure that prevents an agent from misreading the result.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and field list are front-loaded in the first sentence, which is the part that drives selection. The remaining sentences on envelope placement and omitted keys are long and drift into FortiOS API internals, which is informative for interpretation but heavier than strictly needed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so the description rightly avoids explaining return structure, and annotations carry the safety profile. It is nearly complete, only missing any guidance on the optional 'target' parameter's default behavior or when a multi-appliance scenario applies.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single 'target' parameter is fully documented in the schema, so the description carries no additional parameter burden. Baseline 3 applies since the description adds nothing beyond the schema here.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Report') and resource ('appliance identity and health') and then enumerates the concrete fields returned: model, serial, firmware, load. Combined with the title, an agent can immediately distinguish this single-status tool from the surrounding list_* siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: the field list (identity, health) makes the intended situation clear, but there is no explicit when-to-use statement, no prerequisites, and no alternatives named versus siblings like find_device or search_config. Adequate but with a clear gap.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_address_groupsList address groupsARead-onlyIdempotent
List firewall address groups and their members.
Compare count against total_available before concluding anything
about the whole table. When truncated is present this is one page.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum groups to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| name_contains | No | Case-insensitive substring filter on the group name. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint, and destructiveHint=false. The description adds useful behavioral context beyond annotations by explaining that count should be compared to total_available and that truncated indicates a single page, which helps the agent interpret completeness.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with the core purpose and followed by a high-value pagination caveat. No repetition or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has an output schema, so return values need not be explained. Annotations cover safety, the schema covers parameters, and the description supplies the key pagination caveat, making the definition complete for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 100% schema description coverage, the schema already documents all five input parameters. The description adds no input-parameter semantics; the fields it mentions (count, total_available, truncated) are output fields, not inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'List firewall address groups and their members.' It distinguishes itself from generic listing by including group members, but it does not name or compare against closely related siblings like list_address_objects.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives no explicit guidance on when to use this tool versus alternatives such as list_address_objects. The only practical guidance concerns interpreting pagination output, not tool selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_address_objectsList address objectsARead-onlyIdempotent
List firewall address objects, optionally filtered.
Address objects are the named source and destination values that policies reference. Each is reported with its type and a single readable value, so a subnet object shows CIDR, an FQDN object shows the hostname, and a MAC object shows the MAC addresses.
Compare count against total_available before concluding anything
about the whole table. When truncated is present this is one page and
next_offset says where the following one begins.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum objects to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| address_type | No | Exact FortiOS type filter, such as ipmask, fqdn, iprange, geography, or mac. | |
| name_contains | No | Case-insensitive substring filter on the object NAME only, never the value. Searching for a subnet or an IP finds nothing here; search_config is the tool that looks at values. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false and openWorldHint, so the safety profile is covered. The description adds genuine behavioral context beyond that: how each object type is rendered into a single readable value, and how pagination flags (truncated, next_offset, count vs total_available) behave. Some of this overlaps with the output schema, which keeps it from a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the purpose in the first clause, then spends its remaining sentences on information an agent actually needs (value rendering, pagination interpretation). Three short blocks with no filler, though the pagination paragraph is slightly verbose for what it conveys.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only list tool with full annotation coverage, 100% schema description coverage, and an output schema, the description fills the remaining gaps: what an address object is, how values are surfaced, and how to read pagination metadata. Nothing an agent needs to call or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all six parameters (including the vdom/target/limit/offset semantics and the name_contains-vs-values distinction) are already documented in the schema. The description itself only alludes to filtering generically. Baseline 3 is appropriate when the schema carries parameter detail.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('List firewall address objects, optionally filtered') and then defines the resource domain by explaining that address objects are the named source/destination values policies reference, which separates it from list_address_groups and list_services. The type-to-value mapping ('a subnet object shows CIDR, an FQDN object shows the hostname') further pins down what this tool returns versus other list_* siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete interpretive guidance for this tool's output ('Compare count against total_available before concluding anything about the whole table'), which tells the agent how to use the results correctly. It does not, however, state when to reach for a sibling such as search_config or list_address_groups in the description body itself. Clear context, but no explicit exclusions or alternatives at the description level.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_dhcp_leasesList DHCP leasesARead-onlyIdempotent
List current DHCP leases issued by the appliance.
Read filtered_out before concluding anything from a short list. Both
filters here drop rows without saying so otherwise, and an interface
name with a typo produces an empty list that looks exactly like an
appliance handing out no leases.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum rows to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| interface | No | Keep only leases issued on this interface, matched exactly. | |
| hostname_contains | No | Case-insensitive substring filter on the hostname, falling back to the vendor class identifier when the client sent no name. A lease with neither is dropped by this filter. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnly, idempotent, non-destructive), yet the description adds genuinely new behavioral facts: both filters silently drop rows, and a mistyped interface yields an empty result indistinguishable from an appliance issuing no leases. That is exactly the kind of hidden-behavior disclosure annotations cannot express.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, purpose front-loaded, then the single most consequential caveat. No filler and no repetition of schema content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a low-complexity, all-optional list tool with an output schema and full annotation coverage, the description supplies everything an agent needs: what it returns, how filters can mislead, and which response field to check before drawing conclusions.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description goes beyond the schema by cross-referencing filtered_out and warning that both filter parameters drop rows without signalling it. It adds interpretive meaning the parameter descriptions alone do not convey, though it does not touch limit/offset/target semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence states a specific verb and resource ('List current DHCP leases issued by the appliance'), which is enough for an agent to distinguish it from siblings like list_wifi_clients or get_arp_table. It stops short of explicitly contrasting with any alternative, but the resource is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear operational guidance for interpreting results ('Read filtered_out before concluding anything from a short list') and explains the trap of a typo'd interface name producing an empty list. It does not, however, name a sibling tool or say when a different listing tool would be the better choice.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_interfacesList interfacesARead-onlyIdempotent
List interfaces with their addresses, VLAN tags, and link state.
FortiOS creates bookkeeping interfaces alongside real ones, such as the
quarantine interface that accompanies every wireless VAP. Those are
hidden by default and named in hidden_internal so the omission is
visible.
Which ones count as bookkeeping is decided by the appliance rather than
by the name. FortiOS reports whether each interface is offered as a
policy endpoint, and anything it offers is shown regardless of what it
is called, so this tool cannot hide an interface a policy could name.
hidden_internal_basis says whether that lookup succeeded; when it did
not, the fallback is the name and the omission is less trustworthy.
Filters narrow the list silently unless you read filtered_out, which
names each active filter and how many rows it removed. with_ip_only
keeps interfaces carrying an IPv4 address in the configuration, which
includes a DHCP interface that has taken a lease, because FortiOS writes
the leased address back into the config.
An interface that holds no address reports addressing instead of ip.
Use get_routing_table for the runtime view.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum interfaces to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| with_ip_only | No | Keep only interfaces carrying an IPv4 address in the configuration. | |
| interface_type | No | Exact FortiOS type filter, such as physical, vlan, aggregate, hard-switch, switch, or tunnel. Not a substring match. | |
| include_internal | No | Include FortiOS-generated interfaces, meaning those the appliance does not offer as policy endpoints and whose names carry a generated prefix such as wqtn., vap., ssl., or naf. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover only the safety profile (readOnly/idempotent/destructive/openWorld); the description adds substantial behavioral context: bookkeeping interfaces hidden by default, the appliance-driven rather than name-driven hiding rule, the hidden_internal_basis fallback and when the omission is less trustworthy, and silent filtering disclosed via filtered_out. This is far beyond what the structured fields convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first sentence and every paragraph carries distinct information. It is dense and longer than typical, with several sentences devoted to output-field semantics rather than the call itself, but little is truly redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a seven-parameter listing tool with an output schema and rich annotations, this is complete: it covers identity, scoping, hidden-by-default behavior, filter transparency, the address-field quirk, and the sibling alternative. An agent could call this correctly without reading anything else.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning the schema lacks: it clarifies that with_ip_only counts a DHCP interface holding a leased address, and explains why interfaces without an address report `addressing` instead of `ip`. It does not touch limit/offset/interface_type, which the schema already covers well.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource ('List interfaces') and immediately names what is returned: addresses, VLAN tags, link state. It also distinguishes itself from get_routing_table by scoping itself to configuration rather than the runtime view.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent to get_routing_table for the runtime view and explains when include_internal and with_ip_only are relevant. It stops short of a full 'use this vs. list_vlans' disambiguation, but the runtime/configuration split is a clear contextual guide.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_policiesList firewall policiesARead-onlyIdempotent
List firewall policies in evaluation order, optionally filtered.
Policies come back in the order FortiOS evaluates them, and each carries
an explicit order index so that ordering survives filtering and
re-serialization. Order is the entire meaning of a ruleset, and a list
position is not something downstream is obliged to preserve.
Three counts appear and they mean different things. count is rows in
this page, total_available is rows matching your filters, and
total_policies is the size of the whole ruleset. A truncated flag
means this is one page of the matches, not all of them, which matters
more here than elsewhere: reasoning about a ruleset from an arbitrary
prefix of it produces confident wrong answers about what traffic is
allowed.
The filters match names exactly and do not expand indirection. A policy
referencing a group that contains your address will not match address,
and a policy referencing a zone that contains your interface will not
match interface. If filtered_out shows a filter removed everything,
that is the cue: the object is probably reached through a group or a
zone, and find_references answers "what touches this object" properly,
walking those containers.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum policies to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large ruleset. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| address | No | Keep only policies naming this address object or group directly, on either side. | |
| service | No | Keep only policies naming this service object directly. | |
| interface | No | Keep only policies naming this interface directly as a source or destination interface. | |
| enabled_only | No | Drop policies whose status is disabled. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnly, idempotent, non-destructive), so the bar is lower, yet the description still discloses non-obvious behavior: the meaning of the three distinct counts, the `truncated` paging flag and why prefix-based reasoning about a ruleset is dangerous, and the fact that filters match names exactly and do not expand indirection.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short paragraphs, each front-loaded with its point (ordering, counts, filter semantics, fallback to find_references), and no filler sentences. It is longer than average but every paragraph carries distinct, actionable information, so it stops just short of maximal conciseness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, yet the description explains the counts and truncation flag that an agent must interpret to avoid confident wrong answers. Combined with filtering caveats and the find_references fallback, nothing needed to call this tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description adds real semantic content the schema lacks: that `address`/`interface`/`service` filters match only direct references and will not match objects reached through a group or zone. That changes how an agent interprets a null result.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List firewall policies') and immediately adds the decisive scope detail: results come back in FortiOS evaluation order with an explicit `order` index. It is clearly separable from siblings like list_address_objects or find_references.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use context and names the alternative: when `filtered_out` shows the filter removed everything, the object is likely reached through a group or zone and 'find_references answers "what touches this object" properly'. That is a concrete condition plus a named sibling, not inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_servicesList servicesARead-onlyIdempotent
List firewall service objects with their protocols and ports.
Compare count against total_available before concluding anything
about the whole table. When truncated is present this is one page.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum services to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| name_contains | No | Case-insensitive substring filter on the service name. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so safety is covered. The description adds genuine behavioral context beyond that: it warns that the response may be paginated and tells the agent to compare count against total_available and to treat a truncated field as a single-page signal, which prevents premature conclusions about the whole table.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences: the purpose is front-loaded and the pagination caveat follows immediately, with zero filler. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value explanation is not required, and the description appropriately focuses on the pagination fields (count, total_available, truncated) that matter for correct use. It is nearly complete, though it never explains that the tool requires no required parameters or how offset interacts with limit for paging.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so every parameter (vdom, limit, offset, target, name_contains) is already documented, including the 200 default and 1000 cap. The description adds no additional parameter syntax or format detail, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'List firewall service objects with their protocols and ports,' which also hints at what the returned objects contain. It distinguishes itself implicitly from siblings like list_vips or list_policies by resource name, but never explicitly names an alternative or scoping boundary.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use guidance or routing to an alternative sibling tool (e.g., search_config or find_references) for drilling into services. The sentences about count/total_available and truncated are about interpreting results, not about when this tool is the right choice over another.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_static_routesList configured static routesARead-onlyIdempotent
List configured static routes.
These are the routes an operator configured, which is not the same as the routes in use. A DHCP-assigned default route never appears here because it was never configured. Use get_routing_table for what the appliance is actually forwarding on.
A route whose destination is a named address object is resolved to the underlying CIDR where possible, with the object name kept alongside it.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. Routes are per-vdom, so this changes the answer on a multi-VDOM appliance. The `vdom` field names the one actually read. | |
| limit | No | Maximum rows to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare readOnlyHint=true, idempotentHint=true, openWorldHint=true, and destructiveHint=false, covering the safety profile. The description adds useful behavioral context beyond that: configured routes are not necessarily routes in use, DHCP default routes never appear, and named address objects are resolved to CIDRs with the object name preserved. It does not discuss authentication or rate limits, but for a read-only list operation the added data-scoping behavior is substantial.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose, then adds the distinction from get_routing_table and the object-resolution behavior in a logical order. Every sentence carries useful information without redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema, full schema descriptions for all four optional parameters, and annotations covering safety and idempotency, the description only needs to add context that the structured fields cannot convey. It does so by clarifying the configured-vs-in-use distinction, the DHCP exclusion, and the address-object resolution behavior, leaving no essential gap for an agent to call the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents vdom, limit, offset, and target thoroughly. The description adds no parameter-level meaning beyond what the schema provides, which makes the baseline score of 3 appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb and resource ('List configured static routes') and immediately distinguishes it from the sibling get_routing_table by explaining the configured-vs-in-use difference. It also excludes DHCP-assigned default routes, so an agent can tell exactly what this tool returns versus what it does not.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly names the alternative tool and the condition that selects it: 'Use get_routing_table for what the appliance is actually forwarding on.' It further clarifies when a route will not appear here (DHCP-assigned default routes), which is a clear when-not-to-use signal.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_targetsList configured FortiGatesARead-onlyIdempotent
List the FortiGate appliances this server can reach.
Call this first when several appliances may be configured, since every other tool takes an optional target argument naming one of these. When exactly one is configured, that argument can be omitted entirely.
Credentials are never included in the response.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already establish read-only, idempotent, non-destructive behavior, but the description adds useful context beyond them: credentials are never included in the response, and this tool orients later calls by discovering valid target names. It does not discuss rate limits or auth requirements, but for a parameterless list operation with output schema present, the disclosure is strong.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short sentences, front-loaded with the core purpose, then the operational guidance. Every sentence earns its place without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a parameterless discovery tool with annotations and an output schema, the description covers the essential context: what it lists, when to call it, how its results relate to other tools, and a key privacy guarantee. Nothing needed for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With zero parameters, the baseline is 4. The description does not need to explain parameters for this tool, though it helpfully explains that other tools take an optional target argument derived from this tool's results.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'List the FortiGate appliances this server can reach.' It clearly distinguishes this discovery/orientation tool from the many sibling list_* tools, which list configuration objects rather than reachable appliances.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says to call this first when several appliances may be configured, explains why (every other tool takes an optional target argument), and clarifies that the target argument can be omitted when exactly one appliance is configured. This gives direct when-to-use guidance and the condition for the alternative behavior.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_vipsList virtual IPsARead-onlyIdempotent
List virtual IPs, which are the destination NAT rules.
A VIP maps an external address, and optionally an external port, to an internal one. FortiOS stores those addresses inline on the VIP rather than as references to address objects.
Compare count against total_available before concluding anything
about the whole table. When truncated is present this is one page.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum VIPs to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive/openWorld, so the safety profile is covered. The description earns credit by adding real behavioral context the annotations lack: how to read `count` vs `total_available` and what `truncated` means for paging.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads what a VIP is, then the storage detail, then the pagination caveat; no filler. Slightly more sprawling than a single tight sentence, but each clause carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and annotations covering safety, the description supplies the only missing piece — pagination semantics — so an agent can call it correctly. It stops short of explicit routing advice against the many sibling list_* tools.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so vdom, limit, offset and target are already fully documented in the schema. The description adds no parameter-level syntax or constraint beyond that, which is the correct baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb+resource ("List virtual IPs") plus domain grounding: "destination NAT rules" that map an external address/port to an internal one. This is enough to separate it from siblings like list_address_objects and list_policies without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the resource described, and the pagination note tells the agent when the result is partial, but no sibling tool is named as an alternative and no when-not-to-use condition is given. Adequate but with a clear gap.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_vlansList VLANsARead-onlyIdempotent
List VLAN sub-interfaces with their tags and parent interfaces.
A focused view of the VLAN subset of the interface table, since what
VLANs exist and what they attach to is asked far more often than the full
interface list. FortiOS quarantine VLANs are excluded, and filtered_out
says how many that was.
| Name | Required | Description | Default |
|---|---|---|---|
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum rows to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds valuable behavioral context beyond annotations: FortiOS quarantine VLANs are excluded, and a filtered_out count reports how many were omitted. It does not mention pagination behavior, but the schema already documents limit and offset.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences and front-loads the core purpose before explaining the rationale and exclusion behavior. It is efficient and avoids repetition, though the second sentence carries some explanation that could be trimmed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a full output schema, four fully documented parameters, and complete annotations, the description does not need to explain return values or parameter formats. It adds the key domain-specific caveat about quarantine VLAN exclusion and filtered_out. The only minor gap is explicit guidance on sibling selection, which would make usage unambiguous.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all four parameters are fully documented in the input schema including defaults and caps. The description does not add parameter syntax, format, or semantics beyond the schema, which is the expected baseline when the schema does the work.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: list VLAN sub-interfaces with their tags and parent interfaces. It also distinguishes itself from the closest sibling, list_interfaces, by framing itself as a focused subset. However, it does not explicitly name list_interfaces or list_vlans alternatives, leaving a small gap.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives implied usage: it explains that this is a focused view because VLANs are asked about more often than the full interface list. But it never states when an agent should choose list_vlans instead of list_interfaces or any other sibling, nor any prerequisites such as required target or vdom context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_wifi_clientsList connected wireless clientsARead-onlyIdempotent
List wireless clients currently associated, enriched with DHCP and ARP.
Each client is joined against the DHCP lease and ARP tables by MAC, which is what turns an anonymous MAC into a recognizable device. The hostname comes from the DHCP lease, falling back to the vendor class identifier when the client sent no name. MAC addresses are reported in one canonical lowercase colon-separated form whatever spelling the source used, since that is what makes the join work at all.
authenticated is true or false only when the appliance said so, and
absent when it did not, because inferring "not authenticated" from a
missing field would fabricate a security-relevant claim.
| Name | Required | Description | Default |
|---|---|---|---|
| ssid | No | Keep only clients associated to this SSID, matched exactly. `filtered_out` reports how many clients it removed. | |
| vdom | No | Virtual domain to read. Defaults to the one configured for this target. The `vdom` field in the response names the one actually read. | |
| limit | No | Maximum rows to return. Defaults to 200, capped at 1000. | |
| offset | No | Index to start from, for paging through a large table. | |
| target | No | Which FortiGate to query. Optional when only one is configured. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover the safety profile (readOnly, idempotent, openWorld), so the description carries the behavioral load and does so well: it explains the MAC-based join, the hostname fallback to vendor class identifier, MAC canonicalization, and the tri-state `authenticated` field where absence is deliberately not treated as false to avoid fabricating a security claim.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the verb and resource, and the multi-paragraph structure separates data-provenance semantics from the field-level caveat. Slightly verbose on MAC canonicalization, but every sentence carries substantive information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and full annotations, the description need not explain return values, and it complements them with the provenance and null-semantics an agent needs. The only real gap is guidance on when to prefer this over the DHCP-lease or ARP siblings.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so ssid, vdom, limit, offset and target are already fully documented in the schema. The description adds no parameter-level format or syntax detail, so the baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List wireless clients currently associated') and immediately qualifies the scope with the DHCP/ARP enrichment, which cleanly distinguishes it from siblings like list_dhcp_leases and get_arp_table. An agent can route to it without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The enrichment explanation implies when this is preferable to a raw MAC listing, but there is no explicit when-to-use or when-not-to-use statement naming alternatives among the many list_* siblings. Usage is inferable rather than stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_configSearch the whole configurationARead-onlyIdempotent
Search the whole configuration for a term, across every object type.
Looks through address objects and groups, services, interfaces, static routes, and optionally policies, matching names, values, comments, and member lists. This is the tool for an open question such as "where does 203.0.113.0/24 appear" or "what mentions guest", when you do not yet know which kind of object holds the answer.
Check sources_checked before concluding from a zero result. A source
that could not be read contributes no matches, so no matches is not the
same as nothing found.
| Name | Required | Description | Default |
|---|---|---|---|
| term | Yes | Case-insensitive substring to look for. | |
| target | No | Which FortiGate to query. Optional when only one is configured. | |
| include_policies | No | Search policy names, comments, and member lists. Policies are the largest table, so this can be turned off when only object definitions matter. |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnlyHint, idempotentHint, destructiveHint=false, openWorldHint), so the bar is lower, yet the description adds a genuinely non-obvious caveat: a source that could not be read contributes no matches, so an empty result is not proof of absence and sources_checked must be inspected. It also discloses what fields are matched (names, values, comments, member lists) and the case-insensitive substring semantics.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and scope, then the object coverage, then the critical zero-result caveat. The coverage sentence and the open-question sentence overlap slightly in function, so it is efficient but not maximally tight.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With three well-documented parameters, an output schema, and rich annotations, the description supplies exactly the missing piece: how to interpret a zero result via sources_checked. Nothing an agent needs to invoke it or read its output correctly is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already explains term, target, and the include_policies tradeoff in full detail. The description only echoes the optional-policies behavior and adds no new syntax, format, or defaulting meaning beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (search) and scope (the whole configuration, across every object type), then enumerates the covered domains: address objects and groups, services, interfaces, static routes, and optionally policies. An agent can distinguish this cross-cutting search from the enumerating siblings (list_address_objects, list_policies, list_static_routes) without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly frames the use case as the open-ended question ('where does 203.0.113.0/24 appear', 'what mentions guest') when the object type is unknown, which implicitly excludes the list_* siblings that require knowing the type. It also gives a concrete tuning rule: drop include_policies when only object definitions matter.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
17 tool updates
v0.1.0- First observed
find_device - First observed
find_references - First observed
get_arp_table - First observed
get_routing_table - First observed
get_system_status - First observed
list_address_groups - First observed
list_address_objects - First observed
list_dhcp_leases - First observed
list_interfaces - First observed
list_policies - First observed
list_services - First observed
list_static_routes - First observed
list_targets - First observed
list_vips - First observed
list_vlans - First observed
list_wifi_clients - First observed
search_config
TDQS
Scored across 17 tools
Each tool targets a clearly distinct FortiGate resource or action. Overlaps such as list_interfaces vs list_vlans, find_device vs the individual client/lease/ARP lists, and find_references vs search_config are explicitly distinguished in the descriptions.
All tools follow a consistent snake_case verb_noun pattern: list_*, get_*, find_*, search_*. The verb choice accurately reflects the operation, with no mixing of conventions.
17 tools is slightly above the typical 3-15 range, but each covers a distinct inspection surface for FortiGate appliances. A few could be consolidated (e.g. list_vlans as a filtered list_interfaces), but the count is reasonable for the domain breadth.
The set provides broad read-only coverage of firewall policies, addresses, services, interfaces, routes, references, and client/lease/ARP data. Some useful read surfaces are missing (logs, security profiles, zones/schedules, single-object detail beyond list filters), and mutation operations appear intentionally out of scope.
Maintenance
Related MCP Connectors
Read-only MCP access to a documented IT fleet: state, changes, posture. 15 tools.
XFA's remote MCP server — query device posture, compliance, policies & CVEs. Read-only.
- mcpOAuthcom.vibgrate
Query your team's drift, vulnerability, and upgrade data from any AI assistant. OAuth 2.1, 51 tools.
Read-only AI coding tools for change verification, release readiness, capacity, and guidance.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceProvides real-time access to Firewalla firewall data through 28 specialized tools for network monitoring, security analysis, bandwidth tracking, and firewall rule management. Enables users to query security alerts, analyze network flows, monitor device status, and manage firewall configurations through natural language.451-
- AlicenseNot gradedqualityDmaintenanceEnables read-only querying and diagnostics of Fortigate firewalls via SSH, providing security analysis, traffic monitoring, and configuration inspection through natural language.MIT
- AlicenseAqualityBmaintenanceProvides read-only SSH access to network devices (routers, switches, firewalls) with command allow/deny policies, nt-templates output parsing, and an audit trail, enabling an AI agent to query device state securely.10MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI assistants to manage FortiGate firewalls from bootstrap to production, with safe preview/apply changes and secure credential handling. Covers inventory, VDOMs, interfaces, routing, firewall policies, VPN, security analysis, and documentation generation.MIT