Omnissa Horizon MCP Server
by ellswoau
README.md
# Omnissa Horizon MCP Server (Python / FastMCP)
A [Model Context Protocol](https://modelcontextprotocol.io) 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.
## 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).
### Option A — interactive wizard (recommended)
```bash
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:
```json
{
"base_url": "https://horizon.example.com",
"domain": "EXAMPLE",
"username": "svc-horizon-mcp",
"password": "…",
"verify_ssl": true
}
```
### Option B — environment variables
```bash
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
```bash
python -m omnissa_horizon_mcp ping --config ./horizon.json
```
## Running the MCP server
```bash
# 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`)
```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`.
## 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
```bash
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:
```bash
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:
```json
{
"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:
```bash
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)
```bash
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):
```bash
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
```bash
curl -i http://localhost:8000/mcp # expect a JSON-RPC "missing session ID" response
```
## 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
```This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues