Skip to content

feat(proxmox): interactive VM panel via the MCP Apps extension - #33

Closed
Showdown76py wants to merge 2 commits into
mainfrom
feat/mcp-apps-vm-panel
Closed

feat(proxmox): interactive VM panel via the MCP Apps extension#33
Showdown76py wants to merge 2 commits into
mainfrom
feat/mcp-apps-vm-panel

Conversation

@Showdown76py

Copy link
Copy Markdown
Owner

Quoi

Un nouvel outil proxmox_vm_panel qui ouvre un panneau de contrôle pour une VM ou un CT dans le client : état CPU / RAM / disque en direct, boutons start / stop / restart, et champs pour le nombre de cœurs et la mémoire.

Il s'appuie sur l'extension MCP Apps (io.modelcontextprotocol/ui) : l'outil porte _meta.ui.resourceUri qui pointe vers ui://beaconmcp/vm-panel.html, un document HTML autonome servi en text/html;profile=mcp-app que l'hôte rend dans une iframe sandboxée et avec laquelle il dialogue en JSON-RPC sur postMessage.

Pas de migration mcp 2.0 requise

C'est le point qui a décidé de l'approche. La classe Apps qui emballe tout ça vit dans mcp 2.0 et exige MCPServer, mais elle ne fait que deux choses : estampiller meta= sur l'outil et poser mime_type= sur la ressource. Les deux existent déjà sur FastMCP en 1.29, et le format de fil est identique.

La fonctionnalité n'attend donc pas la migration derrière le pin <2 de #32.

Sécurité

Le panneau n'a aucun accès propre au cluster. Ses boutons émettent des tools/call ordinaires vers proxmox_vm_start / _stop / _restart / _config, donc l'approbation que le client applique à n'importe quel appel d'outil s'applique ici aussi. C'est une façon plus agréable d'émettre l'appel, pas une façon de contourner la demande de confirmation.

proxmox_vm_panel lui-même est en lecture seule et n'a pas besoin d'être ajouté à _NEEDS_CONFIRMATION.

Dégradation

Un client qui n'a pas négocié l'extension ignore _meta.ui et affiche la valeur de retour de l'outil, qui est le même instantané sous forme de données. Rien ne casse, on perd juste le cadre. C'est pour ça que l'outil renvoie l'état complet plutôt qu'un texte du genre « voir le panneau ».

Vérification

Les tests unitaires couvrent le format de fil (le _meta.ui de l'outil, le type MIME de la ressource, et le fait que l'URI annoncée résout vraiment — une resourceUri qui renvoie 404 donne une iframe blanche) et le mappage de l'instantané, dont deux pièges : disk: 0 de QEMU veut dire « pas d'agent invité » et pas « 0 octet utilisé », et seule une erreur « does not exist » justifie d'essayer l'autre type d'invité.

Le JS a été testé dans un navigateur contre un harnais qui joue le côté hôte du protocole : handshake ui/initialize, rendu initial via ui/notifications/tool-result, actions power suivies du rafraîchissement, application de config n'envoyant que les clés modifiées, cas « rien à changer » sans appel, remontée d'une erreur d'outil sans bloquer les contrôles, et bascule de thème via ui/notifications/host-context-changed.

358 passed (346 avant, +12), ruff check src/ tests/ clean.

proxmox_vm_panel carries _meta.ui.resourceUri pointing at a ui:// resource
served as text/html;profile=mcp-app, which an Apps-capable client renders in
a sandboxed iframe: live CPU/RAM/disk, start/stop/restart, and fields for
core count and memory.

Runs on mcp 1.x. The Apps class that wraps this lives in 2.0 and needs
MCPServer, but the two knobs it sets -- meta= on the tool, mime_type= on the
resource -- are already on FastMCP, so the panel does not wait on that
migration.

The panel holds no cluster access of its own. Its buttons issue ordinary
tools/call requests for proxmox_vm_start / _stop / _restart / _config, so the
client's approval prompt still stands in front of every action. Clients that
skipped the extension ignore _meta.ui and get the same snapshot as data,
which is why the tool returns the full state rather than a placeholder.

Verified against a harness that speaks the host side of the protocol:
handshake, initial render, power actions with refresh, config apply sending
only changed keys, tool errors surfacing without wedging the controls, and
the theme switch.
The handshake nested appCapabilities under a `capabilities` key and sent
`appInfo` as `clientInfo`. The real shape, per the ext-apps App.connect()
implementation, is flat: appInfo / appCapabilities / protocolVersion.

A rejected handshake is silent -- the host simply does not reply. So the
promise never settled, `ui/notifications/initialized` never went out, the
host never delivered `ui/notifications/tool-result`, and the panel sat on
"Loading..." with an empty frame and nothing in the console. That is what
showed up in Claude: the host reported the widget as rendered while the
iframe stayed blank.

Also surface the failure instead of hanging on it. A handshake that goes
unanswered for 5s now replaces the spinner with the reason, so the next
protocol mismatch is one glance rather than an afternoon.

The browser harness this was first tested against replied to any
ui/initialize it received, which is why the bad shape passed. It now
validates the params like a host does, and the new test pins the flat
shape against the shipped HTML -- it fails on the old file.
@Showdown76py

Copy link
Copy Markdown
Owner Author

Fermée au profit de #34, qui la remplace intégralement.

La branche de #34 partait déjà de celle-ci, donc ses trois commits sont tous là-bas — b0e93eb (le panneau VM), 9f9e987 (le correctif du handshake ui/initialize) et d487744 (les deux autres panneaux). Rien n'est perdu, il n'y a plus qu'une PR à relire au lieu d'une pile.

#34 reste en brouillon : le serveur est prêt et les panneaux tournent dans les clients externes compatibles, mais le dashboard intégré ne les rend pas encore, et ça dépend de la migration mcp 2.0. Détails dans #35.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant