feat(proxmox): interactive VM panel via the MCP Apps extension - #33
Closed
Showdown76py wants to merge 2 commits into
Closed
feat(proxmox): interactive VM panel via the MCP Apps extension#33Showdown76py wants to merge 2 commits into
Showdown76py wants to merge 2 commits into
Conversation
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.
This was referenced Jul 29, 2026
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 — #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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Quoi
Un nouvel outil
proxmox_vm_panelqui 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.resourceUriqui pointe versui://beaconmcp/vm-panel.html, un document HTML autonome servi entext/html;profile=mcp-appque l'hôte rend dans une iframe sandboxée et avec laquelle il dialogue en JSON-RPC surpostMessage.Pas de migration mcp 2.0 requise
C'est le point qui a décidé de l'approche. La classe
Appsqui emballe tout ça vit dans mcp 2.0 et exigeMCPServer, mais elle ne fait que deux choses : estampillermeta=sur l'outil et posermime_type=sur la ressource. Les deux existent déjà surFastMCPen 1.29, et le format de fil est identique.La fonctionnalité n'attend donc pas la migration derrière le pin
<2de #32.Sécurité
Le panneau n'a aucun accès propre au cluster. Ses boutons émettent des
tools/callordinaires versproxmox_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_panellui-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.uiet 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.uide l'outil, le type MIME de la ressource, et le fait que l'URI annoncée résout vraiment — uneresourceUriqui renvoie 404 donne une iframe blanche) et le mappage de l'instantané, dont deux pièges :disk: 0de 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 viaui/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 viaui/notifications/host-context-changed.358 passed(346 avant, +12),ruff check src/ tests/clean.