Skip to main content
Glama
humyai99

FortiGate MCP Server

by humyai99

FortiGate MCP Server

Test Lint Security Release

FortiGate MCP Server - A comprehensive Model Context Protocol (MCP) server for managing FortiGate devices. This project provides programmatic access to FortiGate devices and enables integration with MCP-compatible clients such as Claude Desktop, Claude Code, and Cursor.

๐Ÿš€ Features

FortiGate MCP Server exposes 33 unique tools, 61 total tool-surface registrations across stdio (30) and HTTP (31) transports; 3 create-tools have transport-specific parameter shapes. Covered areas:

  • Device Management: Add, remove, and test connections to FortiGate devices

  • Firewall Management: List, create, update, and delete firewall rules

  • Network Management: Manage address and service objects

  • Routing Management: Manage static routes and interfaces

  • Virtual IP Management: Manage virtual IPs (VIP/DNAT)

  • HTTP Transport: MCP protocol over HTTP using FastMCP

  • Docker Support: Easy installation and deployment

  • MCP Client Integration: Works with Claude Desktop, Claude Code, Cursor, and other MCP-compatible clients

Related MCP server: FortiOS 7.6.x MCP Server

๐Ÿ“‹ Requirements

  • Python 3.11+

  • uv package manager (recommended) or pip

  • Access to FortiGate device

  • API token or username/password

๐Ÿ› ๏ธ Installation

1. Clone the Project

git clone https://github.com/humyai99/mcp-fortigate.git
cd fortigate-mcp-server

2. Install Dependencies

# Using uv (recommended) - installs the locked, reproducible dependency set
uv sync --locked

# Or using pip
pip install -e .

3. Configuration

Create your local config from the committed example (config/config.json is gitignored and does not exist on a fresh clone):

cp config/config.example.json config/config.json

Then edit config/config.json:

{
  "server": {
    "allow_writes": false
  },
  "fortigate": {
    "devices": {
      "default": {
        "host": "192.168.1.1",
        "port": 443,
        "username": "admin",
        "password": "your_password",
        "api_token": "your-api-token",
        "vdom": "root",
        "verify_ssl": true,
        "timeout": 30
      }
    }

  },
  "logging": {
    "level": "INFO",
    "file": "./logs/fortigate_mcp.log"
  }
}

Interactive credential setup

To enter the FortiGate host and credential without putting the secret in the shell or chat history, run:

uv run python scripts/setup_credentials.py

The API token/password is hidden while typing and is written only to the gitignored config/config.json. TLS verification remains enabled and writes remain disabled by default.

๐Ÿš€ Usage

Start HTTP Server

# Start with script
./start_http_server.sh

# Or manually
uv run python -m src.fortigate_mcp.server_http \
  --host 127.0.0.1 \
  --port 8814 \
  --path /fortigate-mcp \
  --config config/config.json

Use --host 0.0.0.0 only if you need access from other machines on the network AND have auth.require_auth=true configured in config/config.json; otherwise keep 127.0.0.1 โ€” unauthenticated HTTP should never bind wider than loopback.

Run with Docker

# Build and start
docker-compose up -d

# View logs
docker-compose logs -f fortigate-mcp-server

The compose file publishes port 8814 on loopback only (127.0.0.1:8814:8814), so the server is reachable solely from the Docker host itself. To expose it beyond 127.0.0.1, first set auth.require_auth=true in config/config.json, then widen the ports: mapping deliberately โ€” see SECURITY.md.

๐Ÿ”ง MCP Client Integration

FortiGate MCP Server works with any MCP-compatible client. Verified, ready-to-use config examples are provided for Claude Desktop, Claude Code, and Cursor:

Every stdio example in this project launches the server via uv run --directory <path> python -m src.fortigate_mcp.server instead of a bare python -m ... command. This matters because GUI-launched MCP clients (like Claude Desktop) commonly start commands from a directory other than the repo root โ€” the explicit --directory flag makes the invocation working-directory independent, so it keeps working regardless of where the client process happens to start from.

Claude Code (.mcp.json)

Add a project-scope .mcp.json at your repository root (see examples/claude_code_mcp.json for the full file, including the HTTP entry):

{
  "mcpServers": {
    "fortigate-mcp-stdio": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "${CLAUDE_PROJECT_DIR}", "python", "-m", "src.fortigate_mcp.server"],
      "env": {
        "FORTIGATE_MCP_CONFIG": "${CLAUDE_PROJECT_DIR}/config/config.json"
      }
    }
  }
}

Or register the HTTP transport via the Claude Code CLI:

claude mcp add --transport http fortigate-mcp http://127.0.0.1:8814/fortigate-mcp --header "Authorization: Bearer <token>"

--header is only needed when auth.require_auth=true is set in config/config.json.

Claude Desktop

Claude Desktop's native config schema validates stdio servers only. Use examples/claude_desktop_config.stdio.json for the stdio transport (recommended). For the HTTP transport, examples/claude_desktop_config.http.json bridges to this server through the community mcp-remote npm package (npx -y mcp-remote ...) โ€” this package is not vetted or installed by this project; inspect it yourself before running it, since npx fetches and executes third-party code on your behalf.

Cursor

See examples/cursor_mcp_config.json for a working stdio configuration.

๐Ÿ“š API Commands

Device Management

  • list_devices - List registered devices

  • get_device_status - Get device status

  • test_device_connection - Test connection

  • add_device - Add new device

  • remove_device - Remove device

  • discover_vdoms - Discover VDOMs

Firewall Management

  • list_firewall_policies - List firewall rules

  • create_firewall_policy - Create new rule

  • update_firewall_policy - Update rule

  • delete_firewall_policy - Delete rule

Network Management

  • list_address_objects - List address objects

  • create_address_object - Create address object

  • list_service_objects - List service objects

  • create_service_object - Create service object

Virtual IP Management

  • list_virtual_ips - List virtual IPs

  • create_virtual_ip - Create virtual IP

  • update_virtual_ip - Update virtual IP

  • get_virtual_ip_detail - Get virtual IP detail

  • delete_virtual_ip - Delete virtual IP

Routing Management

  • list_static_routes - List static routes

  • create_static_route - Create static route

  • update_static_route - Update static route

  • delete_static_route - Delete static route

  • get_static_route_detail - Get static route detail

  • get_routing_table - Get routing table

  • list_interfaces - List interfaces

  • get_interface_status - Get interface status

System Commands

  • health - Health check

  • test_connection - Connection test

  • get_schema_info - Schema information

๐Ÿงช Testing

Run Tests

# One-time setup: test dependencies (pytest, pytest-cov, respx, pyyaml) live in the
# dev/test extras โ€” the plain `uv sync --locked` from the install step does not install them
uv sync --locked --all-extras

# Quick run with coverage disabled (a bare -q only reduces verbosity;
# without --no-cov the coverage gate from pyproject.toml addopts still runs)
uv run pytest -q --no-cov

# Full suite with coverage (--cov-fail-under=67 enforced per pyproject.toml)
uv run pytest

# Run specific test files
uv run pytest tests/test_device_manager.py
uv run pytest tests/test_fortigate_api.py
uv run pytest tests/test_tools.py

# Verbose output
uv run pytest -v

# Detailed error information
uv run pytest --tb=long

Test Categories

  • Unit Tests: Test individual components and functions

  • Coverage: Code coverage reporting with HTML output

Manual Testing

The /health route lives at the app root, is exempt from Bearer-token auth, and works with plain curl:

# Liveness probe
curl http://127.0.0.1:8814/health

The MCP protocol itself cannot be exercised with a bare curl POST: the streamable-HTTP transport requires an initialize handshake, session management, and tools/call framing. Use a real MCP client for protocol-level testing โ€” for example fastmcp.Client:

uv run python - <<'PY'
import asyncio
from fastmcp import Client

async def main():
    # Trailing slash matches the mount convention; the non-slash form 307-redirects
    async with Client("http://127.0.0.1:8814/fortigate-mcp/") as client:
        tools = await client.list_tools()
        print(f"{len(tools)} tools registered")
        result = await client.call_tool("health", {})
        print(result.content[0].text)

asyncio.run(main())
PY

Alternatively, bridge with npx -y mcp-remote http://127.0.0.1:8814/fortigate-mcp or use the MCP Inspector.

๐Ÿ“ Project Structure

fortigate-mcp-server/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ fortigate_mcp/
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ”œโ”€โ”€ server.py               # STDIO MCP server
โ”‚       โ”œโ”€โ”€ server_http.py          # HTTP MCP server
โ”‚       โ”œโ”€โ”€ config/                 # Configuration management
โ”‚       โ”œโ”€โ”€ core/                   # Core components
โ”‚       โ”œโ”€โ”€ tools/                  # MCP tools
โ”‚       โ””โ”€โ”€ formatting/             # Response formatting
โ”œโ”€โ”€ config/
โ”‚   โ”œโ”€โ”€ config.json                # Main configuration (gitignored, created from example)
โ”‚   โ””โ”€โ”€ config.example.json        # Example configuration
โ”œโ”€โ”€ examples/
โ”‚   โ”œโ”€โ”€ claude_desktop_config.stdio.json  # Claude Desktop, stdio transport
โ”‚   โ”œโ”€โ”€ claude_desktop_config.http.json   # Claude Desktop, HTTP via mcp-remote
โ”‚   โ”œโ”€โ”€ claude_code_mcp.json               # Claude Code .mcp.json
โ”‚   โ””โ”€โ”€ cursor_mcp_config.json             # Cursor MCP config
โ”œโ”€โ”€ logs/                          # Log files
โ”œโ”€โ”€ tests/                         # Test files
โ”œโ”€โ”€ docker-compose.yml             # Docker compose
โ”œโ”€โ”€ Dockerfile                     # Docker image
โ”œโ”€โ”€ start_server.sh                # STDIO startup script
โ”œโ”€โ”€ start_http_server.sh           # HTTP startup script
โ””โ”€โ”€ README.md                      # This file

๐Ÿ” Troubleshooting

Common Issues

  1. Connection Error

    • Ensure FortiGate device is accessible

    • Verify API token or username/password

    • If you see SSL certificate errors, install the FortiGate device's certificate as trusted (or replace it with a CA-signed certificate) โ€” do not disable certificate verification. verify_ssl defaults to true and must stay true; see SECURITY.md for the rationale.

  2. Port Conflict

    • Ensure port 8814 is available

    • Change port using --port parameter

  3. Configuration Error

    • Ensure config.json is properly formatted

    • Check JSON syntax

  4. MCP Client Connection Issue

    • Ensure the server is running

    • Verify the config file path and URL are correct

    • Restart the MCP client (Claude Desktop / Claude Code / Cursor)

Logs

Check logs using:

# HTTP server logs
tail -f logs/fortigate_mcp.log

# Docker logs
docker-compose logs -f fortigate-mcp-server

๐Ÿ”’ Security

Implemented Controls

  1. Write protection (default: read-only)

    • Write and destructive tools are rejected unless explicitly enabled via server.allow_writes: true in config, or the FORTIGATE_MCP_ALLOW_WRITES=1 environment variable

  2. TLS certificate verification (default: on)

    • Config-loaded devices verify TLS certificates by default (verify_ssl: true)

  3. Bearer-token authentication (optional, default: off)

    • Available via auth.require_auth / auth.api_tokens; unauthenticated by default โ€” run only on trusted networks when disabled

  4. Secret redaction

    • API tokens and passwords are stored as SecretStr and are never written to logs

Known Limitations

  • Rate limiting is parsed from config but not enforced โ€” do not rely on it as a working control.

  • Unauthenticated HTTP (the default) should bind to 127.0.0.1 (loopback) only; binding to 0.0.0.0 for wider network access requires enabling Bearer auth (auth.require_auth=true) plus network-level controls (firewall rules, VPN, reverse-proxy allowlists).

See SECURITY.md for the full threat model and known limitations.

๐Ÿค Contributing

  1. Fork the repository

  2. Create a feature branch (git checkout -b feature/amazing-feature)

  3. Commit your changes (git commit -m 'Add amazing feature')

  4. Push to the branch (git push origin feature/amazing-feature)

  5. Open a Pull Request

๐Ÿ“„ License

This project is licensed under the MIT License. See the LICENSE file for details.

๐Ÿ™ Acknowledgments

๐Ÿ“ž Support

For issues:

  • Use the Issues page

  • Check the documentation

  • Review the logs


Note: This project has been tested with FortiGate devices. Please perform comprehensive testing before using in production.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

โ€“Maintainers
โ€“Response time
โ€“Release cycle
โ€“Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

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/humyai99/mcp-fortigate'

If you have feedback or need assistance with the MCP directory API, please join our Discord server