v2.9.0 — Per-provider managers + full MCP lifecycle parity
Highlights
Per-provider manager screens. New ⚙ Manage entries under both Hetzner and OVH in the sidebar — each opens a provider-scoped table with a state-aware action toolbar:
- Hetzner → ⚙ Manage — Create / Power on / Shutdown / Power off / Reboot / Delete. Start enables only on stopped servers, Shutdown / Power off / Reboot only on running ones.
- OVH → ⚙ Manage — Create / Start / Stop / Reboot / Delete, routed automatically across Cloud / VPS / dedicated bare-metal per row's
provider_type.
The unified InstanceListScreen stays unchanged as the search-and-SSH fast path.
MCP tool lifecycle parity. 10 new tools so AI agents have the same surface as the UI:
| Provider | New tools |
|---|---|
| Hetzner | power_on, power_off, shutdown, reboot (standard tier), delete_ssh_key (dangerous) |
| OVH | create_instance, delete_instance (dangerous), start_instance, stop_instance, reboot_instance (standard) |
Power management at the standard guard tier; create/delete at dangerous. Every mutating tool's description carries an explicit "Confirm with the user before calling" cue, plus a top-level confirmation-protocol prompt in the MCP server's instructions block (summarise → state args → wait for affirmative reply).
Total tool count: 39 (up from 24 in the README's prior count — hetzner_* was missing from the docs entirely).
Hetzner SSH keys screen. New Hetzner → 🔑 SSH Keys surface with full CRUD against the Hetzner Cloud project's key registry — the same registry the create-server wizard injects from. Replaces the prior CLI-hint placeholder.
OVH SSH keys screen migrated. Was managing /me/sshKey (account-level, used by VPS / dedicated). Now manages /cloud/project/{id}/sshkey — the registry the cloud-create wizard reads from. Adding a key actually shows up in the wizard now.
OVH cloud-create wizard fixes.
- Region-first picker. Region is a
Selectpopulated fromGET /cloud/project/{id}/region; on change, flavors and images reload filtered. Picking a flavor in the wrong region is impossible by construction (was the source ofFlavor X could not be founderrors). - Datacentre labels. Region dropdown reads
GRA11 — Gravelines, Franceinstead of bare codes. Stable mapping for ~13 known prefixes; unknown codes fall back gracefully. - Pricing surfaced.
_extract_ovh_pricehelper parses all three OVH price-block shapes (text/value/ legacypriceInUcents); flavor table grows Hourly / Monthly columns and the confirm modal includes the cost line. - Hide dead regions. Regions with zero deployable flavors are filtered out of the picker;
available: falseflavors are hidden from the table within otherwise-active regions.
Notable bug fixes
fix(relay)— login wrote auth tokens but neverrelay.{base_url, mercure_url}, so freshly-logged-in users tripped a spurious "MCP relay URLs not configured" toast. NewRelayManager.ensure_configured()derives URLs from the API base (prod splits api/apex; staging single-host) and persists.fix(screens)— Textual 8.x rejectspush_screen_waitoutside a worker context. Audited every callsite; six screens were latently broken and would've crashed the moment a user clicked a destructive action button. Mechanical fix acrosshetzner_create,ovh_cloud_create,ovh_resize,ovh_snapshots,ovh_firewall,ovh_ip_management.fix(custom-servers)— Save / Cancel row was clipped on small terminals (sat at the end of#add_form, off-screen on a 24-row terminal). Now docked to the screen bottom, mirroring the wizard pattern.fix(ovh-storage)—OVHStorageScreen._get_project_idsreached for a non-existentconfig_manager.configattribute, always returned[]regardless of what was configured. Bug was unique to this one screen — verified by grep across the codebase.fix(hetzner-create)— wizard relied on the service-side fallback todefault_hetzner_ssh_key; if that wasn't set the footgun guard fired with a generic message. Now pre-selects the configured default in the keys table + adds an actionable submit-time check pointing at the right Settings field.fix(ovh-ip)— global.hidden { display: none }rule was missing; several screens (instance_listmemory banner,ovh_ip_managementforms,ovh_firewall) toggledclasses="hidden"to no effect. Fixed at the root.
UX polish
- Sidebar relay indicator moved from the dock-bottom row to the brand block (under "Server Manager"), aligned to the same left margin.
- Curved-border style language applied uniformly to every OVH-screen DataTable + the new manager surfaces. Action toolbars match the Snapshot Manager visual rhythm.
- OVH IP Management restructured — each form now in its own card-styled
Containerso show/hide is atomic; removes the gaps the user reported between sections.
Tests
~150 new tests landed across the release covering: new service methods (HetznerService.{power_on,power_off,shutdown,reboot,delete_ssh_key,list_locations,list_images}, OVHCloudService.list_regions, flavor available + price field shapes), 15 region-label parametrised cases, all 11 new MCP tools (dispatch / guard rejection / validation / api-error wrapping), Hetzner setup screen dropdown seeding/refresh/saved-deprecated value preservation, relay URL derivation and ensure_configured.
Upgrade
pipx install --force servonaut
# or
pip install --upgrade servonautFull diff: v2.7.0...v2.9.0