Skip to main content
Glama
forticrevs
by forticrevs
README.md
# FortiAnalyzer MCP Server

A Python Model Context Protocol server for the FortiAnalyzer JSON-RPC API. It
provides structured tools for system health, ADOM and device inventory, alerts,
reports, LogView searches, and documented long-tail read endpoints.

The bundled offline catalog contains searchable factual metadata derived from
the public
[FortiAnalyzer JSON-RPC API documentation](https://how-to-fortianalyzer-api.readthedocs.io/en/latest/).
It includes generated summaries and links back to each source page; the source
documentation itself is not redistributed in this repository. See
[NOTICE](NOTICE) for provenance and licensing details.

The searchable catalog preserves the guide's published endpoint paths. Endpoint
compatibility can vary by appliance release, so validate the tools you plan to
use in a non-production environment first.

This is an independent project and is not affiliated with or endorsed by
Fortinet. Fortinet and FortiAnalyzer are trademarks of Fortinet, Inc.

## Highlights

- API-key and username/password session authentication
- Secure TLS verification by default, with custom CA support
- Lazy, concurrency-safe login and one read retry after documented session
  expiry (`-10`)
- Bounded LogView submit/poll/fetch/cleanup workflows
- Structured MCP output with request and endpoint metadata
- Offline API-document metadata search plus an opt-in generic read tool
- Server-managed credentials and session IDs are never exposed in tool schemas
  or server-generated metadata
- Generic state-changing access disabled by default
- stdio, Streamable HTTP, and SSE transports

## Requirements

- Python 3.10 or newer
- Network access to a FortiAnalyzer appliance
- A least-privileged FortiAnalyzer API user
- A FortiAnalyzer release that supports the selected JSON-RPC endpoints

## Install

```bash
git clone https://github.com/forticrevs/fortianalyzer-mcp.git
cd fortianalyzer-mcp
python3 -m venv .venv
. .venv/bin/activate
python -m pip install .
```

Copy the example environment file and configure one authentication method:

```bash
cp .env.example .env
```

API-key authentication is preferred for long-running automation:

```dotenv
FAZ_HOST=https://fortianalyzer.example.com
FAZ_API_KEY=replace-with-a-secret-from-your-secret-store
FAZ_VERIFY_SSL=true
```

Session authentication is also supported:

```dotenv
FAZ_HOST=https://fortianalyzer.example.com
FAZ_USERNAME=mcp-readonly
FAZ_PASSWORD=replace-with-a-secret-from-your-secret-store
FAZ_VERIFY_SSL=true
```

Do not commit `.env`; it is ignored by Git.

Run the stdio server:

```bash
fortianalyzer-mcp
```

An MCP client can launch that executable directly. A representative client
configuration is:

```json
{
  "mcpServers": {
    "fortianalyzer": {
      "command": "/absolute/path/to/.venv/bin/fortianalyzer-mcp",
      "env": {
        "FAZ_HOST": "https://fortianalyzer.example.com",
        "FAZ_API_KEY": "${FAZ_API_KEY}",
        "FAZ_VERIFY_SSL": "true"
      }
    }
  }
}
```

Environment interpolation depends on the MCP client. If it is unsupported, use
the client's secret facility or arrange for the server process to inherit the
variables.

## Tools

| Tool | Purpose |
|---|---|
| `faz_search_api_docs` | Search bundled API-guide metadata and public source URLs |
| `faz_connection_info` | Show non-secret local connection settings |
| `faz_get_system_status` | Read version, model, license, disk, and memory status |
| `faz_get_system_performance` | Read CPU, memory, and disk performance |
| `faz_list_adoms`, `faz_get_adom` | Inspect administrative domains |
| `faz_list_devices` | List managed devices in an ADOM |
| `faz_list_unregistered_devices` | List unregistered devices |
| `faz_list_alerts` | Query bounded event-management alerts |
| `faz_list_event_handlers` | Read basic and correlation event-handler configuration |
| `faz_list_report_layouts` | List SQL report layouts |
| `faz_list_report_templates` | List report templates |
| `faz_list_report_schedules` | List report schedules |
| `faz_list_log_forwarders` | Read global log-forwarding configuration |
| `faz_search_logs` | Run a bounded one-page LogView workflow |
| `faz_start_log_search` | Start a manual/paginated LogView task |
| `faz_get_log_search` | Poll or page through a LogView task |
| `faz_cancel_log_search` | Cancel and release a LogView task |
| `faz_raw_get` | Call an appliance-local endpoint with JSON-RPC `get` |

`faz_raw_write` is registered only when `FAZ_ENABLE_WRITE_TOOLS=true`. It
requires `confirm=true`, cannot call authentication or reboot endpoints, and
does not automatically replay a mutation after session expiry or an ambiguous
transport failure. The confirmation argument and MCP annotations are not an
authorization or human-approval boundary. Enabling this tool grants connected
clients broad mutation capability within the FortiAnalyzer account's
permissions; use a separate least-privileged account and an external approval
control. Keep it disabled for read-only deployments.

### Log search behavior

Device scope is always explicit. Use a concrete list such as
`["branch-fgt-01"]`; use `["All_FortiGate"]` only intentionally.

Relative searches default to the last hour. Absolute times must be paired,
FortiAnalyzer-local timestamps:

```text
2025-01-15 09:00:00
```

By default:

- a specific-device search is limited to 24 hours;
- an all-device search is limited to 1 hour;
- one tool result is limited to 200 rows;
- LogView fetch pages never exceed the appliance limit of 500 rows;
- at most eight LogView searches can be active or starting concurrently;
- the convenience workflow times out after 30 seconds;
- task deletion is attempted after completion, timeout, errors, and server
  shutdown; failed cleanup remains tracked for a later shutdown retry.

For pagination, start a task and retrieve each required offset. A completed full
page does not prove that all rows were consumed, so the server conservatively
keeps that task alive even when `cleanup_when_done=true`. Set
`cleanup_when_done=false` while managing pages explicitly, then call
`faz_cancel_log_search`. The convenience `faz_search_logs` workflow intentionally
returns one page and always attempts to release its task.

## Configuration

| Variable | Default | Meaning |
|---|---:|---|
| `FAZ_HOST` | required | HTTPS origin, optionally ending in `/jsonrpc` |
| `FAZ_AUTH_MODE` | `auto` | `auto`, `api_key`, or `session` |
| `FAZ_API_KEY` | | Bearer API key (`FAZ_API_TOKEN` is an alias) |
| `FAZ_USERNAME`, `FAZ_PASSWORD` | | Session credentials |
| `FAZ_VERIFY_SSL` | `true` | Verify the appliance certificate |
| `FAZ_CA_BUNDLE` | | PEM bundle for a private CA |
| `FAZ_ALLOW_HTTP` | `false` | Explicit development-only HTTP opt-in |
| `FAZ_TIMEOUT_SECONDS` | `30` | Normal API timeout |
| `FAZ_DEFAULT_ADOM` | `root` | Default ADOM |
| `FAZ_MAX_RESULTS` | `200` | Maximum rows accepted by bounded tools |
| `FAZ_LOG_SEARCH_TIMEOUT_SECONDS` | `30` | Convenience search timeout |
| `FAZ_MAX_LOG_SEARCH_HOURS` | `24` | Specific-device search window |
| `FAZ_MAX_ALL_DEVICES_SEARCH_HOURS` | `1` | All-device search window |
| `FAZ_MAX_ACTIVE_LOG_SEARCHES` | `8` | Maximum active/starting LogView tasks |
| `FAZ_ENABLE_WRITE_TOOLS` | `false` | Register the guarded raw mutation tool |
| `FAZ_MCP_TRANSPORT` | `stdio` | `stdio`, `streamable-http`, or `sse` |
| `FAZ_MCP_BIND_HOST` | `127.0.0.1` | Network transport bind address |
| `FAZ_MCP_PORT` | `8000` | Network transport port |
| `FAZ_MCP_ALLOWED_ORIGINS` | | Comma-separated browser-origin allowlist |
| `FAZ_MCP_ALLOWED_HOSTS` | | Additional comma-separated Host header allowlist |
| `FAZ_MCP_STATELESS` | `false` | Streamable HTTP stateless mode |
| `FAZ_MCP_JSON_RESPONSE` | `true` | Use JSON rather than SSE response bodies |
| `FAZ_LOG_LEVEL` | `INFO` | Server logging level |

For self-signed appliance certificates, install the issuing CA and set
`FAZ_CA_BUNDLE`. `FAZ_VERIFY_SSL=false` is available for development but should
not be used in production.

Network transports do not add client authentication to the MCP endpoint. Keep
the default loopback binding or place the server behind an authenticated,
TLS-protected gateway.

Browser clients must be explicitly allowed. For example:

```dotenv
FAZ_MCP_ALLOWED_ORIGINS=http://model.example.test,https://another-ui.example
```

Origins are exact HTTP(S) origins without paths or trailing slashes. A terminal
port wildcard such as `http://model.example.test:*` is supported. The setting drives
both DNS-rebinding validation and CORS/preflight responses. Localhost origins
remain allowed automatically. If a proxy forwards a non-local `Host` header,
allow that separately, for example
`FAZ_MCP_ALLOWED_HOSTS=model.example.test,model.example.test:*`.
These allowlists are request-validation controls, not client authentication.

## Development

Install the development extra and run the test suite:

```bash
python -m pip install -e '.[dev]'
pytest
```

Rebuild the offline catalog from a documentation tree you are authorized to
use:

```bash
python scripts/build_catalog.py \
  --source /path/to/fortianalyzer-api-docs \
  --output src/fortianalyzer_mcp/data/api_catalog.json
```

The automated suite uses mocked HTTP transports and does not require an
appliance. Validate against your appliance because releases can differ in
endpoint-specific request and response fields.

`faz_raw_get` accepts any syntactically valid appliance-local path except
blocked authentication paths; catalog membership is guidance, not an enforced
allowlist. It prevents request-field overrides and always sends JSON-RPC
`get`, but returns endpoint data without field-level redaction or a generic
response-size cap. Use a least-privileged account, call only trusted endpoints,
and pass endpoint-specific `limit`, `range`, or field-selection parameters.

TDQS

A3.7/5.0

Scored across 19 tools

Disambiguation5/5

Each tool targets a distinct resource or lifecycle stage: list/get for different entity types, start/get/cancel/search for log search, and status vs performance are clearly separated. Even the raw_get fallback is distinct from high-level tools, and search_api_docs is unique. The only near-overlap (search_logs vs start+get) is clearly differentiated by description.

Naming Consistency4/5

Names consistently follow faz_<verb>_<noun> pattern for almost all tools (list_*, get_*, start_*, search_*). The lone outlier is faz_connection_info, which is a noun phrase rather than a verb phrase, and faz_raw_get mixes an adjective before the verb. Overall the pattern is highly predictable with minor deviations.

Tool Count4/5

19 tools is on the heavier side but appropriate for a comprehensive FortiAnalyzer management interface covering system, ADOMs, devices, alerts, reports, logs, and a raw API fallback. The count feels slightly above the ideal 3-15 range, but each tool serves a distinct function and nothing feels redundant.

Completeness4/5

The tool set covers a broad read-only surface: system status/perf, ADOM listing/getting, device listing (registered and unregistered), alerts, event handlers, reports (layouts/templates/schedules), log search with full lifecycle, and a raw read endpoint for uncovered paths. Missing write operations and per-item get-by-ID are notable but align with the apparent read/query purpose; the raw_get gap-filler increases completeness.

Maintenance

ActivitySlowing
ResponsivenessNo issues