v1.30 - pre-correlated views, request-tls, OAuth polish
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/extensionsgroups, 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. Acceptshost_idorhost(name).hostgroup_overview_get- host group health roll-up with the top-N noisiest hosts. Acceptsgroupidorgroup,top_n(default 5).infrastructure_summary_get- whole-deployment dashboard summary (problem counts by severity, busiest groups, biggest hostgroups).top_ncontrols breadth.item_history_summary_get- item metadata + history window + min/max/avg over the period. Acceptsitemidor(host, key),period(default"1h"),limit(default 100).- All four reuse the existing
_filter_active_problemshelper so they stay consistent withproblem_active_get. Each is registered inmonitoringandextensionsso monitoring-only tokens see them after the per-token tools/list filter.
./deploy/install.sh request-tlssubcommand automates Let's Encrypt issuance when the MCP server terminates TLS itself (discussion #27). Wrapscertbot certonly(auto-detects standalone vs webroot based on whether anything is bound to :80 already), symlinksfullchain.pem/privkey.peminto/etc/zabbix-mcp/tls/, idempotently writes[server].tls_cert_fileand[server].tls_key_fileintoconfig.toml, installs a deploy hook at/etc/letsencrypt/renewal-hooks/deploy/zabbix-mcp-server.shso post-renewal the service auto-reloads, and enablescertbot.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-updatesendpoint that callsforce_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:PORTstill allowed for dev loops. Admin role only. Audit log entryoauth.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].enabledread-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 andcursor: not-allowedso 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 = falseinconfig.tomland restart.
Security
- Pre-correlated view tools enforced single-prefix scope only (HIGH from pre-release security audit).
host_status_getchecked thehostprefix at the wrapper boundary, then internally calledhost.get + hostinterface.get + problem.get + trigger.get + item.get. A token scoped to onlyhostcould pull problem/item data this way that it could not viaproblem_get/item_getdirectly. Same gap on the other three view tools.check_token_authorization()now acceptstool_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 vials. Tightened tochgrp $SERVICE_USER + chmod 0710(group traversal-only, no listing). Privkey hardened from0640to0440 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-checkslast_checkedpost-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_getissued three host.get calls per invocation - pattern was_count("host.get") and len(host.get(filter:status=0)) or 0(theand ... orantipattern), so it called host.get for the count truthiness gate, again to fetch the hostid list, thenlen()-d it. Replaced with onehost.get(filter={status:0}, output=count)round-trip.- CRUD smoke test default
_gethandler matched the new view tools first -if n.endswith("_get")returned{limit:2, output:"extend"}forhost_status_getetc., which their Pydantic schema rejects, so the pre-release smoke matrix flagged red on the new tools. Added a_CUSTOM_GETSallowlist so the per-tool handlers fire before the catch-all. item.get sortfield="lastclock"was rejected by Zabbix inhost_status_get("Sorting by field 'lastclock' not allowed"). Switched tosortfield="name"; the response still carrieslastclockso the LLM can read recency directly.- Pre-existing em-dashes removed from new install.sh strings (project policy: ASCII hyphen only).
Documentation
docs/OAUTH.mdgains 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, plushealth_checkandzabbix_raw_api_callthat 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-tlsone-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.mdand 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 by10-and11-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 /--headermatching the Client MCP Wizard generator; Installer CLI table was missingtest-config,--with-reporting,--without-reportingrows;[oauth_clients.<id>].granted_scopesrow renamed to the actual config keyscope(RFC 7591 space-separated string);[oauth]and[oauth_clients.<id>]blocks added toconfig.example.toml.