vsphere-console-mcp
# vsphere-console-mcp
<!-- mcp-name: io.github.Razer0028/vsphere-console-mcp -->
**Let an AI agent see and use the console of your VMware vSphere VMs — screen, mouse and keyboard — through
vCenter, with nothing installed in the guest.**
[日本語](https://github.com/Razer0028/vsphere-console-mcp/blob/main/README.ja.md)
An [MCP](https://modelcontextprotocol.io) server with one tool, `vm`, that works the way the vSphere Client's
console window does. Because it never touches the guest OS, it reaches the places SSH, RDP and in-guest agents
cannot: sign-in and lock screens, the BIOS/UEFI setup, OS installers, a VM whose network is down, a Windows VM
stuck on a blue screen or "Preparing to configure Windows".
```
You: web-02 stopped answering. Look at its console.
Agent: [vm look web-02] It is in emergency mode: "Give root password for maintenance".
The journal line above says /data failed to mount. Do you want me to log in and check /etc/fstab?
```
It is meant for **an agent working with a person**, not for unattended automation: the person decides, the agent
looks, reads and types. See [Good to know before you use it](#good-to-know-before-you-use-it).
## Why this one
- **Zero dependencies.** Python standard library only — no pyVmomi, no `websockets`, no `requests`. It speaks
vSphere SOAP, the console screenshot endpoint and WebMKS (RFB over a websocket) itself, in about 1,300
lines you can read in an afternoon. Installing it pulls in exactly one package.
- **Least privilege, verified.** The service account needs Read-only plus **one** privilege, *Console
interaction*. Typing works through the console connection, so it does **not** need
`VirtualMachine.Interact.PutUsbScanCodes` (or any power, snapshot or configuration privilege).
- **No power button.** There is no code to power on/off, reset, snapshot or reconfigure a VM.
- **Correct symbols on non-US keyboards.** Keys go as scan codes for the guest's layout (`us`, `jis`), not as
keysyms that vCenter would remap as US, so `@ : _ \` arrive as typed.
- **Guardrails on the MCP side too**: an allow-list of VM names, a read-only mode, a JSON audit log that never
records typed text, and a guard that stops retrying a rejected password before vCenter locks the account.
## Use cases
| Situation | What the agent does |
|---|---|
| A VM is unreachable over the network | Reads the console: kernel panic, emergency-mode prompt, full disk, a hung boot; tells you what it sees |
| Incident triage | Screenshots several VMs in turn and summarizes which are at a login prompt and which are stuck |
| Windows VM stuck before sign-in | Reads update/repair screens, sends `ctrl+alt+delete`, walks recovery menus — you approve each step |
| OS installation, first boot, OOBE | Goes through screens that have no network or automation hook yet |
| BIOS / UEFI | Reads firmware screens and changes settings with the keyboard |
| Labs, training, homelabs | Drives disposable VMs end to end, including the part before SSH works |
| Runbooks and tickets | Captures the exact console screen |
## Quick start
### 1. Install
```bash
pipx install git+https://github.com/Razer0028/vsphere-console-mcp
# or: uv tool install git+https://github.com/Razer0028/vsphere-console-mcp
# pip install git+https://github.com/Razer0028/vsphere-console-mcp
```
Or run it without installing: `uvx --from git+https://github.com/Razer0028/vsphere-console-mcp vsphere-console-mcp`.
### 2. Give it an account
Create a dedicated vCenter account with the role in [Permissions](#permissions-for-vcenter-administrators), and
put its details in a file only you can read:
```bash
install -m 600 /dev/null ~/.config/vsphere-console-mcp.env
cat >> ~/.config/vsphere-console-mcp.env <<'EOF'
VSPHERE_SERVER=https://vcenter.example.com
VSPHERE_USER=svc-ai-console@vsphere.local
VSPHERE_PASSWORD=...
EOF
VSPHERE_CREDENTIALS_FILE=~/.config/vsphere-console-mcp.env vsphere-console-mcp check
```
`check` logs in and prints how many VMs the account can use. `vsphere-console-mcp vms` and
`vsphere-console-mcp look <vm> -o screen.png` work from the shell too.
### 3. Add it to your MCP client
**Claude Code**
```bash
claude mcp add vsphere-console -e VSPHERE_CREDENTIALS_FILE=$HOME/.config/vsphere-console-mcp.env -- vsphere-console-mcp
```
**Claude Desktop, Cursor, VS Code and other clients** (`mcpServers` JSON):
```json
{
"mcpServers": {
"vsphere-console": {
"command": "vsphere-console-mcp",
"env": { "VSPHERE_CREDENTIALS_FILE": "/home/me/.config/vsphere-console-mcp.env" }
}
}
}
```
If only another machine can reach vCenter, run the server there over SSH:
`"command": "ssh", "args": ["jump-host", "vsphere-console-mcp"]`.
**Docker**: build the image from this repository (`docker build -t vsphere-console-mcp .`), then `docker run -i --rm -e VSPHERE_CREDENTIALS_FILE=/run/creds.env -v ~/.config/vsphere-console-mcp.env:/run/creds.env:ro -v vsphere-console-mcp-lock:/lock vsphere-console-mcp`.
Keep `VSPHERE_LOCK_DIR` on a volume that every container using the account shares; see
[Accounts and lockout](#accounts-and-lockout).
## The `vm` tool
| action | What it does |
|---|---|
| `vms` | Lists the VMs the account can see (and the allow-list permits): power state, guest OS, IP address |
| `look` | Returns a PNG of the screen (scaled to `max_width`, default 1568 px) and the real screen size |
| `click` / `move` / `drag` / `scroll` | Mouse at `rx`,`ry` (0–1000 across and down; survives resolution changes) or `x`,`y` (real screen pixels, as reported by `look` — not pixels of the scaled picture) |
| `type` | Types text with scan codes for the guest's `layout` (`us` or `jis`). If any character is not on that keyboard, nothing is sent |
| `key` | Key combinations: `"ctrl+alt+delete"`, `"win+r"`, `"alt+f4"`, `"tab tab enter"` |
`vm` names a VM by its vCenter name, its managed object id (`vm-123`) or its IP address, and is required on every
call except `vms` — there is no default VM, so an agent cannot act on a machine nobody named. `then_look: true`
returns a fresh screenshot after an input action.
## Configuration
All settings are environment variables. `VSPHERE_CREDENTIALS_FILE` may hold any of them as `KEY=VALUE` lines;
variables set in the environment win.
| Variable | Default | Meaning |
|---|---|---|
| `VSPHERE_SERVER` | — | vCenter address, e.g. `https://vcenter.example.com` (a port may follow; IPv6 in brackets) |
| `VSPHERE_USER` / `VSPHERE_PASSWORD` | — | The account. Prefer the credentials file to putting the password in client config |
| `VSPHERE_CREDENTIALS_FILE` | — | Path to a `KEY=VALUE` file with the settings above |
| `VSPHERE_CA_FILE` | system CAs | CA bundle for vCenter's certificate (e.g. your VMCA root certificate) |
| `VSPHERE_INSECURE` | off | `1` skips verification of vCenter's certificate. For labs only |
| `VSPHERE_VM_ALLOW` | all | Comma-separated name patterns (`lab-*,web-0?`); other VMs are hidden and refused |
| `VSPHERE_READ_ONLY` | off | `1` offers only `vms` and `look`; input actions are not advertised to the client |
| `VSPHERE_KEYBOARD` | `us` | Default guest keyboard layout: `us` or `jis` (the caller can override per call) |
| `VSPHERE_AUDIT_LOG` | off | File to append a JSON line per call to (`-` = stderr) |
| `VSPHERE_LOCKOUT_MINUTES` | `15` | After vCenter rejects the password, do not try again for this long (`0` = never pause) |
| `VSPHERE_LOCK_DIR` | `~/.cache/vsphere-console-mcp` | Where the login-failure mark is kept |
| `VSPHERE_TIMEOUT` | `20` | Seconds per request |
vCenter's certificate is verified unless `VSPHERE_INSECURE=1`. The certificate of each ESXi host (used for
keyboard and mouse input) is always checked against the SHA-1 or SHA-256 thumbprint that vCenter puts in the
WebMKS ticket, independently of `VSPHERE_INSECURE`.
## Permissions (for vCenter administrators)
Create a **dedicated service account** for the agent; do not reuse a person's account or an administrator's.
### The role
| Privilege (UI name) | Privilege ID | Needed for |
|---|---|---|
| Read-only (part of every role) | `System.Anonymous`, `System.View`, `System.Read` | Login, listing VMs |
| Virtual machine → Interaction → **Console interaction** | `VirtualMachine.Interact.ConsoleInteract` | `look` (the screen) **and** mouse/keyboard (the WebMKS ticket) |
That is all. Tested with vCenter 8.0.3: a Read-only account lists VMs but gets HTTP 401 for the screen and
`NoPermission (VirtualMachine.Interact.ConsoleInteract)` for input; with Console interaction added, everything
works, without `VirtualMachine.Interact.PutUsbScanCodes`.
With PowerCLI:
```powershell
New-VIRole -Name "AI Console Operator" -Privilege (Get-VIPrivilege -Id System.Anonymous,System.View,System.Read,VirtualMachine.Interact.ConsoleInteract)
New-VIPermission -Entity (Get-Folder "AI-Managed") -Principal "VSPHERE.LOCAL\svc-ai-console" -Role "AI Console Operator" -Propagate:$true
```
With govc:
```bash
govc role.create "AI Console Operator" System.Anonymous System.View System.Read VirtualMachine.Interact.ConsoleInteract
govc permissions.set -principal svc-ai-console@vsphere.local -role "AI Console Operator" -propagate=true /Datacenter/vm/AI-Managed
```
### Scope it
- **Grant the role on a VM folder (or a resource pool), not on the vCenter root.** The account then sees only the
VMs there; nothing else exists for it. This is the main control, and vCenter enforces it.
- vSphere has **no privilege that allows the screen but not input** — Console interaction covers both.
`VSPHERE_READ_ONLY=1` makes this server refuse input, but the server enforces it, not vCenter.
- `VSPHERE_VM_ALLOW` is a second, client-side filter (for example to keep production VMs out of reach even inside a
shared folder). Treat it as defense in depth, not as the boundary.
- If your organization manages permissions through SSO or AD groups, grant the role to a group and put the
service account in it.
- vCenter records the account's logins and console tickets in its events. `VSPHERE_AUDIT_LOG` adds what the agent
looked at and did on the screen.
### Accounts and lockout
vCenter Single Sign-On locks an account after a few failed logins (5 by default). When vCenter **rejects the
password** (`InvalidLogin`), this server writes a mark and does not try that account again for
`VSPHERE_LOCKOUT_MINUTES` (15) — in this process and in every process that shares `VSPHERE_LOCK_DIR` — so a wrong
password in a config file does not lock the service account for everyone. Network errors, timeouts and an
overloaded vCenter do **not** set the mark. In containers, put `VSPHERE_LOCK_DIR` on a shared volume, or each new
container starts without the mark. The server keeps one session for its lifetime and logs out on exit (including
SIGTERM).
## Good to know before you use it
- **Typing into a VM is full control of that VM.** No power privilege does not mean safe: if a shell is open, the
agent can type anything a person could. Keep a human approving input actions (most MCP clients ask before each
tool call; do not auto-approve this tool for production VMs), and scope the account to the VMs it may touch.
- **Prefer the keyboard to the mouse.** Vision models estimate positions in a screenshot only roughly; a click can
land a few pixels off. `tab`, arrow keys, `enter` and shortcuts are deterministic. After a click, `then_look`
and check.
- **Screens move faster than an agent.** Look → think → act takes seconds, so a boot menu with a 3-second timeout
or a progress bar will not wait. This is a tool for screens that wait for input.
- **`layout` must match the guest's current keyboard layout.** Keys are sent as scan codes and the guest maps them.
BIOS screens, installers and Linux text consoles usually use `us` until configured otherwise.
- **Each tool call opens one console connection**, sends all its events (a whole `type` or `drag`), and closes it.
If the connection breaks in the middle, the server releases any keys it was holding; if it cannot, the result
says so, so you can look and press the key once.
- **Screenshots are sent to your AI provider** as part of the conversation. Do not point the agent at consoles that
show data you may not send there.
- Passwords are used only in the login request and are never put in tool results, errors, the audit log or command
lines; neither are WebMKS tickets. The audit log records the length of typed text, not the text, and records
single-character keys as `*`.
## How it works
```
MCP client ──stdio──▶ vsphere-console-mcp
├─ SOAP https://vcenter/sdk Login, CreateContainerView + RetrievePropertiesEx (list),
│ AcquireTicket("webmks")
├─ HTTPS https://vcenter/screen?id=vm the console screenshot (PNG, scaled by vCenter)
└─ WSS wss://esxi/ticket/<ticket> RFB over a websocket: pointer events and VMware's
key-scancode message; connects, sends, closes
```
- The RFB connection asks for a shared session, so it does not disconnect anyone else's console window. It never
asks for screen updates and never changes the guest's resolution.
- The screen size can change under you (another console window may resize it). Relative coordinates always point
at the same place; pixel coordinates are mapped from the size seen at the last `look`.
## Limitations
- Keyboard layouts: `us` and `jis`. Other layouts are welcome as pull requests (`keyboard.py`).
- Text that is not on the keyboard (e.g. CJK, emoji) has to be entered with the guest's input method.
- Tested with vCenter 8.0.3 and Debian and Windows 11 guests. A standalone ESXi host without vCenter is untested.
- The WebMKS wire protocol (RFB over a websocket, plus VMware's own key-scancode message) is not publicly
documented; VMware only published a JavaScript SDK on top of it. The SOAP calls have been stable for many vSphere
releases, but a future version could change the console protocol; please open an issue with the version if it
stops working.
## Related projects
This server only does the console. For inventory, power and configuration, use it alongside a vSphere management
MCP server such as [InfraMCP/vsphere-mcp-server](https://github.com/InfraMCP/vsphere-mcp-server) (VMs, hosts,
datastores, networks, power on/off) or VMware's own tooling — giving each its own account and role.
## Development
```bash
python -m venv .venv && .venv/bin/pip install -e '.[test]' && .venv/bin/pytest
```
The tests use no network: SOAP and WebMKS are faked.
## License
MIT. See [LICENSE](https://github.com/Razer0028/vsphere-console-mcp/blob/main/LICENSE). VMware, vSphere, vCenter and
ESXi are trademarks of Broadcom; this project is not affiliated with or endorsed by Broadcom. Security issues:
[SECURITY.md](https://github.com/Razer0028/vsphere-console-mcp/blob/main/SECURITY.md).
TDQS
Scored across 1 tool
With a single tool, the real disambiguation burden falls on the action enum, and those actions are well-differentiated: look, click, move, drag, scroll, type and key each map to a distinct console input or observation. Minor overlap exists between move and click (a click inherently moves the pointer) and between type and key, but the descriptions make the intended use of each clear.
Action names are uniformly short lowercase words (vms, look, click, move, drag, scroll, type, key), plus a consistent then_look modifier. The lone tool name 'vm' is a bare noun rather than a verb_noun form, and 'vms'/'look' mix noun and verb styles slightly, but with one tool there is no convention clash to speak of.
Everything lives behind one multiplexed 'vm' tool, which is thin for a surface this broad — an agent must read a long action list and cannot discover capabilities from tool names alone. The multiplexing is a defensible way to avoid power-operation confusion, but 1 tool for the whole vSphere console domain sits at the borderline end of the scale.
The console-interaction domain is well covered: VM enumeration with power/IP metadata, screen capture with scaling, full mouse (click/move/drag/scroll), and keyboard (type/key) input, all with then_look feedback. Gaps are minor and explicitly scoped out (no power on/off or reconfiguration), though clipboard/file transfer and waiting-for-screen-change helpers are absent.