MCP Galaxy / Directory / FortiGate Read-Only
Firewall Read-Only Docker / Podman

FortiGate Read-Only

A production-grade, read-only Model Context Protocol server for the Fortinet FortiGate FortiOS REST API v2. Launches directly from your MCP client over stdio, in a Docker or Podman container, pinned to a single VDOM at startup.

Runtime
Docker / Podman
Transport
stdio
API
FortiOS REST v2
Overview

What This Server Does

FortiGate Read-Only exposes a curated set of GET-only tools that wrap the FortiOS REST API v2. It's designed to be safe in production: no tool will ever issue a POST, PUT, PATCH, or DELETE against the firewall.

The server is pinned to a single VDOM at startup via the FORTIGATE_VDOM environment variable. Tools take no vdom parameter at all, and the shared request helper strips any caller-supplied vdom, there is no surface for the model to widen scope to a virtual domain the token can also reach.

Ships as both a Docker and a Podman container from the same server.py, pick whichever runtime you already run.

Try Asking

What to Ask, in Plain English

A handful of real questions you can put to your AI client once this server is wired in. The model figures out which tools to call, you describe the outcome.

  • "Which firewall policies have NAT enabled but logging disabled?"
  • "Are any IPsec tunnels currently down, for how long?"
  • "How many active sessions are there from source 10.0.0.5 right now?"
  • "Show me policies 1 through 10 with their UTM profiles and hit counts."
  • "What admin accounts exist, and what access profile does each have?"
  • "Pull the last 25 system event logs and summarize what failed."
Capabilities

37 Read-Only Tools Across the FortiOS Surface

A
System & Health
System status, resource & performance counters, firmware state, HA cluster, interface live status, sensors, licenses.
B
Network & Routing
Active routing table, ARP, DHCP leases, firewall session table, per-policy hit counts.
C
Firewall Policy & Objects
IPv4 policy table, address objects & groups, service objects & groups, VIP/DNAT, IP pools, static routes, interface config, zones.
D
VPN
IPsec phase-1 & phase-2 config, live IPsec tunnel status, SSL-VPN sessions.
E
Security Profiles (UTM)
Antivirus, IPS, web filter, application, DNS filter, SSL-SSH, file filter, email filter.
F
Administration
Admin accounts and access profiles, no password hashes returned.
G
Logs
Memory, disk, FortiAnalyzer, and FortiCloud log queries, traffic, event, UTM, and more.
H
Generic GET Passthrough
Validated read of any cmdb table or monitor resource for endpoints not covered by a named tool.
Installation

Build & Configure

Two supported runtimes, pick the one you already run. The docker/ and podman/ backends ship an identical server.py; only the container file and the way the client is wired differ. Generate the REST API token first (it's identical for either path), then expand your runtime below.

New to the Docker MCP Gateway? The Docker pane below uses a catalog → registry → gateway flow with three bind-mounts. If any of that terminology is unfamiliar, skim Docker MCP Gateway under Good to Know first, it walks through the architecture these steps build on. The Podman pane runs the container directly and doesn't depend on the gateway.

Step 0, Generate a REST API token (out of band). On the FortiGate, go to System → Administrators → Create New → REST API Admin. Assign a read-only access profile, set a Trusthost that matches where the container will run, and pin the admin to a single VDOM. Copy the token shown once at creation, FortiOS does not display it again.

Docker Docker Desktop · MCP Gateway
Step 1, Get the project files

Clone the repository. The Docker backend lives in docker/ and ships a custom-catalog.yaml alongside the Dockerfile.

Clone
$ git clone https://github.com/rosarion97/Fortigate-mcp-server-public.git
$ cd Fortigate-mcp-server-public/docker
Step 2, Build the image

The image tag must match the image: field in custom-catalog.yaml (fortigate-readonly-mcp:latest).

Build
$ docker build -t fortigate-readonly-mcp:latest .
Step 3, Provide secrets

Three values are required: the API token, the FortiGate host, and the VDOM you want this instance to serve. Option A keeps your token in Docker's encrypted store and is recommended; Option B writes a plaintext .env for quick local testing.

Option A, Docker secret store (recommended)
$ docker mcp secret set FORTIGATE_API_TOKEN="..."
$ docker mcp secret set FORTIGATE_HOST="fw01.example.com"
$ docker mcp secret set FORTIGATE_VDOM="root"
$ docker mcp secret set FORTIGATE_VERIFY_SSL="yes"   # optional
$ docker mcp secret ls   # values are masked

With Option A, continue through Steps 4–7 below.

Option B writes your token to disk in clear text. Use it only for local testing, chmod 600 .env, never commit it. This path skips the gateway: skip Steps 4–6, point your client straight at docker run --env-file (below), then jump to Step 7.
Option B, plaintext .env (testing only)
$ cp .env.example .env
$ chmod 600 .env   # then set FORTIGATE_API_TOKEN, FORTIGATE_HOST, FORTIGATE_VDOM
claude_desktop_config.json (Option B)
{
  "mcpServers": {
    "fortigate-readonly": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "--env-file", "/absolute/path/to/.env",
        "fortigate-readonly-mcp:latest"
      ]
    }
  }
}
Step 4, Install the custom catalog

Steps 4–6 are the Option A (secret store) path. Used Option B? Skip to Step 7.

Catalog
$ mkdir -p ~/.docker/mcp/catalogs
$ cp custom-catalog.yaml ~/.docker/mcp/catalogs/custom.yaml
Step 5, Enable the server in the registry

Add the entry under the single top-level registry: key in ~/.docker/mcp/registry.yaml. Don't overwrite the file if it already exists.

~/.docker/mcp/registry.yaml
registry:
  fortigate-readonly:
    catalog: custom
    enabled: true
Step 6, Point Claude Desktop at the gateway

Add the gateway block to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json). The gateway spawns the FortiGate image on demand, reads your catalog and registry, and resolves your FORTIGATE_* secrets at request time. Replace <your-username> with your macOS username (run whoami to check):

claude_desktop_config.json, gateway block
{
  "mcpServers": {
    "mcp-toolkit-gateway": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-v", "/var/run/docker.sock:/var/run/docker.sock",
        "-v", "/Users/<your-username>/.docker/mcp:/mcp",
        "-v", "/Users/<your-username>/Library/Caches/docker-secrets-engine/engine.sock:/root/.cache/docker-secrets-engine/engine.sock",
        "docker/mcp-gateway:latest",
        "--catalog=/mcp/catalogs/custom.yaml",
        "--registry=/mcp/registry.yaml",
        "--transport=stdio"
      ]
    }
  }
}

All three bind-mounts are required: the Docker socket lets the gateway spawn the server container; ~/.docker/mcp is where it reads your catalog and registry; the docker-secrets-engine socket is the resolver Docker Desktop exposes for the secret store, without it your FORTIGATE_* URLs resolve to empty strings and the server never starts. On Linux Docker Desktop the path is ~/.docker/desktop/secrets-engine/engine.sock; find it with find ~ -name engine.sock.

Works the same for other clients. Claude Code takes the same args via claude mcp add -s user mcp-toolkit-gateway -- docker run …; Codex reads the same shape from ~/.codex/config.toml as [mcp_servers.mcp-toolkit-gateway]. Any MCP-capable client, Google's Gemini included, reads an mcpServers block from its own settings file; only the location changes, the entry is identical.

Then quit and reopen the client. claude_desktop_config.json never contains FORTIGATE_API_TOKEN, the gateway resolves it from Docker's secret store at request time. As a shortcut, docker mcp client connect claude-desktop (or MCP Toolkit → Clients in Docker Desktop) writes a similar block automatically; the explicit JSON above survives Docker Desktop updates that may rewrite the auto-managed entry.

Step 7, Verify
Verify
$ docker mcp server list   # fortigate-readonly → enabled
$ docker mcp tools list

Or run the image directly, it starts and waits silently on stdin (correct for an MCP stdio server). Press Ctrl+C to exit; a missing required value fails fast on stderr.

Smoke test (Option B)
$ docker run --rm -i --env-file .env fortigate-readonly-mcp:latest
Podman Rootless · stdio
Step 1, Get the project files

Clone the repository. The Podman backend lives in podman/.

Clone
$ git clone https://github.com/rosarion97/Fortigate-mcp-server-public.git
$ cd Fortigate-mcp-server-public
Step 2, Build the image

On macOS / Windows, start a Podman machine first (podman machine init && podman machine start); Linux runs natively.

Build
$ podman build -t fortigate-readonly-mcp:latest podman/
Step 3, Provide credentials

Three required values: FORTIGATE_API_TOKEN, FORTIGATE_HOST, and FORTIGATE_VDOM (one container serves one VDOM, use root if VDOMs are disabled). Set them in podman/.env; the file is gitignored and chmod 600'd.

podman/.env
$ cp podman/.env.example podman/.env
$ chmod 600 podman/.env   # then edit: FORTIGATE_API_TOKEN, FORTIGATE_HOST, FORTIGATE_VDOM

If the FortiGate uses a self-signed cert, also set FORTIGATE_VERIFY_SSL=no. Optional: FORTIGATE_PORT (default 443) and FORTIGATE_MAX_RESPONSE_BYTES (default 120000).

Step 4, Configure your AI client

Point Claude Desktop at the container with the absolute path to podman/.env:

claude_desktop_config.json
{
  "mcpServers": {
    "fortigate": {
      "command": "podman",
      "args": [
        "run", "--rm", "-i",
        "--env-file", "/absolute/path/to/podman/.env",
        "fortigate-readonly-mcp:latest"
      ]
    }
  }
}
Works the same for other assistants. Any MCP-capable client reads an mcpServers block from its own settings.json, Google's Gemini included. Drop this same entry into that client's settings.json and the FortiGate tools appear there too. Only the file's name and location change; the server entry is identical.

Claude Code takes the same invocation:

Claude Code
$ claude mcp add -s user fortigate -- \
    podman run --rm -i \
    --env-file /absolute/path/to/podman/.env \
    fortigate-readonly-mcp:latest

-i is required, MCP stdio needs stdin attached, and every --env-file path must be absolute.

Step 5, Verify

Run the image manually, it starts and waits silently on stdin. Press Ctrl+C to exit; a missing required value fails fast on stderr.

Smoke test
$ podman run --rm -i --env-file podman/.env fortigate-readonly-mcp:latest

One container serves one VDOM, run a second container with its own credentials to serve another VDOM. --rm cleans up the container after each session.

Configuration

Three Required, Three Optional

  • FORTIGATE_API_TOKEN, required. Bearer token used for every API call. Generated under System → Administrators → REST API Admin, shown once at creation.
  • FORTIGATE_HOST, required. Management host/IP of the firewall, the same address you'd reach the admin GUI on.
  • FORTIGATE_VDOM, required. Pins this instance to a single virtual domain. Use root if VDOMs are disabled.
  • FORTIGATE_PORT, optional. HTTPS admin port. Default 443; set if the FortiGate uses a non-standard port.
  • FORTIGATE_VERIFY_SSL, optional. TLS verification. Default yes; set no only for an un-validatable self-signed cert.
  • FORTIGATE_MAX_RESPONSE_BYTES, optional. Caps the JSON size of any one tool response. Default 120000 (~30k tokens). Over the cap, the server returns a truncation envelope with a _hint field.
Requirements

Before You Start

  • Docker Desktop 4.27+ with the MCP Toolkit feature, or Podman 4.x+ on $PATH. Pick one; the step-by-step for each is above.
  • A FortiGate running FortiOS with the REST API enabled and reachable from the host the container runs on.
  • A REST API admin scoped to read-only, with a Trusthost matching where this container runs and pinned to the VDOM you want to serve. Defense-in-depth on top of the protocol-layer read-only guarantee.
  • An MCP-capable client, Claude Desktop, Claude Code, Codex, Gemini CLI, or any client speaking MCP over stdio.
License & Policy

Read-Only by Construction

Provided as-is for internal use. Every data query maps to an HTTP GET against the FortiOS REST API v2. The server contains no code path that issues a write verb, so it is incapable of modifying any FortiGate configuration regardless of what an LLM or user asks for. Pull requests that add additional read-only endpoints are welcome; any change that introduces a write-capable verb against the firewall will be rejected. Not affiliated with or endorsed by Fortinet.