gitlab-ci-mcp
An MCP server for GitLab CI/CD that lets an LLM agent inspect and operate a project's pipelines, jobs, schedules, branches, merge requests, and repository files.
Pipelines — list, inspect, trigger, retry, or cancel pipelines; list pipeline jobs; fetch job logs (with tail and regex grep filtering).
Pipeline health — aggregate 7/30-day success rates and trend for a branch.
Schedules — list, create, update, and delete CI/CD schedules (secret values redacted in listings).
Branches & tags — list branches/tags and compare two refs (commits + changed files).
Merge requests — list, get details, view changed files/diffs, create, and merge MRs.
Repository & project — read files, list the repository tree, and fetch project metadata.
Extras — structured output/errors, pagination, per-tool annotations, and opt-in update checking.
Provides tools for interacting with GitLab CI/CD pipelines, jobs, schedules, branches, tags, merge requests, and repository files, enabling AI agents to manage GitLab projects programmatically.
gitlab-ci-mcp
MCP server for GitLab CI/CD. Lets an LLM agent (Claude Code, Cursor, OpenCode, DevX Agent, etc.) work with pipelines, jobs, schedules, branches, tags, merge requests and repository files.
Python and FastMCP. Local stdio remains the default; opt-in Streamable HTTP supports shared deployments with one GitLab PAT per caller.
Works with any GitLab — SaaS gitlab.com or self-hosted / on-prem. Designed with corporate networks in mind: configurable NO_PROXY handling, optional SSL-verify toggle, per-project scoping via env vars.
Design highlights
Tool annotations — every tool carries
readOnlyHint/destructiveHint/idempotentHint/openWorldHintso MCP clients can classify operations (e.g. ask for confirmation only on destructive ones likegitlab_merge_mr,gitlab_delete_schedule).Structured output on every tool — each tool declares a TypedDict return type, so FastMCP auto-generates an
outputSchemaand every result carriesstructuredContentalongside a pre-rendered markdown text block. Clients that can render structured data use it; agents that prefer compact text get the markdown. Noresponse_formatparameter needed.Structured errors — authentication, 404, 403, 429 (rate-limit), 5xx, missing-env errors are converted to actionable
ToolErrormessages (e.g. "GitLab authentication failed… verify GITLAB_TOKEN hasapiscope") and surfaced asisError=Trueresults.Pydantic input validation — every argument has typed constraints (ranges, lengths, literals) auto-exposed as JSON Schema.
Explicit project scoping — every GitLab tool requires
project_pathby default. SetGITLAB_ALLOW_DEFAULT_PROJECT=trueto opt into the legacy environment fallback.Pagination — list tools return a
paginationblock withpage,total,has_more,next_pageand a next-page hint in the markdown footer.Per-user HTTP credentials — in Streamable HTTP mode every request must carry
X-GitLab-Token; GitLab API calls and cached clients are isolated by PAT. Stdio remains single-user withGITLAB_TOKEN.MCP Resources —
gitlab://project/infoandgitlab://project/ci-configmirror common lookups for clients that prefer the Resource model over tools.Lifespan management —
python-gitlabHTTP sessions are closed cleanly on server shutdown via anasynccontextmanagerlifespan hook.Log grep —
gitlab_get_job_logacceptsgrep_pattern+grep_context(surrounding lines) for regex-filtering megabyte-scale CI logs without pulling the whole trace into agent context.
Threading model
FastMCP automatically runs synchronous tools in a worker thread
(anyio.to_thread.run_sync), so they do not block the asyncio event loop —
python-gitlab is a synchronous library and wrapping every call in
asyncio.to_thread ourselves would be ceremony. Tools that benefit from the
MCP Context (progress, info logs) are written as async def and explicitly
wrap python-gitlab calls with asyncio.to_thread.
Related MCP server: gitlab-mcp
Features
24 tools covering the everyday CI/CD surface:
Pipelines
gitlab_list_pipelines · gitlab_get_pipeline · gitlab_get_pipeline_jobs · gitlab_get_job_log · gitlab_trigger_pipeline · gitlab_retry_pipeline · gitlab_cancel_pipeline · gitlab_pipeline_health
Schedules
gitlab_list_schedules · gitlab_create_schedule · gitlab_update_schedule · gitlab_delete_schedule
Branches & tags
gitlab_list_branches · gitlab_list_tags · gitlab_compare_branches
Merge requests
gitlab_list_merge_requests · gitlab_get_merge_request · gitlab_get_merge_request_changes · gitlab_create_merge_request · gitlab_merge_mr
Repository & project
gitlab_get_file · gitlab_list_repository_tree · gitlab_project_info
Package
gitlab_ci_mcp_check_updates
Pipeline health report
gitlab_pipeline_health returns a ready-to-read summary over 7/30 days:
Last 7d: 96.4% up | 27/28 success
Last 30d: 92.1% | 105/114 success
Last 10: success success success failed success ...Handy for on-call / triage: покажи health master за последние 7 дней.
Installation
Requires Python 3.10+.
# via uvx (recommended)
uvx --from gitlab-ci-mcp gitlab-ci-mcp
# or via pip/pipx
pipx install gitlab-ci-mcpConfiguration
All config is via environment variables:
Variable | Required | Description |
| yes | Base URL, e.g. |
| stdio only | Personal Access Token with |
| no |
|
| no | HTTP bind host; defaults to |
| no | HTTP bind port; defaults to |
| no | Legacy fallback project; used only with |
| no | Set to |
| no |
|
| no |
|
| no | Comma-separated domains to add to |
Every tool accepts a project_path arg. It is required by default so a call
cannot silently target an environment-wide project. For legacy deployments,
set GITLAB_ALLOW_DEFAULT_PROJECT=true together with GITLAB_PROJECT_PATH to
opt into the old fallback behavior.
Update checking is disabled by default. Set MCP_GITLAB_UPDATE_CHECK_ENABLED=true
to enable the explicit gitlab_ci_mcp_check_updates tool. When enabled, it
makes one bounded request to fixed PyPI JSON, caches a result for five minutes,
reports failures and unsupported versions, and never installs anything. No
startup or other tool call performs this request. If called while disabled,
the tool reports disabled without making a network request.
Pipeline health success rate uses completed pipelines only: success and
failed are in the denominator; running, pending, manual, canceled,
skipped, and other non-completed statuses are excluded. A missing comparison
period or a sample without completed pipelines is reported as insufficient
data, not as a zero rate or a flat trend.
Shared Streamable HTTP deployment
For a shared server, run with MCP_TRANSPORT=streamable-http. Each caller
must send their own GitLab PAT in X-GitLab-Token; the server never falls back
to the process GITLAB_TOKEN in this mode. GitLab applies that PAT's identity
and permissions to each API operation. The header is a custom credential
mechanism, not MCP OAuth; clients need support for configured custom headers.
The server binds to loopback (127.0.0.1:8000) by default. Expose it through
an HTTPS reverse proxy, restrict network access, and redact the PAT header from
proxy, tracing, and error logs. Initialize and tool discovery only check that
the header is non-empty; GitLab validates the PAT when an API call is made.
Example for Claude Code (provide GITLAB_PAT through a secret manager; do not
put a literal token in shell history):
claude mcp add --transport http \
--header "X-GitLab-Token: ${GITLAB_PAT}" \
gitlab https://mcp.example.com/mcpThe client must attach the header to every request. Each GitLab tool still
requires an explicit project_path. Fixed resources
gitlab://project/info and gitlab://project/ci-config have no project
argument; they require the explicit compatibility setting
GITLAB_ALLOW_DEFAULT_PROJECT=true and GITLAB_PROJECT_PATH.
Full credential details and limitations: docs/per-user-auth.md.
Claude Code
Full walkthrough: docs/claude-code.md — prerequisites, two install paths, multi-project setup, self-hosted GitLab behind a corp proxy, troubleshooting, uninstall.
Short version:
claude mcp add --transport stdio \
--env GITLAB_URL=https://gitlab.example.com \
--env GITLAB_TOKEN="$GITLAB_PAT" \
gitlab -- uvx --from gitlab-ci-mcp gitlab-ci-mcpEach GitLab tool call must include project_path, for example
project_path="my-org/my-repo". For legacy default-project behavior, also set
GITLAB_PROJECT_PATH and GITLAB_ALLOW_DEFAULT_PROJECT=true.
Or in ~/.claude.json / project .mcp.json:
{
"mcpServers": {
"gitlab": {
"type": "stdio",
"command": "uvx",
"args": ["--from", "gitlab-ci-mcp", "gitlab-ci-mcp"],
"env": {
"GITLAB_URL": "https://gitlab.example.com",
"GITLAB_TOKEN": "${GITLAB_PAT}",
"GITLAB_PROJECT_PATH": "my-org/my-repo",
"GITLAB_ALLOW_DEFAULT_PROJECT": "true",
"GITLAB_SSL_VERIFY": "true"
}
}
}
}Check:
claude mcp list
# gitlab: uvx --from gitlab-ci-mcp gitlab-ci-mcp - ✓ ConnectedCursor / OpenCode / DevX Agent
Same idea — point the MCP config to uvx --from gitlab-ci-mcp gitlab-ci-mcp with the env vars above. See each tool's own MCP config syntax.
Example prompts
что сломалось в последнем pipeline masterпокажи health master за 7 дней для проекта my-org/other-repoсоздай MR из feature/foo в master с title "feat: foo"покажи содержимое .gitlab-ci.yml из masterRate limits & connection reuse
GitLab enforces a per-user rate limit (typically 2000 req/h for the REST API,
configurable by the admin — see your instance's /admin/application_settings/network).
The server caches a bounded
python-gitlabHTTP session per GitLab URL, project, and PAT fingerprint. Different users never reuse one another's cached client.List tools default to
per_page=20to keep a single call within a small number of API requests.If you hit a
429 Too Many Requests, the error handler returns an actionable message — wait and try again with largerper_pageor fewer calls.
Self-hosted GitLab behind a corporate proxy
When your laptop has a local HTTP proxy (e.g. http://127.0.0.1:3128 for corp web access) but GitLab is on the intranet, the proxy intercepts and kills internal requests. Two options:
Set
GITLAB_NO_PROXY_DOMAINS— the server will add them toNO_PROXYat startup and clearHTTP_PROXY/HTTPS_PROXYfrom its own process so they don't affect GitLab traffic.Pass explicitly empty
HTTP_PROXY=""etc. in the MCPenvsection.
Development
git clone https://github.com/mshegolev/gitlab-ci-mcp
cd gitlab-ci-mcp
python -m venv .venv && . .venv/bin/activate
pip install -e '.[dev]'
pytestRun the server directly (stdio transport is the default and waits on stdin for MCP messages):
GITLAB_URL=... GITLAB_TOKEN=... gitlab-ci-mcpLicense
MIT — see LICENSE.
Acknowledgements
Built on python-gitlab and the MCP Python SDK.
Available Tools
24 toolsgitlab_cancel_pipelineADestructiveIdempotent
Cancel a running pipeline. In-flight jobs will be interrupted.
Destructive for in-progress work. Cancelling an already-finished pipeline is a no-op.
Examples:
- "Pipeline 123 is stuck, cancel it" → pipeline_id=123
- Don't use on finished pipelines — no effect; use gitlab_retry_pipeline
if you want to rerun it.
Inputs: pipeline_id (required), project_path (required). Returns: ref, status, web_url, created_at, +2 more
gitlab_ci_mcp_check_updatesBRead-onlyIdempotent
Check updates when enabled; defaults off and never installs or runs at startup.
Inputs: no named parameters. Returns: cached, detail, status, release_url, +4 more
gitlab_compare_branchesARead-onlyIdempotent
Compare two branches — returns up to 30 commits and the list of changed files.
Use for "what's in release/x.y vs master?" or for release-note drafting.
Examples:
- "What's new in release/1.5 vs master" → source='release/1.5', target='master'
- Don't use to fetch full diffs of an MR — use gitlab_get_merge_request_changes.
Inputs: source (required), project_path (required), target. Returns: source, target, commits, diffs_count, +2 more
gitlab_create_merge_requestA
Create a merge request from source_branch into target_branch.
Not idempotent: creates a new MR each call. Check existing MRs first
via gitlab_list_merge_requests if you want to avoid duplicates.
Examples:
- "Open an MR from feature/login to master" → source_branch='feature/login'
- "Open a WIP MR with a label" → title='Draft: ...', labels=['wip']
- Don't use to merge an already-open MR — use gitlab_merge_mr.
Inputs: project_path (required), source_branch (required), title, labels, +3 more. Returns: iid, hint, state, title, +6 more
gitlab_create_scheduleA
Create a new CI/CD schedule with the given cron and variables.
Not idempotent: duplicate calls create duplicate schedules with auto-incrementing IDs.
Examples:
- "Schedule a nightly build on master at 02:00 Europe/Berlin" →
description='Nightly build', cron='0 2 * * *', ref='master',
timezone='Europe/Berlin', variables={'NIGHTLY': '1'}
- Don't use to update existing schedules — use gitlab_update_schedule.
Inputs: cron (required), variables (required), description (required), project_path (required), +3 more. Returns: ref, cron, active, status, +2 more
gitlab_delete_scheduleADestructiveIdempotent
Delete a schedule by ID. Cannot be undone.
Examples:
- "Delete schedule 42" → schedule_id=42
- If you only want to pause it temporarily, call gitlab_update_schedule
with active=False instead.
Inputs: schedule_id (required), project_path (required). Returns: ref, cron, active, status, +2 more
gitlab_get_fileARead-onlyIdempotent
Read a text file from the repository, truncated to 500 lines.
For binaries, gets decoded as UTF-8 with errors replaced — you will likely get garbage; use for text content only.
Examples:
- "Show me .gitlab-ci.yml on master" → file_path='.gitlab-ci.yml'
- "Read src/app.py from the release-1.2 tag" → file_path='src/app.py', ref='release-1.2'
- Don't use for listings — use gitlab_list_repository_tree.
Inputs: file_path (required), project_path (required), ref. Returns: ref, size, content, file_path, +2 more
gitlab_get_job_logARead-onlyIdempotent
Fetch the trace/log of a job, with optional regex filter.
Two modes:
Default: return the last
taillines (token-efficient, good for "why did this just fail?").With
grep_pattern: return only matching lines withgrep_contextsurrounding lines on each side — ideal for finding "ERROR" / "Traceback" in megabyte-scale CI logs without pulling the whole trace into context.
Examples:
- "Why did job 789 fail" → default tail=100, look at the end of the log
- "Show me the first stage output of job 789" → tail=5000 and scan for stage separator
- "Find every Traceback in job 789" → grep_pattern='Traceback', grep_context=5
- "All ERROR lines from job 789" → grep_pattern='ERROR|FAIL'
Inputs: job_id (required), project_path (required), tail, grep_context, +1 more. Returns: log, job_id, truncated, bytes_read, +6 more
gitlab_get_merge_requestARead-onlyIdempotent
Get full information about a merge request by internal ID (iid).
Includes state, branches, author, assignees, reviewers, labels, conflict status, description and timestamps.
Examples:
- "Show me the description and state of !42" → mr_iid=42
- Don't use to see changed files — use gitlab_get_merge_request_changes.
Inputs: mr_iid (required), project_path (required). Returns: iid, state, title, author, +13 more
gitlab_get_merge_request_changesARead-onlyIdempotent
List changed files in a merge request with truncated diffs (2KB per file).
Useful for code-review-style queries ("what changed in !42?"). Diffs beyond
2KB are truncated — fetch the raw file via gitlab_get_file for full
content.
Examples:
- "What did MR !42 change" → mr_iid=42
- If you need full content of a changed file, use gitlab_get_file
with the MR's source branch.
Inputs: mr_iid (required), project_path (required). Returns: files, title, mr_iid, files_count, +4 more
gitlab_get_pipelineARead-onlyIdempotent
Get a single pipeline with full timing details.
Useful right after gitlab_list_pipelines — lists only return summaries.
Returns status, ref, source, durations (queued/total), and started/finished timestamps.
Examples:
- "Why was pipeline 123 slow" → check queued_duration and duration fields
- "Is pipeline 456 still running" → look at status
- Don't use to see individual jobs — use gitlab_get_pipeline_jobs.
Inputs: pipeline_id (required), project_path (required). Returns: id, ref, source, status, +7 more
gitlab_get_pipeline_jobsARead-onlyIdempotent
List jobs of a pipeline with stage, status, duration and web URL.
Use after noticing a failed pipeline to drill down into which specific job
broke and fetch its log via gitlab_get_job_log.
Examples:
- "What jobs are in pipeline 123" → pipeline_id=123
- "Which job failed in pipeline 456" → filter result by status='failed' client-side
- Don't use for overall pipeline status — use gitlab_get_pipeline instead.
Inputs: pipeline_id (required), project_path (required). Returns: jobs, jobs_count, pipeline_id
gitlab_list_branchesARead-onlyIdempotent
List branches of a project, optionally filtered by substring.
Includes default, protected and merged flags, and the short id
of the tip commit with its title and date.
Examples:
- "List all branches with 'release' in name" → search='release'
- "Next page of branches" → page=2
- Don't use when you want to check if a specific branch exists by exact name —
use gitlab_get_file on that ref and look at the error instead.
Inputs: project_path (required), page, search, per_page. Returns: count, project, branches, pagination
gitlab_list_merge_requestsARead-onlyIdempotent
List merge requests of a project, optionally filtered by state.
Examples:
- "What MRs are open right now" → default (state='opened')
- "What merged last week" → state='merged' then filter by updated_at client-side
- "Everything regardless of state" → state='all'
- Don't use when you have an MR IID — use gitlab_get_merge_request for detail.
Inputs: project_path (required), page, state, per_page. Returns: count, state, project, pagination, +1 more
gitlab_list_pipelinesARead-onlyIdempotent
List recent pipelines of a project, newest first.
Use for triage ("show failed pipelines on master"), release readiness checks, or feeding pipeline IDs into follow-up calls. Read-only and idempotent.
Returns PipelinesListOutput: project, count, pagination and
pipelines[] (each PipelineSummary). The tool result additionally
carries a markdown table in its text content.
Examples:
- "Show failed pipelines on master" → status='failed', ref='master'
- "Last nightly schedule runs" → source='schedule'
- "Second page of pipelines" → page=2
- Don't use when you have a specific pipeline ID — use gitlab_get_pipeline instead.
Inputs: project_path (required), ref, page, source, +2 more. Returns: count, project, pipelines, pagination
gitlab_list_repository_treeARead-onlyIdempotent
List files and directories at a given path in the repository.
Examples:
- "Show top-level files" → default call
- "All .py files recursively" → recursive=True then filter on .py in path
- Don't use for full-text content — use gitlab_get_file for that.
Inputs: project_path (required), ref, page, path, +2 more. Returns: ref, path, count, items, +2 more
gitlab_list_schedulesARead-onlyIdempotent
List all CI/CD schedules of a project.
Variable keys whose name hints at a secret (TOKEN, PASSWORD,
SECRET, CREDENTIAL, PRIVATE_KEY, API_KEY) keep the key
but have the value replaced by *** so the agent still sees which
variables exist.
Examples:
- "What schedules do we have and are they all active" → default call
- Don't use to run a schedule now — use gitlab_trigger_pipeline
with the schedule's variables instead.
Inputs: project_path (required). Returns: project, schedules, active_count, schedules_count
gitlab_list_tagsARead-onlyIdempotent
List tags of a project, newest first.
Useful for release-note generation or checking the last shipped version.
Examples:
- "What was the last release tag" → default call, take the first item
- "All v2.x releases" → search='v2.'
Inputs: project_path (required), page, search, per_page. Returns: tags, count, project, pagination
gitlab_merge_mrADestructiveIdempotent
Perform the actual merge if GitLab reports the MR can be merged.
Destructive: writes to the target branch. Checks merge_status first
and returns status='cannot_merge' if conflicts exist or pipelines are
required.
Examples:
- "Merge !42" → mr_iid=42
- Don't call without checking gitlab_get_merge_request first when you suspect conflicts.
Inputs: mr_iid (required), project_path (required). Returns: iid, hint, state, title, +6 more
gitlab_pipeline_healthARead-onlyIdempotent
Aggregate success rate over 7 and 30 days with a trend indicator.
Great for stand-ups and on-call hand-offs. Returns success rate %, totals,
last-10 statuses and a trend (up/down/flat).
Emits progress via the MCP Context (info log + report_progress) —
useful in IDEs that show per-tool progress bars.
Examples:
- "How stable is master" → default (ref='master', source='schedule')
- "Push-driven pipeline health" → source='push'
- Don't use for a single pipeline — use gitlab_get_pipeline.
Inputs: project_path (required), ref, source. Returns: ref, trend, source, status, +16 more
gitlab_project_infoARead-onlyIdempotent
Return basic metadata about a project: ID, default branch, visibility, counts.
Examples:
- "What's the project ID and default branch" → default call
- "Is this repo public or private" → look at visibility
Inputs: project_path (required). Returns: id, name, web_url, created_at, +7 more
gitlab_retry_pipelineA
Retry all failed jobs of an existing pipeline.
Creates new job runs (new history entries). Safe to call when the pipeline has at least one failed/canceled job; has no effect if everything already passed.
Examples:
- "Retry the failed jobs in pipeline 123" → pipeline_id=123
- Don't use to rerun a successful pipeline — use gitlab_trigger_pipeline instead.
Inputs: pipeline_id (required), project_path (required). Returns: ref, status, web_url, created_at, +2 more
gitlab_trigger_pipelineA
Create a new pipeline on the given ref, optionally with CI variables.
Not idempotent: each call creates a new pipeline. Consumes minutes on your runners — avoid calling in loops.
Examples:
- "Run the pipeline on master" → default (ref='master')
- "Run the pipeline on feature/x with DEBUG=1" → ref='feature/x', variables={'DEBUG': '1'}
- Don't call to retry — use gitlab_retry_pipeline which keeps the same pipeline ID.
Inputs: project_path (required), ref, variables. Returns: ref, status, web_url, created_at, +2 more
gitlab_update_scheduleADestructiveIdempotent
Update an existing schedule. Only provided fields change.
Destructive when variables is set: the entire variable set is replaced,
so ensure the caller sends a full list.
Examples:
- "Deactivate schedule 42" → schedule_id=42, active=False
- "Change cron of schedule 42 to hourly" → schedule_id=42, cron='0 * * * *'
- Don't pass variables unless you want to replace them entirely.
Inputs: schedule_id (required), project_path (required), ref, cron, +3 more. Returns: ref, cron, active, status, +2 more
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 24 tools
Tools are clearly separated by resource and action: pipeline tools split into list/get/jobs/log/trigger/retry/cancel/health, MR tools into list/get/changes/create/merge, and schedule CRUD is explicit. The only mild overlap is among pipeline-read tools, but the descriptions make boundaries clear.
Most tools follow a predictable gitlab_verb_noun pattern such as gitlab_get_pipeline, gitlab_list_branches, and gitlab_create_schedule. Exceptions include noun-phrase names like gitlab_pipeline_health and gitlab_project_info, the abbreviated gitlab_merge_mr, and the meta gitlab_ci_mcp_check_updates.
At 24 tools the set is on the heavy side, but it covers several distinct areas—pipeline operations, job logs, merge requests, repository browsing, and schedules—and most tools have a clear role. The lone meta update-check tool is slightly out of place but does not dominate the surface.
Core CI lifecycle coverage is strong: pipelines can be listed, inspected, triggered, retried, cancelled, and health-checked, and schedules have full CRUD. However, notable gaps remain: merge requests cannot be updated, closed, commented on, or approved; branches and tags are list-only; and repository file operations are read-only.
Maintenance
Related MCP Connectors
GitLab MCP — wraps the GitLab REST API v4 (BYO API key)
Go MCP server for GitLab: 2 dynamic tools reach 1000+ REST/GraphQL actions. Free/CE, no paid tier.
An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform
GitLab Public MCP — wraps the GitLab REST API v4 (public endpoints, no auth)
Related MCP Servers
- -licenseAqualityBmaintenanceMCP Server for the GitLab API, enabling project management, file operations, and more.95,950 npm91,952MIT
- AlicenseNot gradedqualityCmaintenanceMCP server for interacting with GitLab API, supporting both self-hosted instances and gitlab.com. Provides tools for managing issues, merge requests, code review, pipelines, milestones, releases, search, and file access.1,908 npmMIT
- AlicenseNot gradedqualityBmaintenanceMCP server for the GitLab REST API providing tools to manage projects, merge requests, pipelines, CI/CD variables, approvals, issues, and code reviews.7MIT
- AlicenseAqualityCmaintenanceMCP server for GitLab REST API enabling AI agents to manage pipelines, merge requests, diffs, and local reviews.159 npmMIT