{
  "markdown": "# NANDI Proxmox MCP\n\n> Turn your Proxmox cluster into an AI-driven platform with 140+ tools for automation, monitoring, and controlled execution.\n\nOpen source MCP server for Proxmox VE, powered by NANDI Services.\n\n`nandi-proxmox-mcp` exposes Proxmox inventory, lifecycle, storage, backup, networking, firewall, access, monitoring, SSH diagnostics, and guarded remote/container operations without removing the safety rails needed for production clusters.\n\n## What stays enabled\n\n- 140+ tools across nodes, cluster, QEMU, LXC, storage, backup, tasks, network, firewall, pools, access, templates, monitoring, and remote operations.\n- Access tiers: `read-only`, `read-execute`, `full`.\n- Module split: `PVE_MODULE_MODE=core|advanced`.\n- Tool filters: `PVE_CATEGORIES`, `PVE_TOOL_BLACKLIST`, `PVE_TOOL_WHITELIST`.\n- Destructive guardrails via `confirm=true`.\n- Backward-compatible aliases such as `listNodes`, `getVMStatus`, `startVM`, `stopContainer`.\n- `stdio` transport for MCP clients and Streamable HTTP transport for controlled remote deployments.\n\n## Required permissions\n\nThe server needs two trust channels and both are preserved intentionally:\n\n- Proxmox API token\n  - Used for inventory, lifecycle, configuration, and management endpoints.\n  - Keep ACLs minimal: only grant the roles needed for the tools you actually enable.\n- SSH batch access to the Proxmox host\n  - Required for `pct exec`, batch SSH diagnostics, and container-level Docker inspection tools.\n  - This is still necessary because Proxmox API coverage does not replace host-side `pct` and SSH-based diagnostics.\n\nMore detail: [docs/PERMISSIONS.md](docs/PERMISSIONS.md)\n\n## Destructive confirmations\n\nOperations marked destructive do not execute unless the caller sends `confirm=true`.\n\nExamples:\n- VM/container stop, shutdown, reboot, suspend, delete, migrate, snapshot rollback\n- storage/network/firewall/access writes that can alter cluster state\n- advanced remote execution such as `pve_exec_in_container`\n\nThe server returns a structured `CONFIRMATION_REQUIRED` error when confirmation is missing. This behavior is unchanged and reinforced.\n\n## Human approval\n\n`confirm=true` is supplied by the agent, not by you. A model that reads the rejection can simply\nretry with the flag set, so on its own that check guards against an accident rather than against a\nconfident agent — and it never asks you anything.\n\nSo the 47 tools that require confirmation are also announced to the client as needing a person:\n\n```json\n\"_meta\": { \"anthropic/requiresUserInteraction\": true }\n```\n\nIn Claude Code 2.1.199 and later, a tool marked this way prompts on **every** call — including in\n`auto` and `bypassPermissions` modes — and cannot be pre-approved by an `allow` rule or by a\n`PreToolUse` hook returning `allow`. Under `--permission-prompt-tool` an automated approval is\nconverted to a denial, and Remote Control withholds one-tap approval and sends you to the full\nprompt. The operator who answered is the one who authorised the operation.\n\nOne caveat, measured on 2.1.229 rather than taken from the documentation: the prompt still offers\n*\"Yes, and don't ask again\"*, even though the documentation says a flagged tool has no such option.\nChoosing it writes an `allow` rule that **does not** retire the gate — the next call prompts again.\nSo the behaviour is right and only the button is misleading. Do not read its absence as the signal\nthat the guard is on; verify by calling a gated tool twice.\n\nStarting and resuming a guest are deliberately left out: they change state without destroying\nanything, and a guard people resent is a guard people route around.\n\n`setup` additionally writes matching `permissions.ask` rules into `.claude/settings.json`, which\ncover Claude Code versions that predate the annotation. Rules are evaluated deny, then ask, then\nallow — first match wins — so an `ask` rule survives both `bypassPermissions` and a later\n\"yes, don't ask again\". For an install that was configured by hand rather than through `setup`:\n\n```bash\nnandi-proxmox-mcp harden              # every configured instance\nnandi-proxmox-mcp harden --name lab   # just one\n```\n\nBoth mechanisms are Claude Code specific. In any other client the guards are `confirm=true` and the\naccess tier, so choose the tier deliberately there.\n\n## Access tiers\n\n- `read-only`\n  - Inventory, status, logs, metrics, and non-mutating diagnostics.\n- `read-execute`\n  - Read-only plus selected execution/lifecycle actions.\n- `full`\n  - Create, update, delete, migrate, restore, and admin-level operations.\n\n`PVE_MODULE_MODE=core` hides advanced tools without renaming or removing canonical tool IDs from the codebase.\n\n## Runtime configuration\n\n### Environment variables\n\nRequired:\n- `PROXMOX_HOST`\n- `PROXMOX_USER`\n- `PROXMOX_REALM`\n- `PROXMOX_TOKEN_NAME`\n- `PROXMOX_TOKEN_SECRET`\n- `PROXMOX_SSH_HOST`\n- `PROXMOX_SSH_USER`\n- `PROXMOX_SSH_KEY_PATH`\n\nOptional:\n- `PROXMOX_PORT` default `8006`\n- `PROXMOX_SSH_PORT` default `22`\n- `PROXMOX_ALLOW_INSECURE_TLS` default `false`\n- `PVE_ACCESS_TIER=read-only|read-execute|full`\n- `PVE_MODULE_MODE=core|advanced`\n- `PVE_CATEGORIES`\n- `PVE_TOOL_BLACKLIST`\n- `PVE_TOOL_WHITELIST`\n\nHTTP transport:\n- `MCP_TRANSPORT=stdio|http`\n- `MCP_HOST` default `0.0.0.0`\n- `MCP_PORT` default `3000`\n- `MCP_ALLOWED_HOSTS`\n- `MCP_ALLOWED_ORIGINS`\n- `MCP_RATE_LIMIT_WINDOW_MS`\n- `MCP_RATE_LIMIT_MAX`\n- `MCP_MAX_BODY_SIZE_BYTES`\n- `MCP_HEADERS_TIMEOUT_MS`\n- `MCP_REQUEST_TIMEOUT_MS`\n- `MCP_KEEPALIVE_TIMEOUT_MS`\n- `MCP_MAX_HEADERS_COUNT`\n\n### Local config file\n\nSetup writes one credentials file per configured Proxmox,\n`.nandi-proxmox-mcp/<instance>.json`, plus a registration entry in each client\nconfig it was asked for — `.mcp.json` for Claude Code and `.vscode/mcp.json` for\nVS Code, by default both.\n\nThe credentials file is the only one holding the token, and it is gitignored.\nWhen `NANDI_PROXMOX_CONFIG` is not set, the server discovers it: a single\nconfigured instance is used automatically, and more than one is an error naming\nthem rather than a guess.\n\nThe config loader now rejects:\n- empty or malformed config paths\n- oversized config files\n- control characters in config paths\n\n## Quick start\n\n> **Never used an MCP before?** Start with\n> [docs/EMPEZAR.md](docs/EMPEZAR.md) — a step-by-step guide (in Spanish) that\n> assumes no prior MCP knowledge and covers creating the Proxmox token, which\n> is the part that trips most people up.\n\nYou need an API token from your own Proxmox first. This prints the commands\nthat create one, ready to paste into the Proxmox shell — it connects to\nnothing:\n\n```powershell\nnpx nandi-proxmox-mcp bootstrap --tier read-only\n```\n\nThen guided setup. By default this writes config for **Claude Code**\n(`.mcp.json`) and **VS Code** (`.vscode/mcp.json`), merging into either file if\nit already exists:\n\n```powershell\nnpx nandi-proxmox-mcp setup --access-tier read-only\nnpx nandi-proxmox-mcp doctor --check mcp-config,nodes,vms,cts,node-status,remote-op\n```\n\nStart with `--access-tier read-only`. The server's built-in default is `full`,\nwhich exposes every destructive tool including arbitrary command execution;\npassing the flag writes the tier explicitly into the client config so the\nchoice is visible rather than implicit. Raise it once you trust the setup.\n\nPick specific clients, or print a block for any other MCP client:\n\n```powershell\nnpx nandi-proxmox-mcp setup --clients claude-code\nnpx nandi-proxmox-mcp setup --print-config          # writes nothing, safe to pipe\n```\n\n`.mcp.json` holds only a config path and policy settings, so it is safe to\ncommit and share. Your API token stays in `.nandi-proxmox-mcp/config.json`,\nwhich is gitignored.\n\nDirect run with environment variables:\n\n```powershell\n$env:PROXMOX_HOST=\"pve.local\"\n$env:PROXMOX_PORT=\"8006\"\n$env:PROXMOX_USER=\"svc_mcp\"\n$env:PROXMOX_REALM=\"pve\"\n$env:PROXMOX_TOKEN_NAME=\"nandi-mcp\"\n$env:PROXMOX_TOKEN_SECRET=\"<SECRET>\"\n$env:PROXMOX_SSH_HOST=\"pve.local\"\n$env:PROXMOX_SSH_USER=\"root\"\n$env:PROXMOX_SSH_KEY_PATH=\"$env:USERPROFILE\\.ssh\\id_ed25519\"\n\nnpx nandi-proxmox-mcp run\n```\n\n## Security Model & Residual Risk\n\nThis MCP server operates real Proxmox infrastructure and is not a sandboxed environment.\n\n### Trust Assumptions\n- The server is deployed in a trusted environment\n- Only authorized operators can access it\n- Network exposure is controlled (not publicly exposed)\n- Credentials are securely managed\n\n### Residual Risks\nThe following risks are inherent to the system design:\n\n- **Privileged Operations**  \n  Full access tier and container execution capabilities can perform destructive or system-level actions.\n\n- **SSH Execution Boundary**  \n  Remote command execution relies on SSH and inherits the security posture of the target system.\n\n- **Optional Insecure TLS Mode**  \n  When enabled (`PROXMOX_ALLOW_INSECURE_TLS=true`), TLS certificate validation is bypassed and may expose connections to MITM attacks. Intended for lab use only.\n\n- **External Dependency Synchronization**  \n  Package distribution and listing visibility depend on npm, MCP Registry, and marketplace propagation timing.\n\n### Security Responsibilities\nUsers are responsible for:\n- Restricting access to trusted operators only\n- Using least-privilege API tokens and SSH keys\n- Avoiding insecure TLS in production environments\n- Properly securing the underlying infrastructure\n\n### Safety Controls Implemented\n- Access tiers (read-only, read-execute, full)\n- Confirmation required for destructive operations\n- Human approval required for those same operations, see [Human approval](#human-approval)\n- Input validation and command hardening\n- Rate limiting and request validation\n\n## HTTP hardening\n\n> **The HTTP transport performs no authentication.** There is no bearer token,\n> API key, or client-certificate check on `POST /mcp`; the controls below are\n> network-level only. `MCP_HOST` also defaults to `0.0.0.0`, and the host\n> allowlist includes your configured Proxmox and SSH hosts. Anyone who can\n> reach the port and send a matching `Host` header gets the full registered\n> tool surface — which, with the default `PVE_ACCESS_TIER=full`, includes\n> destructive tools and arbitrary command execution.\n>\n> Only enable `MCP_TRANSPORT=http` on a trusted network, behind an\n> authenticating reverse proxy, or bound to `127.0.0.1` via `MCP_HOST`. The\n> default stdio transport is not affected: it has no network surface.\n\nWhen `MCP_TRANSPORT=http` is enabled, the server applies:\n\n- host allowlist enforcement, including wildcard-bind protection\n- origin validation for requests that send an `Origin` header\n- explicit body-size limits and sanitized `413` responses\n- rate limiting on `/mcp`\n- request/header/keep-alive timeouts\n- `X-Content-Type-Options: nosniff`\n- `Cache-Control: no-store`\n- sanitized error payloads without stack traces\n\nHealth/readiness endpoints:\n- `GET /health`\n- `GET /ready`\n- `POST /mcp`\n\n## SSH and command-execution hardening\n\nFunctionality is unchanged, but the execution path is stricter:\n\n- local command execution still uses `spawn(..., { shell: false })`\n- SSH host/user values cannot smuggle CLI options\n- SSH uses `BatchMode`, `IdentitiesOnly`, public-key auth, and explicit connection liveness controls\n- output buffers are capped to prevent unbounded memory growth\n- `dockerLogsInContainer` now validates and shell-escapes container names instead of interpolating raw user input\n- arbitrary container command execution remains available only through the already-destructive `pve_exec_in_container` flow with confirmation required\n\n## Security posture\n\nMitigations in the repo:\n- pinned direct dependency versions and npm `overrides` for critical transitive packages\n- verifiable package metadata and repository links for npm/package scanners\n- descriptor/version sync validation for npm, registry, and marketplace artifacts\n- redaction of token/header/password-like values in logs\n- no stack traces or secrets returned to clients\n- CI gates for lint, typecheck, build, tests, metadata validation, descriptor sync, `npm pack --dry-run`, and audit\n\nThreat model and residual risks: [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md)\n\n## Publish flow\n\n**Releases are automatic.** A push to `main` runs `auto-release.yml`, which reads the bump level\nfrom the conventional-commit subjects since the last `v*` tag, writes the new version into every\nfile that carries one, commits `chore(release): vX.Y.Z`, and pushes the tag. The tag is what\nstarts `release.yml` and the publish below.\n\n| Commit | Bump |\n| :-- | :-- |\n| `feat:` | minor |\n| `fix:`, `perf:`, `revert:` | patch |\n| `feat!:` or a `BREAKING CHANGE:` footer | **major** — strict semver, so `0.x` goes to `1.0.0` |\n| `chore:`, `docs:`, `test:`, `ci:`, `build:`, `style:`, `refactor:` | nothing is published |\n\nA push with nothing releasable finishes green and lists the commits it skipped. Run the workflow\nby hand with `dry_run: true` to see the version and the diff without publishing.\n\nThe version lives in eight places — manifests, both registry descriptors, the marketplace plugin,\ntwo docs examples and two TypeScript literals. `scripts/set-version.mjs` writes all of them and\n`scripts/validate-package-metadata.mjs` gates all of them. **Adding a ninth means editing both**:\na writer that touches a file the validator ignores is how `0.3.1` shipped announcing itself as\n`0.2.4`.\n\nRelease order, once the tag exists, is strict:\n\n1. `npm run lint`\n2. `npm run typecheck`\n3. `npm run build`\n4. `npm test`\n5. `npm audit --include=dev --audit-level=moderate`\n6. `npm ls express`\n7. `npm ls path-to-regexp`\n8. `npm pack --dry-run`\n9. `npm pack`\n10. `npm whoami`\n11. `npm publish --access public`\n12. `npm view nandi-proxmox-mcp version`\n13. `mcp-publisher validate .mcp/server.json`\n14. `mcp-publisher publish .mcp/server.json`\n\nThe tag-based `release.yml` now publishes npm first and only then publishes the MCP Registry descriptor, preventing npm/registry drift on the same version.\n\n**If a release dies halfway, re-run it** — `gh workflow run release.yml --ref vX.Y.Z` — rather than\nfinishing it by hand. Every publishing step asks its destination first and skips what is already\nthere, so the re-run completes only the parts that did not happen. The job refuses any ref that is\nnot a tag, and any tag that disagrees with the version in `package.json`.\n\nManual fallback and troubleshooting: [docs/RELEASE.md](docs/RELEASE.md)\n\n## Development\n\n```bash\nnpm ci\nnpm run lint\nnpm run typecheck\nnpm run build\nnpm test\nnpm run validate:release\nnpm pack --dry-run\n```\n\n## Documentation Maintenance Policy\n\nThis repository follows a documentation sync policy, enforced in review rather than by a git hook.\n\n> There is no pre-commit hook. The one gate that *is* automated is in CI\n> (`.github/workflows/ci.yml`): it regenerates `docs/TOOLS.md` and fails the\n> build on any drift. Note also that a repo-local `.git/hooks/pre-commit` would\n> not run on a machine where `core.hooksPath` is redirected, which is common.\n\n- Before closing a `change`, `fix`, or `refactor`, evaluate whether `README.md`, `AGENTS.md`, and `CONTRIBUTING.md` must be updated.\n- If a document is relevant to the behavioral or process impact, it must be updated in the same change set.\n- If no update is needed, an explicit `no-doc-change` justification is required.\n- A task is not considered ready-to-commit until this gate is satisfied.\n\n## Docs\n\n- [docs/EMPEZAR.md](docs/EMPEZAR.md) — start here if MCP servers are new to you\n- [docs/CLAUDE_CODE_SETUP.md](docs/CLAUDE_CODE_SETUP.md)\n- [docs/QUICKSTART.md](docs/QUICKSTART.md)\n- [docs/PERMISSIONS.md](docs/PERMISSIONS.md)\n- [docs/SECURITY.md](docs/SECURITY.md)\n- [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md)\n- [docs/RELEASE.md](docs/RELEASE.md)\n- [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)\n- [docs/TOOLS.md](docs/TOOLS.md)\n- [docs/MARKETPLACE_GO_LIVE.md](docs/MARKETPLACE_GO_LIVE.md)\n\n## Registry and marketplace\n\n- npm: `https://www.npmjs.com/package/nandi-proxmox-mcp`\n- MCP Registry: `https://registry.modelcontextprotocol.io/`\n- MCP Marketplace listing: `https://mcp-marketplace.io/server/io-github-nandi-services-nandi-proxmox-mcp`\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n",
  "bytes": 16146,
  "sha": "a8a4d2c1ef1b79c9f6d7bd1fa9cb455e35aaef8d601182d2cb33fe93b155f250",
  "repo_slug": "nandi-services/proxmox-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_nandi_services_nandi_proxmox_m_8906b865/readme"
}