FortiManager MCP Server
FortiManager MCP Server
A Model Context Protocol (MCP) server for FortiManager JSON-RPC API. This server enables AI assistants like Claude to interact with FortiManager for centralized firewall policy management, device provisioning, and network configuration.
Note: This is an independent open-source project and is not affiliated with, endorsed by, or supported by Fortinet, Inc. FortiManager is a trademark of Fortinet, Inc.
Disclaimer: This MCP server can create, modify, and delete configurations on FortiManager. Misuse or misconfiguration can impact production networks. Use at your own risk. Always test in a non-production environment first and ensure appropriate ADOM permissions are configured.
Overview
This MCP server provides a comprehensive interface to FortiManager's capabilities, allowing AI assistants to:
Create and manage firewall policies and policy packages, including security-profile (UTM) inspection
Configure firewall objects (addresses, services, VIPs)
Add, provision, and manage FortiGate devices
Configure device-DB interfaces, DHCP scopes, and wireless (VAPs, FortiAP/WTP profiles, managed APs)
Execute CLI scripts on managed devices
Configure provisioning and SD-WAN templates
Monitor tasks and installations
Manage ADOMs and workspace locking
Features
Category | Capabilities |
Policy Management | Create/update/delete firewall policies, manage policy packages, clone packages, security-profile (UTM) inspection fields |
Object Management | Addresses, address groups, services, service groups, search objects |
Device Management | Add/delete devices, bulk operations, device status, VDOM management |
Device Configuration | Interfaces/VLAN subinterfaces, DHCP scopes, wireless VAPs/SSIDs, FortiAP (WTP) profiles and managed AP registration -- all via the device DB, credential fields stripped from every read |
Script Execution | Create/run CLI scripts, execute on devices/groups, view execution logs |
Templates | System templates, CLI template groups, template assignment and validation |
SD-WAN | SD-WAN templates, rule configuration, template assignment |
System | System status, ADOM management, task monitoring, workspace locking |
Requirements
Python: 3.12 or higher
FortiManager: 7.x with JSON-RPC API access enabled
Authentication: API token (recommended) or username/password
Network: HTTPS access to FortiManager management interface
Installation
Using uv (Recommended)
# Clone the repository
git clone https://github.com/rstierli/fortimanager-mcp.git
cd fortimanager-mcp
# Create and activate virtual environment
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
uv syncUsing pip
# Clone the repository
git clone https://github.com/rstierli/fortimanager-mcp.git
cd fortimanager-mcp
# Create virtual environment
python -m venv .venv
source .venv/bin/activate
# Install package
pip install -e .Using Docker
Pre-built images are available on GitHub Container Registry:
docker pull ghcr.io/rstierli/fortimanager-mcp:latestQuick start with Docker Compose:
# docker-compose.yml
services:
fortimanager-mcp:
image: ghcr.io/rstierli/fortimanager-mcp:latest
container_name: fortimanager-mcp
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- .env
environment:
- MCP_SERVER_MODE=http
- MCP_SERVER_HOST=0.0.0.0
- MCP_SERVER_PORT=8000
- FORTIMANAGER_HOST=your-fmg-hostname
# Keep TLS verification on; import the FortiManager CA for self-signed
# certs. FORTIMANAGER_VERIFY_SSL=false disables MITM protection.
- FORTIMANAGER_VERIFY_SSL=true
- DEFAULT_ADOM=root
- FMG_TOOL_MODE=full
- LOG_LEVEL=INFOCreate a .env file for secrets (not tracked in git):
# .env
FORTIMANAGER_API_TOKEN=your-api-token
MCP_AUTH_TOKEN=your-secret-bearer-token # optional, enables HTTP authchmod 600 .env
docker compose up -dVerify the server is running:
curl http://localhost:8000/health
# {"status": "healthy", "service": "fortimanager-mcp", "fortimanager_connected": true}Configuration
Environment Variables
Create a .env file from the example:
cp .env.example .envEdit .env with your FortiManager settings:
# FortiManager Connection (Required)
FORTIMANAGER_HOST=192.168.1.100
# Authentication Option 1: API Token (Recommended)
FORTIMANAGER_API_TOKEN=your-api-token-here
# Authentication Option 2: Username/Password
# FORTIMANAGER_USERNAME=admin
# FORTIMANAGER_PASSWORD=your-password
# SSL Verification — keep this TRUE. For self-signed FortiManager certs,
# import the FortiManager CA into your trust store instead of disabling it
# (see docs/SETUP_GUIDE.md "Trusting the FortiManager CA"). Setting this to
# false disables TLS verification and exposes the connection to MITM attacks.
FORTIMANAGER_VERIFY_SSL=true
# Request Settings
FORTIMANAGER_TIMEOUT=30
FORTIMANAGER_MAX_RETRIES=3
# Logging
LOG_LEVEL=INFO # DEBUG for troubleshooting
# Tool Loading Mode (important for context window optimization)
FMG_TOOL_MODE=full # or "dynamic" for ~90% context reduction
# Default ADOM (optional - defaults to "root")
DEFAULT_ADOM=root
# HTTP Authentication (optional, recommended for Docker/HTTP deployments)
# MCP_AUTH_TOKEN=your-secret-token
# MCP Server Settings (for HTTP/Docker mode)
# MCP_SERVER_MODE=http # "http" for Docker, "stdio" for Claude Desktop, "auto" to detect
# MCP_SERVER_HOST=0.0.0.0 # Bind address (0.0.0.0 for Docker)
# MCP_SERVER_PORT=8000 # Server port
# Allowed Host headers for HTTP/Docker deployments (optional)
# Set to the value clients use in their connection URL — NOT the client's IP.
# The MCP SDK rejects non-localhost Host headers by default for DNS rebinding protection.
# Examples: ["mcp.example.com"], ["10.1.5.62:8000"], or wildcard ["10.1.5.62:*"]
# MCP_ALLOWED_HOSTS=["mcp.example.com"]
# Streamable HTTP transport mode (optional - stateful by default)
# Set true behind a load balancer / multiple replicas, or a proxy that does not
# preserve the Mcp-Session-Id header, so each request is handled independently.
# MCP_STATELESS_HTTP=true
# HTTP request bounds (optional - bounded by default)
# MCP_MAX_REQUEST_BYTES=10485760 # Max request body; oversize gets 413. 0 disables.
# MCP_MAX_CONCURRENT_REQUESTS=64 # Max in-flight requests; excess gets 503. 0 disables.
# Reversible data masking (optional - off by default, issue #34)
# Masks IOC-bearing VALUES in tool outputs (IPs, subnets, serials, FQDNs,
# admin usernames). Names (object/ADOM/package/VDOM/device) route calls
# and are never masked. Masked values are read-only context: a token sent
# back as a tool argument is refused, so create and modify with real
# values. Requires 32/48/64 hex chars; enabling without a key aborts
# startup. Shares token compatibility with the FortiAnalyzer sibling when
# both use the same key, which also means one shared blast radius.
# MASKING_ENABLED=false
# FMG_MASKING_KEY=
# Safety Guardrails (optional - strict by default)
# FMG_SCRIPT_SAFETY=strict # Block dangerous CLI commands in scripts (factory-reset, reboot, etc.)
# FMG_POLICY_SAFETY=strict # Block overly permissive policies (srcaddr=all + dstaddr=all + accept)Tool Loading Modes
FortiManager MCP supports two tool loading modes to optimize context window usage:
Mode | Tools Loaded | Context Usage | Best For |
| All 232 tools | ~100% | Large context windows, full functionality |
| 4 discovery tools | ~10% | Smaller context windows, on-demand loading |
Full Mode (default): All 232 tools are loaded at startup. Best when you have sufficient context window and need immediate access to all FortiManager operations.
Dynamic Mode: Only lightweight discovery tools are loaded:
find_fortimanager_tool(operation)- Search for tools by keywordlist_fortimanager_categories()- List tool categoriesexecute_fortimanager_tool(name, params)- Execute any tool by namehealth_check()- Server health status
To enable dynamic mode:
FMG_TOOL_MODE=dynamicDefault ADOM
The DEFAULT_ADOM environment variable sets the default Administrative Domain (ADOM) for all FortiManager operations. When a tool is called without specifying an ADOM, this value is used.
DEFAULT_ADOM=root # default valueThis is particularly useful when:
Your FortiManager only uses a single ADOM
Most of your work is within one specific ADOM
You want to avoid repeatedly specifying the ADOM in each tool call
If not set, defaults to root (the global ADOM).
Default Device
The DEFAULT_DEVICE environment variable sets a fallback managed device for
device-scoped tools (e.g. get_device_client_location,
get_device_interface_config, get_device_sdwan_monitor). When such a tool is
called without a device, this value is used.
DEFAULT_DEVICE=myfw01 # unset by defaultIt is unset by default because there is no universal device name. Setting it is
recommended for single-FortiGate deployments (and when driving the tools from an
LLM, which may omit device if it treats it as an implied default) — the tool
then resolves to DEFAULT_DEVICE instead of erroring. If neither a device
argument nor DEFAULT_DEVICE is provided, the tool returns a clear
device_required error.
Generating an API Token
Log into FortiManager web interface
Go to System Settings > Admin > Administrators
Edit your admin user or create a new one
Under JSON API Access, click Regenerate or New API Key
Copy the generated token
Running the Server
Standalone Mode
# Using the installed command
fortimanager-mcp
# Or using Python module
python -m fortimanager_mcpClaude Desktop Integration
Add to your Claude Desktop configuration file:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"fortimanager": {
"command": "/path/to/fortimanager-mcp/.venv/bin/fortimanager-mcp",
"env": {
"FORTIMANAGER_HOST": "your-fmg-hostname",
"FORTIMANAGER_API_TOKEN": "your-api-token",
"FORTIMANAGER_VERIFY_SSL": "true",
"LOG_LEVEL": "INFO"
}
}
}
}Note: Use the full path to the fortimanager-mcp executable in your virtual environment.
Claude Code Integration
Add to ~/.claude/mcp_servers.json:
{
"mcpServers": {
"fortimanager": {
"command": "/path/to/fortimanager-mcp/.venv/bin/fortimanager-mcp",
"env": {
"FORTIMANAGER_HOST": "your-fmg-hostname",
"FORTIMANAGER_API_TOKEN": "your-api-token",
"FORTIMANAGER_VERIFY_SSL": "true",
"DEFAULT_ADOM": "root",
"LOG_LEVEL": "INFO"
}
}
}
}Docker Mode
# Start the server
docker compose up -d
# View logs
docker compose logs -f
# Stop the server
docker compose downHTTP Mode (Remote Access)
When running in HTTP mode (Docker or standalone with MCP_SERVER_MODE=http), MCP clients connect via the Streamable HTTP transport:
Claude Code (~/.claude/mcp_servers.json):
{
"mcpServers": {
"fortimanager": {
"type": "streamable-http",
"url": "https://your-mcp-host.example.com/mcp",
"headers": {
"Authorization": "Bearer your-mcp-auth-token"
}
}
}
}Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"fortimanager": {
"type": "streamable-http",
"url": "https://your-mcp-host.example.com/mcp",
"headers": {
"Authorization": "Bearer your-mcp-auth-token"
}
}
}
}Production Deployment (Reverse Proxy)
For production deployments behind a TLS-terminating reverse proxy:
MCP Client → HTTPS → Reverse Proxy (Traefik/nginx) → HTTP → MCP Container → FortiManagerKey considerations:
MCP_ALLOWED_HOSTS — The MCP SDK validates the Host header to prevent DNS rebinding attacks. By default only
localhostand127.0.0.1are accepted. Set this to the value clients put in their connection URL (NOT the client's IP):# Reverse-proxy hostname (Traefik/nginx): MCP_ALLOWED_HOSTS=["mcp.example.com"] # Direct Docker exposure on IP+port: MCP_ALLOWED_HOSTS=["10.1.5.62:8000"] # Port wildcard (any port on the host): MCP_ALLOWED_HOSTS=["10.1.5.62:*"]MCP_AUTH_TOKEN — Always set a Bearer token for HTTP deployments:
MCP_AUTH_TOKEN=$(openssl rand -hex 32)Secrets management — Keep API tokens and auth tokens in an
env_file(.env), not inline indocker-compose.yml.MCP_STATELESS_HTTP — When the server runs behind a load balancer or as multiple replicas (or behind a proxy that does not preserve the
Mcp-Session-Idheader), enable stateless mode so each request is self-contained and no sticky sessions are required:MCP_STATELESS_HTTP=trueLeave it unset (stateful, the default) for single-instance deployments. Stateless mode disables server-initiated streaming that relies on a persistent session.
Example with Traefik:
services:
fortimanager-mcp:
image: ghcr.io/rstierli/fortimanager-mcp:latest
container_name: fortimanager-mcp
restart: unless-stopped
security_opt:
- no-new-privileges:true
env_file:
- .env
environment:
- MCP_SERVER_MODE=http
- MCP_SERVER_HOST=0.0.0.0
- MCP_SERVER_PORT=8000
- FORTIMANAGER_HOST=your-fmg-hostname
# Keep TLS verification on; import the FortiManager CA for self-signed
# certs. FORTIMANAGER_VERIFY_SSL=false disables MITM protection.
- FORTIMANAGER_VERIFY_SSL=true
- MCP_ALLOWED_HOSTS=["mcp.example.com"]
- DEFAULT_ADOM=root
- FMG_TOOL_MODE=full
- LOG_LEVEL=INFO
networks:
- frontend
labels:
- "traefik.enable=true"
- "traefik.http.routers.fmg-mcp-secure.entrypoints=https"
- "traefik.http.routers.fmg-mcp-secure.rule=Host(`mcp.example.com`)"
- "traefik.http.routers.fmg-mcp-secure.tls=true"
- "traefik.http.services.fmg-mcp.loadbalancer.server.port=8000"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
frontend:
external: trueAvailable Tools (232 tools)
System Tools (17 tools)
Tool | Description |
| Get FortiManager system status and version info |
| Get High Availability cluster status |
| List all Administrative Domains |
| Get specific ADOM details |
| List devices in an ADOM |
| Get specific device information |
| List device groups in an ADOM |
| List background tasks |
| Get task details by ID |
| Wait for a task to complete |
| List policy packages in an ADOM |
| Get policy package details |
| Install policy package to devices |
| Install device settings only |
| Lock ADOM for editing (workspace mode) |
| Unlock ADOM |
| Commit ADOM changes |
Device Management Tools (14 tools)
Tool | Description |
| List VDOMs for a device |
| Get device connection and sync status |
| Search devices with filters |
| Add a new device to FortiManager |
| Add offline model device |
| Remove a device from FortiManager |
| Add multiple devices at once |
| Remove multiple devices at once |
| Update device metadata |
| Refresh device list cache |
| Get live device status |
| Get device interface information |
| Read device-DB interface config objects, filterable by VLAN id / interface name (maps a client IP to its VLAN/interface/port) |
| Asset Identity Center: locate a client (by ip/mac/hostname) via the live detected-device inventory — resolves the FortiAP/FortiSwitch, port and VLAN it is connected through |
Device Configuration Tools (21 tools)
Typed device-DB configuration (issues #45, #52): everything is staged in FortiManager's
device database and pushed with preview_install + install_device_settings;
nothing talks to the FortiGate directly.
Tool | Description |
| Create a VLAN subinterface (parent, vlanid, ip, allowaccess, role, alias) |
| Update device-DB interface fields |
| Delete a device-DB interface |
| List DHCP server scopes in the device DB |
| Create a DHCP scope (interface, range, netmask, gateway, DNS) |
| Update a DHCP scope by id |
| Delete a DHCP scope by id |
| List wireless VAPs (SSIDs), passphrases stripped |
| Create a wireless VAP/SSID with security mode and VLAN mapping |
| Delete a wireless VAP |
| Add a VAP to FortiAP profile radios so the SSID broadcasts |
| List FortiAP (WTP) profiles in the device DB |
| Get a FortiAP (WTP) profile, radios included |
| Update one radio's |
| List managed FortiAPs ( |
| Get a managed FortiAP by wtp-id (serial number) |
| Register a managed FortiAP (wtp-id, wtp-profile, authorization state) |
| Update a managed FortiAP's profile/name/admin/location/comment |
| Delete a managed FortiAP registration |
| Create a firewall sniffer definition ( |
| Create a bounded, name-keyed on-demand packet sniffer ( |
Policy Tools (25 tools)
create_firewall_policy and update_firewall_policy accept security-profile
(UTM) fields -- utm_status, av_profile, ips_sensor, webfilter_profile,
dnsfilter_profile, application_list, file_filter_profile,
ssl_ssh_profile, profile_protocol_options, profile_group -- so a policy
can actually apply inspection, not just route traffic. profile_group is
mutually exclusive with the individual profile fields it bundles; see
"Security Profile Field Validation" under Safety Guardrails below.
Tool | Description |
| Create a new policy package |
| Delete a policy package |
| Clone an existing package |
| Assign package to devices |
| List policies in a package |
| Get policy details |
| Create a new firewall policy |
| Update an existing policy |
| Delete a firewall policy |
| Bulk delete policies |
| Reorder policy position |
| Search policies with filters |
| Get policy services with optional group resolution |
| Preview installation changes |
| Get preview results |
| Create a new IPv4 local-in policy |
| Create a new IPv6 local-in policy |
| Delete an IPv4 local-in policy |
| Delete an IPv6 local-in policy |
| Get detailed information about a specific IPv4 local-in policy |
| Get detailed information about a specific IPv6 local-in policy |
| List IPv4 local-in policies in a policy package |
| List IPv6 local-in policies in a policy package |
| Update an existing IPv4 local-in policy |
| Update an existing IPv6 local-in policy |
Object Tools (25 tools)
Tool | Description |
| List firewall address objects |
| Get address object details |
| Create subnet address |
| Create host address |
| Create FQDN address |
| Create IP range address |
| Update address object |
| Delete address object |
| List address groups |
| Get address group details |
| Create address group |
| Update address group |
| Delete address group |
| List service objects |
| Get service details |
| Create TCP/UDP service |
| Create ICMP service |
| Update service object |
| Delete service object |
| List service groups |
| Get service group details |
| Create service group |
| Update service group members and comment |
| Delete service group |
| Search all object types |
Script Tools (12 tools)
Tool | Description |
| List CLI scripts in ADOM |
| Get script content and details |
| Create a new CLI script |
| Update existing script |
| Delete a script |
| Run script on single device |
| Run script on multiple devices |
| Run script on device group |
| Run script on package/ADOM DB |
| Get latest execution log |
| Get execution history |
| Get specific log output |
Template Tools (15 tools)
Tool | Description |
| List provisioning templates |
| Get template details |
| List system templates (devprof) |
| Get system template details |
| Assign template to device |
| Bulk assign system template |
| Remove template assignment |
| List CLI template groups |
| Get CLI template group |
| Create CLI template group |
| Delete CLI template group |
| List template groups |
| Get template group |
| Assign template group |
| Validate template against device |
SD-WAN Tools (10 tools)
Tool | Description |
| List SD-WAN templates |
| Get SD-WAN template details |
| Create SD-WAN template |
| Delete SD-WAN template |
| Assign template to device |
| Bulk assign SD-WAN template |
| Remove template assignment |
| Read a device's SD-WAN config (members/zones/health-checks/rules) from the device DB — for SD-WAN configured locally, not via a template |
| Live SD-WAN Monitor via the device proxy — per-member link/bandwidth ( |
| Generic config-DB introspection: resolve the objects a config attribute is allowed to reference ( |
Security Profile Tools (20 tools)
Tool | Description |
| Create an antivirus profile |
| Create an application-control list |
| Create a DNS filter profile |
| Create a web filter profile |
| Delete an antivirus profile |
| Delete an application-control list |
| Delete a DNS filter profile |
| Delete a web filter profile |
| Get detailed information about an antivirus profile |
| Get detailed information about an application-control list |
| Get detailed information about a DNS filter profile |
| Get detailed information about a web filter profile |
| List antivirus profiles in an ADOM |
| List application-control lists in an ADOM |
| List DNS filter profiles in an ADOM |
| List web filter profiles in an ADOM |
| Update an existing antivirus profile |
| Update an existing application-control list |
| Update an existing DNS filter profile |
| Update an existing web filter profile |
Security Profile (Advanced) Tools (23 tools)
Tool | Description |
| Add a signature filter/override entry to an IPS sensor |
| Create a DLP (Data Loss Prevention) profile |
| Create an IPS sensor |
| Create an SSL/SSH inspection profile |
| Create a WAF (Web Application Firewall) profile |
| Delete a DLP profile |
| Delete an IPS sensor |
| Delete an SSL/SSH inspection profile |
| Delete a WAF profile |
| Get detailed information about a DLP profile |
| Get detailed information about an IPS sensor |
| Get detailed information about an SSL/SSH inspection profile |
| Get detailed information about a WAF profile |
| List DLP (Data Loss Prevention) profiles in an ADOM |
| List the signature-filter/override entries of an IPS sensor |
| List IPS sensors in an ADOM |
| List SSL/SSH inspection profiles in an ADOM |
| List WAF (Web Application Firewall) profiles in an ADOM |
| Remove a signature filter/override entry from an IPS sensor |
| Update a DLP profile's top-level settings |
| Update an IPS sensor's top-level settings |
| Update an SSL/SSH inspection profile |
| Update a WAF profile's top-level settings |
VPN Tools (14 tools)
Tool | Description |
| Create an IPsec phase1-interface (remote gateway / IKE SA) in a device's device DB |
| Create an IPsec phase2-interface (tunnel/traffic selector) in a device's device DB |
| Delete an IPsec phase1-interface from a device's device DB |
| Delete an IPsec phase2-interface from a device's device DB |
| Get one IPsec phase1-interface (remote gateway) from a device's device DB |
| Get one IPsec phase2-interface (tunnel/selector) from a device's device DB |
| Get the SSL-VPN (Agentless VPN) settings object from a device's device DB |
| Get an SSL-VPN web portal from a device's device DB |
| List IPsec phase1-interface (remote gateway) definitions in a device's device DB |
| List IPsec phase2-interface (tunnel/selector) definitions in a device's device DB |
| Update fields on a device-DB IPsec phase1-interface, unspecified fields unchanged |
| Update fields on a device-DB IPsec phase2-interface, unspecified fields unchanged |
| Update the SSL-VPN (Agentless VPN) settings object in a device's device DB |
| Update an SSL-VPN web portal in a device's device DB |
Revision History Tools (11 tools)
Tool | Description |
| Diff a past ADOM DB revision against the CURRENT live ADOM |
| Diff a past device DB revision against the CURRENT device DB |
| Diff a policy package's objects at a past ADOM revision against its CURRENT live state |
| Get one ADOM DB revision's metadata |
| Check out one device DB revision's stored configuration text |
| List the ADOM DB revision history for an ADOM |
| List the device DB revision history for a managed device |
| List the change log for a policy package's firewall policies |
| Revert the live ADOM DB to a past revision |
| Revert a device's device DB to a past revision |
| Restore a firewall policy to a past change-log snapshot |
FortiManager Operations Tools (9 tools)
Tool | Description |
| Add a new FortiManager packet capture definition |
| Delete a task record from FortiManager's task list |
| Get the FortiManager license/contract status |
| Get the running/packet-count status of packet captures |
| List existing FortiManager packet capture definitions |
| Start a packet capture from an existing definition |
| Stop a running packet capture |
| Trigger a FortiManager system backup to a remote server |
| Restore the FortiManager system from a backup on a remote server |
Device Group Tools (8 tools)
Tool | Description |
| Add a single device to a device group |
| Add multiple devices to a device group in one call |
| Nest a device group inside another device group |
| Create a device group in FortiManager's device manager database |
| Delete a device group from FortiManager |
| Remove a single device from a device group |
| Remove multiple devices from a device group in one call |
| Remove a nested group from its parent device group |
Firmware Tools (5 tools)
Tool | Description |
| Preview the multi-step firmware upgrade path to a target version |
| Get the firmware upgrade report for a device under a named profile |
| List firmware versions available for a platform |
| List firmware image files stored on the FortiManager's local disk |
| Trigger a firmware upgrade on a managed device |
Object Usage Tools (2 tools)
Tool | Description |
| Find objects with identical content configured under different names |
| Find everywhere an ADOM object is referenced (where-used) |
Policy Lookup Tools (1 tool)
Tool | Description |
| Simulate a firewall policy lookup for a traffic 5-tuple against a managed device |
Usage Examples
Policy Management
"List all firewall policies in the 'default' package"
"Create a new policy to allow HTTP traffic from internal to wan1"
"Move policy 10 before policy 5 in the default package"
"Install the branch-policy package to FGT-01"Object Management
"Create an address object for the web server at 192.168.10.10"
"List all address groups in the root ADOM"
"Create a service for TCP port 8443"
"Search for all objects containing 'web' in the name"Device Management
"List all devices in the root ADOM"
"Add a new FortiGate device at 10.0.0.1"
"Get the connection status for FGT-01"
"Show the VDOMs configured on FGT-01"Script Execution
"List all CLI scripts in the root ADOM"
"Create a backup script that runs 'execute backup config ftp'"
"Execute the backup script on FGT-01"
"Show the latest script execution log for FGT-01"Template Management
"List all system templates in the ADOM"
"Assign the 'Branch-Template' to FGT-01"
"Show available SD-WAN templates"
"Validate the template against device FGT-01"System Operations
"What is the FortiManager system status?"
"Lock the root ADOM for editing"
"Show all running tasks"
"Wait for task 123 to complete"Architecture
fortimanager-mcp/
├── src/fortimanager_mcp/
│ ├── api/
│ │ └── client.py # FortiManager API client (JSON-RPC)
│ ├── tools/
│ │ ├── system_tools.py # System, ADOM, task management
│ │ ├── dvm_tools.py # Device management tools
│ │ ├── policy_tools.py # Policy and package tools
│ │ ├── object_tools.py # Address, service objects
│ │ ├── script_tools.py # CLI script tools
│ │ ├── template_tools.py # Provisioning templates
│ │ └── sdwan_tools.py # SD-WAN templates
│ ├── utils/
│ │ ├── config.py # Configuration management
│ │ └── errors.py # Error handling
│ └── server.py # MCP server implementation
├── tests/ # Test suite (190+ tests)
├── docs/ # API documentation
├── .env.example # Example configuration
├── pyproject.toml # Project configuration
├── Dockerfile # Container image definition
└── docker-compose.yml # Container orchestrationAPI Reference
The server communicates with FortiManager using the JSON-RPC API over HTTPS. All requests are sent to the /jsonrpc endpoint.
Supported FortiManager Versions
FortiManager 7.0.x
FortiManager 7.2.x
FortiManager 7.4.x
FortiManager 7.6.x (primary development target)
Authentication Methods
API Token (Recommended)
More secure, no session management
Tokens can be revoked without changing passwords
Works with FortiManager 7.0+
Username/Password
Traditional session-based authentication
Session automatically managed by the client
Troubleshooting
Enable Debug Logging
Set LOG_LEVEL=DEBUG in your environment to see detailed API requests and responses:
LOG_LEVEL=DEBUG fortimanager-mcpCommon Issues
Connection Failed
Verify FortiManager hostname/IP is correct
Check network connectivity and firewall rules
Ensure HTTPS port (443) is accessible
Authentication Failed
Verify API token or credentials are correct
Check if the admin account has API access enabled
Ensure the account has sufficient permissions
SSL Certificate Errors
For self-signed FortiManager certs, import the FortiManager CA certificate into your trust store and keep
FORTIMANAGER_VERIFY_SSL=true(see SETUP_GUIDE.md → "Trusting the FortiManager CA")For production, use valid SSL certificates signed by a trusted CA
Last resort only:
FORTIMANAGER_VERIFY_SSL=falsedisables TLS verification and exposes the connection to man-in-the-middle attacks — avoid in production
ADOM Locked
Another user may have the ADOM locked
Use
unlock_adomto release the lock (requires permissions)Check workspace mode settings in FortiManager
MCP Transport Issues
Invalid Host header (HTTP/Docker mode)
Symptom — server logs show:
mcp.server.transport_security - WARNING - Invalid Host header: 10.x.y.z:8000
INFO: ... "POST /mcp HTTP/1.1" 421 Misdirected RequestCause: the MCP SDK validates the Host header for DNS rebinding protection. By default only localhost and 127.0.0.1 are accepted. The header value is whatever the client puts in its connection URL — not the client's IP.
Fix: add the URL value (with port, if used) to MCP_ALLOWED_HOSTS:
# If the client connects to http://10.1.5.62:8000/mcp:
MCP_ALLOWED_HOSTS=["10.1.5.62:8000"]
# Or use a port wildcard to allow any port on that host:
MCP_ALLOWED_HOSTS=["10.1.5.62:*"]
# For a reverse-proxy hostname:
MCP_ALLOWED_HOSTS=["mcp.example.com"]PermissionError: pyvenv.cfg (macOS stdio mode)
Symptom — Claude Desktop MCP logs show:
Fatal Python error: init_import_site: Failed to import the site module
PermissionError: [Errno 1] Operation not permitted: '.../.venv/pyvenv.cfg'Cause: macOS TCC (Transparency, Consent, Control) blocks Claude Desktop from launching executables from inside ~/Documents, ~/Desktop, or ~/Downloads.
Fix (preferred): move the project out of those folders, recreate the venv, and update Claude Desktop's MCP config to the new path:
mv ~/Documents/mcp ~/mcp
cd ~/mcp/fortimanager-mcp
rm -rf .venv && uv sync
# Then update the "command" path in claude_desktop_config.jsonFix (alternative): grant Claude Desktop Full Disk Access — System Settings → Privacy & Security → Full Disk Access → add Claude. Broader permission; only use if relocation isn't feasible.
Viewing Logs
Claude Desktop MCP Server Logs:
macOS:
~/Library/Logs/Claude/mcp-server-fortimanager.logWindows:
%APPDATA%\Claude\logs\mcp-server-fortimanager.log
Development
Running Tests
The project includes 190+ tests covering all tool modules, error handling, and validation logic.
# Install dev dependencies
uv sync --all-extras
# Run all unit tests
pytest
# Run with coverage report
pytest --cov=src/fortimanager_mcp --cov-report=html
# Run specific test file
pytest tests/test_policy_tools.py -v
# Run tests with verbose output
pytest -vIntegration Tests
Integration tests require a real FortiManager instance and are not run in CI.
# Set up environment
export FORTIMANAGER_HOST=your-fmg-host
export FORTIMANAGER_API_TOKEN=your-token
# Keep verification on; import the FortiManager CA for self-signed certs.
export FORTIMANAGER_VERIFY_SSL=true
# Run integration tests (requires live FMG)
pytest tests/integration/ -vNote: Integration tests are verified against FortiManager 7.6.2. Some features may behave differently on older versions.
CI Workflow
The project uses GitHub Actions for continuous integration:
Linting: ruff check on all source files
Type checking: mypy with strict mode
Unit tests: pytest with coverage reporting
Python versions: 3.12+
All CI checks must pass before merging pull requests.
Code Quality
# Linting
ruff check src/
# Type checking
mypy src/
# Formatting
ruff format src/Security Considerations
HTTP Authentication
When running in HTTP mode (Docker), you can secure the MCP endpoint with Bearer token authentication:
# Set in .env or environment
MCP_AUTH_TOKEN=your-secret-tokenWhen configured, all HTTP requests (except /health) must include the Authorization: Bearer <token> header. If not set, the server runs without authentication (backwards compatible).
Environment File Permissions
Protect your .env files containing API tokens:
chmod 600 .env .env.*Dynamic Tool Dispatch Security
In dynamic mode, the tool dispatcher validates tool names:
Rejects private/internal functions (underscore-prefixed names)
Validates that resolved attributes are callable
Error responses never include request parameters (prevents credential leakage)
Safety Guardrails
The MCP server includes built-in safety checks to prevent accidental damage to managed infrastructure. Both are enabled by default.
Script Content Safety (FMG_SCRIPT_SAFETY)
Blocks dangerous CLI commands in create_script and update_script:
Blocked Command | Risk |
| Wipes device configuration |
| Causes device outage |
| Powers off device |
| Formats device disk |
| Erases device disk |
Handles FortiOS abbreviations (exec for execute) and case variations.
FMG_SCRIPT_SAFETY=strict # Default: block dangerous commands
FMG_SCRIPT_SAFETY=disabled # Allow all commands (use with extreme caution)Policy Permissiveness Safety (FMG_POLICY_SAFETY)
Blocks overly permissive firewall policies in create_firewall_policy and update_firewall_policy. Detects policies where srcaddr=all + dstaddr=all + action=accept, which allows unrestricted traffic.
FMG_POLICY_SAFETY=strict # Default: block overly permissive policies
FMG_POLICY_SAFETY=warn # Allow but include warning in response
FMG_POLICY_SAFETY=disabled # Allow all policiesRestore Safety (FMG_RESTORE_SAFETY)
Blocks trigger_fmg_restore (replaces FortiManager's entire configuration and interrupts the service) unless the caller also passes confirm=True.
FMG_RESTORE_SAFETY=strict # Default: refuse without confirm=True
FMG_RESTORE_SAFETY=disabled # Allow unconditionallyRevert Safety (FMG_REVERT_SAFETY)
Blocks revert_adom_revision (restores the entire live ADOM DB in one call) and revert_device_revision (rewrites a device's entire stored config from a revision) unless the caller also passes confirm=True.
FMG_REVERT_SAFETY=strict # Default: refuse without confirm=True
FMG_REVERT_SAFETY=disabled # Allow unconditionallyFirmware Upgrade Safety (FMG_FIRMWARE_SAFETY)
Blocks upgrade_device_firmware (reboots the real managed device) unless the caller also passes confirm=True.
FMG_FIRMWARE_SAFETY=strict # Default: refuse without confirm=True
FMG_FIRMWARE_SAFETY=disabled # Allow unconditionallySecurity Profile Field Validation
create_firewall_policy and update_firewall_policy validate security-profile
(UTM) field combinations before sending the payload to FortiManager, so an
invalid combination fails with a clear message instead of an opaque FMG error
code. This check always runs (no environment toggle):
profile_groupis mutually exclusive withav_profile,ips_sensor,webfilter_profile,dnsfilter_profile,application_list,file_filter_profile,ssl_ssh_profile, andprofile_protocol_options-- FortiOS rejects a policy that sets both a security-profile group and any individual profile it bundles.profile_protocol_optionsis itself a member of thefirewall profile-groupobject, so it is part of this exclusion set too.Setting any of the fields above together with
utm_status=Falsein the same call is rejected -- FortiOS ignores security profiles whenutm-statusis disabled, so the combination is almost certainly a mistake.
General Security
API Tokens: Store tokens securely, never commit to version control
SSL Verification: Enable SSL verification in production environments
Least Privilege: Use FortiManager accounts with minimal required permissions
Network Security: Restrict access to FortiManager management interface
Workspace Locking: Use ADOM locking to prevent concurrent modifications
Credential Sanitization: Device credentials are automatically stripped from API responses
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines on how to submit bug reports, feature requests, and pull requests.
License
MIT License - See LICENSE file for details.
Acknowledgments
Anthropic for the Model Context Protocol
Fortinet for FortiManager
pyfmg library for FortiManager/FortiAnalyzer API
jmpijll/fortimanager-mcp - Architectural inspiration
Related Projects
fortianalyzer-mcp - MCP server for FortiAnalyzer with 70+ tools
pyfmg - FortiManager/FortiAnalyzer Python library
Author
Roland Stierli
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/rstierli/fortimanager-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server