vcf-mcp
An MCP server that gives LLM agents full API access to a VMware Cloud Foundation 9.1 estate, exposing 8,931 operations through eight tools with automatic authentication, async task tracking, and write safety.
README
vcf-mcp
An MCP server that gives an LLM agent full API access to a VMware Cloud Foundation 9.1 estate.
It connects to seven appliances — SDDC Manager, VCF Installer, vCenter, NSX, Avi Load Balancer, VCF Operations and vSAN Data Protection — and exposes their 8,931 API operations through eight tools. It handles authentication for each appliance, resolves request paths, follows async tasks, and records every mutating call.
It speaks MCP over stdio, so it works with any MCP client: Claude Code, Claude Desktop, Cursor, Windsurf, Zed, Continue, or your own agent built on an MCP SDK. There is nothing to install — point a client at:
uvx --from git+https://github.com/NiranEC77/vcf-mcp vcf-mcp
Contents
- How it works · Install · Configure · Connect an agent · Tools · Targets · Environment variables · Write safety · Tests · VCF 9.1 behaviour
How it works
One tool per endpoint does not scale. 8,931 tool schemas would exhaust the context window before the agent asked its first question, and tool selection degrades badly past a few dozen options.
Instead, the OpenAPI specs are indexed once at startup into a compact record per operation (method, path, summary, operationId, tags). The agent then works the way an engineer does — search, read the schema, dry-run, execute, follow the task:
vcf_search_api("commission hosts") -> POST /v1/hosts (commissionHosts)
vcf_describe_api(operation_id=...) -> required fields, types, responses
vcf_validate(target, path, body) -> dry-runs the spec, changes nothing
vcf_call(target, method, path, body) -> executes it, auth handled
vcf_task(target, task_id) -> follows the async result
Context cost stays fixed however many operations exist. Adding an appliance means adding a spec file and a registry entry, not a new tool.
Authentication is per-appliance and automatic. Each target has its own scheme (see Targets); the server mints a token on first use, caches it in memory for the process lifetime, never writes it to disk, and re-mints it automatically on a 401/403.
Spec handling. Both dialects are parsed: OpenAPI 3.x (SDDC Manager,
Installer, Operations, vCenter) and Swagger 2.0 (NSX). Base paths differ per
spec — /suite-api for Operations, /api for vCenter, /policy/api/v1 for
NSX policy — and are resolved at index time, so paths returned by search are
real request paths you can pass straight to vcf_call.
Install
Requires network access to the appliances. Nothing else — uvx fetches,
builds and runs the server in one step, and the API specs ship inside the
package, so there is no separate download:
uvx --from git+https://github.com/NiranEC77/vcf-mcp vcf-mcp check
That is also the command an MCP client should launch (see
Connect an agent). uvx comes with
uv; install it with
curl -LsSf https://astral.sh/uv/install.sh | sh.
To install it as a normal command instead:
uv tool install git+https://github.com/NiranEC77/vcf-mcp # then: vcf-mcp
pipx install git+https://github.com/NiranEC77/vcf-mcp # same, via pipx
pip install git+https://github.com/NiranEC77/vcf-mcp # into a venv
Or work from a clone (Python 3.10+):
git clone https://github.com/NiranEC77/vcf-mcp.git && cd vcf-mcp
uv venv --python 3.12 && uv pip install -e .
A clone keeps its config and logs in the repo directory; an installed copy
uses ~/.config/vcf-mcp/ and ~/.local/state/vcf-mcp/. Either way the
environment variables below override both.
Configure
1. Appliance addresses
No addresses are stored in this repo. Create a hosts.json — in
~/.config/vcf-mcp/ for an installed copy, or the repo root for a clone
(where it is gitignored), or anywhere if you set VCF_MCP_HOSTS_FILE:
{
"hosts": {
"sddc": "sddc-manager.example.local",
"installer": "vcf-installer.example.local",
"vcenter": "vcenter.example.local",
"nsx": "nsx-vip.example.local",
"ops": "vcf-ops.example.local",
"avi": "avi-controller.example.local",
"vsan-dp": "vcenter.example.local"
}
}
Any target can instead be set with VCF_MCP_<TARGET>_HOST, which wins over the
file. vsan-dp is served by the vCenter appliance, so it takes the same
address as vcenter. Targets you leave out are reported as unconfigured by
vcf_targets rather than called.
2. Credentials
Passwords are read from a .env file — point VCF_MCP_ENV_FILE at whichever
file is already your rotation point, or create one next to hosts.json:
NSX_ADMIN_PASSWORD=...
SDDC_MANAGER_PASSWORD=...
VCF_INSTALLER_PASSWORD=...
NESTED_VCSA_PASSWORD=...
VCF_APPLIANCE_PASSWORD=...
Each target tries its own ordered subset of these keys; empty values and
anything containing CHANGEME are skipped. A single target can be overridden
with VCF_MCP_<TARGET>_PASSWORD. Nothing is copied into the repo, and no tool
ever returns a secret — failures name the key they looked for, never a value.
Authentication is capped at 3 attempts per target
(config.MAX_AUTH_ATTEMPTS). vSphere SSO locks accounts after repeated
failures, so trying every password in the file is not a harmless fallback.
Avi has no standing credential anywhere. Its admin password is
VCF-generated and lives only in SDDC Manager's credential store. The server
fetches it at auth time (GET /v1/credentials, resourceType NSX_ALB), uses
it to log in, and never returns, logs or persists it. Set
VCF_MCP_AVI_PASSWORD to override this for a controller VCF does not manage.
Avi rejects HTTP Basic outright — only the session flow works.
3. Verify
vcf-mcp index # index all operations (~8s, then cached to disk)
vcf-mcp check # print every target and whether it answers
Prefix with uvx --from git+https://github.com/NiranEC77/vcf-mcp if you have
not installed it. check names any target whose address is still unset.
Connect an agent
The server is a stdio process: run vcf-mcp with no arguments (equivalently,
python -m vcf_mcp) and it speaks MCP on stdin/stdout.
Any MCP client
Most clients read the same JSON shape. Add this to the client's MCP config —
no prior install needed, uvx handles it:
{
"mcpServers": {
"vcf": {
"command": "uvx",
"args": ["--from", "git+https://github.com/NiranEC77/vcf-mcp", "vcf-mcp"],
"env": {
"VCF_MCP_HOSTS_FILE": "/absolute/path/to/hosts.json",
"VCF_MCP_ENV_FILE": "/absolute/path/to/your/.env"
}
}
}
}
If you installed it already, replace those two fields with
"command": "vcf-mcp" (or the absolute path to the executable, which some
clients require because they do not inherit your shell's PATH).
.mcp.example.json in this repo is that file, ready to copy. Where each client
keeps its config:
| Client | Config location |
|---|---|
| Claude Code | .mcp.json in the project, or claude mcp add (below) |
| Claude Desktop | claude_desktop_config.json |
| Cursor | .cursor/mcp.json |
| Windsurf | ~/.codeium/windsurf/mcp_config.json |
| Zed | settings.json, under context_servers |
| Continue | config.yaml, under mcpServers |
Claude Code
claude mcp add vcf \
--env VCF_MCP_HOSTS_FILE=/absolute/path/to/hosts.json \
--env VCF_MCP_ENV_FILE=/absolute/path/to/your/.env \
-- uvx --from git+https://github.com/NiranEC77/vcf-mcp vcf-mcp
Your own agent
Any MCP SDK can launch it as a subprocess. With the Python SDK:
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
params = StdioServerParameters(
command="uvx",
args=["--from", "git+https://github.com/NiranEC77/vcf-mcp", "vcf-mcp"],
env={"VCF_MCP_HOSTS_FILE": "/absolute/path/to/hosts.json"},
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool("vcf_search_api", {"query": "commission hosts"})
The server advertises read_only and destructive annotations per tool, so a
client that gates writes can do so without a hardcoded tool list.
Tools
| Tool | Arguments | Returns |
|---|---|---|
vcf_targets |
check_reachability=true |
Every appliance: name, product, address, auth scheme, operation count, whether it answers |
vcf_search_api |
query, target?, method?, limit=25, include_deprecated=false |
Ranked operations with method, full path, summary, operationId |
vcf_describe_api |
operation_id? or method+path, target?, depth=3, max_properties=60 |
Path/query parameters, resolved request body schema with required fields, response schemas |
vcf_validate |
target, path, body?, wait_seconds=120 |
validated true/false plus each failed check — executes nothing |
vcf_call |
target, method, path, query?, body?, timeout?, max_response_chars=20000 |
Status and response body; task id for async operations |
vcf_task |
target, task_id, wait_seconds=0, poll_interval=5.0 |
Task status and, on failure, which subtask failed and why |
vcf_inventory |
targets?, per_section_limit=25 |
Domains, clusters, hosts, gateways and alerts in one snapshot |
vcf_audit |
limit=50 |
Recent mutating calls made through this server |
vcf_call is annotated as destructive; every other tool is annotated read-only.
Typical sequence for a change: vcf_search_api → vcf_describe_api →
vcf_validate → vcf_call → vcf_task.
Targets
| Target | Product | Authentication | Operations |
|---|---|---|---|
sddc |
SDDC Manager | POST /v1/tokens → Bearer |
500 |
installer |
VCF Installer | POST /v1/tokens → Bearer |
57 |
vcenter |
vCenter Server | POST /api/session → vmware-api-session-id |
1,367 |
nsx |
NSX Manager (VIP) | HTTP Basic | 5,182 |
avi |
Avi Load Balancer (NSX ALB) | POST /login → session cookies + X-CSRFToken |
1,233 |
ops |
VCF Operations | POST /suite-api/api/auth/token/acquire → vRealizeOpsToken |
527 |
vsan-dp |
vSAN Data Protection | vCenter session | 65 |
Every scheme above was verified against a live 9.1 estate.
Environment variables
| Variable | Effect |
|---|---|
| Defaults differ between a clone and an installed copy, as noted: |
| Variable | Effect | Default (clone → installed) |
|---|---|---|
VCF_MCP_HOSTS_FILE |
Path to the addresses file | ./hosts.json → ~/.config/vcf-mcp/hosts.json |
VCF_MCP_<TARGET>_HOST |
Override one address, e.g. VCF_MCP_NSX_HOST |
— |
VCF_MCP_ENV_FILE |
Path to the credentials .env |
./.env → ~/.config/vcf-mcp/.env |
VCF_MCP_<TARGET>_PASSWORD |
Override one target's password, e.g. VCF_MCP_SDDC_PASSWORD |
— |
VCF_MCP_INSTALLER_CREDS |
Installer's generated credentials file | alongside the .env |
VCF_MCP_VERIFY_TLS=1 |
Enforce TLS verification | off — appliances present self-signed certs |
VCF_MCP_AUDIT_LOG |
Where mutations are recorded | ./logs/vcf-mcp-audit.jsonl → ~/.local/state/vcf-mcp/vcf-mcp-audit.jsonl |
VCF_MCP_SPEC_DIR |
Spec source directory | ./specs → the copy bundled in the package |
VCF_MCP_CACHE_DIR |
Index cache directory | ~/.cache/vcf-mcp |
Write safety
There is no write gate. Any operation the API allows — including
DELETE /v1/domains/{id} and host decommission — executes immediately when the
agent calls it. This is deliberate: the server does not try to second-guess
which operations are safe.
What exists instead is a record. Every POST/PATCH/PUT/DELETE is appended to
logs/vcf-mcp-audit.jsonl with target, path, status, duration and a
redacted body — anything keyed like a password, token, secret or credential
is replaced before the line is written. vcf_audit reads it back, including
changes made by earlier sessions.
If you want a gate, client.request() is the single chokepoint that every call
in the server passes through.
Tests
.venv/bin/python -m pytest tests/ -q
36 offline tests, no appliance required. Each pins a bug found during the
build: camelCase tokenisation, plural stemming, $ref cycle handling,
truncation across differently-named collections, secret redaction, task-id
detection, case-insensitive task states, and the rule that no appliance address
is ever hardcoded into the registry.
Specs
Vendored from vmware/vcf-api-specs
at commit 3949fc3 (2026-05-13), version 9.1.0.0. Provenance in
specs/SPECS-PROVENANCE.txt.
The 170 Avi object specs in specs/avi/ were downloaded from an Avi
controller's own swagger endpoint (/swagger/<Object>.yaml), so they are
version-matched to the deployed build by construction. Avi's per-object files
re-declare related objects' paths; the index deduplicates them and keeps the
declaration from the file named after the resource.
VCF 9.1 behaviour
Discovered while building against a live estate, and encoded in the server:
POST /v1/system/prechecksis gone; the replacement isPOST /v1/system/health-summary(startHealthCheck).- The whole
/v1/edge-clustersfamily on SDDC Manager is deprecated, includingupdateEdgeCluster(PATCH /v1/edge-clusters/{id}). - Deprecated operations are hidden from search unless
include_deprecatedis set. Well-scoring ones are still reported underhidden_deprecated, so a legacy path found in old documentation is identified as legacy rather than appearing not to exist. - SDDC Manager returns task status as
"Successful", not"SUCCESSFUL";vcf_taskcompares case-insensitively. - vCenter (vAPI) specs declare enums as prose ("Possible values: ...").
vcf_describe_apilifts them into a realenumlist. - NSX often has the strictest password complexity rules of the fleet, so an
estate is frequently built with one password NSX accepts.
NSX_ADMIN_PASSWORDis therefore tried first for several targets.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。