Skip to main content
Glama
Snille

Proxmox Notes MCP Server

by Snille

Proxmox Notes

A self-hosted service that creates, reads and edits the Notes (description) of Proxmox LXC containers and VMs. It has a web GUI, a REST API and an MCP server, and it replaces the browser-only Proxmox Notes Generator.

Status: 0.9.0, first release. The Notes format, the server (REST, MCP, Docker), reading and writing Notes in Proxmox, the web GUI with its settings page (English and Swedish) and the icon library work, and a ready-made image is published. Icon generation with AI is not built yet.

What it will do

  • Read the Notes of existing guests on one or more Proxmox hosts, edit them in the web GUI and save them back.

  • Create Notes for new guests, prefilled from Proxmox (hostname, IP, MAC, DHCP, OS).

  • Export and import a Note as a loose JSON file. Files saved by the old Notes Generator can be imported.

  • Embed icons in the Note itself, so no web server is needed for images. A URL to an icon set is still possible.

  • Make icons when one is missing: from ComfyUI (your own workflow), from a cloud image service (Nano Banana, OpenAI or Grok, to be tested), or from the open dashboard-icons set.

  • Give an AI assistant the same functions through MCP, with a preview before every write.

Related MCP server: mcp-server-example

Decisions

Decision

Choice

Why

Where it runs

Its own Docker container, one independent instance per site

Anyone can run their own copy (home, work, other people). No dependency on another service.

Publishing

Public GitHub repository (MIT licence), image on GitHub Container Registry

Others can use it, and the image lives next to the code. Nothing site-specific may be in the code or the repository; it goes in configuration.

Icons

An icon library in the service; icons are always embedded as WebP (data: URI). A web icon set is only an import source.

Proxmox shows embedded raster images (tested 2026-10-17), so no web server is needed. SVG cannot be embedded. See docs/NOTES_FORMAT.md.

Language

Node.js 22+, plain ES modules, Vitest

Same stack as the original generator logic, so it ports directly.

Layout

One full-width HTML table with the header inside; the old Markdown layout is still read and can be chosen

It fills the Proxmox Notes panel and gets the alternating row colours (tested in Proxmox 2026-10-17).

Data model

One JSON document per Note

The web GUI, REST, MCP and JSON export all use the same model; Markdown is rendered from it.

Writes

Preview first; the old Note is saved as a snapshot before every write

A write overwrites the old text in Proxmox. The snapshot makes it reversible.

Login

None in the service; put it behind a reverse proxy with authentication (for example Authelia)

One less thing to build and maintain; the proxy already does it well.

Connect a Proxmox host

Each Proxmox host gets its own user and API token. Run this on the Proxmox host as root. It creates a role, a user that cannot log in, and a token:

pveum role add ProxmoxNotesEditor -privs VM.Audit,VM.Config.Options
pveum user add proxmox-notes@pve -comment 'Proxmox Notes service'
pveum acl modify /vms -user proxmox-notes@pve -role ProxmoxNotesEditor
pveum user token add proxmox-notes@pve notes -privsep 1 -comment 'Proxmox Notes service'
pveum acl modify /vms -token 'proxmox-notes@pve!notes' -role ProxmoxNotesEditor

pveum user token add shows the token secret once. Save it in a file, for example as a Docker secret. The token has privilege separation (-privsep 1), so it needs its own ACL as well as the user's.

Privilege

Needed for

VM.Audit

List guests and read their config and Notes

VM.Config.Options

Write the Notes (description). Leave it out for a read-only instance.

VM.Config.Options is the narrowest privilege Proxmox has for the description, but it also covers other general options (name, onboot, OS type and similar). The service only ever sends description.

Writing

A write takes two steps. notes_prepare_write reads the guest's config and returns the current text, the new text and a single-use confirmation (valid 10 minutes); the web GUI shows both side by side. notes_write_confirmed then:

  1. reads the config again and stops if it changed since the preview (Proxmox's config digest);

  2. saves the current Notes as a snapshot in /data/snapshots/<host>/<vmid>/;

  3. sends only description, with the digest, so Proxmox itself refuses the write if the config changed in between;

  4. reads the Notes back and checks that Proxmox stored exactly what was sent.

Snapshots are never deleted automatically. History in the web GUI (or notes_snapshots and notes_snapshot) lists them; loading one puts it in the editor, and saving writes it back.

Then add the host under Settings → Proxmox hosts in the web GUI: address, token id and token secret. Read the certificate shows the host's certificate fingerprint; compare it with pvenode cert info on the host and pin it. Test connection shows the Proxmox version, how many guests the token sees, and whether it may write Notes. The secret is sent to the server once and never shown again.

The hosts are kept in /data/targets.json, which the settings page writes with mode 600. Secrets typed in the web GUI are stored in that file, so back the volume up as carefully as the tokens themselves. A host can also be written there by hand (see targets.example.json), with the secret in a separate file such as a Docker secret:

Field

Meaning

id

Short name used in tools and URLs, lower case

baseUrl

https://<host>:8006

tokenId

proxmox-notes@pve!notes

tokenSecretFile

Path inside the container to the file with the secret (or tokenSecret inline, not recommended)

fingerprint

SHA-256 fingerprint of the host's certificate. The service then accepts only that certificate. Read it on the host with pvenode cert info (pve-ssl.pem, or pveproxy-ssl.pem if you installed your own).

verifyTls

Without a fingerprint: true (default) checks the certificate against the system CAs; false accepts any certificate (not recommended)

A pinned connection makes a full TLS handshake every time (no session reuse), because a resumed session carries no certificate to check.

The guests' facts come from their config: hostname, IP (static, or the DHCP address of a running LXC), MAC (written in lower case), DHCP and OS type. A VM's address is only known when it uses cloud-init; otherwise it needs the QEMU guest agent, which is not used yet.

The web GUI

  • Guest list: pick a Proxmox host; every guest shows whether its Notes are in the Notes format, other text, or empty.

  • Editor: heading icon and title, icon image, description, notes and the sections with their rows (add, move, remove). A guest with no Notes gets a new Note filled in from the template and from Proxmox. Notes that do not follow the format open as raw text, so nothing is lost.

  • Live preview, rendered by the same code as the server, cleaned the way current Proxmox versions clean Notes (inline styles removed, only safe tags, attributes and URLs) and styled with Proxmox's own Notes CSS in a panel of the same fixed width. A bar shows the size against the 8192-character limit, and a badge shows when the Note differs from what Proxmox has.

  • Layout: full-width table (default) or the old Markdown layout. A Note stored in the other layout is converted when it is saved, and the editor says so.

  • Icons: Choose icon opens the icon library as a searchable grid, with a second tab for dashboard-icons. An uploaded image is added to the library first. The icon is always embedded; if the Note has too little room left, it is encoded again at a lower quality.

  • Icon library (Settings → Icons): a grid of every icon with search, rename and remove; upload several files or drop them on the page; add icons from dashboard-icons by searching for them; import a whole web icon set once (for example the old Notes Generator's, from its select.json), after which that web server is no longer needed. An uploaded file is named from its file name without the size (sonarr01-512x512.png becomes "Sonarr 01", taking the name from the icon-set list when the key is in it), and with Replace icons with the same name an upload updates an icon instead of adding a copy, so a whole folder can be uploaded again, for example in a larger size. Heading icons are chips, added from a searchable list of all 786 Font Awesome 4 icons.

  • From Proxmox: one button updates IP, MAC, DHCP and OS from the guest's config. When the IP changes, service links that pointed at the old IP move along. An exact OS release in the Note ("Debian 13") is kept when Proxmox only knows the family ("Debian").

  • Import and export: export a Note as JSON; import JSON from this tool or from the old Notes Generator, or Markdown. Copy Markdown puts the text on the clipboard for the Proxmox Notes field.

  • Save to Proxmox: a dialog shows the Notes now in Proxmox and the Notes that will be written, side by side, before anything is written. History lists the snapshots kept before each save.

  • Settings: Proxmox hosts (add, edit, remove, read and pin the certificate, test the connection); Notes defaults (layout, heading icon, the template for a new Note with default values such as the owner, OS names); icons (icon-set address and size, image list, heading icons, import of the old select.json); appearance (theme and language for this browser, and the defaults for every browser).

  • Languages: English and Swedish. A missing Swedish text falls back to English; a test checks that both languages have every text the page uses.

  • Themes: dark (default), light, or follow the system.

The browser loads the Notes format modules from /format/, so the preview matches what the server renders. Font Awesome 4.7 is bundled (font: SIL OFL 1.1, CSS: MIT; see src/web/vendor/font-awesome/LICENSE.txt), because Proxmox uses that version for the icons in Notes.

Run it

The image is ghcr.io/snille/proxmox-notes-mcp, built for linux/amd64 and linux/arm64 by GitHub Actions for every release. Tags: latest, the version (0.9.0) and the minor version (0.9).

As a Portainer stack

  1. Stacks → Add stack, name it for example proxmox-notes.

  2. Web editor: paste compose.yaml. (Or Repository: https://github.com/Snille/Proxmox-Notes-MCP, compose path compose.yaml.)

  3. Change the port, ALLOWED_HOSTS and MCP_TOKEN if you need to, then Deploy the stack.

  4. Open http://<host>:8080/ and add a Proxmox host under Settings → Proxmox hosts (see Connect a Proxmox host).

To update, open the stack, choose Update the stack and turn on Re-pull image. The data stays in the volume.

With Docker Compose

docker compose up -d

Then open http://<host>:8080/.

The container keeps its data (options, hosts with their token secrets, snapshots and the icon library) in the volume mounted at /data. Back the volume up, and protect the backup as carefully as the tokens.

To build the image from the source instead, use compose.dev.yaml, which serves on port 8085. It mounts DEV_DATA_DIR (default private/dev-data) at /data and DEV_SECRETS_DIR (default private/secrets) read-only at /run/secrets/proxmox-notes:

docker compose -f compose.dev.yaml up -d --build

The host it runs on must reach the Proxmox API (port 8006) of every Proxmox host it manages.

Settings

All settings are environment variables:

Variable

Default

Meaning

PORT

8080

Port inside the container

DATA_DIR

/data in the image

Where options, targets, snapshots and icons are kept

TARGETS_FILE

$DATA_DIR/targets.json

The Proxmox hosts and their tokens

ALLOWED_HOSTS

empty (any)

Comma-separated host names the service answers to, for example notes.example.com. Recommended behind a reverse proxy.

MCP_TOKEN

empty (none)

When set, /mcp needs Authorization: Bearer <token>. Use it when the proxy lets /mcp through without its own login, because MCP clients cannot do a browser login.

The service has no login of its own. Never expose it without a reverse proxy that has authentication. Requests from a web page on another site are refused (Origin check), so a logged-in browser cannot be used against it by another page.

Options

The choices in the forms (layout, heading icons, icon-set images, OS names, the template for a new Note, and the default theme and language) are kept in /data/options.json and edited on the settings page. On the first start, a select.json from the old Notes Generator in /data is converted automatically; the settings page can also import one. Read and save them with GET and PUT /api/options; a save is checked against a schema.

The host settings (/api/settings/...) are offered in the web GUI and REST only, not over MCP, so token secrets never pass through an assistant.

Interfaces

Path

What

/

Web GUI

/api/icons

The icon library: GET lists, POST adds (a WebP drawn by the browser plus the original, as base64), PATCH/DELETE /api/icons/<id> rename and remove, GET /api/icons/<id>.webp serves one

/api/dashboard-icons?q=<words>

Searches the names in dashboard-icons; GET /api/dashboard-icons/<name>.png fetches one through the server

/api/icon?key=<key>

An icon from the configured web icon set, for the import. Only URLs built from that icon set are fetched.

/api/tools/{name}

REST: POST a tool's arguments as JSON

/api/options

GET or PUT the options

/mcp

MCP over Streamable HTTP

/health/live

Health check

MCP over stdio, for an assistant on the same machine: node src/stdio.mjs.

Tools: notes_status, notes_options, notes_icons, notes_hosts, notes_guests, notes_read, notes_prefill, notes_new, notes_render, notes_parse, notes_import_legacy, notes_prepare_write, notes_write_confirmed, notes_snapshots, notes_snapshot. Only notes_write_confirmed writes to Proxmox, and only with a confirmation from notes_prepare_write.

Development

npm install
npm run check

npm run check runs all tests. The tests include a round-trip check: every Note in reference/examples/ is parsed and rendered again, and the result must be identical. GitHub Actions runs them on every push and pull request.

To release a version:

  1. Set the version in package.json (the only place it is kept) and run npm install --package-lock-only.

  2. Add a section ## <version> - <date> at the top of HISTORY.md.

  3. Commit, tag v<version> and push with git push --follow-tags.

The workflow checks that the tag matches package.json, runs the tests, builds and pushes the image, and creates the GitHub release with the text from HISTORY.md.

Layout

Path

Content

src/format/

The Notes document model: render.mjs, parse.mjs, defaults.mjs, legacy.mjs (old generator JSON)

src/server.mjs, src/mcp.mjs, src/tools.mjs

HTTP server, MCP server and the tools both use

src/options.mjs, src/config.mjs

Instance options (/data/options.json) and environment settings

src/proxmox/

Targets (targets.json), the API client with certificate pinning, and guest facts

src/icons.mjs, src/dashboard-icons.mjs

The icon library (/data/icons) and the dashboard-icons search

src/web/

The web GUI: app.js, settings.js, library.js (icon library and picker), preview.js (Proxmox-like rendering and sanitizing), icons.js (embedding within the size limit), i18n.js (texts), bundled Font Awesome

test/

Vitest tests

.github/workflows/ci.yml

Tests on every push; image and GitHub release on a version tag

reference/examples/

Eleven real Notes with addresses and names replaced, used as golden test files

reference/select.json

The old generator's options file (icons, image keys, OS names)

reference/test/

A Note with an embedded icon, used for the Proxmox display test

docs/NOTES_FORMAT.md

The format, the Proxmox display rules and the size budget

private/

Local only, ignored by git: the original hand-over and the real Notes the fixtures were made from

Plan

  1. ✅ Notes format: document model, render, parse, round-trip tests.

  2. ✅ Server: Docker image, configuration, REST API, MCP (HTTP and stdio).

  3. ✅ Proxmox: list guests on several hosts, read Notes, prefill from facts, write with preview, digest check and snapshot.

  4. ✅ Web GUI: guest list, editor with live preview, icons, JSON export and import, save to Proxmox, history.

  5. ✅ Settings page in the web GUI: Proxmox hosts, Notes defaults and template, icons, theme and language. The image generator settings come with icon generation.

  6. ✅ Icons: icon library, upload, dashboard-icons, import of a web icon set, icon picker in the editor.

  7. ✅ Release: MIT licence, image on GitHub Container Registry built by GitHub Actions, ready-made Portainer stack.

  8. Icon generation: ComfyUI first, then the cloud services one at a time.

Open question: the project name (this could become the next major version of Snille/Proxmox-Notes).

History: HISTORY.md.

Licence

MIT, see LICENSE. The bundled Font Awesome 4.7 has its own licence (font: SIL OFL 1.1, CSS: MIT).

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI assistants to interact with a Markdown-based knowledge base with backlinks, tags, and full-text search, supporting note creation, reading, updating, renaming, and deletion through MCP tools.
    25 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI assistants to manage in-memory notes by listing, fetching, and creating notes through MCP tools over stdio.
    66 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables managing plain-text notes on disk through MCP resources, tools, and prompts, so natural-language requests can create, read, search, delete, and summarize notes via an interactive client.
    MIT