Jenkins MCP Server
by TelmanH
README.md
# Jenkins MCP Server
Read-only, multi-instance Jenkins access for AI assistants: **VS Code GitHub Copilot** (Agent mode) at work,
**Claude Code** for local testing, or any other MCP client. Large build logs are analysed **on the server**; the model only receives small,
relevant excerpts. A **team knowledge base** remembers solved problems, so the next analysis starts from
what the team already knows.
## Two ways to run it
| | Company use | Local testing / development |
|---|---|---|
| Who | everyone who analyses builds at work | developers of this server |
| Platform | **Windows** | **macOS** or **Windows** |
| AI client | **VS Code GitHub Copilot** (Agent mode) | **Claude Code** (macOS) or **VS Code Copilot** (Windows) |
| Jenkins | your company Jenkins | a test Jenkins in Docker (or any reachable one) |
| Setup | `setup\windows\setup.ps1` | `tests/macos/setup.sh` or `setup\windows\setup.ps1 -Dev` |
| Step-by-step guide | **[setup/windows/README.md](setup/windows/README.md)** | **[tests/README.md](tests/README.md)** |
| Client config | `.vscode/mcp.json`, generated by `setup.ps1` (token prompts, VS Code secure storage) | macOS: registered by the script (`claude mcp add`); Windows: `.vscode/mcp.json` |
The server code is the same in both cases. Day-to-day usage: [docs/USER_GUIDE.md](docs/USER_GUIDE.md).
## Read-only guarantee
The server **cannot change anything in Jenkins**:
- Its HTTP session (`ReadOnlySession` in `src/jenkins_mcp/client.py`) refuses every method except
GET and HEAD before a request leaves the process. A tool that tried to trigger, stop, delete or
configure something would fail with "this MCP server is read-only".
- Credentials are only sent to the configured Jenkins URLs; URLs of other hosts are refused.
- Every tool that talks to Jenkins is marked `readOnlyHint` for the AI client. Only `draft_kb_entry` and
`propose_kb_update` write: one **new local file** in `knowledge/drafts/` each time (never overwrites, never
Jenkins), so they are honestly marked as local writes.
- The only other local writes are the log cache (`~/.cache/jenkins-mcp`, owner-only permissions).
- Tested: offline (no non-GET request is ever sent, POST/PUT/PATCH/DELETE blocked) and live against a
real Jenkins (all build numbers unchanged after running every tool).
## Project layout
```
jenkins-mcp/
├── src/jenkins_mcp/ SERVER CODE (same on Windows and macOS, installed with pip)
├── config/ YOUR SETTINGS: *.example templates committed; your yaml/.env are git-ignored
├── knowledge/ TEAM DATA: known issues (Markdown); may also live in its own repo
├── docs/USER_GUIDE.md how to use it day to day
│
├── setup/windows/ COMPANY: Windows + VS Code Copilot
│ ├── setup.ps1 install script (-Dev: + test tools, for developers)
│ └── README.md step-by-step guide
├── .vscode/ VS Code: mcp.json = Copilot server (generated, git-ignored; template mcp.example.json),
│ settings.json = pytest
│
└── tests/ LOCAL TESTING / DEVELOPMENT (never installed)
├── README.md step-by-step guide: macOS + Claude Code, Windows + VS Code
├── macos/setup.sh macOS: install + Claude Code registration + offline tests
├── test_*.py offline tests (pytest, fake Jenkins)
├── fixtures/ sample log + example knowledge base for the tests
└── live/ real Jenkins in Docker + live check of every tool (run_live.sh / run_live.ps1)
```
Source package:
```
src/jenkins_mcp/
├── app.py FastMCP object, server instructions, shared parameter types
├── main.py imports tools + prompts, run() (command: jenkins-mcp)
├── vscode_config.py writes .vscode/mcp.json (token prompts per instance), called by setup.ps1
├── xmlutil.py Jenkins XML 1.1 parsing
├── settings.py paths (JENKINS_MCP_HOME), env tunables
├── config.py config/jenkins_instances.yaml + config/.env -> Settings, validation, instance lookup
├── client.py ReadOnlySession (GET/HEAD only), Instance
├── targets.py job/build/URL resolution
├── text.py secret masking, output budget, formatting
├── rules/ built-in error/ignore/noise rules (YAML)
├── logs/ download + cache, cleaning, stage detection, error grouping
├── kb/ knowledge base: load entries, match against logs
├── tools/ MCP tools: instances, jobs, builds, logs, artifacts, infra, knowledge
└── prompts.py analyse_build, document_session
```
## Configuration
Files (templates in `config/`, your copies are git-ignored):
| File | Content |
|---|---|
| `config/jenkins_instances.yaml` | Jenkins instances, TLS, log rules, knowledge base folder (template: `jenkins_instances.example.yaml`) |
| `config/.env` | Credentials, one pair per instance (template: `.env.example`) |
Environment variables (set them in `config/.env`, in the MCP client config, or in the shell):
| Variable | Default | Purpose |
|---|---|---|
| `JENKINS_<NAME>_USER` / `JENKINS_<NAME>_TOKEN` | - | Credentials of instance `<name>` (upper case, non-alphanumeric -> `_`). Other names: `user_env` / `token_env` in the yaml |
| `JENKINS_MCP_HOME` | nearest parent of the working dir with `config/jenkins_instances(.example).yaml` | Project folder with `config/` and `knowledge/` |
| `JENKINS_MCP_CONFIG` | `<home>/config/jenkins_instances.yaml` | Instance file |
| `JENKINS_MCP_KB_DIR` | `knowledge.dir` in the yaml, else `<home>/knowledge` | Knowledge base folder (e.g. a private team repo) |
| `JENKINS_MCP_CACHE_DIR` | `~/.cache/jenkins-mcp` | Log cache (contains raw console logs) |
| `JENKINS_MCP_CACHE_MAX_MB` | `1024` | Cache size limit |
| `JENKINS_MCP_MAX_LOG_MB` | `200` | Larger logs keep the first 20 % and the end |
| `JENKINS_MCP_MAX_CHARS` | `12000` | Hard size limit of one tool answer |
| `JENKINS_MCP_JOB_CACHE_S` | `60` | Job lists (`list_jobs`, overviews) are reused for this long; `0` = off |
| `JENKINS_MCP_LOG_LEVEL` | `WARNING` | Server log level (stderr) |
After editing the yaml or `.env`, call `list_instances(reload_config=true)`; no restart is needed.
Config problems are reported by `list_instances` under `config_errors`.
Use a **dedicated Jenkins user with read permissions only** (Overall/Read, Job/Read) for the API token.
The server never writes to Jenkins, but a read-only account limits the damage if a token leaks.
## Tools (34)
| Tool | Purpose |
|---|---|
| `list_instances` | instances, token source, connection check (version, user/groups, admin rights, quiet-down), config errors, knowledge base state; `reload_config` |
| `list_jobs` | recursive job list with status/folder/name filter; failing jobs show the last green build |
| `failures_overview` | red/yellow jobs in the last N hours across all instances, with the last green build |
| `recent_builds` | every build in the last N hours across all instances: result, culprits, long-running builds |
| `get_job` | job details, permalinks, recent builds, parameters, children |
| `find_jobs_by_scm` | which jobs build a repository (or load a Shared Library), across instances; any URL spelling |
| `get_build_summary` | **start here**: result, params, agent, culprits, downstream builds, pending input, stages, failed stage, known issues, root-cause candidates, tail |
| `get_stage_log` | errors and tail of one stage (default: failed stage); `exact` = per-step log via wfapi (parallel branches) |
| `get_pending_inputs` | what a running build waits for (`input` step); approving stays in the UI |
| `get_log_errors` | de-duplicated error groups, annotated by the knowledge base |
| `search_log` / `get_log_lines` | regex search with context / line windows |
| `get_test_failures` | failed JUnit cases incl. how long they fail |
| `get_pipeline_info` | what actually ran: Jenkinsfile source, git checkouts, Shared Library versions (hint on LF-version mismatch), replay, slowest steps; `show_script` = executed Jenkinsfile |
| `compare_builds` | good vs bad: agent, params, stages, commits (Jenkinsfile/groovy changes flagged), new errors, errors also in good build, env.log diff; pick builds by node (`good_on_node`/`bad_on_node`) or take the good build from another instance (`other_instance`) |
| `get_job_config` | config.xml as a summary: SCM, Jenkinsfile path, parameters, triggers, retention, label, build steps, Config File Provider files, credential IDs; `raw` = masked XML |
| `read_managed_file` | content of a Config File Provider file (settings.xml...), secrets masked |
| `get_job_config_history`, `diff_job_config` | who changed the job config and when (Job Config History plugin); semantic + XML diff of two versions or against the live config |
| `list_artifacts`, `read_artifact`, `get_env_log` | artifact list (sizes, download URL), text artifacts (UTF-8, UTF-16, cp1252), secrets masked |
| `compare_artifacts` | artifacts of two builds: added/removed, changed (fingerprint, size, bytes); diff of one text artifact, checksum of a binary |
| `list_workspace`, `read_workspace_file` | files that were not archived, from the job / Pipeline node-block workspace (current state on the agent) |
| `node_health`, `get_queue` | agents (labels, disk, memory, offline reason, clock) and queue |
| `get_node` | one node: labels, which build runs on which executor, Java version, config.xml (remote root, launcher, node env vars masked, tool locations) |
| `find_known_issues` | match a build or a pasted error text against the knowledge base |
| `get_known_issue`, `list_known_issues` | read / list entries, with how often each matched since the server started |
| `draft_kb_entry` | write a **new local draft** in `knowledge/drafts/` (local write, never Jenkins) |
| `propose_kb_update` | propose an update of an existing entry (new build, extra regexes) as a new draft next to it (local write) |
| `recurring_errors` | errors across the newest builds of a job: regression ("new since #N") vs. also in green builds |
Prompts: `analyse_build`, `document_session`, `morning_report` (Copilot: type `/` and pick `mcp.jenkins...`;
Claude Code: `/mcp__jenkins__analyse_build`).
All tools are read-only towards Jenkins; see [Read-only guarantee](#read-only-guarantee).
## Knowledge base workflow
1. Analyse a build. `get_build_summary` shows `known_issues` if an entry matches.
2. When the cause is found and confirmed, run the prompt `document_session` (or ask the agent).
It checks for duplicates and calls `draft_kb_entry`, or `propose_kb_update` when an entry already exists.
`recurring_errors` finds red-herring candidates (errors that also occur in green builds).
3. Review the draft in `knowledge/drafts/`, move it to `knowledge/`, commit it to your **team's private**
knowledge repo. The server picks up file changes automatically.
Company knowledge does not belong in this public repository: `.gitignore` excludes `knowledge/KB-*.md` and
drafts. Keep entries in a private repo and point `JENKINS_MCP_KB_DIR` (or `knowledge.dir`) at its checkout.
Format and field reference: [knowledge/README.md](knowledge/README.md). Start new entries from `knowledge/TEMPLATE.md`.
## Development
See [tests/README.md](tests/README.md). Adding a tool: write a function with
`@mcp.tool(annotations=READ_ONLY)` in a module under `src/jenkins_mcp/tools/` (for a new module also import
it in `tools/__init__.py`), and add a test to `tests/test_tools.py`.
## Notes and limits
- Multibranch branch names containing `/` are addressed with `%2F` (as `list_jobs` shows them).
- Stage line ranges come from console markers; output of parallel branches can interleave
(`get_stage_log exact=true` reads the stage step by step from wfapi instead).
- Builds still running are not cached on disk; their log is fetched incrementally (only the new part).
- Downstream builds are recognised from the console line `Starting building: <display name> #N`. If a job
has a custom display name, it cannot be resolved by that name, and the summary says so.
- Logs larger than `JENKINS_MCP_MAX_LOG_MB` keep the first 20 % and the end.
- Log content is untrusted: a log line can contain text that looks like an instruction.
The server cannot change Jenkins, so such text cannot trigger actions through it.
## Security
See [SECURITY.md](SECURITY.md) for the threat model and how to report a vulnerability.
## License
[MIT](LICENSE) © 2026 Telman Huseynli
## Roadmap
- Release 1.1.0: the live Docker test (Jenkins 2.580.1) and a manual Windows / VS Code Copilot run against real
instances passed. Set the package status back to `5 - Production/Stable`, turn `Unreleased` in CHANGELOG.md into
`1.1.0`, tag `v1.1.0` and publish a GitHub release.
- Replace the module-level singletons (`config`, knowledge base cache, log LRU, job tree cache) with one application
context object, and pass log rules into `logs/` instead of reading the global config there.
- CI pipeline on Windows: ruff, mypy, pytest.
- Read-only plugin tools: installed plugins and versions, plugins with problems (missing dependencies), available
updates. `/pluginManager/api/json?depth=1` usually needs Overall/SystemRead or Administer: check it with a normal
user's token first; without that permission the tools would only return 403.
- More read tools: views, node env/PATH (system info, needs admin), Jenkinsfile and JCasC history from
Git (Bitbucket).
- Optional HTTP transport for one shared team server (needs authentication / a gateway).
- Human-in-the-loop write actions (not implemented): `plan_*` tool -> one-time token ->
human confirms -> `execute_plan` with a separate service account and an allow-list.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues