Add a 'Managing Multiple ESXi Hosts' section to the environment reference
and CLAUDE.md: numbered ESXI_HOST[_N] families, credential inheritance for
suffixed hosts, lazy connections, and the per-call host argument. Update
the source layout with servers.py, connection_manager.py, and mixins/_base.py.
Rolls the host: str | None = None selector and conn = self._conn(host)
routing across all remaining tools in the 12 mixins (the inventory tools
landed earlier). 97 of 98 tools now accept an optional host to target a
specific managed ESXi server; list_servers is the only exception (it is
the registry itself).
Private helpers that touched self.conn (e.g. ovf NFC/datastore helpers,
nic _get_network_backing, vcenter_ops folder/pool/cluster finders,
host_management _get_host, resources datastore browse/stream) now take a
conn parameter threaded from their tool call sites. Several tools had a
local 'host' variable renamed to avoid shadowing the new selector.
Verified: full server builds (98 tools), and get_host_info/list_hosts
route correctly to two distinct live hosts (205 vs 222).
Adds a pluggable server inventory and per-host connection routing:
- servers.py: load_servers() returns the managed host list. Today it reads
ESXI_HOST[_N] env vars (a suffixed host inherits the unsuffixed
USER/PASS/INSECURE/NETWORK); this one function is the seam an API-backed
source replaces later. Servers are identified by host.
- connection_manager.py: ConnectionManager holds the inventory and lazily
connects per host (one dead host doesn't block startup or the others),
keyed by host, with a default.
- mixins/_base.py: VSphereMixin base gives every mixin self.conn (default
host) and self._conn(host) for per-call routing. All 13 mixins now take
the manager instead of a single connection.
- server.py builds the manager from load_servers() (falling back to the
single VCENTER_* server) and eager-connects the default.
- New list_servers tool; list_vms/get_vm_info take an optional host arg as
the per-call routing pattern.
Verified across two live hosts: default + host='10.22.22.222' route to the
right box; unknown host gives a clear error.
Adds add_cdrom (attach a CD/DVD drive to an existing VM, optionally with
an ISO and CD-first boot) and extends deploy_ova with iso_path/
iso_datastore/boot_from_iso so a diskless appliance OVA can be deployed
and pointed at a bootable installer ISO in one call.
deploy_ova reuses the OVA's existing primary CD/DVD drive rather than
adding a new one — Cisco templates (CUCM) already define empty drives and
the BIOS boots the first, so the ISO must land there. Verified end to end:
CUCM 15 Small OVA + bootable ISO boots to the UCOS installer.
Cisco-style OVAs (CUCM/CUC/UCCX) ship multiple deployment configurations
(e.g. Small/Medium/Large) in a DeploymentOptionSection and default to one.
deploy_ova now takes deployment_option to select a config; without it the
OVF default is used.
inspect_ova reads an OVA (local path or URL) and reports product, guest
OS, hardware version, selectable deployment options, networks, and disks
so clients can choose deployment_option/network before deploying.
Verified against CUCM 15 OVA: Small config yields 2 vCPU / 10 GB / 110 GB
/ VMXNET3 exactly per Cisco's spec.
Implements OVA deployment over an HttpNfcLease, the existing deploy_ovf
explicitly rejected OVA and its disk upload was a no-op stub. deploy_ova
reads an OVA from a local path or http(s) URL, builds an import spec
(mapping every OVF network to the target port group), opens an NFC lease,
and streams each disk to the lease URL with a keepalive thread.
Key details: the NFC PUT needs an 'Overwrite: t' header because
ImportVApp pre-creates the disk file; lease device URLs use '*' for the
host on direct ESXi connections, so the configured host is substituted.
Previously created VMs had no CD/DVD drive, so attach_iso failed with
'No CD/DVD drive found' and there was no way to install a guest from an
ISO. create_vm now always adds a CD/DVD drive on the default IDE
controller. New optional args iso_path/iso_datastore back the drive with
an ISO and boot_from_iso puts the CD/DVD first in the boot order, giving
MCP clients a complete create-and-install-from-ISO path.
Toggles the TSM-SSH service via the vSphere API (govc + .env creds) with
on/off/status/shell subcommands. The shell subcommand restores the prior
service state on exit so it leaves the host as it found it.
- Update Dockerfile to default to streamable-http transport
- Add docker-compose.oauth-standalone.yml for existing OIDC providers
- Add .env.oauth.example template with all required settings
- Update README_DOCKER.md with OAuth deployment instructions
OAuth mode requires streamable-http transport (not stdio) since
authentication happens via browser redirect flow.
- Fix OAuth user/group extraction using FastMCP's get_access_token()
- Update README with correct RBAC group names (vsphere-readers, not viewers)
- Add missing vsphere-host-admins group to documentation
The previous implementation tried to access OAuth token claims via
context.request_context.access_token.claims, which doesn't exist in
FastMCP's context structure.
FastMCP stores access tokens in Python's ContextVars, accessible via
the get_access_token() dependency function. This fix updates both
extract_user_from_context() and RBACMiddleware._extract_user_from_context()
to use this correct approach.
Before: Users appeared as "anonymous" with no groups (RBAC denied all)
After: User identity and groups correctly extracted from OAuth claims
Rewrites the architecture doc from design proposal to implementation
reference. Documents the complete RBAC system including:
- 5 permission levels (READ_ONLY → FULL_ADMIN)
- 5 OAuth groups with permission mappings
- RBACMiddleware implementation details
- Audit log format with user identity
- Configuration environment variables
- OIDC provider setup (Authentik example)
- Troubleshooting guide for common issues
Updates implementation checklist to reflect completed status.
Security fix: Previously users with no OAuth groups or unrecognized
groups would default to READ_ONLY access. Now:
- Empty groups = no permissions (denied access to all tools)
- Unrecognized groups = no permissions (denied access)
Also adds missing restart_service mapping to FULL_ADMIN permission.
Add RBACMiddleware that integrates with FastMCP's middleware system to
enforce role-based access control on all tool calls:
- Intercepts every tool call via on_call_tool() hook
- Extracts user groups from OAuth token claims
- Checks permissions using existing permissions.py mappings
- Logs all tool invocations with user identity via audit.py
- Denies access with clear PermissionDeniedError when unauthorized
Permission levels (from permissions.py):
- READ_ONLY: view operations (vsphere-readers)
- POWER_OPS: power/snapshot ops (vsphere-operators)
- VM_LIFECYCLE: create/delete VMs (vsphere-admins)
- HOST_ADMIN: ESXi host management (vsphere-host-admins)
- FULL_ADMIN: guest ops, services (vsphere-super-admins)
Middleware only enabled when OAuth is active (OAUTH_ENABLED=true).
STDIO mode continues to work without permission checking.
- Add Multi-User / OAuth Mode section with quick setup
- Document permission groups for RBAC
- Update transport option to streamable-http
- Link to OAUTH-ARCHITECTURE.md for details
OAuth 2.1 + PKCE authentication for mcvsphere MCP server.
Features:
- OIDC integration via FastMCP's OIDCProxy
- Authentik identity provider support
- Dynamic Client Registration for MCP clients
- PKCE flow for secure authorization
- Permission groups mapped from Authentik groups
- Audit logging with user identity
Tested end-to-end with Claude Code CLI.
- Remove required_scopes validation (Authentik doesn't embed scopes in JWT)
- Add oauth_base_url config for proper HTTPS callback URLs
- Add docker-compose.dev.yml for host proxy via Caddy
- Update docker-compose.oauth.yml with unique domain label
Authentik uses opaque access tokens that don't include scope claims.
Authentication is enforced at the IdP level, so scope validation in
the token is unnecessary and was causing 401 errors.
- Service Account + OAuth Audit model for vCenter integration
- Authentik as OIDC provider with JWT validation
- Permission escalation based on OAuth groups
- Credential broker pattern for user mapping
- Implementation checklist and environment variables
mcvsphere = Model Control for vSphere
Updates:
- Package renamed from esxi_mcp_server to mcvsphere
- CLI entry point: mcvsphere (was esxi-mcp-server)
- All imports and references updated
- Docker configs updated
- Test suites updated
New features:
- ConsoleMixin: vm_screenshot, wait_for_vm_tools, get_vm_tools_status
- SerialPortMixin: setup_serial_port, get_serial_port, connect_serial_port,
clear_serial_port, remove_serial_port
Enables:
- VM console screenshots via vSphere HTTP API
- VMware Tools status monitoring and wait utilities
- Network serial ports for headless VMs and network appliances
README rewritten with comprehensive documentation of all 94 tools.
FileInfo attributes (owner, modificationTime, size, type) vary across
guest OS types. Use getattr with defaults to prevent AttributeError
when these optional fields are missing.
Mark tools that handle already-in-target-state gracefully:
- storage_vmotion: returns "no_migration_needed" if already on target
- convert_to_template: returns "already_template" if already converted
- convert_to_vm: returns "already_vm" if already a VM
This signals to LLM clients that these operations are safe to retry.
VCenterOpsMixin provides tools that require vCenter Server:
Storage vMotion:
- storage_vmotion: Move VM disks to different datastore
- move_vm_disk: Move specific disk to different datastore
Template Management:
- convert_to_template: Convert VM to template
- convert_to_vm: Convert template back to VM
- deploy_from_template: Deploy new VM from template
Folder Organization:
- list_folders: List VM folders in datacenter
- create_folder: Create new VM folder
- move_vm_to_folder: Move VM to different folder
Tasks & Events:
- list_recent_tasks: List recent vCenter tasks
- list_recent_events: List recent vCenter events
Cluster Operations:
- list_clusters: List clusters with DRS/HA status
- get_drs_recommendations: Get DRS recommendations for cluster
Also fixes empty-list serialization issue in FastMCP by returning
informative messages when results are empty.
Total: 86 tools, 6 resources