# Jenkins Read-Only Build Status Tools — Design Notes - **Status:** Design (implementation-ready notes; **no implementation in this repo**) - **Issue:** #72 (parent umbrella: #75; boundary decision: ADR-0001, #71) - **Related:** #77 (repo/branch/PR → job mapping, designed separately) - **Date:** 2026-07-02 ## 1. Purpose and scope Define the minimum **read-only** Jenkins MCP tool set that lets an LLM answer: *"Did the latest build for this project/branch succeed or fail?"* — plus enough detail (build URL, number, timing, result) to report or investigate. Phase 1 is **strictly read-only**, per ADR-0001 ([`adr-0001-mcp-control-plane-boundaries.md`](adr-0001-mcp-control-plane-boundaries.md)): - **Excluded: build triggers.** - **Excluded: deploy triggers.** - **Excluded: parameterized job launches.** - Excluded: job creation/deletion/config changes, queue manipulation, node management — any Jenkins mutation whatsoever. ## 2. Boundary placement These tools belong to the **`jenkins-mcp`** package/server of the MCP Control Plane — **never** inside `gitea-mcp` (`mcp_server.py` in this repo). Consequences (from `tool-boundaries.md`, `credential-isolation.md`, ADR-0001): - `jenkins-mcp` runs as its own server process with its own `.env`. - **Jenkins credentials never enter the Gitea MCP runtime**, and Gitea credentials never enter `jenkins-mcp`. - This document lands in this repo only because the repo currently hosts the Control Plane's architecture docs; the code ships elsewhere (owner decision #1 of ADR-0001). ## 3. Minimum read-only tool set | Tool | Purpose | |---|---| | `jenkins_whoami` | Verify authenticated Jenkins identity + active profile (mirror of `gitea_whoami`; fail-closed identity proof before anything else) | | `jenkins_list_jobs` | List visible jobs (supports folder paths), with pagination bounds | | `jenkins_latest_build` | The primary question: latest build of a job (or job+branch for multibranch) → status summary | | `jenkins_build_status` | Status of a specific build number (job, number) | | `jenkins_get_build` | Full safe detail of a build (fields in §4) | | `jenkins_console_tail` | Bounded, redacted tail of a build's console log (§6) — optional, approval-gated addition | All tools are `GET`-only against the Jenkins JSON API (`/api/json`, `.../lastBuild/api/json`, `.../consoleText`). No tool issues POST/PUT/DELETE. ## 4. Return payloads (safe fields) `jenkins_latest_build` / `jenkins_build_status` / `jenkins_get_build` return: | Field | Source | Notes | |---|---|---| | `job` | request | Fully-qualified job path (folders joined with `/`) | | `build_number` | `number` | int | | `result` | `result` | `SUCCESS` / `FAILURE` / `UNSTABLE` / `ABORTED` / `NOT_BUILT`; `null` → `IN_PROGRESS` when `building=true` | | `building` | `building` | bool | | `url` | `url` | Build URL | | `branch` | multibranch job name / SCM action | Best-effort; omitted when unknown | | `timestamp` | `timestamp` | ISO-8601 UTC (converted from epoch ms) | | `duration_seconds` | `duration` | 0/omitted while building | | `commit_sha` | SCM build action | Best-effort; omitted when unknown | Rules: no raw Jenkins payload passthrough (allowlist projection only); no `Authorization` header, token, or crumb material in any output or error (reuse the shared redaction approach of `safety-model.md` §3 / `gitea_audit`). ## 5. Failure behavior (fail closed, clear, safe) | Condition | Behavior | |---|---| | Unknown job | Explicit `{"found": false, "job": "", "error": "job not found"}` — never guess or fuzzy-match a job name (hard rule; see also #77) | | Jenkins unreachable (DNS/timeout/conn refused) | Clear `"network error contacting Jenkins: "`; no retry storm — mirror `gitea_auth.api_request` timeout + failure conversion | | 502/503/504 | Explicit "Jenkins upstream unavailable" | | 401/403 | "Jenkins auth failed / insufficient permissions" — **without** echoing credentials or the request's auth material | | Malformed JSON | "malformed JSON response from Jenkins" (no raw-body dump) | | Missing profile/creds | Fail closed before any network call (§7) | ## 6. Console tail safety (`jenkins_console_tail`) Console logs are the highest-risk surface (secrets, tokens, internal hosts routinely leak into build logs). If included at all (owner may defer it): - **Bounded:** hard server-side cap (default: last 200 lines AND ≤ 64 KiB, whichever is smaller; caller may request less, never more). - **Redacted:** pass through the shared secret redactor (token/`Basic`/`Bearer`/ password/key-value patterns) before returning; redaction failure ⇒ return an error, never the raw text. - **Default off:** summary fields (`result`, failing stage if cheaply available) are preferred; the tail requires an explicit `allowed_operations` entry (`jenkins.console.read`) distinct from plain `jenkins.build.read`. ## 7. Credentials and profile requirements Follows the per-service profile model (`gitea-execution-profiles.md`, extended by #76): - Env/config: `JENKINS_URL`, `JENKINS_USER`, `JENKINS_TOKEN_SOURCE_NAME` (name-of-secret only — value resolved at runtime, never logged/committed). - Profile: e.g. `jenkins-readonly` with namespaced `allowed_operations: ["jenkins.read", "jenkins.build.read"]` (+ `jenkins.console.read` only if the tail tool is approved); `forbidden_operations: ["jenkins.build.trigger", "jenkins.deploy", "jenkins.job.configure"]` as belt-and-braces even though no mutating tool exists. - Missing URL/user/token/profile ⇒ **fail closed** with a clear message. - Since every tool is read-only, no confirmation gates are needed — but identity (`jenkins_whoami`) must still work so workflows can prove which Jenkins account they act as. ## 8. Job addressing and mapping Tools accept an explicit fully-qualified job path (folder-aware: `folder/subfolder/job`). How a *repo/branch/PR* resolves to that job path is **out of scope here** and designed in **#77**, with these fixed constraints: - No silent guessing of job names — unmapped input returns an explicit "no mapping" result. - Multibranch pipelines address a branch job as `/` with proper URL-encoding of branch names (e.g. `feature%2Fx`). ## 9. Testing strategy (for the implementing package) Mocked-Jenkins unit tests only (no live Jenkins in unit CI), mirroring this repo's conventions (`docs/developer-testing-guidelines.md`): - Patch the HTTP client; assert method is always `GET` and URL shape is correct (folders, multibranch encoding). - Success projections: field allowlist exactly as §4; unknown fields dropped. - `result=null + building=true` ⇒ `IN_PROGRESS`. - Unknown job ⇒ found:false, no fuzzy match, no API retry. - Timeout/DNS/5xx/malformed-JSON ⇒ safe errors, no secret/credential leakage (explicit no-token-in-error assertions). - Console tail: cap enforcement (lines and bytes), redaction applied, redaction failure ⇒ error not raw text, gated behind `jenkins.console.read`. - Profile gate: missing/insufficient profile ⇒ no network call (`mock_api.assert_not_called()` pattern). ## 10. Implementation-readiness checklist Ready to implement in `jenkins-mcp` once: 1. ADR-0001 owner decision #1 (where `jenkins-mcp` lives) is made. 2. #76 profile schema exists (or a minimal `jenkins-readonly` profile is hand-rolled to the same rules). 3. #77 mapping design is accepted (or tools ship path-addressed only, mapping deferred). Explicitly **not** unlocked by this document: build triggers, deploys, parameterized launches, any Jenkins code in `mcp_server.py`.