Agentic service that translates CI/CD pipeline definitions between Jenkins, GitLab CI, and GitHub Actions using a pluggable LLM (Gemini, OpenAI, Azure OpenAI, GitHub Models, Anthropic, or Ollama — chosen via
LLM_PROVIDER), LangGraph memory, and an MCP (Model Context Protocol) tool surface.
- Highlights
- Architecture
- Sequence diagrams
- Quick start (Makefile)
- How to use
- Configuration
- API reference
- MCP server
- Adding a new provider
- Production deployment
- Testing
- Security
- Project layout
- Further docs
- License
- Three-step agentic workflow (analyse → translate → validate) implemented in LangGraph.
- Jenkins Declarative and Scripted pipelines — the agent classifies the style during the
analysestep and faithfully translates bothpipeline { ... }blocks and free-form Groovy (@Library,defhelpers,parallel(...),try/catch/finally,properties([...]),withCredentials,currentBuild.result, etc.). - Pluggable provider registry — add Bitbucket Pipelines, CircleCI, Azure DevOps, etc., without touching core code (entry-point discovery supported).
- Dual transports — FastAPI REST API and an MCP server exposing the same translation tools to Claude, Copilot, Cursor, etc.
- Pluggable persistence — SQLite for local dev, PostgreSQL for production (LangGraph checkpoints).
- RBAC — JWT auth with
admin/translator/viewerroles. - Production-ready container — multi-stage Dockerfile, non-root user, healthcheck, resource limits.
- One-command local dev via Makefile.
flowchart LR
subgraph Clients
UI[Web / CLI]
MCPCLI[MCP Client<br/>Claude · Cursor · Copilot]
end
subgraph App["cicd-translator (FastAPI + MCP)"]
AUTH[/Auth Router<br/>JWT + RBAC/]
TRANS[/Translation Router/]
MCP[/MCP Server<br/>/mcp/]
WF[LangGraph Workflow<br/>analyse → translate → validate]
REG[Provider Registry]
end
subgraph Providers
JEN[Jenkins REST]
GL[GitLab API]
GH[GitHub API]
EXT[3rd-party<br/>entry-points]
end
subgraph Infra
PG[(PostgreSQL<br/>LangGraph<br/>checkpoints)]
GEM[LLM API<br/>Gemini · OpenAI · Azure · GH Models · Anthropic · Ollama]
end
UI -->|HTTPS + JWT| AUTH
UI -->|HTTPS + JWT| TRANS
MCPCLI -->|MCP/SSE| MCP
TRANS --> WF
MCP --> WF
WF -->|LLM calls| GEM
WF -->|persist state| PG
TRANS --> REG
MCP --> REG
REG --> JEN
REG --> GL
REG --> GH
REG --> EXT
| Layer | Module | Responsibility |
|---|---|---|
| API | api/auth_router.py |
Login, user CRUD |
| API | api/translation_router.py |
Translate inline / fetch-and-translate / push |
| MCP | mcp_server.py |
translate_pipeline, fetch_pipeline, fetch_and_translate tools |
| Agent | agent/workflow.py |
3-node LangGraph: analyse → translate → validate |
| Agent | agent/checkpointer.py |
SQLite / Postgres checkpoint factory |
| Providers | providers/registry.py |
Pluggable provider registry (+ entry-point discovery) |
| Providers | providers/{jenkins,gitlab_provider,github_provider}.py |
Built-in connectors |
| Auth | auth/security.py |
JWT, password hashing, RBAC dependencies |
| Config | config.py |
pydantic-settings environment loader |
sequenceDiagram
autonumber
participant C as Client
participant API as FastAPI /auth/token
participant SEC as auth.security
C->>API: POST username + password (form)
API->>SEC: authenticate_user()
SEC-->>API: user dict (or None)
alt invalid credentials
API-->>C: 401 Unauthorized
else valid
API->>SEC: create_access_token(role)
SEC-->>API: signed JWT
API-->>C: 200 { access_token, token_type }
end
sequenceDiagram
autonumber
participant C as Client
participant API as Translation Router
participant WF as LangGraph Workflow
participant CP as Checkpointer<br/>(SQLite / Postgres)
participant LLM as LLM (chosen via LLM_PROVIDER)
C->>API: POST pipeline_content + source/target + JWT
API->>API: require_translator (RBAC)
API->>WF: run_translation(request)
WF->>CP: open checkpoint store
WF->>LLM: analyse_node(source pipeline)
LLM-->>WF: structured analysis
WF->>LLM: translate_node(messages)
LLM-->>WF: candidate translation
WF->>LLM: validate_node(candidate)
LLM-->>WF: LGTM or corrected output
WF->>CP: persist final state (thread_id)
WF-->>API: TranslationResponse
API-->>C: 200 { thread_id, translated_pipeline, notes }
sequenceDiagram
autonumber
participant C as Client
participant API as Translation Router
participant REG as Provider Registry
participant SRC as Source Provider
participant WF as LangGraph Workflow
participant TGT as Target Provider
C->>API: POST source+target+repo+JWT
API->>REG: get_provider(source)
REG-->>API: SourceProvider
API->>SRC: fetch_pipeline(repo, ref, path)
SRC-->>API: raw pipeline file
API->>WF: run_translation(...)
WF-->>API: translated_pipeline
API->>REG: get_provider(target)
REG-->>API: TargetProvider
API->>TGT: push_pipeline(repo, branch, path, content)
TGT-->>API: commit URL
API-->>C: 200 { thread_id, pushed_to, notes }
sequenceDiagram
autonumber
participant M as MCP Client
participant SRV as FastApiMCP (/mcp)
participant T as POST /api/v1/translate (auto-exposed MCP tool)
participant WF as LangGraph Workflow
participant LLM as LLM (chosen via LLM_PROVIDER)
M->>SRV: tools/call translate_pipeline_api_v1_translate__post { args }
SRV->>T: dispatch
T->>WF: run_translation(request)
WF->>LLM: 3-step agentic loop
LLM-->>WF: validated output
WF-->>T: TranslationResponse
T-->>SRV: dict
SRV-->>M: MCP result
# 1. Create .env from template and pick an LLM
make env
$EDITOR .env # set LLM_PROVIDER + the matching API key + APP_SECRET_KEY
# 2. Install (creates .venv, installs project + dev deps)
make install
# 3. Run unit tests
make test
# 4. One-shot end-to-end test against a live LLM call (SQLite checkpoints)
make smoke
# 5. Or: end-to-end test against a Postgres checkpoint store
make smoke-postgresOther useful targets:
| Target | Description |
|---|---|
make run |
Run API in dev mode with hot reload |
make run-prod |
Run API in production mode (2 workers) |
make serve-bg / make stop |
Background server + stop |
make e2e |
Run the live-LLM E2E script against $BASE_URL |
make db-up / make db-down / make db-psql |
Manage local Postgres via docker compose |
make docker-up / make docker-down |
Dev stack in containers (API + Postgres) |
make docker-prod-up / make docker-prod-down |
Production stack |
make lint |
ruff + mypy |
make clean |
Remove caches |
Run make help for the complete list.
This project is an agentic CI/CD pipeline translator. The typical flow is:
- You point it at (or send it the content of) a Jenkinsfile /
.gitlab-ci.yml/ GitHub Actions workflow. - A 3-step LangGraph agent (analyse → translate → validate) executes against the configured LLM provider (Gemini by default) with persistent memory.
- You get back an idiomatic target-platform pipeline, plus reviewer notes.
Three usage modes:
| Mode | Best for | Entry point |
|---|---|---|
| REST API | CI scripts, CLI, web UI | POST /api/v1/translate/* |
| MCP | Claude · Cursor · Copilot · custom agents | http://localhost:8000/mcp |
| End-to-end smoke (declarative) | Quick demo / regression | make smoke (uses sample/Jenkinsfile) |
| End-to-end smoke (scripted) | Verify scripted Groovy support | make smoke-scripted (uses sample/Jenkinsfile.scripted) |
A realistic Jenkins pipeline is included at sample/Jenkinsfile. After make smoke, the translated GitHub Actions workflow appears at:
sample/out/translated.github.yml
For full step-by-step recipes (REST, MCP, iterative thread_id conversations, Docker) see docs/USAGE.md.
For an in-depth explanation of the agentic graph and how to extend it see docs/AGENTIC_FLOW.md.
Configuration is loaded from environment variables (or .env for local dev) by pydantic-settings. See .env.example.
| Variable | Default | Notes |
|---|---|---|
APP_ENV |
development |
development enables CORS *, reload |
APP_SECRET_KEY |
— (required, ≥32 chars) | JWT signing key |
ACCESS_TOKEN_EXPIRE_MINUTES |
60 |
|
CORS_ALLOW_ORIGINS |
empty | Comma-separated, used when APP_ENV != development |
LLM_PROVIDER |
gemini |
gemini · openai · azure_openai · github_models · anthropic · ollama |
GEMINI_API_KEY / GEMINI_MODEL |
— / gemini-2.5-pro |
Required when LLM_PROVIDER=gemini |
OPENAI_API_KEY / OPENAI_MODEL / OPENAI_BASE_URL |
— / gpt-4o / empty |
Required when LLM_PROVIDER=openai; set OPENAI_BASE_URL for OpenAI-compatible servers |
AZURE_OPENAI_API_KEY / AZURE_OPENAI_ENDPOINT / AZURE_OPENAI_DEPLOYMENT / AZURE_OPENAI_API_VERSION |
— / — / — / 2024-10-21 |
Required when LLM_PROVIDER=azure_openai |
GITHUB_MODELS_TOKEN / GITHUB_MODELS_MODEL / GITHUB_MODELS_BASE_URL |
— / openai/gpt-4o-mini / https://models.github.ai/inference |
GitHub Models marketplace; falls back to GITHUB_TOKEN |
ANTHROPIC_API_KEY / ANTHROPIC_MODEL |
— / claude-sonnet-4-5-20250929 |
Required when LLM_PROVIDER=anthropic |
OLLAMA_BASE_URL / OLLAMA_MODEL |
http://localhost:11434 / llama3.1 |
Local / self-hosted |
JENKINS_URL / JENKINS_USERNAME / JENKINS_TOKEN |
empty | Only needed when using Jenkins fetch |
GITLAB_URL / GITLAB_TOKEN |
https://gitlab.com / empty |
|
GITHUB_TOKEN |
empty | |
CHECKPOINT_BACKEND |
sqlite |
sqlite or postgres |
CHECKPOINT_DB_PATH |
./data/checkpoints.db |
Used when backend = sqlite |
CHECKPOINT_POSTGRES_URL |
empty | e.g. postgresql://cicd:cicd@localhost:5432/cicd_translator |
DATABASE_URL |
sqlite | Reserved for future app-data storage |
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB / POSTGRES_PORT |
cicd / — / cicd_translator / 5432 |
Used by docker compose |
HOST / PORT / UVICORN_WORKERS / LOG_LEVEL |
0.0.0.0 / 8000 / 1 / info |
Server runtime |
OpenAPI/Swagger UI: http://localhost:8000/docs
ReDoc: http://localhost:8000/redoc
MCP endpoint: http://localhost:8000/mcp
Healthcheck: GET /health
| Method | Path | Body | Returns |
|---|---|---|---|
POST |
/api/v1/auth/token |
form: username, password |
{ access_token, token_type } |
POST |
/api/v1/auth/users |
{ username, password, role } (admin) |
UserOut |
GET |
/api/v1/auth/me |
— | UserOut |
Default user: admin / changeme123! (change immediately).
| Method | Path | Purpose |
|---|---|---|
POST |
/api/v1/translate/ |
Translate inline pipeline content |
POST |
/api/v1/translate/from-provider |
Fetch from source provider, then translate |
POST |
/api/v1/translate/push |
Translate, then commit translated file to target provider |
TOKEN=$(curl -sX POST http://localhost:8000/api/v1/auth/token \
-d 'username=admin&password=changeme123!' | jq -r .access_token)
curl -sX POST http://localhost:8000/api/v1/translate/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_provider": "jenkins",
"target_provider": "github",
"pipeline_content": "pipeline { agent any; stages { stage(\"build\") { steps { sh \"make\" } } } }"
}' | jq .The same translation capabilities are exposed over the Model Context Protocol at /mcp using fastapi-mcp. Three tools are available:
| Tool | Inputs | Description |
|---|---|---|
translate_pipeline |
source_provider, target_provider, pipeline_content, thread_id? |
Translate inline content |
fetch_pipeline |
provider, repo_or_job, ref?, pipeline_path? |
Return raw pipeline file |
fetch_and_translate |
as above + target_provider |
Fetch + translate in one call |
Point an MCP client (Claude Desktop, Cursor, etc.) at http://<host>:8000/mcp.
The provider system is fully pluggable. To add (for example) Bitbucket Pipelines:
-
Implement
BaseProvider:from cicd_translator.providers.base import BaseProvider class BitbucketProvider(BaseProvider): async def fetch_pipeline(self, repo_or_job, ref, pipeline_path): ... async def push_pipeline(self, repo, branch, pipeline_path, content, message): ...
-
Register it — either in-process:
from cicd_translator.providers.registry import register_provider register_provider("bitbucket", BitbucketProvider)
…or via a
pyproject.tomlentry point in your own package (auto-discovered):[project.entry-points."cicd_translator.providers"] bitbucket = "my_pkg.bitbucket:BitbucketProvider"
-
Extend
CICDProvideronly if you want strict Pydantic validation for the new value — the registry itself accepts any string name.
That's it: the new provider becomes usable through both the REST API and MCP tools.
cp .env.example .env
# Edit .env — at minimum set:
# APP_SECRET_KEY (>= 32 random chars)
# LLM_PROVIDER + the matching API key (e.g. GEMINI_API_KEY / OPENAI_API_KEY / ...)
# POSTGRES_PASSWORD
# CORS_ALLOW_ORIGINS
docker compose -f docker-compose.prod.yml --env-file .env up -d
docker compose -f docker-compose.prod.yml logs -f cicd-translatorThe production stack provides:
- Multi-stage build → small image, no build toolchain at runtime
- Non-root
appuser (UID 10001) - HTTP healthcheck on
/health - 4 uvicorn workers,
proxy_headers=true(place behind nginx/Traefik/ALB for TLS) - Restart policy
always, CPU/memory limits, rotated JSON logs - Persistent Postgres volume for LangGraph checkpoints
Terminate TLS upstream and forward to port 8000. Recommended headers:
X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host.
The Docker image is plain python -m cicd_translator. A minimal Deployment + Service + Secret (env) + managed Postgres (RDS / Cloud SQL / Neon) is sufficient. Configure readinessProbe and livenessProbe at /health.
Not strictly — any container runtime (Kubernetes, ECS, Cloud Run, Fly.io, Render, App Service) works because the image is a self-contained ASGI app. Docker compose is the recommended baseline because it co-provisions Postgres, sets sensible defaults (healthchecks, restart policy, log rotation, resource limits), and is reproducible across environments.
make test # unit tests (httpx ASGI transport, no external calls)
make smoke # e2e — live LLM call via SQLite checkpoint
make smoke-postgres # e2e — live LLM call via Postgres checkpointThe scripts/e2e_test.py script performs:
- Boots the FastAPI app in-process via the
httpxASGI transport (no real HTTP server required). - Authenticates as the bootstrapped admin to obtain a JWT.
- POSTs the contents of a Jenkins pipeline (
sample/Jenkinsfilefor declarative,sample/Jenkinsfile.scriptedfor scripted) to/api/v1/translate. - Asserts the response is a valid GitHub Actions workflow (
name:+jobs:keys) and writes it tosample/out/. - Exits non-zero on any assertion or upstream LLM failure, so it is safe to run in CI.
.
├── README.md
├── LICENSE
├── Dockerfile
├── docker-compose.yml Local dev stack (API + Postgres)
├── docker-compose.prod.yml Production stack (hardened)
├── Makefile Dev + e2e workflow
├── pyproject.toml Project metadata + deps
├── .env.example Reference configuration
├── sample/ Reference pipelines used by `make smoke`
│ ├── Jenkinsfile Mid-complexity Jenkins declarative pipeline
│ ├── Jenkinsfile.scripted Scripted Groovy pipeline
│ ├── .gitlab-ci.yml GitLab CI equivalent
│ └── README.md
├── docs/ Long-form documentation
│ ├── HLD.md High-level design (C4 context + containers + sequences)
│ ├── AGENTIC_FLOW.md How the LangGraph workflow works
│ ├── PROJECT_LOGIC.md LangGraph + LLM + Postgres deep dive
│ └── USAGE.md Step-by-step usage recipes
├── scripts/
│ └── e2e_test.py Live end-to-end smoke test
├── src/cicd_translator/
│ ├── __main__.py `python -m cicd_translator` entrypoint
│ ├── main.py FastAPI app factory + MCP mount
│ ├── config.py pydantic-settings configuration
│ ├── schemas.py Pydantic models + enums
│ ├── mcp_server.py Standalone FastMCP tool definitions (legacy)
│ ├── agent/
│ │ ├── workflow.py 3-node LangGraph workflow
│ │ ├── llm.py Pluggable LLM factory (LLM_PROVIDER)
│ │ └── checkpointer.py SQLite / Postgres checkpointer factory
│ ├── api/
│ │ ├── auth_router.py JWT login + user CRUD
│ │ └── translation_router.py
│ ├── auth/security.py JWT + RBAC
│ └── providers/
│ ├── base.py BaseProvider ABC
│ ├── registry.py Pluggable provider registry
│ ├── factory.py Backwards-compatible factory
│ ├── jenkins.py Jenkins (declarative + scripted) fetcher
│ ├── gitlab_provider.py
│ └── github_provider.py
└── tests/
└── test_api.py
- docs/HLD.md — system context, container view, deployment topology, runtime sequence diagrams.
- docs/AGENTIC_FLOW.md — the LangGraph workflow explained (state, nodes, prompt, tuning, extension).
- docs/PROJECT_LOGIC.md — deep dive into how LangGraph, the LLM layer, and Postgres interact.
- docs/USAGE.md — install, REST API, MCP, sample translation, iterative
thread_idusage, Docker, troubleshooting. - sample/README.md — pipeline fixtures used by
make smokeandmake smoke-scripted.
Contributions are welcome. To propose a change:
- Fork the repository and create a feature branch:
git checkout -b feat/<short-description>. - Set up the dev environment:
python -m venv .venv && source .venv/bin/activate && pip install -e .. - Make your change following the existing layout
(
src/cicd_translator/...). Keep public APIs typed; add or update a provider inproviders/via the registry, not by editing the factory. - Verify locally before pushing:
pytest -q— unit tests must pass.make smokeand/ormake smoke-scripted— end-to-end translation against a configuredLLM_PROVIDER.docker compose -f docker-compose.prod.yml config -qif you touched the prod compose file.
- Commit with a clear, imperative subject line
(e.g.
feat(providers): add CircleCI provider). Reference an issue when one exists. - Open a PR describing the motivation, the change, and how you
validated it. Do not commit secrets —
.envis gitignored; use.env.exampleto document new settings.
Bug reports and feature requests are tracked on GitHub Issues.
This project is licensed under the MIT License — see LICENSE for the full text.