loading…
Search for a command to run...
loading…
Enables triggering Jenkins release and patch pipelines through natural language, with support for MCP and web interfaces.
Enables triggering Jenkins release and patch pipelines through natural language, with support for MCP and web interfaces.
A small Python service that wraps your private Jenkins controller and lets a team
trigger the NetLinQ EMS Release pipeline and Patch Single Repository Pipeline jobs
through natural language. One codebase, two run modes:
docker compose up on an internal
server, the whole team logs in via browser and gets the same tools.Hosting note: GitHub-hosted runners cannot reach a private Jenkins. The code lives in a private GitHub repo; the runtime runs wherever it has a network path to Jenkins (a teammate's laptop with VPN, or an internal Linux VM).
flowchart LR
subgraph github [Private GitHub Repo]
repo[netlinq-jenkins-mcp]
end
subgraph local [Local laptop - DevOps user]
cursor[Cursor IDE]
mcp["FastMCP stdio server<br/>mcp_server.py"]
cursor -->|stdio| mcp
end
subgraph shared [Internal VM - team]
web["FastAPI web app<br/>web.py + Vite UI"]
chat["Chat UI - browser"]
chat -->|HTTPS basic auth| web
end
subgraph core [Shared Python core]
tools["tools.py<br/>5 tool functions"]
llm["llm.py<br/>LiteLLM router"]
jc["jenkins_client.py<br/>httpx + crumb"]
end
repo -.git clone.-> local
repo -.git clone.-> shared
mcp --> tools
web --> llm
web --> tools
llm -->|"tool calls"| tools
tools --> jc
jc -->|REST + basic auth| jenkins[(Jenkins<br/>private network)]
tools.py is the single source of truth. Both the MCP server and the LiteLLM
agent in the web app call into the same five functions, so behavior is identical
between Cursor and the team chat UI.
The five tools:
| Tool | What it does |
|---|---|
trigger_release_build(release_tag, ...) |
Queues NetLinQ EMS Release pipeline for a tag like 7.0. Optional: release_name, branch_name, release_mode, source_tag, target_tag, repository_selection, selected_repositories, dry_run. |
patch_repository(repository, release_tag, ...) |
Queues Patch Single Repository Pipeline for one repo at an existing tag. repository is auto-prefixed with blinqnet/ if you give a short name. |
get_build_status(pipeline, build_number?) |
Latest or specific build's result, duration, parameters |
list_recent_builds(pipeline, limit?) |
History (newest first) |
tail_build_log(pipeline, build_number, n_lines?) |
Last N lines of console output |
Full walkthrough: docs/CURSOR_MCP.md. Short version:
Generate a Jenkins API token at <JENKINS_URL>/me/configure -> Add new Token.
Install uv: pipx install uv
Edit ~/.cursor/mcp.json (Windows: %USERPROFILE%\.cursor\mcp.json):
{
"mcpServers": {
"netlinq-jenkins": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/RadhaKrishna0018/netlinq-jenkins-mcp.git@main",
"netlinq-jenkins-mcp"
],
"env": {
"JENKINS_URL": "http://192.168.168.216:8080",
"JENKINS_USER": "radhakrishna",
"JENKINS_TOKEN": "your-fresh-api-token",
"REPO_PREFIX": "blinqnet"
}
}
}
}
Restart Cursor. Look for the green dot next to netlinq-jenkins in Settings -> MCP.
In the chat, try: "build 7.0 release package". The agent will confirm before actually triggering Jenkins.
git clone [email protected]:<your-org>/netlinq-jenkins-mcp.git
cd netlinq-jenkins-mcp
cp .env.example .env
# edit .env: JENKINS_*, LLM_*, WEB_USERS
# Create at least one web user. The hash MUST be bcrypt-hashed.
python -c "from passlib.hash import bcrypt; print('alice:' + bcrypt.hash('secret123'))"
# paste the line into WEB_USERS=
docker compose up -d --build
# browse http://<host>:8000 - log in with alice / secret123
What the team sees:
# Python side
python -m venv .venv
.\.venv\Scripts\Activate.ps1 # PowerShell
# or: source .venv/bin/activate # bash
pip install -e ".[dev]"
# Frontend side (only needed for the web mode)
cd ui
npm install
npm run build # writes ui/dist/, which web.py auto-serves
cd ..
# Run the web app
netlinq-jenkins-web
# or, with auto-reload:
uvicorn netlinq_jenkins.web:create_app --factory --reload --port 8000
# Or run as MCP (stdio - the way Cursor will spawn it)
netlinq-jenkins-mcp
# Run tests
pytest
All settings come from environment variables (or a .env file).
See .env.example for the canonical list.
| Variable | Default | Purpose |
|---|---|---|
JENKINS_URL |
required | Base URL of the Jenkins controller |
JENKINS_USER |
required | Service-account username |
JENKINS_TOKEN |
required | API token (preferred) or password |
JENKINS_CA_BUNDLE |
empty | Path to a CA bundle for self-signed TLS, or false to skip verification |
RELEASE_PIPELINE_NAME |
NetLinQ EMS Release pipeline |
Override if your release job is renamed |
PATCH_PIPELINE_NAME |
Patch Single Repository Pipeline |
Override if your patch job is renamed |
RELEASE_TAG_PARAM / RELEASE_NAME_PARAM / BRANCH_NAME_PARAM |
RELEASE_TAG / RELEASE_NAME / BRANCH_NAME |
Shared parameter names |
RELEASE_MODE_PARAM / SOURCE_TAG_PARAM / TARGET_TAG_PARAM |
RELEASE_MODE / SOURCE_TAG / TARGET_TAG |
Release pipeline-only |
REPOSITORY_SELECTION_PARAM / SELECTED_REPOSITORIES_PARAM / DRY_RUN_PARAM |
REPOSITORY_SELECTION / SELECTED_REPOSITORIES / DRY_RUN |
Release pipeline-only |
PATCH_REPOSITORY_PARAM |
REPOSITORY |
Patch pipeline-only |
REPO_PREFIX |
blinqnet |
Auto-prepended to short repo names. Empty to disable. |
DEFAULT_RELEASE_NAME_TEMPLATE |
{tag} Release Package |
Default RELEASE_NAME if user does not specify one |
DEFAULT_PATCH_NAME_TEMPLATE |
{tag} Patch ({repo}) |
Same, for patch pipeline |
LLM_PROVIDER |
openai |
Informational - LiteLLM picks based on LLM_MODEL |
LLM_MODEL |
gpt-4o |
Any LiteLLM-supported model string |
LLM_API_KEY |
- | Provider key (web mode only) |
LLM_API_BASE |
- | For Azure / Ollama / self-hosted endpoints |
WEB_HOST |
0.0.0.0 |
FastAPI bind host |
WEB_PORT |
8000 |
FastAPI bind port |
WEB_USERS |
empty | user1:bcrypt-hash,user2:bcrypt-hash for web Basic auth |
WEB_API_SHARED_SECRET |
- | Optional X-API-Secret header value for /api/* |
AUDIT_LOG_PATH |
audit.jsonl |
JSONL file every tool call is appended to |
The defaults match the live NetLinQ jobs as of May 2026:
RELEASE_MODE, RELEASE_NAME, RELEASE_TAG, BRANCH_NAME,
SOURCE_TAG, TARGET_TAG, REPOSITORY_SELECTION, SELECTED_REPOSITORIES, DRY_RUN.RELEASE_NAME, RELEASE_TAG, BRANCH_NAME, REPOSITORY.If your jobs change later, ask Jenkins for the current shape:
$pair = "$($env:JENKINS_USER):$($env:JENKINS_TOKEN)"
$auth = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))
Invoke-RestMethod "$env:JENKINS_URL/job/NetLinQ%20EMS%20Release%20pipeline/api/json?tree=property[parameterDefinitions[name,type,choices,defaultParameterValue[value]]]" -Headers @{Authorization=$auth} | ConvertTo-Json -Depth 6
Then override the relevant *_PARAM env var in .env or in your Cursor mcp.json.
The web mode uses LiteLLM so you can swap providers purely by env var. Common combos:
| Provider | LLM_MODEL |
LLM_API_KEY |
LLM_API_BASE |
|---|---|---|---|
| OpenAI | gpt-4o |
sk-... |
- |
| Anthropic | claude-sonnet-4-5 |
sk-ant-... |
- |
| Azure OpenAI | azure/<deployment> |
Azure key | https://<resource>.openai.azure.com |
| Ollama (local) | ollama/llama3.1 |
- | http://localhost:11434 |
| OpenAI-compatible | openai/<model> |
key | https://your.host/v1 |
The MCP-in-Cursor mode does not need any of this - Cursor's own model drives the conversation and just calls our tools.
.env is git-ignored - secrets never leave the host.WEB_USERS).WEB_API_SHARED_SECRET adds a header-based second factor on /api/*,
meant for "behind a reverse proxy" deployments.version, repo, tag) before any
HTTP call goes out, so a chatty LLM cannot smuggle shell metacharacters.Every successful trigger writes a JSONL line to ${AUDIT_LOG_PATH}:
{"ts": "2026-05-06T20:30:11+00:00", "event": "trigger",
"pipeline": "NetLinQ EMS Release pipeline",
"parameters": {"VERSION": "7.0"},
"queue_url": "https://jenkins.internal.example.com/queue/item/812/"}
In Docker mode the file is bind-mounted at ./logs/audit.jsonl on the host.
netlinq-jenkins-mcp/
├── src/netlinq_jenkins/
│ ├── config.py # pydantic-settings
│ ├── jenkins_client.py # async httpx wrapper, crumb handling
│ ├── tools.py # 5 tool functions, used by both modes
│ ├── llm.py # LiteLLM tool-calling agent (web mode only)
│ ├── mcp_server.py # FastMCP stdio entrypoint (Cursor)
│ └── web.py # FastAPI app + serves the bundled UI
├── ui/ # Vite + React + Tailwind chat UI
│ ├── src/App.tsx # main chat layout
│ └── src/components/ # ToolCard, BuildsPanel
├── tests/ # pytest + pytest-httpx
├── docs/CURSOR_MCP.md # detailed Cursor integration guide
├── examples/cursor-mcp.json
├── Dockerfile # multi-stage: builds UI, then Python wheel
├── docker-compose.yml
├── .env.example
└── pyproject.toml
/build 7.0 slash commands into the same tools.READ_ONLY=true) that disables the trigger tools.Add this to claude_desktop_config.json and restart Claude Desktop.
{
"mcpServers": {
"netlinq-jenkins-mcp": {
"command": "npx",
"args": []
}
}
}