VMware vCenter 8.0 MCP server with dry-run safety
VMware vCenter 8.0 MCP Server
A Model Context Protocol server that lets Cursor (or any MCP-compatible client) manage a VMware vCenter 8.0 deployment end-to-end: read inventory, query statistics, take snapshots, power VMs on and off, vMotion across hosts, mount ISOs, configure DRS/HA, remediate clusters with vLCM, and more.
The server fronts three vSphere API surfaces behind a single session:
- vSphere Automation REST API (
/api/...) - primary, modern JSON. - VI/JSON API (
/sdk/vim25/{release}/..., vCenter 8.0 U1+) - full vim25 parity for ops not exposed by the Automation API (snapshots, advanced VM config, performance counters, alarms, RBAC). - vSphere Web Services SOAP via
@vates/node-vsphere-soap- lazy fallback; exposed through a singlesoap_runCommandescape hatch.
Safety first. Every destructive tool (
*_delete,*_powerOff,snapshot_remove,host_reboot, ...) requires an explicitconfirm: trueargument. Otherwise, the tool returns a structured dry-run describing the exact request that would have been sent, but never touches vCenter. A globalVCENTER_READ_ONLY=truekill switch refuses every destructive tool regardless ofconfirm.
Quick start
git clone https://github.com/TheEvalon/vmware-vcenter-mcp.git
cd vmware-vcenter-mcp
npm install
copy .env.example .env
# edit .env with your vCenter details
npm run build
npm start
For development you can run the server with tsx:
npm run dev
Run the unit test suite:
npm test
Run the comprehensive read-only integration suite against a real vCenter
(recommended pre-publish gate). It spawns the MCP server twice over stdio,
discovers your lab inventory, exercises every read-only tool, dry-runs every
destructive tool, and verifies the VCENTER_READ_ONLY=true kill switch
blocks every write path:
npm run test:integration:readonly
The legacy single-test smoke pass is still available:
$env:VCENTER_INTEGRATION = "true"; npm run test:integration
Integrating with Cursor
This MCP server uses the standard stdio transport and registers with
Cursor the same way any MCP server does: a single entry in your
mcp.json. Five steps end-to-end:
1. Prerequisites
- Node.js >= 22 on PATH (
node --version). - This repo cloned and built once -- see Quick start. The
important output is
dist/index.js. - Cursor 0.42 or newer. Older Cursor builds support MCP but use a different config schema.
- vCenter credentials handy (
VCENTER_HOST,VCENTER_USER,VCENTER_PASS).
2. Get the absolute path to dist/index.js
Cursor launches the MCP server itself, so it needs an absolute path -- a relative path will silently fail to start. From inside your clone:
# Windows (PowerShell)
(Resolve-Path .\dist\index.js).Path
# macOS / Linux
realpath dist/index.js
Copy the printed path; you'll paste it into args below.
3. Edit your Cursor MCP config
Cursor reads MCP servers from one of two locations. Either works -- the project-scoped one is recommended if you want the MCP available only inside specific Cursor workspaces.
| Scope | File |
|---------------|--------------------------------------------------|
| Global | ~/.cursor/mcp.json (macOS/Linux) |
| Global | %USERPROFILE%\.cursor\mcp.json (Windows) |
| Project-only | <your-project>/.cursor/mcp.json |
Open the file (create it if missing) and add the vmware entry:
{
"mcpServers": {
"vmware": {
"command": "node",
"args": ["<absolute-path-from-step-2>/dist/index.js"],
"env": {
"VCENTER_HOST": "vcsa.lab.local",
"VCENTER_USER": "administrator@vsphere.local",
"VCENTER_PASS": "********",
"VCENTER_INSECURE": "false",
"VCENTER_READ_ONLY": "true",
"VCENTER_LOG_LEVEL": "info"
}
}
}
}
Notes on the snippet:
VCENTER_READ_ONLY=trueis the recommended starting point. Every destructive tool is blocked globally until you explicitly flip it tofalse. See Safety / dry-run.VCENTER_INSECUREshould stayfalsein production. Set it totrueonly for homelabs with self-signed certs.- If you already have other MCP servers in
mcp.json, just add the"vmware"key alongside them inside the existing"mcpServers"object -- don't replace the file. - On Windows, write the path with forward slashes or
double-escaped backslashes in JSON:
"D:/Repos/vmware-vcenter-mcp/dist/index.js"or"D:\\Repos\\vmware-vcenter-mcp\\dist\\index.js".
4. Reload Cursor and verify the server is up
- Quit Cursor fully (don't just close the window).
- Reopen Cursor.
- Open Settings -> MCP (or Cmd/Ctrl+Shift+J -> "MCP"). You should
see a
vmwareentry with a green dot. The tools list should show 100+ entries (vm_list,snapshot_create,host_enterMaintenance, etc.) once the server has finished registering. - If the dot is red, click it to view the stderr log -- the most
common cause is a wrong absolute path or unreachable
VCENTER_HOST. See Troubleshooting below.
5. Try it from a Cursor chat
Open a new chat in any project and ask, for example:
"List the VMs in my vCenter and show their power state."
"Take a snapshot of
vm-101namedpre-upgrade. Use dry-run first, then ask me before applying."
"Show DRS recommendations for cluster
cl-prod-01."
Cursor will pick the right tool from the catalog automatically. Because
VCENTER_READ_ONLY=true is set, any destructive ask will be answered
with a structured dry-run preview rather than a real change -- exactly
what you want for the first session.
Development variant (run from source via tsx)
If you're hacking on this MCP and want changes to take effect without a
rebuild, swap the command/args for:
"command": "npx",
"args": ["tsx", "<absolute-path-to-clone>/src/index.ts"]
Cursor restarts the MCP on every config save, so a Cursor reload picks up the new source.
Environment variables
| Variable | Required | Default | Description |
|---------------------------|----------|---------|-------------|
| VCENTER_HOST | yes | - | Hostname or IP of the vCenter Server (no scheme). |
| VCENTER_PORT | no | 443 | HTTPS port. |
| VCENTER_USER | yes | - | SSO username (e.g. administrator@vsphere.local). |
| VCENTER_PASS | yes | - | SSO password. |
| VCENTER_INSECURE | no | false | Skip TLS verification (homelab / self-signed certs). |
| VCENTER_LOG_LEVEL | no | info | trace, debug, info, warn, error. Logs go to stderr only. |
| VCENTER_TASK_TIMEOUT_MS | no | 600000| Max wait for any long-running task (clones, vMotion, remediation). |
| VCENTER_TASK_POLL_MS | no | 1500 | Task polling interval. |
| VCENTER_READ_ONLY | no | false | Block all destructive tools regardless of confirm. |
The server reads .env automatically via dotenv.
Safety / dry-run
// First call: no confirm flag, returns a structured dry-run preview.
{
"tool": "snapshot_create",
"arguments": { "vmId": "vm-101", "name": "pre-upgrade" }
}
// -> {
// "content": [{"type":"text","text":"DRY RUN: Would create snapshot \"pre-upgrade\" on vm-101 ..."}],
// "structuredContent": {
// "dryRun": true,
// "tool": "snapshot_create",
// "summary": "...",
// "request": {"method":"POST","path":"/sdk/vim25/{release}/.../CreateSnapshotEx_Task","body":{...}},
// "hint": "Re-run with confirm:true to execute."
// }
// }
// Second call: confirm:true actually creates the snapshot.
{
"tool": "snapshot_create",
"arguments": { "vmId": "vm-101", "name": "pre-upgrade", "confirm": true }
}
Pre-publish testing
npm run test:integration:readonly is wired into prepublishOnly, so a
broken build cannot be published to npm. The suite covers, against a live
vCenter:
- Tool registration - every name in the README catalog is registered
exactly once and exposes both
inputSchemaandoutputSchema. - Read-only happy paths - every
*_list,*_get,vcenter_about,vcenter_health,task_*,event_list,alarm_list,stats_*,iso_listFromDatastore,customization_*,role_list,permission_list,identityProvider_list,lifecycle_listClusterImage,lifecycle_checkCompliance,drs_recommendations,dvswitch_list,dvportgroup_list,template_list,contentLibrary*is invoked through the MCP stdio transport and the parsed envelope is structurally validated. - Safety: dry-run - every destructive tool is called WITHOUT
confirmand asserted to return a structured{ dryRun: true, ... }preview instead of touching vCenter. - Safety: kill switch - every destructive tool is called WITH
confirm:trueagainst aVCENTER_READ_ONLY=trueserver fixture and asserted to returnisError:truewith the read-only refusal message. - Protocol stream - the server's stdout is parsed line-by-line to
guarantee every chunk is a JSON-RPC envelope (no stray
console.log).
The suite is inventory-agnostic: tools that need an entity that may not
exist in every lab (vm_consoleTicket, customization_get, vLCM-managed
clusters, content libraries) skip-with-warn instead of failing.
Read-only mode
Set VCENTER_READ_ONLY=true to disable every destructive tool globally:
snapshot_create blocked: Server is running in read-only mode (VCENTER_READ_ONLY=true).
Set VCENTER_READ_ONLY=false to enable writes.
Read-only tools (*_list, *_get, vcenter_health, task_get, etc.) are
unaffected.
Tool catalog
Names in bold are destructive (require
confirm: true).
vCenter
vcenter_about- product version / build / api version.vcenter_health- aggregate appliance health components.
VM lifecycle
vm_list,vm_get,vm_powerStatevm_create,vm_clone,vm_deletevm_powerOn,vm_powerOff,vm_reset,vm_suspendvm_shutdown,vm_reboot(guest-OS-aware)vm_reconfigure(CPU / memory / name)vm_migrate(vMotion),vm_relocate(Storage vMotion)vm_consoleTicket(mints a one-shot VMRC ticket)vm_attachNetwork
Snapshots
snapshot_listsnapshot_create,snapshot_revert,snapshot_remove,snapshot_removeAll
Hosts (ESXi)
host_list,host_gethost_enterMaintenance,host_exitMaintenancehost_reboot,host_shutdownhost_disconnect,host_reconnecthost_addToCluster
Clusters / DRS / HA
cluster_list,cluster_getcluster_create,cluster_deletecluster_setDrs,cluster_setHadrs_recommendations,drs_apply
Datacenters / folders
datacenter_list,datacenter_create,datacenter_deletefolder_list,folder_create,folder_delete
Datastores
datastore_list,datastore_getdatastore_browse- lists one folder; supports amatchPatternglob array.datastore_searchRecursive- finds files recursively by glob (e.g.["*.vmdk"],["*.iso", "*.img"],["myvm-*.vmx"]). Auto-resolves the datastore root whenpathis omitted. OptionalfileTypesrestricts to vSphere file-type queries (VmDiskFileQuery,IsoImageFileQuery,FloppyImageFileQuery,FolderFileQuery,VmConfigFileQuery,VmTemplateFileQuery,VmLogFileQuery,VmNvramFileQuery,VmSnapshotFileQuery) for faster scans on large datastores.caseInsensitivedefaults totrue.datastore_deleteFile,datastore_moveFile
Networks
network_list,dvswitch_list,dvportgroup_listportgroup_create,portgroup_delete
Resource pools
resourcepool_list,resourcepool_create,resourcepool_delete,resourcepool_reconfigure
Templates / content library
template_list,template_deploycontentLibrary_list,contentLibraryItem_list,contentLibraryItem_deploycontentLibrary_publish
Tags
category_list,tag_list,tag_createtag_attach,tag_detach
Alarms / events
alarm_list,alarm_acknowledgeevent_list
Performance / stats
stats_listCounters,stats_query,stats_summary
ISO / media
iso_listFromDatastore,iso_mount,iso_unmount
Customization specs
customization_list,customization_get,customization_apply
Identity / RBAC
role_list,permission_list,permission_assign,identityProvider_list
vSphere Lifecycle Manager (vLCM)
lifecycle_listClusterImage,lifecycle_checkCompliance,lifecycle_remediate
Tasks
task_list,task_get
SOAP escape hatch
soap_runCommand- invokes any vim25 SOAP method when REST and VI/JSON do not expose the operation. Lazy-loads@vates/node-vsphere-soapon first use.
Architecture
src/
index.ts stdio entry, McpServer + StdioServerTransport
config.ts Zod-validated env loader
client/
session-manager.ts POST /api/session, cached vmware-api-session-id
http-client.ts shared HTTP layer (auto re-auth on 401)
rest-client.ts Automation REST helpers (/api/...)
vimjson-client.ts VI/JSON helpers (/sdk/vim25/{release}/...)
soap-client.ts lazy @vates/node-vsphere-soap wrapper
task-tracker.ts poll vim25 Task to terminal state
errors.ts vCenter error mapping
http-agent.ts undici Agent with self-signed cert handling
tools/
_register.ts registers every tool module
_safety.ts withConfirm() / safeReadOnly() helpers
vm/, snapshot/, host/, ... one folder per domain
schemas/ shared Zod schemas (MoRef, PowerState, ...)
utils/ logger (stderr-only; stdout reserved for JSON-RPC)
types/ ambient module declarations
tests/
unit/ vitest + undici MockAgent
integration/ gated by VCENTER_INTEGRATION=true
Why three API surfaces?
| Concern | Surface used | Why |
|---------------------------------|--------------|-----|
| VM CRUD, hosts, clusters, networks, datastores, resource pools | Automation REST | Modern, JSON, fully documented. |
| Snapshots, advanced VM config, alarms, events, performance counters, RBAC | VI/JSON | The Automation API does not expose these. |
| Anything else (legacy or 8.0 GA without U1) | SOAP via soap_runCommand | Maintained library, full vim25 parity. |
All three share the same vmware-api-session-id token established once by
POST /api/session, with transparent re-auth on 401 responses.
Troubleshooting
Failed to reach vCenter at https://...- check connectivity, hostname resolution, and that the vCenter Server is reachable onVCENTER_PORT(default 443). For homelab or self-signed certs, setVCENTER_INSECURE=true.Login failed (401)- checkVCENTER_USERandVCENTER_PASS. The user must include the SSO domain (administrator@vsphere.local).Tool blocked: Server is running in read-only mode- clear or setVCENTER_READ_ONLY=falseif you intend to make changes.Could not auto-detect VI/JSON release- vCenter is older than 8.0 U1 (or the user lacks permission for the ServiceInstance). The client falls back to the literalreleasesegment, which only works on U1+. Use thesoap_runCommandescape hatch for older deployments.Task ... did not complete within 600000ms- bumpVCENTER_TASK_TIMEOUT_MSfor clones/migrations of large VMs.- MCP client logs say "tool result is not valid JSON" - confirm that no
custom logger is writing to stdout. The bundled logger only writes to
stderr; if you add
console.log()calls anywhere they will corrupt the JSON-RPC stream.
About
Built and maintained by iOblako
(info@iOblako.com). oblako means cloud in Russian — fitting for an
MCP that talks to a virtualized cloud. Pull requests, bug reports, and
field notes from real lab and production deployments are very welcome.
Other projects by iOblako
- oses.iOblako.com — operating-system experiments and write-ups.
- easycpu.iOblako.com — accessible CPU internals and hands-on tooling.
Support / contact
- Bug reports and feature requests: GitHub Issues.
- Security disclosures (private): see SECURITY.md, or
email
info@iOblako.com. - Other inquiries:
info@iOblako.com.
License
MIT © 2026 Gregory / iOblako