Metadata-Version: 2.4
Name: mcp-zuul
Version: 0.10.0
Summary: MCP server for Zuul CI — build failure analysis, log search, pipeline status, and job configuration
Project-URL: Homepage, https://github.com/imatza-rh/mcp-zuul
Project-URL: Documentation, https://github.com/imatza-rh/mcp-zuul#why-mcp-zuul
Project-URL: Repository, https://github.com/imatza-rh/mcp-zuul
Project-URL: Issues, https://github.com/imatza-rh/mcp-zuul/issues
Project-URL: Changelog, https://github.com/imatza-rh/mcp-zuul/releases
Author: Itay Matza
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ci,mcp,model-context-protocol,zuul,zuul-ci
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.11
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: httpx>=0.28.0
Requires-Dist: mcp<2.0.0,>=1.26.0
Provides-Extra: console
Requires-Dist: websockets>=14.0; extra == 'console'
Provides-Extra: dev
Requires-Dist: mypy>=1.14; extra == 'dev'
Requires-Dist: pip-audit>=2.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.22; extra == 'dev'
Requires-Dist: ruff>=0.9; extra == 'dev'
Requires-Dist: types-defusedxml>=0.7; extra == 'dev'
Provides-Extra: kerberos
Requires-Dist: gssapi>=1.8.0; extra == 'kerberos'
Description-Content-Type: text/markdown

<!-- mcp-name: io.github.imatza-rh/mcp-zuul -->

# mcp-zuul

<p align="center">
  <a href="https://pypi.org/project/mcp-zuul/"><img src="https://img.shields.io/pypi/v/mcp-zuul" alt="PyPI"></a>
  <a href="https://pypi.org/project/mcp-zuul/"><img src="https://img.shields.io/pypi/pyversions/mcp-zuul" alt="Python"></a>
  <a href="https://github.com/imatza-rh/mcp-zuul/blob/main/LICENSE"><img src="https://img.shields.io/github/license/imatza-rh/mcp-zuul" alt="License"></a>
  <a href="https://github.com/imatza-rh/mcp-zuul/actions/workflows/ci.yml"><img src="https://github.com/imatza-rh/mcp-zuul/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
  <a href="https://glama.ai/mcp/servers/imatza-rh/mcp-zuul"><img src="https://glama.ai/mcp/servers/imatza-rh/mcp-zuul/badges/score.svg" alt="MCP"></a>
  <a href="https://pepy.tech/projects/mcp-zuul"><img src="https://img.shields.io/pepy/dt/mcp-zuul" alt="Downloads"></a>
  <a href="https://codecov.io/gh/imatza-rh/mcp-zuul"><img src="https://codecov.io/gh/imatza-rh/mcp-zuul/graph/badge.svg" alt="codecov"></a>
  <a href="https://hifriendbot.com/ai-list/zuul-ci-by-imatza-rh/"><img src="https://hifriendbot.com/ai-list/badge/zuul-ci-by-imatza-rh.svg" alt="Listed on AiList"></a>
</p>

Debug build failures by asking questions, not clicking through web UIs. An [MCP](https://modelcontextprotocol.io/) server for [Zuul CI](https://zuul-ci.org/).

<p align="center">
  <img src="assets/demo.gif" alt="mcp-zuul diagnosing a build failure" width="800" />
</p>

> If mcp-zuul saves you a debugging session, a ⭐ [star](https://github.com/imatza-rh/mcp-zuul) helps others find it.

**One command, no install:**
```bash
claude mcp add zuul -e ZUUL_URL=https://your-zuul.example.com -- uvx mcp-zuul
```

47 tools, 3 prompts, 3 resources — covering builds, logs, pipelines, jobs, infrastructure, and live status. Works with Claude Code, Claude Desktop, Cursor, Codex, Windsurf, and any MCP-compatible client.

## Why mcp-zuul?

| | mcp-zuul | Raw Zuul API | Zuul web UI |
|---|---|---|---|
| **Failure analysis** | Structured — task, host, error, rc | Raw JSON, parse yourself | Click through log pages |
| **Log search** | Regex + context lines + line ranges | Not available | Browser Ctrl+F |
| **Flaky detection** | Automatic pass/fail statistics | Manual query + calculate | Not available |
| **Test results** | Parsed JUnit XML with failure details | Not available | External link |
| **Anomaly detection** | ML-based via LogJuicer | Not available | Not available |
| **Live status** | Job progress, ETA, pre-failure alerts | Polling API | Manual refresh |
| **Multi-instance** | One config entry per Zuul | Different base URLs | Different browser tabs |

## Quick Start

**uvx** (no install, recommended):
```bash
claude mcp add zuul \
               -e ZUUL_URL=https://softwarefactory-project.io/zuul \
               -e ZUUL_DEFAULT_TENANT=rdoproject.org \
               -- uvx mcp-zuul
```

**pip**:
```bash
pip install mcp-zuul
```

**Docker**:
```bash
docker build -t mcp-zuul .
```

**[LobeHub](https://lobehub.com/mcp/imatza-rh-mcp-zuul)** — send this to your AI agent:
```
Read https://lobehub.com/mcp/imatza-rh-mcp-zuul/skill.md and follow the instructions to install the MCP server.
```

See [Setup](#setup) for full configuration options including Kerberos and multi-instance.

## Features

**Structured failure analysis** — `get_build_failures` parses Zuul's `job-output.json` and returns exactly which Ansible task failed, on which host, with error message, return code, and stderr. No log scrolling needed.

**Read any log file** — `get_build_log` isn't limited to `job-output.txt`. Pass `log_name` to read any file in the build's log directory (ci_script logs, ansible.log, deployment logs) with full grep, tail, and line-range support.

**Precise log navigation** — Jump to exact line ranges with `start_line`/`end_line`. After finding an error at line 6148, read lines 6130-6160 instead of scrolling through 200-line chunks.

**Smart grep** — Regex search with context lines. Auto-converts common shell-grep `\|` syntax to Python regex `|` so patterns like `error\|failed\|timeout` just work.

**Live pipeline awareness** — `get_change_status` returns live job progress with elapsed times, estimated completion, and pre-failure detection (`pre_fail` field). When the change isn't in pipeline, automatically fetches the latest completed buildset.

**Tool filtering** — Reduce LLM tool-selection noise with `ZUUL_ENABLED_TOOLS` or `ZUUL_DISABLED_TOOLS`. Only expose the tools your workflow needs — the rest are removed from the server entirely.

**URL-based input** — Paste a Zuul build URL directly. Tools auto-parse the tenant and UUID from URLs like `https://zuul.example.com/t/tenant/build/abc123` — no manual extraction needed.

**Flaky job detection** — `find_flaky_jobs` analyzes recent build history and computes pass/fail statistics to identify intermittent failures automatically.

**Job dependency graph** — `get_freeze_jobs` returns the fully-resolved job graph for a pipeline/project/branch, showing all jobs with their dependencies after inheritance resolution.

**Kerberos/SPNEGO auth** — First-class support for Zuul instances behind OIDC + Kerberos. Drives the full SPNEGO redirect chain automatically. Session cookies persist and re-authenticate transparently on expiry.

**Streamable HTTP transport** — Run as a persistent HTTP server with `MCP_TRANSPORT=streamable-http` for remote/shared deployment. Supports stdio (default), SSE, and streamable-http.

**Write operations** — Enqueue/dequeue/promote changes, re-enqueue buildsets, and manage autoholds. Disabled by default (`ZUUL_READ_ONLY=true`), write tools are removed from the server entirely so LLMs don't even see them until explicitly enabled.

**LogJuicer integration** — `get_build_anomalies` uses ML-based log analysis to find unusual lines by comparing failed logs against successful baselines. Optional — requires `LOGJUICER_URL`.

**Token-efficient output** — All responses strip None values and use compact formatters. `tail_build_log` returns just the last N lines — the fastest way to check why a build failed.

**Error handling** — All tools return JSON, errors included. Network failures, auth issues, and invalid parameters produce `{"error": "descriptive message"}`. Tools never raise unhandled exceptions.

## Tools

### Builds & Failures

| Tool | What it does |
|------|-------------|
| `list_builds` | Search builds by project, pipeline, job, change, result. Includes `buildset_uuid` for cross-referencing. |
| `get_build` | Full build details — nodeset, log URL, artifacts, error detail. Accepts `url` or `uuid`. |
| `get_build_failures` | **Start here for failures.** Structured task-level data from `job-output.json` — failed play, task, host, msg, rc, stderr/stdout. Accepts `url` or `uuid`. |
| `diagnose_build` | **One-call failure diagnosis.** Combines structured failures from `job-output.json` with targeted log context (fatal/FAILED lines with surrounding context from `job-output.txt`). Use instead of calling `get_build_failures` + `get_build_log` separately. Accepts `url` or `uuid`. |
| `get_build_log` | Read and search log files. Modes: `summary` (tail + error lines), `full` (paginated), `grep` (regex + context), `start_line`/`end_line` (exact range). Supports `log_name` for any file. Accepts `url` or `uuid`. |
| `tail_build_log` | **Fastest failure check.** Last N lines of a log (default 50, max 500). More token-efficient than `get_build_log` summary mode. Accepts `url` or `uuid`. |
| `browse_build_logs` | List log directory contents or fetch specific files (inventory, artifacts, must-gather). Max 512KB per file. Accepts `url` or `uuid`. |
| `stream_build_console` | **Live console from RUNNING builds.** Connects to Zuul WebSocket, returns last N lines (tail). For completed builds, use `tail_build_log`. Optional — requires `pip install mcp-zuul[console]`. |

### Buildsets

| Tool | What it does |
|------|-------------|
| `list_buildsets` | Search buildsets. Use `include_builds=true` to inline full build details (saves round-trips). |
| `get_buildset` | Full buildset with all builds and events. Accepts `url` or `uuid`. |

### Pipeline & Status

| Tool | What it does |
|------|-------------|
| `get_status` | Live pipeline status — what's queued, running, with job progress and ETA. Filterable by pipeline and project. |
| `get_change_status` | Status for a change/PR/MR. In pipeline: live jobs with elapsed times. Not in pipeline: auto-fetches latest completed buildset. Accepts `url` or `change`. |
| `list_pipelines` | All pipelines with their trigger types. |

### Jobs & Projects

| Tool | What it does |
|------|-------------|
| `list_tenants` | All tenants with project counts. |
| `list_jobs` | List jobs with optional name filter. |
| `get_job` | Job configuration — parent, nodeset, timeout, variants, source project. |
| `get_project` | Which pipelines and jobs are configured for a project. |
| `list_projects` | List all projects in a tenant with optional name filter. |
| `get_config_errors` | **Check this when jobs aren't running.** Configuration errors, missing refs, broken configs. Filterable by project. |
| `get_freeze_jobs` | Resolved job dependency graph for a pipeline/project/branch. Shows exactly which jobs will run with inheritance resolved. |
| `get_freeze_job` | **Resolved job config after inheritance.** Final merged nodeset, playbooks, variables, and timeout for a specific job. Answers "what will this job actually do?" |
| `find_flaky_jobs` | Analyze recent build history for intermittent failures. Computes pass/fail rate and flags jobs as flaky (>20% failure with mixed results). |
| `get_build_times` | Build duration trends with avg/min/max stats. Detect performance regressions or timeout-prone jobs. |
| `get_job_durations` | Batch avg/min/max duration for multiple jobs in one call. Designed for monitoring an entire pipeline chain without N separate calls. |
| `check_health` | Test API connectivity, auth status, and config. Triggers re-auth automatically if the Kerberos session expired. |
| `get_tenant_info` | Tenant capabilities — auth realms, job history support, websocket URL. |

### Infrastructure

| Tool | What it does |
|------|-------------|
| `list_nodes` | Nodepool nodes with state (ready, in-use, building), provider, and label. Includes state summary. |
| `list_labels` | Available nodepool labels — what node types jobs can request. |
| `list_semaphores` | Resource locks with current holders and max capacity. Check when jobs wait unexpectedly. |
| `list_autoholds` | Active autohold requests — nodes held after failure for debugging. |
| `get_autohold` | Full details of a specific autohold request — held nodes, timing, project/job. |
| `list_providers` | Nodepool cloud providers with flavors (VM sizes), images, and labels. |
| `list_images` | Nodepool disk images with build status and provider upload state. |
| `list_system_events` | System events — config updates, reconfigurations, pipeline changes. Useful for "why did my job stop running?" |
| `get_badge` | CI status badge URL (SVG) for a project — embeddable in READMEs with Markdown snippet. |
| `get_connections` | Configured source connections — Gerrit, GitHub, GitLab instances with driver and hostname. |
| `get_components` | System components — schedulers, executors, mergers, web servers with state and version. |

### Write Operations

Disabled by default (`ZUUL_READ_ONLY=true`). Set `ZUUL_READ_ONLY=false` to enable. Requires auth token or Kerberos.

| Tool | What it does |
|------|-------------|
| `enqueue` | Enqueue a change or ref into a pipeline. Supports both change-based (check/gate) and ref-based (periodic) enqueue. |
| `promote` | Promote changes to the top of a pipeline queue. Use for urgent fixes when gate has a long queue. |
| `reenqueue_buildset` | Re-enqueue a buildset — reads project/pipeline/ref from a previous buildset and enqueues it again. |
| `dequeue` | Remove a change or ref from a pipeline. **Destructive.** |
| `autohold_create` | Create an autohold request — hold nodes after failure for debugging. |
| `autohold_delete` | Delete an autohold request. **Destructive.** |

### Test Results & Log Analysis

| Tool | What it does |
|------|-------------|
| `get_build_test_results` | **Parse JUnit XML test results.** Discovers test files via `zuul-manifest.json`, returns structured pass/fail/skip counts with failure details. Works with tempest, tobiko, and any JUnit XML output. |
| `get_build_anomalies` | ML-based log anomaly detection via [LogJuicer](https://github.com/logjuicer/logjuicer). Compares failed logs against successful baselines. Requires `LOGJUICER_URL`. |

## Prompts

Pre-built prompt templates that pre-load context and guide analysis:

| Prompt | What it does |
|--------|-------------|
| `debug_build` | Fetches build details + structured failures, checks for flaky signal from recent history, then guides root cause analysis. |
| `compare_builds` | Loads two builds side-by-side with inline failure data for differential analysis — "why did this start failing?" |
| `check_change` | Determines live pipeline status or latest results for a change, with appropriate next steps. |

## Resources

Browsable context that clients can attach to conversations without tool calls:

| Resource | URI Pattern |
|----------|-------------|
| Build details | `zuul://{tenant}/build/{uuid}` |
| Job configuration | `zuul://{tenant}/job/{name}` |
| Project configuration | `zuul://{tenant}/project/{org}/{repo}` |

## Setup

### MCP client configuration

All clients use the same JSON structure. Add to your client's MCP config file:

**Claude Code** (`~/.claude.json` → `mcpServers`):
```json
{
  "mcpServers": {
    "zuul": {
      "command": "uvx",
      "args": ["mcp-zuul"],
      "env": {
        "ZUUL_URL": "https://softwarefactory-project.io/zuul",
        "ZUUL_DEFAULT_TENANT": "rdoproject.org"
      }
    }
  }
}
```

**Claude Desktop** (`claude_desktop_config.json`), **Cursor** (`.cursor/mcp.json`), and other MCP clients use the same format. GUI-based clients don't inherit your shell `PATH` - use the full path to `uvx` (run `which uvx` to find it).

Or via CLI:
```bash
claude mcp add zuul \
               -e ZUUL_URL=https://softwarefactory-project.io/zuul \
               -e ZUUL_DEFAULT_TENANT=rdoproject.org \
               -- uvx mcp-zuul
```

### Environment variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `ZUUL_URL` | Yes | — | Zuul base URL (e.g. `https://softwarefactory-project.io/zuul`) |
| `ZUUL_DEFAULT_TENANT` | No | — | Default tenant (saves passing `tenant` on every call) |
| `ZUUL_AUTH_TOKEN` | No | — | Bearer token for authenticated instances |
| `ZUUL_USE_KERBEROS` | No | `false` | Enable Kerberos/SPNEGO authentication |
| `ZUUL_TIMEOUT` | No | `30` | HTTP timeout in seconds |
| `ZUUL_VERIFY_SSL` | No | `true` | SSL certificate verification |
| `MCP_TRANSPORT` | No | `stdio` | Transport: `stdio`, `sse`, or `streamable-http` |
| `MCP_HOST` | No | `127.0.0.1` | HTTP server bind address (non-stdio transports) |
| `MCP_PORT` | No | `8000` | HTTP server port (non-stdio transports) |
| `ZUUL_ENABLED_TOOLS` | No | — | Comma-separated list of tools to enable (disables all others) |
| `ZUUL_DISABLED_TOOLS` | No | — | Comma-separated list of tools to disable (mutually exclusive with above) |
| `ZUUL_READ_ONLY` | No | `true` | Set to `false` to enable write operations (enqueue, promote, dequeue, reenqueue_buildset, autohold) |
| `LOGJUICER_URL` | No | — | LogJuicer base URL for ML-based log anomaly detection |

### Token authentication

Pass `ZUUL_AUTH_TOKEN` via host environment — **never hardcode tokens in config files** (visible in `ps` output):

```bash
export ZUUL_AUTH_TOKEN=<your-token>
```

For Docker, forward without a value to inherit from host:
```json
"args": ["run", "-i", "--rm", "-e", "ZUUL_AUTH_TOKEN", "mcp-zuul"]
```

### Kerberos / SPNEGO

For Zuul behind OIDC + Kerberos. Requires a valid Kerberos ticket (`kinit`) and the `gssapi` package.

**Linux prerequisites** - `gssapi` has no pre-built Linux wheels and must compile from source:
```bash
# Fedora/RHEL/CentOS
sudo dnf install krb5-devel python3-devel gcc

# Debian/Ubuntu
sudo apt install libkrb5-dev python3-dev gcc
```

macOS and Windows have pre-built wheels - no extra packages needed.

Then install with Kerberos support:
```bash
pip install mcp-zuul[kerberos]    # or: uvx --with "mcp-zuul[kerberos]" mcp-zuul
```

Via CLI:
```bash
claude mcp add -s user zuul-internal \
               -e ZUUL_URL=https://internal-zuul.example.com/zuul \
               -e ZUUL_DEFAULT_TENANT=my-tenant \
               -e ZUUL_USE_KERBEROS=true \
               -e ZUUL_VERIFY_SSL=false \
               -- uvx --with "mcp-zuul[kerberos]" mcp-zuul
```

Or via JSON config:
```json
{
  "zuul-internal": {
    "command": "uvx",
    "args": ["--with", "mcp-zuul[kerberos]", "mcp-zuul"],
    "env": {
      "ZUUL_URL": "https://internal-zuul.example.com/zuul",
      "ZUUL_USE_KERBEROS": "true",
      "ZUUL_VERIFY_SSL": "false"
    }
  }
}
```

For Docker, mount the Kerberos ticket cache:
```bash
docker run -i --rm \
  -v /etc/krb5.conf:/etc/krb5.conf:ro \
  -v /tmp/krb5cc_$(id -u):/tmp/krb5cc_$(id -u):ro \
  -e KRB5CCNAME=/tmp/krb5cc_$(id -u) \
  -e ZUUL_URL=https://internal-zuul.example.com/zuul \
  -e ZUUL_USE_KERBEROS=true \
  mcp-zuul
```

### Multiple instances

Add separate entries per Zuul instance:
```json
{
  "mcpServers": {
    "zuul-rdo": {
      "command": "uvx", "args": ["mcp-zuul"],
      "env": { "ZUUL_URL": "https://softwarefactory-project.io/zuul", "ZUUL_DEFAULT_TENANT": "rdoproject.org" }
    },
    "zuul-internal": {
      "command": "mcp-zuul",
      "env": { "ZUUL_URL": "https://internal.example.com/zuul", "ZUUL_USE_KERBEROS": "true" }
    }
  }
}
```

## Troubleshooting

**`krb5-config: not found` or `Python.h: No such file`** when installing `mcp-zuul[kerberos]` on Linux:

`gssapi` has no pre-built Linux wheels - it compiles from source. Install system packages first:
```bash
# Fedora/RHEL/CentOS
sudo dnf install krb5-devel python3-devel gcc

# Debian/Ubuntu
sudo apt install libkrb5-dev python3-dev gcc
```

**`uvx: command not found`** in Cursor or Claude Desktop:

GUI-based MCP clients don't inherit your shell `PATH`. Use the full path to `uvx`:
```bash
which uvx    # find the path, e.g. /usr/bin/uvx or ~/.local/bin/uvx
```
Then use that absolute path as `command` in your MCP config:
```json
"command": "/usr/bin/uvx"
```

**Permission errors** on `~/.local/share/uv/`:

If `uv` was previously run with `sudo`, the cache directory may be root-owned:
```bash
sudo chown -R $(whoami) ~/.local/share/uv/
```

## Usage Examples

### Debug a build failure

```
"Why did the latest build of my-project fail?"
```
→ `list_builds(project="my-project", result="FAILURE", limit=1)` → `get_build_failures(uuid="...")` → root cause with task name, error, and return code.

### Deep-dive into logs

```
"The structured data says 'non-zero return code' but no error detail.
 Check the ci_script logs."
```
→ `browse_build_logs(uuid="...", path="controller/ci-framework-data/logs/")` → finds `ci_script_008_run.log` → `get_build_log(uuid="...", log_name="controller/ci-framework-data/logs/ci_script_008_run.log", grep="error|timed out|Error 1", context=2)` → exact error with surrounding context.

### Navigate to a specific error

```
"Show me lines 6478-6484 of the job output"
```
→ `get_build_log(uuid="...", start_line=6478, end_line=6484)` → exactly those 7 lines.

### Check live pipeline status

```
"Is change 54321 in any pipeline?"
```
→ `get_change_status(change="54321")` → live jobs with elapsed times and ETA, or latest completed buildset if not in pipeline.

### Compare build results across a pipeline

```
"Show me all builds from the latest buildset"
```
→ `list_builds` to get `buildset_uuid` → `get_buildset(uuid="...")` → all sibling builds with results and durations.

### Paste a Zuul URL directly

```
"What went wrong with this build?
 https://zuul.example.com/t/tenant/build/abc123def"
```
→ `get_build_failures(url="https://zuul.example.com/t/tenant/build/abc123def")` → tenant and UUID auto-extracted.

### Debug why a job isn't running

```
"My project's check pipeline seems broken — jobs aren't triggering"
```
→ `get_config_errors(project="org/my-project")` → configuration errors, missing refs, or repo access issues.

### Check node availability

```
"Jobs are stuck in queue — are there nodes available?"
```
→ `list_nodes()` → node states with by_state summary → `list_labels()` → available node types.

### Detect flaky jobs

```
"Is this job flaky? It keeps failing intermittently"
```
→ `find_flaky_jobs(job_name="my-deploy-job", limit=30)` → pass/fail stats, failure rate, flaky=true/false.

### See what jobs run for a project

```
"What jobs are configured for openstack-operator in the check pipeline?"
```
→ `get_freeze_jobs(pipeline="check", project="openstack-k8s-operators/openstack-operator")` → resolved job graph with dependencies.

### Quick log tail

```
"Show me the last 30 lines of the build log"
```
→ `tail_build_log(uuid="...", lines=30)` → just the tail, minimal tokens.

### What nodeset does my job use after inheritance?

```
"What nodeset and playbooks will deploy-job actually use?"
```
→ `get_freeze_job(pipeline="check", project="org/repo", job_name="deploy-job")` → resolved nodeset, playbooks, variables, timeout after all parent inheritance.

## Development

```bash
git clone https://github.com/imatza-rh/mcp-zuul.git
cd mcp-zuul
uv sync --extra dev

# Run locally
ZUUL_URL=https://softwarefactory-project.io/zuul uv run mcp-zuul

# Run tests
uv run pytest tests/ -v

# Lint and format
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/

# Type check
uv run mypy src/mcp_zuul/

# Build Docker image
docker build -t mcp-zuul .
```

### Architecture

```
MCP Client (Claude Code, Cursor, etc.)
    │
    ▼
┌──────────────────────────────────────────────────┐
│  src/mcp_zuul/                                   │
│                                                  │
│  server.py       FastMCP instance                │
│  config.py       env vars, transport, filtering  │
│  auth.py         Kerberos/SPNEGO + OIDC          │
│  errors.py       @handle_errors decorator        │
├──────────────────────────────────────────────────┤
│  tools/          47 tools across 8 submodules    │
│  prompts.py      3 prompt templates              │
│  resources.py    3 zuul:// resources             │
├──────────────────────────────────────────────────┤
│  helpers.py      API client, URL parsing         │
│  formatters.py   token-efficient output          │
│  parsers.py      Ansible/JUnit/log parsing       │
│  classifier.py   failure classification          │
├──────────────────────────────────────────────────┤
│  httpx clients   API (auth) + logs (no auth)     │
└────────┬─────────────────────────┬───────────────┘
         ▼                         ▼
    Zuul REST API           Log file hosts
```

See `CLAUDE.md` for full architecture details.

## Listings

<a href="https://glama.ai/mcp/servers/imatza-rh/mcp-zuul">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/imatza-rh/mcp-zuul/badge" alt="mcp-zuul MCP server" />
</a>

- [PyPI](https://pypi.org/project/mcp-zuul/) — `pip install mcp-zuul`
- [Glama](https://glama.ai/mcp/servers/imatza-rh/mcp-zuul) — Quality: A/A/A
- [Conare](https://conare.ai/marketplace/mcp/mcp-zuul) — Marketplace listing
- [PulseMCP](https://www.pulsemcp.com/servers?q=zuul) — Server directory
- [LobeHub](https://lobehub.com/mcp/imatza-rh-mcp-zuul) — MCP marketplace with agent install
- [HiFriendBot](https://hifriendbot.com/ai-list/zuul-ci-by-imatza-rh/) — AI tools directory
- [pepy.tech](https://pepy.tech/projects/mcp-zuul) — Download statistics
- [Docker](https://github.com/imatza-rh/mcp-zuul/pkgs/container/mcp-zuul) — `ghcr.io/imatza-rh/mcp-zuul`

## Contributing

Contributions welcome. Please open an issue first to discuss significant changes.

```bash
# Fork, clone, and install dev dependencies
uv sync --extra dev

# Make changes, then verify
uv run pytest tests/ -v
uv run ruff check src/ tests/
uv run ruff format src/ tests/
uv run mypy src/mcp_zuul/
```

## License

Apache-2.0
