Arista CloudVision
Query an Arista CloudVision instance (CVaaS or on-prem CVP) through the modern state-based Resource APIs. Stdio over Docker or Podman.
What This Server Does
Arista CloudVision exposes a curated set of GET-only tools
that wrap Arista CloudVision's state-based Resource APIs. There
is one shared HTTP helper, it only issues GETs, and no POST,
PUT, PATCH, or DELETE code path exists
anywhere in server.py.
The server is pinned to a single CloudVision cluster at startup via
CVP_HOST, no tool accepts a host parameter, so your AI client
(Claude, Gemini, Codex …) can't pivot to a different instance the token might
also reach. Path segments are hard-coded per tool; free-form values like device
IDs, MAC addresses, and label names are regex-allowlisted before they reach a
query parameter.
Ships as both a Docker and a Podman container, pick whichever runtime you already run.
What to Ask, in Plain English
A handful of real questions you can put to your AI client once this server is wired in. The model figures out which tools to call, you describe the outcome.
- "Which switches reach end-of-support this year, and which have known CVE exposure on top of that?"
- "What change controls ran this week, who approved them, and which ones touched core devices?"
- "Are any devices out of image compliance or config compliance right now?"
- "Find the endpoint with MAC aa:bb:cc:dd:ee:ff and tell me which port it's connected to."
- "Show critical events from the last hour and group them by device."
- "Who changed what in CloudVision in the last 24 hours?"
27 Read-Only Tools Across the Resource APIs
Every list tool takes max_results (hard ceiling 50) and
verbose (full records instead of projected summaries). Time-windowed
tools take since_seconds. Oversized responses come back as a
truncation envelope with a hint on how to narrow the query, so a single tool call
never blows the context window. A visible attempt_write_operation
stub sits in the catalog as model-visible proof of the read-only contract, it
performs no I/O and refuses.
Build & Configure
Two supported runtimes, pick the one you already run. The docker/
and podman/ backends ship byte-identical server.py;
only the container file and how credentials reach it differ. Expand your runtime
below.
In the CloudVision UI under Settings → Access Control → Service Accounts (CVaaS) or Settings → Service Accounts (on-prem), create a dedicated account (e.g. mcp-readonly) bound to the most restrictive read-only role available, network-operator, not network-admin. Generate a token with a finite expiry, copy it once, and treat it like a password. Don't paste it into chat with your AI client.
Docker Docker Desktop · MCP Gateway ›
Clone the repository. The Docker backend lives in docker/ and ships a custom-catalog.yaml alongside the Dockerfile.
$ git clone https://github.com/rosarion97/cloudvision-readonly-mcp-server-public.git $ cd cloudvision-readonly-mcp-server-public/docker
The image tag must match the image: field in custom-catalog.yaml (cloudvision-readonly-mcp:latest).
$ docker build -t cloudvision-readonly-mcp:latest .
Two required values, CVP_HOST and CVP_TOKEN; three optional ones for tuning (CVP_PORT defaults to 443, set 8443 for many on-prem clusters; CVP_VERIFY_SSL defaults to true; CVP_MAX_RESPONSE_BYTES defaults to 120000).
Option A, Docker MCP secret store (recommended). Each value lives in Docker Desktop's encrypted store; the gateway injects them as env vars at launch and they never appear in your client's config file. With Option A, continue through Steps 4–7.
$ docker mcp secret set CVP_HOST="www.arista.io" $ docker mcp secret set CVP_TOKEN="<your-token-here>" $ docker mcp secret set CVP_PORT="443" # 8443 for many on-prem clusters $ docker mcp secret set CVP_VERIFY_SSL="true" $ docker mcp secret list # values are masked
chmod 600 .env, never commit it. This
path skips the gateway: skip Steps 4–6, point your client straight at
docker run --env-file (below), then jump to Step 7.
$ cp .env.example .env $ chmod 600 .env # then set CVP_HOST and CVP_TOKEN
{
"mcpServers": {
"cloudvision-readonly": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"--env-file", "/absolute/path/to/docker/.env",
"cloudvision-readonly-mcp:latest"
]
}
}
}
Steps 4–6 are the Option A (secret store) path. Used Option B? Skip to Step 7.
$ mkdir -p ~/.docker/mcp/catalogs $ cp custom-catalog.yaml ~/.docker/mcp/catalogs/custom.yaml
Already have a custom.yaml from another MCP Galaxy server? Merge the cloudvision-readonly: entry into your existing file under the single registry: key, don't overwrite it.
Add the entry under the single top-level registry: key in ~/.docker/mcp/registry.yaml. Don't overwrite the file if it already exists.
registry:
cloudvision-readonly:
catalog: custom
enabled: true
Add the gateway block to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json). The gateway spawns the CloudVision image on demand, reads your catalog and registry, and resolves your CVP_* secrets at request time. Replace <your-username> with your macOS username (run whoami to check):
{
"mcpServers": {
"mcp-toolkit-gateway": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/var/run/docker.sock:/var/run/docker.sock",
"-v", "/Users/<your-username>/.docker/mcp:/mcp",
"-v", "/Users/<your-username>/Library/Caches/docker-secrets-engine/engine.sock:/root/.cache/docker-secrets-engine/engine.sock",
"docker/mcp-gateway:latest",
"--catalog=/mcp/catalogs/custom.yaml",
"--registry=/mcp/registry.yaml",
"--transport=stdio"
]
}
}
}
All three bind-mounts are required: the Docker socket lets the gateway spawn the server container; ~/.docker/mcp is where it reads your catalog and registry; the docker-secrets-engine socket is the resolver Docker Desktop exposes for the secret store, without it your CVP_* values resolve to empty strings and the server never starts. On Linux Docker Desktop the path is ~/.docker/desktop/secrets-engine/engine.sock; find it with find ~ -name engine.sock.
claude mcp add -s user mcp-toolkit-gateway -- docker run …; Codex reads the same shape from ~/.codex/config.toml as [mcp_servers.mcp-toolkit-gateway]. Any MCP-capable client, Google's Gemini included, reads an mcpServers block from its own settings file; only the location changes, the entry is identical.
Then quit and reopen the client. claude_desktop_config.json never contains CVP_TOKEN, the gateway resolves it from Docker's secret store at request time. As a shortcut, docker mcp client connect claude-desktop (or MCP Toolkit → Clients in Docker Desktop) writes a similar block automatically; the explicit JSON above survives Docker Desktop updates that may rewrite the auto-managed entry.
$ docker mcp server list # cloudvision-readonly → enabled $ docker mcp tools list
Or run the image directly, it logs a startup line to stderr and waits silently on stdin (correct for an MCP stdio server). Press Ctrl+C to exit; a missing required env var fails fast on stderr.
$ docker run --rm -i --env-file /absolute/path/to/.env cloudvision-readonly-mcp:latest
Podman Rootless · stdio ›
Clone the repository. The Podman backend lives in podman/.
$ git clone https://github.com/rosarion97/cloudvision-readonly-mcp-server-public.git $ cd cloudvision-readonly-mcp-server-public/podman
On macOS / Windows, start a Podman machine first (podman machine init && podman machine start); Linux runs natively. Podman 4.4+ is required for the --secret type=env flag below.
$ podman build -t cloudvision-readonly-mcp:latest -f Containerfile .
Two required values (CVP_HOST, CVP_TOKEN) plus optional tuning (CVP_PORT, CVP_VERIFY_SSL, CVP_MAX_RESPONSE_BYTES). Option 1 keeps them in Podman's encrypted secret store and is recommended; Option 2 writes a plaintext .env.
$ printf '%s' 'www.arista.io' | podman secret create CVP_HOST - $ printf '%s' '<your-token-here>' | podman secret create CVP_TOKEN - $ printf '%s' '443' | podman secret create CVP_PORT - $ printf '%s' 'true' | podman secret create CVP_VERIFY_SSL - $ podman secret ls # confirm; values not shown
printf '%s' (no \n) avoids a trailing newline in the secret value, a stray newline in CVP_HOST produces "Could not connect" errors that are tedious to debug.
chmod 600 .env,
never commit it (it's gitignored).
$ cp .env.example .env $ chmod 600 .env # then set CVP_HOST and CVP_TOKEN
Add the entry matching your Step 3 choice. Option 1 injects each secret with --secret NAME,type=env; Option 2 reads them from your .env.
{
"mcpServers": {
"cloudvision-readonly": {
"command": "podman",
"args": [
"run", "-i", "--rm",
"--secret", "CVP_HOST,type=env",
"--secret", "CVP_TOKEN,type=env",
"--secret", "CVP_PORT,type=env",
"--secret", "CVP_VERIFY_SSL,type=env",
"cloudvision-readonly-mcp:latest"
]
}
}
}
{
"mcpServers": {
"cloudvision-readonly": {
"command": "podman",
"args": [
"run", "-i", "--rm",
"--env-file", "/absolute/path/to/podman/.env",
"cloudvision-readonly-mcp:latest"
]
}
}
}
mcpServers block from its own settings.json,
Google's Gemini included. Drop this same entry into that client's
settings.json and the CloudVision tools appear there too. Only
the file's name and location change; the server entry is identical.
If podman isn't on Claude Desktop's PATH (common on macOS, where GUI apps don't inherit your shell's PATH), use the absolute path to the binary, usually /opt/homebrew/bin/podman (Apple Silicon Homebrew) or /usr/local/bin/podman (Intel Homebrew).
Run the image manually, it starts and waits on stdin. Press Ctrl+C to exit; a missing required env var fails fast on stderr.
$ podman run --rm -i \
--secret CVP_HOST,type=env \
--secret CVP_TOKEN,type=env \
--secret CVP_PORT,type=env \
--secret CVP_VERIFY_SSL,type=env \
cloudvision-readonly-mcp:latest
One container serves one CloudVision cluster, run a second container with its own credentials to serve another. --rm cleans up the container after each session.
Five Variables
- CVP_HOST, required. CloudVision hostname only, no scheme or path. CVaaS uses something like
www.arista.ioor a regional cluster; on-prem CVP uses the cluster's DNS name. Pins this instance to one cluster. - CVP_TOKEN, required. Service-account bearer token (Step 0). Generate it once in the CloudVision UI; the server never mints tokens itself.
- CVP_PORT, optional. HTTPS port;
443for CVaaS, often8443for on-prem CVP clusters. - CVP_VERIFY_SSL, optional. Defaults to
true. Setfalseonly for self-signed lab certs, and only if you accept the risk. - CVP_MAX_RESPONSE_BYTES, optional. Caps the JSON size of any one tool response. Default
120000(~30k tokens). Oversize responses come back as a truncation envelope with a hint on how to narrow.
Before You Start
- Docker Engine 24+ / Docker Desktop 4.27+ (MCP Toolkit for the secret-store path), or Podman 4.4+ on
$PATH. Pick one; the step-by-step for each is above. - Arista CloudVision, CVaaS, or on-prem CVP 2021.2.0+ with the Resource APIs enabled. The legacy
/cvpservice/*.doREST surface is never used. - A CloudVision service account with a read-only role (e.g. network-operator) and a generated access token with a finite expiry.
- An MCP-capable client, Claude Desktop, Claude Code, Gemini CLI, Codex, or any client speaking MCP over stdio.
Read-Only by Construction
GET through one shared helper; no POST,
PUT, PATCH, or DELETE code path exists, and
no read-write *Config resource is ever addressed. Read-only is not the
same as harmless, audit logs, configlets, and running configs can contain
sensitive operational data. Scope the service-account role accordingly.