Skip to main content
Glama
ellswoau

Omnissa Horizon MCP Server

by ellswoau

Omnissa Horizon MCP Server (Python / FastMCP)

A Model Context Protocol server exposing support/helpdesk operations for an Omnissa Horizon Server 2506 environment, built with Python FastMCP. It targets the Horizon REST API documented at https://developer.omnissa.com/horizon-apis/horizon-server/versions/2506/.

Works both as a direct python -m process and as a Docker container (see "Running via Docker" below).

Features (helpdesk / support)

User & session management

  • find_user_session / list_sessions — find a user's desktop/session(s)

  • restart_user_desktop / restart_session — reboot a user's desktop

  • logoff_user / logoff_session — log a user off (graceful or forced)

  • disconnect_session — detach the display without logging off

  • send_session_message — popup a message to a user's session

Utilization (processes / CPU / memory / network)

  • user_processes / session_processes — running processes + CPU/mem/disk %

  • user_cpu_memory / session_utilization — CPU, memory, disk & latency stats

  • session_network_performance — estimated bandwidth (kbps), round-trip latency and packet loss (PCoIP/BLAST) to diagnose slow/thin sessions

  • end_session_process — terminate a runaway process

Pool & environment health

  • list_desktop_pools / desktop_pool_status — pool health report (total / available / in-use / errors), view model described below

  • list_machines + restart / reset / shutdown / enter-maintenance / exit-maintenance machine

  • monitor_connection_servers / monitor_gateways / monitor_farms / monitor_rds_servers / monitor_ad_domains / monitor_event_database

  • horizon_ping / horizon_config_summary — connectivity & config

The MCP exposes the most common support/helpdesk actions so an assistant can "restart Jim's hung desktop", "check Bob's CPU/memory", or "is the Sales pool healthy?" directly against Horizon.

Related MCP server: vcf-mcp-sddc-vc

Geometry & requirements

  • Python 3.9+ (tested on 3.12)

  • Install: pip install -r requirements.txt

Configuration (secure service-account credentials)

Credentials are never hard-coded. Provide them via a config JSON file or environment variables (precedence: explicit args > env > file).

cd omnissa-horizon-mcp
python -m omnissa_horizon_mcp config init --config ./horizon.json

This prompts for the Connection Server URL, AD domain, service account username and password (password entered without echoing), and writes the file with 0600 owner-only permissions. Example result:

{
  "base_url": "https://horizon.example.com",
  "domain": "EXAMPLE",
  "username": "svc-horizon-mcp",
  "password": "…",
  "verify_ssl": true
}

Option B — environment variables

export HORIZON_BASE_URL="https://horizon.example.com"
export HORIZON_DOMAIN="EXAMPLE"
export HORIZON_USERNAME="svc-horizon-mcp"
export HORIZON_PASSWORD="…"
export HORIZON_VERIFY_SSL="false"        # default true; set false for test CAs
export HORIZON_TIMEOUT="30"
# optional, to point at a config file:
export HORIZON_CONFIG_FILE="./horizon.json"

A .env file is also honored if python-dotenv is installed and loaded (see .env.example). See omnissa_horizon_mcp/config.py for all HORIZON_* vars.

Verify credentials before connecting to an MCP client

python -m omnissa_horizon_mcp ping --config ./horizon.json

Running the MCP server

# stdio transport (used by Claude Desktop / MCP clients)
python -m omnissa_horizon_mcp --config ./horizon.json   # note: --config passthrough

By default the server resolves config from HORIZON_CONFIG_FILE / env vars. To point at a specific file, set export HORIZON_CONFIG_FILE=./horizon.json before launching, and add a .env load if desired.

Example MCP client config (Claude Desktop claude_desktop_config.json)

{
  "mcpServers": {
    "omnissa-horizon": {
      "command": "python",
      "args": ["-m", "omnissa_horizon_mcp"],
      "env": {
        "HORIZON_CONFIG_FILE": "/abs/path/to/horizon.json"
      }
    }
  }
}

How desktop_pool_status computes "available" vs "used"

It combines three sources:

  • /inventory/v1/desktop-pools — pool metadata (id, name, type, source, enabled)

  • /inventory/v1/machines — machines grouped by pool; non-AVAILABLE/erroring machines are excluded from availability

  • /inventory/v1/sessions + /monitor/desktops — in-use session count and the pool's aggregated monitor status (OK/ERROR/…)

Result summary: total_machines, total_in_use_sessions, total_errors, plus per-pool machines_by_state, in_use_sessions, available_estimate and any cloning/state errors.

Horizon 2506 API notes

A few Horizon REST details that this server handles explicitly (verified against the 2506 OpenAPI spec and the Horizon Server REST Pagination, Filter and Sorting Guide):

  • Inventory filters use a JSON filter object, not the field 'value' string form. list_sessions / list_machines build e.g. {"type":"Equals","name":"desktop_pool_id","value":"…"}, chained with an "And" wrapper for multiple clauses. Field/operator choices: user_name (Contains), desktop_pool_id (Equals), session_state (Equals) for sessions; name (Contains), desktop_pool_id (Equals), state (Equals) for machines.

  • Sessions are listed from /inventory/v7/sessions (not v1): the base SessionInfo model returned by /inventory/v1/sessions has no user_name field (only the raw user_id SID), so it cannot be filtered or displayed by username. user_name is added from the versioned session models (v4+); v7 is the current 2506 model. find_user_session therefore filters server-side on user_name (Equals/Contains) and falls back to an unfiltered client-side scan. If sessions are visible but none expose a user_name, the tool raises a clear error about the likely missing service-account privilege instead of silently reporting zero matches.

  • Helpdesk performance calls use /helpdesk/v1/... with session_id (historical-data, process, display-protocol, and remote-process/action/end-remote-process). The /helpdesk/v2/... endpoints take internal_session_id, a different internal identifier that rejects an inventory session id (helpdesk.session.find.error).

Security notes

  • Passwords are never logged; redacted() masks them.

  • All requests use verify_ssl (on by default). Set false only for self-signed/test environments.

  • Mutating operations (restart, logoff, reset, end_session_process) require the appropriate Horizon privileges; check the Connection Server roles of the service account.

Running via Docker

The project ships a Dockerfile, docker-compose.yml and .dockerignore. Run as a non-root user; the app runs over stdio by default (embedded MCP client) and can also run as a long-lived HTTP/SSE daemon.

Build

docker build -t omnissa-horizon-mcp .

Option A — stdio (embedded MCP client, e.g. Claude Desktop)

Create your config file with config init, then run the container interactively so stdio stays attached; pass credentials via env or a mounted config file:

docker run -i --rm \
  -e HORIZON_CONFIG_FILE=/config/horizon.json \
  -v "$(pwd)/horizon.json:/config/horizon.json:ro" \
  omnissa-horizon-mcp

Point the MCP client at the container, e.g. Claude Desktop:

{
  "mcpServers": {
    "omnissa-horizon": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "-e", "HORIZON_CONFIG_FILE=/config/horizon.json",
               "-v", "/abs/path/horizon.json:/config/horizon.json:ro",
               "omnissa-horizon-mcp"]
    }
  }
}

Or pass secrets via -e instead of mounting a file:

docker run -i --rm \
  -e HORIZON_BASE_URL=https://horizon.example.com \
  -e HORIZON_DOMAIN=EXAMPLE \
  -e HORIZON_USERNAME=svc-horizon-mcp \
  -e HORIZON_PASSWORD=*** \
  -e HORIZON_VERIFY_SSL=false \
  omnissa-horizon-mcp

File-permission note: the container runs as an unprivileged user, so a mounted horizon.json must be world/group-readable for it to open, or it will fail on permissions. Prefer env vars, or chown the file to the container UID, if you hit Permission denied.

Option B — network daemon (HTTP / SSE / streamable-http)

docker run -d --name horizon-mcp -p 8000:8000 \
  -e HORIZON_CONFIG_FILE=/config/horizon.json \
  -v "$(pwd)/horizon.json:/config/horizon.json:ro" \
  omnissa-horizon-mcp --transport http --host 0.0.0.0 --port 8000

or with Compose (bundled):

docker compose up -d --build

Then connect an MCP client that supports HTTP/SSE to http://<host>:8000/mcp. Available transports: stdio (default), sse, streamable-http, http.

Verify the daemon is up

curl -i http://localhost:8000/health        # HTTP 200 + JSON status
curl -i http://localhost:8000/healthz       # alias
curl -i http://localhost:8000/mcp           # expect JSON-RPC "missing session ID"

A health check that requires no Horizon credentials and does not block on a login is exposed at /health (alias /healthz) whenever the daemon transport (http / sse / streamable-http) is running:

{"status":"ok","service":"omnissa-horizon-mcp","version":"0.1.0","uptime_seconds":123,"healthy":true}

The Docker HEALTHCHECK and the bundled docker-compose.yml healthcheck both hit /health automatically.

Authentication (bearer token)

The network transport is gated behind a bearer token when configured. With a token set, every endpoint except /health and /healthz requires Authorization: Bearer <token> and returns 401 otherwise.

Set it via the env var HORIZON_MCP_AUTH_TOKEN or the --token flag. Generate a strong one:

export HORIZON_MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
docker run -d --name horizon-mcp -p 8000:8000 \
  -e HORIZON_CONFIG_FILE=/config/horizon.json \
  -e HORIZON_MCP_AUTH_TOKEN="$HORIZON_MCP_AUTH_TOKEN" \
  -v "$(pwd)/horizon.json:/config/horizon.json:ro" \
  omnissa-horizon-mcp --transport http --host 0.0.0.0 --port 8000

Clients must then send the header on every request:

curl -H "Authorization: Bearer $HORIZON_MCP_AUTH_TOKEN" http://host:8000/mcp

Notes:

  • When HORIZON_MCP_AUTH_TOKEN is unset/empty, auth is disabled (open), preserving the default behaviour. The bundled docker-compose.yml requires the variable so daemon deployments are secured by default.

  • If the token is ever compromised, rotate it and redeploy — the running process does not cache it across restarts.

  • /health stays public so load balancers / uptime monitors can probe liveness without a secret.

Project layout

omnissa-horizon-mcp/
├── README.md
├── requirements.txt
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── .env.example
└── omnissa_horizon_mcp/
    ├── __init__.py
    ├── __main__.py          # python -m entrypoint
    ├── server.py            # FastMCP app + CLI (config init / ping / run)
    ├── config.py            # secure config & credential resolution
    ├── client.py            # Horizon REST client (auth, pagination, errors)
    └── tools/
        ├── __init__.py      # registers all tool modules
        ├── _common.py       # user-session helpers
        ├── connection_tools.py
        ├── session_tools.py
        ├── utilization_tools.py
        ├── pool_tools.py
        └── machine_tools.py

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    Enables AI assistants to manage and monitor VergeOS virtualization platforms through natural language, including VM operations, network management, tenant administration, and cluster monitoring.
    21
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables natural language interaction with VMware SDDC Manager and vCenter APIs through MCP tools, allowing users to query workload domains, VMs, clusters, and more.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    MCP server for managing VMware Horizon VDI environments via the Horizon 8 REST API, enabling AI agents to monitor sessions, manage pools, and perform operations like logoff, reset, and image push with safety previews and audit logging.
    27
    97 PyPI
    1
    MIT