Initial commit: Agentic OS troubleshooting platform
Self-hosted, Docker-based agentic troubleshooting platform: FastAPI backend + LangGraph agent, Next.js UI, tiered LLM routing (local Ollama -> Gemini -> DeepSeek -> OpenRouter), MCP server manager, encrypted device credentials, RBAC, audit log, project-memory + Obsidian integrations, and editable troubleshooting decision rules tuned for the GeneseasX vessel stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
108
mcp-servers/fortigate-mcp/server.py
Normal file
108
mcp-servers/fortigate-mcp/server.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""FortiGate MCP server (FortiOS REST API).
|
||||
|
||||
Read-only diagnostics by default. Token (API key) auth. Self-signed certs are
|
||||
accepted (typical for standalone FortiGates). Writes require FORTIGATE_ALLOW_WRITES=true.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
import httpx
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("fortigate")
|
||||
|
||||
ALLOW_WRITES = os.environ.get("FORTIGATE_ALLOW_WRITES", "false").lower() == "true"
|
||||
|
||||
_active: dict | None = None # {"host","api_key","port"}
|
||||
|
||||
|
||||
def _resolve(host, api_key, port) -> dict:
|
||||
if host:
|
||||
return {"host": host, "api_key": api_key, "port": port or 443}
|
||||
if _active:
|
||||
return _active
|
||||
raise RuntimeError("No FortiGate target. Call fortigate_connect first.")
|
||||
|
||||
|
||||
def _base(target: dict) -> str:
|
||||
return f"https://{target['host']}:{target.get('port', 443)}"
|
||||
|
||||
|
||||
def _get(target: dict, path: str, params: dict | None = None) -> dict:
|
||||
url = f"{_base(target)}{path}"
|
||||
headers = {"Authorization": f"Bearer {target.get('api_key')}"}
|
||||
with httpx.Client(verify=False, timeout=20) as client: # noqa: S501 (self-signed device)
|
||||
r = client.get(url, headers=headers, params=params)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def fortigate_connect(host: str, api_key: str, port: int = 443, username: str | None = None) -> str:
|
||||
"""Set the active FortiGate target (token/API-key auth) and verify connectivity."""
|
||||
global _active
|
||||
target = {"host": host, "api_key": api_key, "port": port}
|
||||
try:
|
||||
_get(target, "/api/v2/monitor/system/status")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return f"connect failed: {exc}"
|
||||
_active = target
|
||||
return f"connected to FortiGate {host}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def fortigate_test_connection(host: str | None = None, api_key: str | None = None, port: int = 443) -> str:
|
||||
"""Test connectivity to a FortiGate."""
|
||||
target = _resolve(host, api_key, port)
|
||||
try:
|
||||
_get(target, "/api/v2/monitor/system/status")
|
||||
return "ok"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return f"failed: {exc}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def fortigate_get_system_status(host: str | None = None, api_key: str | None = None, port: int = 443) -> str:
|
||||
"""System status: model, version, serial, uptime."""
|
||||
target = _resolve(host, api_key, port)
|
||||
return json.dumps(_get(target, "/api/v2/monitor/system/status"), indent=2)[:6000]
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def fortigate_list_interfaces(host: str | None = None, api_key: str | None = None, port: int = 443) -> str:
|
||||
"""List interfaces and their link/IP state."""
|
||||
target = _resolve(host, api_key, port)
|
||||
data = _get(target, "/api/v2/monitor/system/interface")
|
||||
return json.dumps(data, indent=2)[:8000]
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def fortigate_list_policies(host: str | None = None, api_key: str | None = None, port: int = 443) -> str:
|
||||
"""List firewall policies (cmdb)."""
|
||||
target = _resolve(host, api_key, port)
|
||||
data = _get(target, "/api/v2/cmdb/firewall/policy")
|
||||
return json.dumps(data, indent=2)[:8000]
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def fortigate_get_routing(host: str | None = None, api_key: str | None = None, port: int = 443) -> str:
|
||||
"""Active routing table."""
|
||||
target = _resolve(host, api_key, port)
|
||||
return json.dumps(_get(target, "/api/v2/monitor/router/ipv4"), indent=2)[:8000]
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def fortigate_get_logs(lines: int = 50, host: str | None = None, api_key: str | None = None, port: int = 443) -> str:
|
||||
"""Recent event log (best-effort; depends on logging config)."""
|
||||
target = _resolve(host, api_key, port)
|
||||
try:
|
||||
data = _get(target, "/api/v2/log/memory/event", params={"rows": lines})
|
||||
return json.dumps(data, indent=2)[:8000]
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return f"log fetch failed (logging may be disk/forticloud): {exc}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
Reference in New Issue
Block a user