Proxmox MCP Server

Proxmox MCP Server

Manages Proxmox VE clusters through the official PVE API with 22 tools for cluster status, VMs/containers, storage, snapshots, and provisioning. Supports stdio or HTTP/StreamableHTTP transports with optional OAuth for Gemini and multi-server configurations.

Category
访问服务器

README

Proxmox MCP Server

MCP server that manages any Proxmox VE cluster through the official PVE API — 22 tools for cluster status, VMs/CTs, storage, snapshots, and provisioning. Works over stdio (Hermes / Claude / any MCP client) or HTTP (StreamableHTTP) with optional OAuth for Gemini.

Configuration

Point it at your server with either a YAML config file or environment variables. Env vars always win. See proxmox-mcp.example.yaml for a fully commented template (including multi-server).

# 1) config file — copy the example and fill it in
cp proxmox-mcp.example.yaml proxmox-mcp.yaml
#    pve_url: "https://pve.yourhost:8006"
#    pve_token: "root@pam!mcp=<secret>"     # or pve_user + pve_password

# 2) …or env vars (equivalent)
export PVE_URL="https://pve.yourhost:8006"
export PVE_TOKEN="root@pam!mcp=<secret>"    # preferred: API token
# export PVE_USER="root@pam"                 # fallback: password auth
# export PVE_PASSWORD="..."

Config file discovery order: $PROXMOX_MCP_CONFIG./proxmox-mcp.yaml~/.config/proxmox-mcp/config.yaml.

Key Env Notes
pve_url PVE_URL / PROXMOX_URL https://host:8006 (required)
pve_token PVE_TOKEN / PROXMOX_TOKEN user@realm!tokenid=secret
pve_user / pve_password PVE_USER / PVE_PASSWORD fallback auth
pve_verify_ssl PVE_VERIFY_SSL 0 (default, self-signed) / 1
pve_readonly PVE_READONLY 1 disables all mutating tools
mcp_allowed_hosts MCP_ALLOWED_HOSTS DNS-rebinding allowlist (ngrok)
mcp_oauth MCP_OAUTH 1 enables OAuth (Gemini)
mcp_public_url MCP_PUBLIC_URL external base URL (OAuth issuer)
mcp_http_token MCP_HTTP_TOKEN static token for /health
mcp_http_host / mcp_http_port MCP_HTTP_HOST / MCP_HTTP_PORT bind addr, default 127.0.0.1:8766

Multi-server: define servers: {name: {…}, …} + default_server, pick at runtime with PROXMOX_SERVER=name. MCP-side keys (mcp_*) stay global.

Run (stdio)

.venv/bin/python server.py        # needs PVE_URL + creds from config or env

Tools (22)

Read-only

  • cluster_status — version + nodes + all VMs/CTs
  • list_nodes, node_stats (cpu/mem/disk/net per node)
  • list_vms, list_containers, vm_status, vm_config
  • list_storage, list_snapshots, list_pools

Control (safe)

  • vm_start, vm_shutdown, vm_reboot, vm_suspend, vm_resume
  • vm_snapshot, vm_snapshot_delete

Control (destructive — require explicit require_confirm='YES')

  • vm_stop (hard kill), vm_delete (permanent + disks)
  • node_restart (reboots a physical node)

Provisioning

  • vm_create_qemu (vmid, name, node, cores, memory, disk_size, storage, iso, bridge, start)
  • vm_clone (full clone to new VMID)

Hermes / Claude registration

Point any stdio MCP client at server.py. Config goes in the client's env, or in proxmox-mcp.yaml next to the repo. Hermes example (~/.hermes/config.yaml):

mcp_servers:
  proxmox:
    command: "/path/to/proxmox-mcp/.venv/bin/python"
    args: ["/path/to/proxmox-mcp/server.py"]
    env:
      PVE_URL: "https://pve.yourhost:8006"
      PVE_TOKEN: "user@realm!tokenid=secret"
    timeout: 60
    connect_timeout: 30

Requires a Hermes gateway restart to pick up new MCP servers (no hot-reload). Claude Desktop: claude_desktop_config.json, same command/args shape.

Install

python -m venv .venv && .venv/bin/pip install -r requirements.txt

Test

.venv/bin/python test_client.py        # stdio: lists tools, calls cluster_status/list_vms/list_storage/list_nodes

HTTP mode (StreamableHTTP — for ngrok / LAN / remote clients)

Configure via proxmox-mcp.yaml (recommended) or env vars:

# proxmox-mcp.yaml
pve_url: "https://pve.yourhost:8006"
pve_token: "root@pam!mcp=<secret>"
pve_readonly: true                 # public endpoint = inspection only
mcp_http_host: "127.0.0.1"
mcp_http_port: 8766
mcp_http_token: "change-me"        # static bearer token for /health
# start the local HTTP endpoint
.venv/bin/python http_entry.py
#   → http://127.0.0.1:8766/mcp  (health: /health, bearer-token protected)

# tunnel it publicly
/snap/bin/ngrok http 8766 --log stdout > ngrok.log 2>&1
# public URL: https://<random>.ngrok-free.app  (MCP endpoint: /mcp)

# IMPORTANT: restart http_entry.py AFTER the tunnel is up so the OAuth issuer
# and DNS-rebinding allowlist use the public host (env example):
MCP_ALLOWED_HOSTS=<ngrok-host> MCP_OAUTH=1 MCP_PUBLIC_URL=https://<ngrok-host>.ngrok-free.app \
  .venv/bin/python http_entry.py

Security model:

  • pve_readonly: true disables all 12 mutating tools server-side — the public URL can only read. Flip to false only if you truly want remote control.
  • mcp_http_token guards /health (ops-only, not the MCP endpoint).
  • The Proxmox token itself never crosses the tunnel (lives server-side only).

Test:

MCP_HTTP_TOKEN=<token> .venv/bin/python http_test.py https://<host>.ngrok-free.app/mcp

OAuth mode (required for Gemini)

Gemini only connects to MCP servers that support standard OAuth. Enable it in the config file (mcp_oauth: true, mcp_public_url, mcp_allowed_hosts) or with env vars:

# tunnel must already be up
MCP_OAUTH=1 \
MCP_PUBLIC_URL=https://<ngrok-host>.ngrok-free.app \
MCP_ALLOWED_HOSTS=<ngrok-host> \
PVE_READONLY=1 MCP_HTTP_TOKEN=<token> \
  .venv/bin/python http_entry.py

The OAuth Authorization Server then serves:

  • /.well-known/oauth-authorization-server (RFC 8414 metadata)
  • /authorize — interactive HTML consent page for browsers (Google's account-linking UI requires a rendered grant page)
  • /token (authorization-code + PKCE, refresh tokens 30d, rotated)
  • /register (dynamic client registration — Gemini registers itself, no client id/secret to provision by hand)
  • /revoke

Test the whole flow (register → PKCE authorize → token → MCP call → refresh):

.venv/bin/python oauth_test.py https://<host>.ngrok-free.app          # SDK-style client
.venv/bin/python google_flow_test.py https://<host>.ngrok-free.app    # Google/OpenAuth-exact (Basic-only auth)
.venv/bin/python browser_flow_test.py https://<host>.ngrok-free.app   # browser consent-page flow

Notes:

  • Everything is in-memory — server restart invalidates clients/tokens; clients re-register automatically.
  • /health stays behind the static mcp_http_token (ops-only, not MCP).
  • Non-browser authorize requests still get a plain 302 (no consent HTML).
  • The OAuth metadata is served by http_entry.py, not the SDK, because the SDK hardcodes token_endpoint_auth_methods_supported without "none" (public client / PKCE) — Gemini validates that list before registering.
  • Consent is effectively auto-approve (no login); anyone who can reach /authorize with a registered client_id gets a code, but codes require the PKCE verifier. Fine for a personal tunnel; reconsider if shared.

Pitfalls (for future edits)

  • mcp SDK v2.x removed mcp.server.fastmcp — pin mcp<2 (venv has 1.29.0).
  • proxmoxer 2.x uses service="PVE" (not "proxmox"), needs requests installed.
  • Token format for proxmoxer: user='moritz@pve', token_name='moritz', token_value=secret.
  • mcp 1.29 HTTP client yields a 3-tuple (read, write, get_session_id) — unpack 3, not 2.
  • streamable_http_app() must be the top-level ASGI app — Mounting it inside another Starlette app skips its lifespan and every request fails with "Task group is not initialized".
  • Behind ngrok you get 421 "Invalid Host header" unless you pass MCP_ALLOWED_HOSTS=<host> (DNS-rebinding protection).
  • ngrok on this box is a snap: use the absolute path /snap/bin/ngrok in background shells (PATH differs), and free-tier may need ngrok-skip-browser-warning: true + http2=False on the client.
  • /auth/password-login-style auth is NOT used here; this is bearer-token auth.
  • OAuth expires_at must be an int (int(time.time())) — pydantic rejects floats.
  • authorize() must never pass scopes=None into AuthorizationCode — Gemini sends no scope param; fall back to the client's registered scope. Missing this caused a 500 at account-link time (fixed in oauth_provider.py).
  • Google/OpenAuth sends client_id ONLY in the Authorization: Basic header at /token (RFC 6749 §2.3.1). The mcp SDK's ClientAuthenticator AND its TokenHandler both require client_id in the form body → every Gemini token exchange failed with unauthorized_client: Missing client_id (later invalid_request: authorization_code.client_id: Field required). Fixed in http_entry.py: patched ClientAuthenticator.authenticate_request with a Basic-header fallback + custom /token endpoint (our Router handles it; injects the resolved client_id into the form before model validation). Re-verify both against the SDK on upgrade.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选