Metadata-Version: 2.4
Name: zapi-mcp
Version: 0.9.2
Summary: MCP server for Zabbix API — daily brief, problems, hosts, items
Author: AIKAWA Shigechika
License-Expression: MIT
Project-URL: Homepage, https://github.com/shigechika/zapi-mcp
Project-URL: Repository, https://github.com/shigechika/zapi-mcp
Project-URL: Issues, https://github.com/shigechika/zapi-mcp/issues
Keywords: zabbix,mcp,model-context-protocol,monitoring,network
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: zapi-lib<1,>=0.6.0
Requires-Dist: mcp<2,>=1.2
Dynamic: license-file

<!-- mcp-name: io.github.shigechika/zapi-mcp -->

# zapi-mcp

English | [日本語](README.ja.md)

MCP (Model Context Protocol) server for the [Zabbix](https://www.zabbix.com/) API.

Built for network operations: a single `daily_brief` call summarizes active
problems plus site-specific categories (DHCP pool usage, SNAT session usage,
core-network problems, …), and individual tools query problems, hosts, and item
values. Organization-specific tags live in a config file, not the code, so the
server stays generic.

Version-adaptive auth: works against Zabbix 6.0 LTS (`user` + `auth` field) and
forward-compatible with 6.4 / 7.0 (`username` + `Authorization: Bearer`).

Documentation: <https://shigechika.github.io/zapi-mcp/>

## Features

| Tool | Description |
|------|-------------|
| `health_check` | Server version, Zabbix connectivity/auth, detected API version, and configured `daily_brief` categories — call at session start or after a timeout |
| `daily_brief` | Morning patrol: active problems (Warning+), hosts currently in maintenance, plus one section per configured category |
| `get_problems` | Active problems by severity and tag, newest-first with age; header shows the true total (`showing N of TOTAL` when capped); output includes `eventid` |
| `get_hosts` | List hosts filtered by role/tag/group, with IP and tags |
| `get_host_items` | Current item values for a host (server-side host filter) |
| `acknowledge_problem` | Acknowledge problems and add a message (does not close them) |
| `set_maintenance` | Open an idempotent Zabbix maintenance window, selecting hosts by `location` tag or by exact host name (exactly one of the two) |
| `get_maintenance_windows` | List maintenance windows (Active/Upcoming/Expired) — cross-check before treating another tool's alert as a new incident |

## Setup

```bash
# uv
uv pip install zapi-mcp

# pip
pip install zapi-mcp
```

Or from source:

```bash
git clone https://github.com/shigechika/zapi-mcp.git
cd zapi-mcp

# uv
uv sync

# pip
pip install -e .
```

## Configuration

Set the following environment variables:

| Variable | Description | Default |
|---|---|---|
| `ZABBIX_URL` | Zabbix base URL (e.g. `https://zabbix.example.com`); `/api_jsonrpc.php` is appended if absent | *required* |
| `ZABBIX_USER` | Zabbix API user | *required* |
| `ZABBIX_PASSWORD` | Zabbix API password | *required* |
| `ZABBIX_CATEGORIES_INI` | Path to a categories INI file for `daily_brief` (optional) | — |
| `ZABBIX_BRIEF_RECENT_HOURS` | `daily_brief` "recent" window in hours; problems older than this are folded to a count | `24` |
| `ZABBIX_BRIEF_PROBLEM_LIMIT` | Max active problems `daily_brief` fetches per call before counting the rest | `1000` |

The API user needs read permission for the host groups you query, plus
acknowledge permission if you use `acknowledge_problem`, maintenance-write
permission if you use `set_maintenance`, and maintenance-read permission
(usually included by default) for `get_maintenance_windows` and the
`daily_brief` "In Maintenance" section.

### Active problems in `daily_brief`

Problems are grouped by severity and listed **newest-first**, each annotated with
its age (e.g. `3h ago`). Problems older than the recent window
(`ZABBIX_BRIEF_RECENT_HOURS`, default 24h) are folded to a single
`… and N older (stale; oldest …)` line — so a backlog of alerts that Zabbix
keeps active because their recovery is never auto-confirmed (ICMP ping down, RDP
down, …) doesn't bury what just happened. Section headers carry the true total
and show `showing N of TOTAL` when the fetch is capped, never a silent truncation.

### Maintenance windows in `daily_brief`

Right after Active Problems, `daily_brief` lists hosts covered by a
maintenance window that's active now, plus any window starting later today —
so a planned outage isn't mistaken for a new incident by whatever else is
watching those hosts. The `## In Maintenance` section is omitted entirely
when there's nothing to show (no news is no maintenance). Windows starting
tomorrow or later, and expired windows, aren't included here; call
`get_maintenance_windows` (optionally with `include_expired=True`) for the
full picture.

### Categories for `daily_brief` (optional)

`daily_brief` always lists active problems. To add site-specific sections —
DHCP pool exhaustion, SNAT session usage, core-network problems — point
`ZABBIX_CATEGORIES_INI` at an INI file. Each `[section]` is one category:

```ini
[dhcp]
name = DHCP Pool Usage
# Zabbix host tag identifying the group
tag = dhcp-pool-usage
# report current values for this exact item key
item_key = usage
# flag values >= this
threshold = 80

[snat]
name = SNAT Session Pool
tag = snat-pool-usage
# substring match (catches pool.node0.usage etc.)
item_key_search = .usage
threshold = 80

[core]
name = Core Network
tag = role
# the tag must equal this value
tag_value = main
# no item key -> report active problems instead
```

- `tag` (required): host tag identifying the category. With `tag_value`, the tag
  must equal it (Equal); without, any host carrying the tag matches (Exists).
- `item_key` / `item_key_search`: when either is set, the section reports current
  item values sorted high-to-low. `item_key` matches the key exactly; use
  `item_key_search` for keys that embed an id (e.g. `.usage` catches
  `pool.node0.usage`). When neither is set, it reports active problems for the tag.
- `threshold`: optional; values at or above it are flagged.

See [`categories.ini.example`](categories.ini.example). When the variable is
unset or the file is missing, `daily_brief` reports active problems only.

## Usage

### Claude Code

Add to `.mcp.json`:

```json
{
  "mcpServers": {
    "zapi-mcp": {
      "type": "stdio",
      "command": "zapi-mcp",
      "env": {
        "ZABBIX_URL": "https://zabbix.example.com",
        "ZABBIX_USER": "api-user",
        "ZABBIX_PASSWORD": "",
        "ZABBIX_CATEGORIES_INI": "/path/to/categories.ini"
      }
    }
  }
}
```

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "zapi-mcp": {
      "command": "zapi-mcp",
      "env": {
        "ZABBIX_URL": "https://zabbix.example.com",
        "ZABBIX_USER": "api-user",
        "ZABBIX_PASSWORD": ""
      }
    }
  }
}
```

### Direct Execution

```bash
export ZABBIX_URL=https://zabbix.example.com
export ZABBIX_USER=api-user
export ZABBIX_PASSWORD=your-password
zapi-mcp
```

### CLI Options

```bash
zapi-mcp --version   # Print version and exit
zapi-mcp --check     # Verify environment variables and authentication, then exit
zapi-mcp --brief     # Print the daily_brief to stdout and exit (handy for cron)
zapi-mcp             # Start MCP server (STDIO, default)
```

`--check` exit codes: `0` success, `1` config error, `2` auth/connection error.

`--brief` exit codes: `0` success, `1` a section failed (auth, the active-problems
fetch, or category loading — see the embedded `Error:` line in the output).

## Development

```bash
git clone https://github.com/shigechika/zapi-mcp.git
cd zapi-mcp

# uv
uv sync --dev
uv run pytest -v
uv run ruff check .

# pip
python3 -m venv .venv
.venv/bin/pip install -e . && .venv/bin/pip install pytest pytest-cov respx ruff
.venv/bin/pytest -v
.venv/bin/ruff check .
```

### Live smoke test

`pytest` checks logic against fixtures; it cannot tell you that a tool has
stopped returning real data. `scripts/smoke_test.py` runs **every registered
tool** against the configured Zabbix and fails on empty, malformed or error
answers:

```bash
# needs the same ZABBIX_* environment variables as the server
uv run python scripts/smoke_test.py
uv run python scripts/smoke_test.py --only get_problems --traceback
```

- **Read-only.** `acknowledge_problem` and `set_maintenance` are skipped by
  name — an acknowledgement is visible to every operator and cannot be
  quietly undone, and `set_maintenance` opens a real maintenance window that
  suppresses alerts — and a test enforces that. The report prints tool names
  and statuses only, never
  payloads; server-authored error text is redacted too, since Zabbix quotes the
  host it was asked about. `--traceback` still shows the full text on the
  operator's own terminal.
- Arguments that would identify real hosts, groups or tag values are
  **discovered at run time**, never written into `scripts/smoke_probes.py`.
  Two tests enforce that: one refuses those parameters as literals, the other
  bans anything address-shaped (mail address, URL, hostname, IPv4, IPv6)
  anywhere in the file.
- An empty answer is a real observation here — a monitoring system with nothing
  wrong is the goal — so probes assert the envelope the tool must produce
  rather than a row count.
- CI enforces the cheap half: a tool registered without a probe spec fails the
  build (`tests/test_smoke_probes.py`), so adding a tool forces the question
  "how would we know it works?".
- `scripts/smoke_harness.py` is the engine and holds no Zabbix knowledge: it is
  kept identical across the servers that share it, so fix engine bugs once and
  sync the file rather than patching this copy.

## Releasing

Releases are automated with [release-please](https://github.com/googleapis/release-please).
Merging [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, …)
to `main` keeps a release PR open with the next version and changelog. Merging
that PR tags `vX.Y.Z` and publishes a GitHub Release, whose `release: published`
event triggers the `release` workflow to build and publish to PyPI and the MCP
Registry. release-please owns the version in `zapi_mcp/__init__.py` and
`server.json` (do not bump them by hand).

> [!IMPORTANT]
> The release-please workflow should be given a repository secret
> `RELEASE_PLEASE_TOKEN` (a PAT with `contents: write` + `pull-requests: write`).
> The default `GITHUB_TOKEN` cannot create the Release that triggers the
> downstream `release` workflow (GitHub blocks workflow runs triggered by
> `GITHUB_TOKEN`), so without the PAT nothing gets published. The workflow falls
> back to `GITHUB_TOKEN` when the secret is unset so PR CI keeps working on forks.

## Roadmap

- Streamable HTTP transport + OAuth2 for remote / mobile use
- Visual rendering of key metrics

## License

MIT
