Skip to main content
Glama

VISTA-MCP

An MCP (Model Context Protocol) server that exposes a growing catalog of Fortinet VISTA tools to a partner team's agents, for FortiOS / FortiGate engineering and support. Log analysis & visualization is the first tool; the catalog will grow to include things like config/script generation, debug-command helpers, End-of-Support & lifecycle lookups, and more. The tools are independent and varied — each has its own inputs and its own text output; not all take a log file or return a visualization. The agent reads each tool's description in list_tools and calls the one whose purpose matches the task.

Today's toolLog_Analyzer_Visualizer, backed by the Log Visualizer "AgentAssist" API (FortiGate SD-WAN event logs). The client still sends a source_url (+ optional question) and gets back one text report — no client-visible change.

Architecture (current): each MCP tool is a thin entry point into a config-driven orchestrator. On every call it discovers a set of standard analyzers, and an AI Controller decides which optional ones fit the question and how to call each one from the contract it just discovered; they run in parallel, their reports are concatenated, and ORB troubleshooting is appended — with no per-analyzer code. Adding capability is a config edit. Every job is stored in Postgres. Full details:

  • docs/Architecture.md — the whole design and flow

  • docs/analyzer_api.md — the standard analyzer contract every analyzer follows

  • docs/how_to_add_analyzer.md — add an analyzer or a whole new tool (config, no code)

  • docs/gui_guide.md — the operator console: Flow · Dashboard · History

  • docs/db_setup.md — durable job history (the prod psql commands)

  • docs/testing.md — run & test locally, with commands

  • Console at /gui — live flow of every job, dashboard analytics, searchable history, and the TOOL_ENABLEMENT / routing-prompt editor. It also serves two standalone pages: /gui/user-guide (how to drive the console) and /gui/api-integration-guide (the API an analyzer must expose to plug in) — both also under /mcp/gui/…

  • changes.md — what changed in the latest revision, and why

(The older analyzers/ module and docs/ADDING_ANALYZERS.md describe the superseded one-subclass-per-tool proxy pattern; the orchestrator replaced it.)

Production URL: https://vista.fortinet.com/mcp/ (the served MCP path is /mcp/).


1. How it works

Partner agent                     VISTA-MCP (this repo)                    Backend analyzer
─────────────                     ─────────────────────                    ────────────────
list_tools ───────────────────►  return tool name/description/schema
                                  (the model decides whether to call)

call Log_Analyzer_Visualizer
  { source_url:<signed URL>,      1. fetch_log(source_url)  ── stream, ≤50MB ──►  (partner's
    question:"..." }              2. LogVAnalyzer.analyze(bytes, question)         signed URL)
                                     ├─ question?  POST .../filter_analyze_visualyze_logs ──►  Log
                                     └─ no q?      POST .../analyze_visualise_logs        ──►  Visualizer
                                  3. ask ORB for troubleshooting steps  ──────────────────►  ORB API
                                  4. fold JSON + ORB → ONE text report  ◄──── {filter, analysis, viz}
        ◄──────────────────────── return report text

The report the agent receives concatenates, in order:

  1. Filter summaryfilter: matched 2495/29169 · queries 1 + how the logs were selected (the internal engine name is scrubbed).

  2. Open in Log Visualizer — a shareable link to the parsed logs.

  3. Analysis — the AI report (written for a Fortinet TAC engineer).

  4. ORB Suggestions — troubleshooting steps directly relevant to the analysis. This layer sends the analysis + a fixed troubleshoot instruction + the user's own question (when they asked one) to the ORB "ask" API, so the remediation targets what was actually asked, and inserts ORB's answer here (between analysis and the iframe). Fail-open: if ORB is disabled, unreachable, slow, or there's no real analysis, this section is simply omitted and the rest of the report is unaffected.

  5. Interactive Visualization — an iframe URL the agent is told to embed in its answer.

If a question is given, the filter block appears (filter → analyze → visualize). With no question, the Filter block is omitted (just analyze → visualize). ORB Suggestions appear whenever there's a real analysis to troubleshoot. If the uploaded log isn't a supported type, the tool returns a clear "not supported yet" message instead.

The one special input: the log URL

Per the partner contract, we never ask the user for the file — the platform injects a short-lived signed URL into a field we declare (source_url). The tool fetches it promptly (it expires in minutes), streams it (logs can be large; capped by MAX_LOG_BYTES), and does not store it. The model fills only question.


Related MCP server: FortiOS 7.6.x MCP Server

2. Repository layout

VISTA-MCP/
├── server.py                    # FastMCP server: /mcp route, bearer auth, log fetch → orchestrator; mounts /gui
├── config/tool_enablement.json  # TOOL_ENABLEMENT — per-tool: description, routing_system_prompt, orb, analyzers[]
├── orchestrator/                # the config-driven core (no per-analyzer code)
│   ├── models.py                #   the standard contract + ToolConfig/AnalyzerRef/CallPlan/Decision/Job
│   ├── tool_enablement.py       #   load/save the per-tool config (cached, hot-reload)
│   ├── vfr.py                   #   VFR — passthrough today (real routing seam; see Architecture.md §11)
│   ├── discovery.py             #   GET /discover for all analyzers (concurrent, fail-soft) + probe()
│   ├── ai_controller.py         #   the AI gateway client (fail-safe)
│   ├── decide.py · plan.py      #   ALWAYS-ON decision: pick optional analyzers + plan every call
│   │                            #   from live discovery, then validate the plan against it
│   ├── analyzer_client.py       #   the ONE generic call — executes a validated plan
│   ├── orb.py · pipeline.py     #   ORB (fail-open) + the whole flow wired together
│   ├── jobs.py · db.py          #   job/flow registry (CLI logs) + durable history in Postgres
│   └── gui.py                   #   the /gui routes (state, history, analytics, config, probe)
├── gui/index.html               # the operator console — Flow · Dashboard · History (red/white, dark mode)
├── gui/user_guide.html          # served at /gui/user-guide            (console walkthrough)
├── gui/api_guide.html           # served at /gui/api-integration-guide (analyzer contract)
├── fakes/fake_analyzer.py       # a reference analyzer following the contract (+ run_fakes.sh)
├── fakes/fake_analyzer_v2.py    # the same contract with a DIFFERENT request shape (dynamic-discovery test)
├── client_test.py               # end-to-end MCP client (serves a log, list_tools, call_tool)
├── test_data/                   # sample SD-WAN logs
├── docs/                        # Architecture.md · analyzer_api.md · how_to_add_analyzer.md ·
│                                # gui_guide.md · db_setup.md (+ .sql) · testing.md
├── analyzers/                   # SUPERSEDED pre-orchestrator subclass pattern (kept for reference)
└── starter_kit/                 # the partner team's original example (reference, untouched)

3. Run it

python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt

# point at a running Log Visualizer AgentAssist backend (see the LogAssist repo)
export LOGV_API_BASE="http://127.0.0.1:8802/logVisualizer/api/agent_assist"
export LOGV_VIEW_BASE="https://vista.fortinet.com/logVisualizer"   # link/iframe base
export MCP_AUTH_TOKEN="a-strong-shared-token"                       # partner sends this
# for LOCAL testing only, because the test client serves the log on 127.0.0.1:
export MCP_ALLOW_PRIVATE_FETCH=1

python server.py            # → http://0.0.0.0:8100/mcp/

Test it end-to-end (no partner infra needed)

In another shell (server running):

. .venv/bin/activate
export MCP_AUTH_TOKEN="a-strong-shared-token"        # same token the server expects

python client_test.py --list-only                    # inspect the tool schema
python client_test.py --file test_data/sdwan-small.log --question "SLA failures for Austin"
python client_test.py --file test_data/sdwan-small.log --no-question      # analyze + visualize
python client_test.py --file test_data/DEMO_LOG_VISUALIZER_SDWAN.log --question "packet loss over 50%"

The client serves the log over a throwaway local HTTP server (standing in for the partner's signed URL), connects over MCP with the bearer token, and prints the exact text report the agent would receive. (If the MCP server runs on another host, pass --host-ip <ip the server can reach the client at>.)


4. Configuration (env)

Var

Default

Meaning

MCP_HOST

0.0.0.0

bind host

MCP_PORT

8100

bind port (served path is /mcp/)

MCP_AUTH_TOKEN

vista-dev-token

shared bearer token the partner must send. The server refuses to start with an empty/default token unless MCP_ALLOW_INSECURE=1. Set a strong value in prod.

MCP_ALLOW_INSECURE

(off)

1 permits the dev/empty token (local dev only)

MCP_ALLOW_PRIVATE_FETCH

(off)

1 allows fetching private/loopback URLs (needed for the local test client; prod signed URLs are public)

MAX_LOG_BYTES

52428800 (50 MB)

streaming fetch cap for the injected log

MCP_FETCH_CA_BUNDLE

(unset)

PEM bundle of extra CAs to trust on outbound calls (internal PKI). Bad path ⇒ the server refuses to start

MCP_FETCH_INSECURE_TLS_HOSTS

(empty)

comma-separated hosts to skip TLS verification for — a scoped curl -k, for hosts whose cert name doesn't match

MCP_FETCH_INSECURE_TLS

(off)

1 skips TLS verification for every host (blunt; prefer the host list)

MCP_LOG_LEVEL

INFO

terminal flow-log verbosity (DEBUG previews the report text)

LOGV_API_BASE

http://127.0.0.1:8802/logVisualizer/api/agent_assist

Log Visualizer AgentAssist base

LOGV_VIEW_BASE

https://vista.fortinet.com/logVisualizer

SPA base for the returned session links/iframe

ORB_ENABLED

1

set 0 to skip the ORB troubleshooting step (report is built without it)

ORB_ASK_URL

http://172.17.96.58:9345/orb/api/ask

ORB "ask" API queried after the analysis for troubleshooting steps

ORB_USERNAME

logV_mcp_call

username sent to ORB

ORB_TIMEOUT

180

ORB request timeout (s) — ORB is deep-research and slow (~45–140 s); fail-open on timeout

AI_CONTROLLER_ENABLED

1

0 turns the AI Controller off — routing and every request fall back to the deterministic, discovery-built policy

AI_CONTROLLER_GEN_URL

https://vista.fortinet.com/ai-mcp/ds4/generate

AI gateway used for the routing + invocation decision (legacy name DEEPSEEK_GEN_URL still honoured)

AI_CONTROLLER_FALLBACK_URL

https://vista.fortinet.com/ai-mcp/generate

secondary gateway (legacy ANALYSIS_API_URL)

AI_CONTROLLER_TIMEOUT

60

gateway read timeout (s) (legacy DEEPSEEK_TIMEOUT)

DATABASE_URL

(unset)

Postgres for durable job history + console analytics, e.g. postgresql+psycopg://user:pass@host:5432/usage_logs. Unset ⇒ history off, everything else unchanged. See docs/db_setup.md

MCP_DB_AUTO_CREATE

1

0 skips CREATE TABLE IF NOT EXISTS (prod: create the schema by hand)

MCP_DB_STORE_REPORTS

1

0 stores report lengths but not the text

MCP_JOBS_MEMORY

60

how many recent jobs the live console keeps in memory

Every tool call logs the full flow to the terminal with a correlation id:

[633d5f5b] ▶ TOOL CALL  Log_Analyzer_Visualizer  question='SLA failures for LO_C1_2 and High CPU'  source_url=http://…/sdwan.log
[633d5f5b] fetch: done — 13,885,720 bytes, filename='DEMO_LOG_VISUALIZER_SDWAN.log' in 0.31s
[633d5f5b]   ✔ [tool_enablement] ok mandatory=['logv']  optional=['perf']  orb=on
[633d5f5b]   ✔ [discover] ok discovered=['logv', 'perf']
[633d5f5b]   → [ai_controller] asking (4,068-char prompt · mode=select+plan · mandatory=['logv'] optional=['perf'])
[633d5f5b]   ✔ [ai_controller] ok (21022ms) The user asks about 'High CPU', which is a performance concern…
[633d5f5b]   → call[logv] POST …/agent_assist/run  (file=13,885,720B → file, fields=['question'], plan=ai)
[633d5f5b]   → call[perf] POST …/perf/run          (file=13,885,720B → file, fields=['question'], plan=ai)
[633d5f5b]   ✔ [concat] ok 2 section(s), 7,568 chars
[633d5f5b]   ✔ [orb] ok (38214ms) 11,159 chars appended
[633d5f5b] ◀ JOB DONE  report=18,749 chars in 86.2s

5. Auth

The server uses FastMCP's StaticTokenVerifier — a pre-shared bearer token. The partner agent must send Authorization: Bearer <MCP_AUTH_TOKEN>; anything else is 401 Unauthorized (verified). Rotate by changing MCP_AUTH_TOKEN and re-sharing.

Fail-closed: the server refuses to start if MCP_AUTH_TOKEN is empty or the built-in vista-dev-token, unless MCP_ALLOW_INSECURE=1 (local dev only) — so you can't accidentally ship it open or with the public default.

SSRF guard: the injected source_url is fetched server-side, so before fetching (and on every redirect hop) the host is resolved and rejected if it's a private / loopback / link-local / reserved address (e.g. cloud metadata, the internal LogV backend). Production signed URLs are public, so this is transparent; for the local test client (which serves the log on 127.0.0.1) set MCP_ALLOW_PRIVATE_FETCH=1. Signed URLs are never logged (the query string is redacted) and are never echoed back in error messages.

TLS trust on outbound calls

Every outbound call (log fetch, LogV forward, ORB ask) verifies TLS, exactly like curl without -k. A rejected certificate fails as httpx.ConnectError in ~0.1 s — before any HTTP request is sent — and the flow log names it:

✖ fetch failed (ConnectError, TLS certificate rejected) for https://sa-staging.corp.fortinet.com/…
  ↳ TLS certificate rejected by 'sa-staging.corp.fortinet.com' (same failure as `curl` without `-k`). Fix one of: …

Two ways to accommodate an internal host, in order of preference:

Symptom (curl says…)

Fix

unable to get local issuer certificate — signed by an internal CA

MCP_FETCH_CA_BUNDLE=/etc/ssl/certs/corp-ca.pem (mount the PEM into the container)

no alternative certificate subject name matches target host name — name/SAN mismatch. No CA bundle can fix this

MCP_FETCH_INSECURE_TLS_HOSTS=sa-staging.corp.fortinet.com

MCP_FETCH_INSECURE_TLS_HOSTS is a scoped curl -k: verification is skipped for those exact hostnames only, every other host stays fully verified, and each use logs a WARNING. A relaxed request may also only be redirected to another relaxed host — so an unverified hop can't hand the connection off to an arbitrary target. MCP_FETCH_INSECURE_TLS=1 relaxes everything and should be a last resort.


6. Going live (what to send the partner)

Per the starter kit, once the server is reachable from their backend:

  1. Server URLhttps://vista.fortinet.com/mcp/ (include the /mcp/ path).

  2. Auth token — the MCP_AUTH_TOKEN value (share securely).

  3. For each log-consuming tool — the tool name and the input field that receives the injected log URL:

    • Log_Analyzer_Visualizersource_url

They register the server, wire source_url to their signed-URL injector, and enable the tool.


7. Design notes

  • Proxy, not a re-implementation. VISTA-MCP holds no analysis logic; it forwards to the analyzer's API and formats the reply. The Log Visualizer decides the log subtype (SD-WAN today) and how to handle it — VISTA-MCP just surfaces the result (including "not supported").

  • Pluggable analyzers, discovered live. The contract is docs/analyzer_api.md: GET <base>/discover + POST <query.path>. The orchestrator holds one discover function, one call function and one read path, and learns each analyzer's request shape from its discovery on every call — so an analyzer can change its API without a change here. Adding one is a config edit in the console.

  • The model can propose, never decide alone. The AI Controller's request plan is validated field-by-field against the analyzer's own discovery before anything is sent; anything not advertised is corrected or dropped, and the correction is recorded.

  • Fail-safe everywhere. No AI gateway ⇒ deterministic routing + discovery-built requests. No ORB ⇒ report without it. No database ⇒ live view only. One analyzer down ⇒ the others still answer.

  • Bounded output. The report is text; the analysis is already reduced by the backend.

  • Stateless per call for the log. Fetch → forward → return; the log itself is never stored. Job metadata and reports are persisted for the console's history and analytics.

  • Framework. FastMCP (runs on an ASGI/Starlette app via uvicorn) implements the MCP protocol and the /mcp/ HTTP transport — the same framework the partner's starter kit uses.

8. Verified behavior (end-to-end)

Scenario

Result

list_tools

every configured tool (Log_Analyzer_Visualizer today), inputs source_url (required) + question

With question (SD-WAN)

filter block (matched N/total) + LogV link + analysis + ORB Suggestions + iframe URL

No question (SD-WAN)

LogV link + analysis + ORB Suggestions + iframe URL (no filter block)

ORB Suggestions section

troubleshooting steps directly relevant to the findings, added by this MCP layer between the analysis and the visualization (fail-open)

Response time

~1–2 min (full analysis + ORB troubleshooting research); within the 300 s read timeout

Non-SD-WAN log

"classified as Traffic:forward, not supported yet…"

14 MB log

streamed + processed (29,169 logs)

Internal engine name

scrubbed (0 mentions in output)

Wrong/missing bearer token

401 Unauthorized

AI Controller

runs on every job — select+plan with optional analyzers, plan-only without; never skipped

Analyzer with a different contract

called correctly with no code change (renamed file/question params, an extra required param, a different path)

AI gateway unreachable

fail-safe: all optional analyzers run, requests built from discovery, client output unaffected

Job history

every job + step + per-analyzer request/result + final report in Postgres, replayable in the console after a restart

F
license - not found
-
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.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Autopilot MCP server for GEO analyses, reports, content, audits, memories and agents.

  • Hosted MCP server for agent governance: MCP config audits, injection scans, scope-policy checks.

  • MCP Server for agents to onboard, pay, and provision services autonomously with InFlow

View all MCP Connectors

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/ProjectVISTA-Kashcode/vista-agent-mcp'

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