Async + sync + MCP Python client for the FortiSIEM REST API
Project description
pyfortisiem
The first Python client for the FortiSIEM REST API — async-first, fully typed, and the only library that speaks the FortiSIEM 8.0 MCP server.
The pyforti* family covers FortiGate, FortiManager, and FortiZTP — but nothing for FortiSIEM. pyfortisiem fills that gap with a pooled async incident/event client, a sync client that automates all three FortiSIEM auth flows (including the CSRF-gated credential-management surface), and a typed session over the new MCP tool catalogue.
Not affiliated with, authorized, maintained, sponsored, or endorsed by Fortinet. See NOTICE.
Install
pip install pyfortisiem
Quickstart — async incidents + events
FortiSIEM event retrieval is server-side asynchronous (submit query → poll progress → page result). pyfortisiem maps that onto asyncio so many incidents enrich concurrently under one shared concurrency gate.
import asyncio
from pyfortisiem import AsyncFortiSIEMClient
async def main():
async with AsyncFortiSIEMClient.with_basic_auth(
"fortisiem.example.com:443", "admin", "pw", domain="Super",
max_concurrency=10,
) as fsm:
# marquee: one incident + its triggering events, fetched concurrently
inc = await fsm.get_incident_with_events(123456)
print(inc.incident_title, len(inc.events))
# bulk: list incidents and enrich each with events, bounded by the gate
incs = await fsm.list_incidents_with_events(status=[0], size=50)
asyncio.run(main())
Auth modes
| Mode | How | Use for |
|---|---|---|
| Basic auth | with_basic_auth(host, user, pw, domain=...) |
public /phoenix/rest/... reads (incidents, events, queries) |
| OAuth2 API key | with_api_key(host, client_id, client_secret) |
bearer token (lazy mint + 401 re-mint); the only auth the MCP endpoint accepts |
| Session (h5) | sync FortiSIEMClient(...).login() |
the CSRF-gated GUI surface — the only one that can create/revoke API-token credentials |
The h5 surface is CSRF-protected: every state-changing call must echo the s session cookie back hex-encoded as an s: header (the GUI's hexEncode(getCookie("s"))). The sync client handles this for you:
from pyfortisiem import FortiSIEMClient
c = FortiSIEMClient("fortisiem.example.com:443", "admin", "pw", domain="Super")
cred = c.create_oauth_credential("automation") # h5 / CSRF flow
token = c.mint_token(cred.client_id, cred.client_secret)
print([t.name for t in c.mcp_list_tools(token)]) # OAuth bearer → MCP
MCP — FortiSIEM 8.0
MCPSession wraps all 17 tools the FortiSIEM MCP server advertises, each with a typed pydantic input model and decoded response. The server double-wraps payloads in nested content[].text envelopes (sometimes JSON, sometimes Python repr); unwrap_mcp_content peels that off and the response models type what's underneath.
| Tool | Input | Output |
|---|---|---|
get_incidents_by_entity |
EntityIncidentQuery |
list[IncidentSummary] |
get_incident_by_id |
IncidentId |
IncidentDetailResult |
get_related_incidents_by_id |
IncidentId |
list[Incident] |
get_trigger_events_by_incident_id |
IncidentId |
EventTable |
get_context_by_entity |
EntityContextQuery |
EntityContext |
get_reputation_by_entity |
EntityReputationQuery |
list[Reputation] |
get_iocs_by_incident_ids |
IncidentIds |
list[IocEntry] |
update_incident_severity_by_id |
IncidentUpdate |
MutationAck |
update_incident_resolution_by_id |
IncidentUpdate |
MutationAck |
append_incident_comment_by_id |
IncidentUpdate |
MutationAck |
clear_incident_by_id |
IncidentId |
MutationAck |
get_top_10_risky_users_incidents |
— | list[RiskEntry] |
get_top_10_risky_devices_incidents |
— | list[RiskEntry] |
query_fsm_postgres |
sql: str |
list[dict] |
query_fsm_clickhouse |
sql: str |
EventTable |
query_fsm_postgres_prompts |
— | str |
query_fsm_clickhouse_prompts |
— | str |
from pyfortisiem import FortiSIEMClient
from pyfortisiem.models import EntityIncidentQuery
c = FortiSIEMClient("fortisiem.example.com:443", "admin", "pw")
token = c.mint_token(client_id, client_secret)
with c.mcp_session(token) as s:
for inc in s.get_incidents_by_entity(EntityIncidentQuery(ip="203.0.113.7")):
print(inc.incident_id, inc.incident_title)
Two incident shapes
The MCP entity tools (get_incidents_by_entity, get_related_incidents_by_id*) return curated snake_case IncidentSummary rows, while get_incident_by_id's .data and the REST surface return the raw camelCase Incident. The shared models — Incident, Event, EventTable, MutationAck — are reused across both surfaces so you learn them once.
Concurrency & query lifecycle
max_concurrency (default 10) caps in-flight requests via one shared semaphore, and the httpx connection pool is sized to match. For large evidence sets, pass use_query_lifecycle=True to drive the triggeringEvents/start → progress → result async path instead of the direct endpoint.
Development
pip install -e ".[test]"
pytest # offline suites — no appliance needed
pytest -m live # opt-in live tests; needs FSM_* env (see examples/)
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyfortisiem-0.1.0.tar.gz.
File metadata
- Download URL: pyfortisiem-0.1.0.tar.gz
- Upload date:
- Size: 88.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed5216c8edd91e92aee533b1b4338e7a5e364e2920d57c3d248c5d32fda9be25
|
|
| MD5 |
3a215d55661410bea253188c9e1fa25d
|
|
| BLAKE2b-256 |
194ac52c752ba5a5950d83d81a527e1f42b9389c55b22c670eb6220d29555e69
|
Provenance
The following attestation bundles were made for pyfortisiem-0.1.0.tar.gz:
Publisher:
release.yml on ftnt-dspille/pyfortisiem
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfortisiem-0.1.0.tar.gz -
Subject digest:
ed5216c8edd91e92aee533b1b4338e7a5e364e2920d57c3d248c5d32fda9be25 - Sigstore transparency entry: 1952856322
- Sigstore integration time:
-
Permalink:
ftnt-dspille/pyfortisiem@16059f5473230e00ab09071d1b93203dc80ea2aa -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ftnt-dspille
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@16059f5473230e00ab09071d1b93203dc80ea2aa -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyfortisiem-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyfortisiem-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4706891427114261d2d9401a794570384d8189abba9f2d5c1d6d0937f481cb0a
|
|
| MD5 |
01096ca54d9fbd2424a0ddd43925d0ac
|
|
| BLAKE2b-256 |
9d4092c10dff667faa2fb18a734784b0ed060315d324218568ce24612d1a8b2b
|
Provenance
The following attestation bundles were made for pyfortisiem-0.1.0-py3-none-any.whl:
Publisher:
release.yml on ftnt-dspille/pyfortisiem
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyfortisiem-0.1.0-py3-none-any.whl -
Subject digest:
4706891427114261d2d9401a794570384d8189abba9f2d5c1d6d0937f481cb0a - Sigstore transparency entry: 1952856491
- Sigstore integration time:
-
Permalink:
ftnt-dspille/pyfortisiem@16059f5473230e00ab09071d1b93203dc80ea2aa -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/ftnt-dspille
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@16059f5473230e00ab09071d1b93203dc80ea2aa -
Trigger Event:
release
-
Statement type: