Mcp Zuul — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Mcp Zuul (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<!-- mcp-name: io.github.imatza-rh/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 server for Zuul CI.
<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 helps others find it.
One command, no install:
claude mcp add zuul -e ZUUL_URL=https://your-zuul.example.com -- uvx mcp-zuul48 tools, 5 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.
| 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 |
uvx (no install, recommended):
claude mcp add zuul \
-e ZUUL_URL=https://softwarefactory-project.io/zuul \
-e ZUUL_DEFAULT_TENANT=rdoproject.org \
-- uvx mcp-zuulpip:
pip install mcp-zuulDocker:
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 for full configuration options including Kerberos and multi-instance.
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 and re-enqueue buildsets. Pipeline-affecting tools are disabled by default (ZUUL_READ_ONLY=true) and removed from the server entirely so LLMs don't even see them. Autohold management (create/delete) is always available since it doesn't affect running pipelines.
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.
| 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]. |
| 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. |
| 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. |
| 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. |
| 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. |
Pipeline-affecting operations — disabled by default (ZUUL_READ_ONLY=true). Set ZUUL_READ_ONLY=false to enable. Requires auth token or Kerberos. Autohold management (create/delete) is always available since it doesn't affect running pipelines.
| 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. Not gated by ZUUL_READ_ONLY. |
autohold_delete | Delete an autohold request. Not gated by ZUUL_READ_ONLY. |
| 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. Compares failed logs against successful baselines. Requires LOGJUICER_URL. |
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. |
tenant_health | Assesses overall tenant health — components, config errors, and node pool status in one view. |
diagnose_queue_delay | Diagnoses why jobs are queued or delayed — checks nodes, semaphores, and system state. |
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} |
All clients use the same JSON structure. Add to your client's MCP config file:
Claude Code (~/.claude.json → mcpServers):
{
"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:
claude mcp add zuul \
-e ZUUL_URL=https://softwarefactory-project.io/zuul \
-e ZUUL_DEFAULT_TENANT=rdoproject.org \
-- uvx mcp-zuul| 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 pipeline-affecting write operations (enqueue, promote, dequeue, reenqueue_buildset). Autohold management (create/delete) is always available. |
LOGJUICER_URL | No | — | LogJuicer base URL for ML-based log anomaly detection |
Pass ZUUL_AUTH_TOKEN via host environment — never hardcode tokens in config files (visible in ps output):
export ZUUL_AUTH_TOKEN=<your-token>For Docker, forward without a value to inherit from host:
"args": ["run", "-i", "--rm", "-e", "ZUUL_AUTH_TOKEN", "mcp-zuul"]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:
# Fedora/RHEL/CentOS
sudo dnf install krb5-devel python3-devel gcc
# Debian/Ubuntu
sudo apt install libkrb5-dev python3-dev gccmacOS and Windows have pre-built wheels - no extra packages needed.
Then install with Kerberos support:
pip install mcp-zuul[kerberos] # or: uvx --with "mcp-zuul[kerberos]" mcp-zuulVia CLI:
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-zuulOr via JSON config:
{
"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:
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-zuulAdd separate entries per Zuul instance:
{
"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" }
}
}
}`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:
# 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:
which uvx # find the path, e.g. /usr/bin/uvx or ~/.local/bin/uvxThen use that absolute path as command in your MCP config:
"command": "/usr/bin/uvx"Permission errors on ~/.local/share/uv/:
If uv was previously run with sudo, the cache directory may be root-owned:
sudo chown -R $(whoami) ~/.local/share/uv/"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.
"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.
"Show me lines 6478-6484 of the job output"→ get_build_log(uuid="...", start_line=6478, end_line=6484) → exactly those 7 lines.
"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.
"Show me all builds from the latest buildset"→ list_builds to get buildset_uuid → get_buildset(uuid="...") → all sibling builds with results and durations.
"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.
"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.
"Jobs are stuck in queue — are there nodes available?"→ list_nodes() → node states with by_state summary → list_labels() → available node types.
"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.
"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.
"Show me the last 30 lines of the build log"→ tail_build_log(uuid="...", lines=30) → just the tail, minimal tokens.
"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.
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 .MCP Client (Claude Code, Cursor, etc.)
│
▼
┌──────────────────────────────────────────────────┐
│ src/mcp_zuul/ │
│ │
│ server.py MCPServer instance │
│ config.py env vars, transport, filtering │
│ auth.py Kerberos/SPNEGO + OIDC │
│ errors.py @handle_errors decorator │
├──────────────────────────────────────────────────┤
│ tools/ 48 tools across 8 submodules │
│ prompts.py 5 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 hostsSee CLAUDE.md for full architecture details.
<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>
pip install mcp-zuulghcr.io/imatza-rh/mcp-zuulContributions welcome. Please open an issue first to discuss significant changes.
# 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/Apache-2.0
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.