Skip to content

v1.30 - pre-correlated views, request-tls, OAuth polish

Choose a tag to compare

@hermanekt hermanekt released this 04 May 22:59
8dff79e

v1.30 - 2026-05-05

External-feedback release. Three threads of feedback land together: an external review by Quadrata Insights flagged that Claude has to chain too many low-level Zabbix calls (host_get -> interface_get -> problem_get -> item_get -> history_get) just to answer "what's wrong with web01"; discussion #27 asked for a one-shot way to obtain a Let's Encrypt certificate when the MCP server terminates TLS itself; and field-test feedback caught two operator-hygiene gaps (manual GitHub-update poll, in-portal OAuth enable). v1.30 addresses all three plus a pre-release security/code-review pass.

Added

  • Pre-correlated view tools in the monitoring / extensions groups, designed to fold three to five raw Zabbix API calls into a single round-trip the LLM can reason about:
    • host_status_get - host + interfaces + active problems + last value of the top items in one call. Accepts host_id or host (name).
    • hostgroup_overview_get - host group health roll-up with the top-N noisiest hosts. Accepts groupid or group, top_n (default 5).
    • infrastructure_summary_get - whole-deployment dashboard summary (problem counts by severity, busiest groups, biggest hostgroups). top_n controls breadth.
    • item_history_summary_get - item metadata + history window + min/max/avg over the period. Accepts itemid or (host, key), period (default "1h"), limit (default 100).
    • All four reuse the existing _filter_active_problems helper so they stay consistent with problem_active_get. Each is registered in monitoring and extensions so monitoring-only tokens see them after the per-token tools/list filter.
  • ./deploy/install.sh request-tls subcommand automates Let's Encrypt issuance when the MCP server terminates TLS itself (discussion #27). Wraps certbot certonly (auto-detects standalone vs webroot based on whether anything is bound to :80 already), symlinks fullchain.pem / privkey.pem into /etc/zabbix-mcp/tls/, idempotently writes [server].tls_cert_file and [server].tls_key_file into config.toml, installs a deploy hook at /etc/letsencrypt/renewal-hooks/deploy/zabbix-mcp-server.sh so post-renewal the service auto-reloads, and enables certbot.timer. Re-runnable any time you rotate or add a hostname. Usage: sudo ./deploy/install.sh request-tls --hostname mcp.example.com --email you@example.com.
  • "Check now" button in Settings -> Admin Portal (under the "Check for updates" toggle), wired to a new /api/check-updates endpoint that calls force_check() on the update checker. Forces a fresh GitHub release poll bypassing the 60-second throttle - useful right after an upgrade to confirm the new version registered without having to wait the cache out. (The pill that announces an available update stays where it always was, in the page header.)
  • In-portal OAuth enable form on the OAuth Clients page empty state. Replaces the "edit config.toml manually" wall of text with a Public URL field + dynamic-registration toggle + Submit. Validation client-side and server-side rejects (a) plain http:// (ChatGPT and Claude Desktop refuse cleartext discovery), (b) raw IP addresses (public CAs do not issue TLS certs for IPs, so the cert chain would never validate), and (c) bare hostnames without a TLD. http://localhost:PORT still allowed for dev loops. Admin role only. Audit log entry oauth.enable. Restart-needed badge raised on save. End-to-end-tested with the actual ChatGPT custom apps OAuth flow against a Rocky 9 deployment.
  • [admin].enabled read-only status indicator in Settings -> Admin Portal. Earlier versions deliberately removed the toggle ("textbook foot-gun"); v1.30 re-adds it as a disabled toggle styled with a muted-grey track and cursor: not-allowed so the operator can see the current state at a glance and the tooltip explains the SSH-edit recovery path. The toggle is purely visual - submission is a no-op, the only path to disable is to edit [admin].enabled = false in config.toml and restart.

Security

  • Pre-correlated view tools enforced single-prefix scope only (HIGH from pre-release security audit). host_status_get checked the host prefix at the wrapper boundary, then internally called host.get + hostinterface.get + problem.get + trigger.get + item.get. A token scoped to only host could pull problem/item data this way that it could not via problem_get / item_get directly. Same gap on the other three view tools. check_token_authorization() now accepts tool_prefixes=[...] and the four wrappers list every endpoint they internally touch (host_status_get -> host + hostinterface + problem + trigger + item; hostgroup_overview_get -> hostgroup + host + problem + trigger; infrastructure_summary_get -> host + hostgroup + item + trigger + template + problem; item_history_summary_get -> item + history + host).
  • /etc/letsencrypt/{live,archive} directory mode 0755 (MEDIUM from audit) let any local non-root user enumerate certificate subjects via ls. Tightened to chgrp $SERVICE_USER + chmod 0710 (group traversal-only, no listing). Privkey hardened from 0640 to 0440 root:$SERVICE_USER (read-only - certbot only writes new privkeyN.pem, never modifies existing). Live-tested on a Rocky 9 deployment: sudo -u zabbix-mcp ls /etc/letsencrypt/live/ now returns "Permission denied" while the service still loads its own cert correctly. Renewal hook re-applies both modes on every renewal.

Fixed

  • Update notification throttle was too aggressive - 30 minutes between login-triggered GitHub polls meant an operator who upgraded right after a release saw a cached "no update" answer for half an hour. Reduced to 60 seconds, which still absorbs reload loops and double-login bursts but stays well inside the public GitHub rate limit (60 req/h/IP).
  • README mis-described the update check as a hourly daemon thread; the daemon was removed in v1.24 in favour of login-triggered polling. Documentation now matches the real three triggers (boot, every successful login, manual "Check now").
  • force_check() worst-case race - the synchronous "Check now" path was documented to wait-and-reuse but actually issued a duplicate GitHub poll after the lock released. Now re-checks last_checked post-lock and returns the in-flight result when the previous thread filled the cache during the wait. Closes the duplicate-poll window under burst (login + multiple button presses inside one minute).
  • infrastructure_summary_get issued three host.get calls per invocation - pattern was _count("host.get") and len(host.get(filter:status=0)) or 0 (the and ... or antipattern), so it called host.get for the count truthiness gate, again to fetch the hostid list, then len()-d it. Replaced with one host.get(filter={status:0}, output=count) round-trip.
  • CRUD smoke test default _get handler matched the new view tools first - if n.endswith("_get") returned {limit:2, output:"extend"} for host_status_get etc., which their Pydantic schema rejects, so the pre-release smoke matrix flagged red on the new tools. Added a _CUSTOM_GETS allowlist so the per-tool handlers fire before the catch-all.
  • item.get sortfield="lastclock" was rejected by Zabbix in host_status_get ("Sorting by field 'lastclock' not allowed"). Switched to sortfield="name"; the response still carries lastclock so the LLM can read recency directly.
  • Pre-existing em-dashes removed from new install.sh strings (project policy: ASCII hyphen only).

Documentation

  • docs/OAUTH.md gains a "Let's Encrypt one-liner" callout in the TLS section pointing at the new installer subcommand. Plus a pre-flight warning: enabling native TLS on a host that already has a reverse proxy in front will break the proxy's HTTP forwarding - operator has to pick one termination point.
  • README.md TOC restructured. Added OAuth 2.1, Public URL, First-time admin access, Update notifications. New top-level Operate section bundles Installer CLI + Updates + Compatibility + Development + Related Projects + License. Tools count badge and "default" tools count corrected (231 -> 237 with the new pre-correlated views, problem_active_get, plus health_check and zabbix_raw_api_call that were always there but missed in the original count).
  • README.md gains a dedicated OAuth 2.1 Authorization Server section with quick-start config and feature breakdown (discovery, PKCE, two-step consent + role cap, refresh-token reuse detection, per-client IP allowlist + TTL, audit integration, legacy bearer coexist) so the flagship v1.28-v1.29 capability is no longer buried inside the configuration table.
  • README.md and INSTALL.md TLS / HTTPS section restructured into "two production paths" (reverse proxy vs native TLS via request-tls one-liner) with explicit note that this is a general HTTPS feature - works with OAuth, bearer tokens, or no auth.

Removed

  • Client MCP Wizard step 5 ("Reverse proxy & TLS" snippet generator) - shipped in v1.29, removed in v1.30 after operator field-test feedback. The wizard exists to walk an admin from "fresh install" to "Claude Desktop is talking to Zabbix" in two minutes; reverse-proxy / TLS termination is a separate one-off operations task with too many local choices (existing Apache vs nginx vs Caddy vs cloud tunnel; existing certs vs new cert; admin port 9090 vs MCP port 8080 sharing or separating; etc.) for a generated snippet to get right. Operators who hit this on the live deployment skipped pasting the generated snippet anyway because their box already had Apache configured the way they wanted. The TLS / reverse-proxy material that was useful (snippets, the Let's Encrypt one-liner) lives in docs/OAUTH.md and the README "TLS / HTTPS" section, where operators read it once during initial deployment instead of bumping into a snippet generator on every client-onboarding flow.
  • Two orphan OAuth screenshots (docs/screenshots-oauth/08-consent-screen.png, docs/screenshots-oauth/09-consent-scope-expansion.png) - superseded by 10- and 11- when the per-scope consent design landed in v1.29 but never deleted.
  • README.md accuracy fixes caught by a pre-release audit: Token Budget said "all ~232 Zabbix API tools" (now 237 = 223 API + 14 extension); tool group table said "extensions ~10" (now 14 with full list); per-group counts in Token Budget were drifted to varying degrees (monitoring 31 -> 87, data_collection 107 -> 27 - looks like the two were swapped at some point - users 30 -> 39, administration 39 -> 59); Claude Code CLI snippets used the wrong flag syntax (-t / -e / -h) - corrected to --transport / positional URL / --header matching the Client MCP Wizard generator; Installer CLI table was missing test-config, --with-reporting, --without-reporting rows; [oauth_clients.<id>].granted_scopes row renamed to the actual config key scope (RFC 7591 space-separated string); [oauth] and [oauth_clients.<id>] blocks added to config.example.toml.