GitLab MCP
The GitLab MCP server exposes 85+ tools for comprehensive interaction with GitLab (gitlab.com or self-hosted) via its REST API v4. It supports read‑only mode, single‑project locking, and fine‑grained access control through scopes and tokens for secure, controlled operations.
Instance & User: Verify connection (
gitlab_whoami), search across GitLab, manage todos, list events, look up users, and render markdown.Projects & Groups: List, get, create, update, delete, fork projects; manage members; list groups and their resources; view project statistics.
Issues: Full CRUD; manage comments, threaded discussions, time tracking, labels, milestones, boards, issue links, and emoji reactions.
Merge Requests: Full CRUD, merge, approve/unapprove, rebase; view per‑file diffs, comment on code lines, resolve threads, and inspect approvals/commits/pipelines.
Repository: Browse and read files (including images), commit single or multiple files, manage branches/commits/tags/releases, view blame, compare refs, and check branch protection rules.
CI/CD: List/trigger/retry/cancel pipelines and jobs; download logs (tailed for quick failure diagnosis) and artifacts (e.g., test screenshots); manage CI variables; validate
.gitlab-ci.yml; view environments, deployments, and schedules.Attachments & Screenshots: Download authenticated attachments (images returned as viewable), list all attachments of an issue/MR/epic, upload local files to get markdown embed snippets, and make authenticated GET requests to any instance URL.
Security: Read‑only mode, restrict to a single project, and enforce access via scoped tokens.
General API: Call any GitLab REST API v4 endpoint directly as an escape hatch.
Other: Manage wikis, snippets, webhooks; list packages and container registry repositories/tags.
Provides tools for interacting with GitLab, enabling management of issues, merge requests, repository files, CI/CD pipelines, wikis, packages and registries, plus a generic API endpoint. Supports both gitlab.com and self-hosted instances.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@GitLab MCPshow me the latest pipeline status for the main branch"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
GitLab MCP
An MCP server for GitLab — works with gitlab.com and with any self-hosted instance
(https://gitlab.velocorner.ch, https://dev.tectiers.com, anything else).
85 tools covering issues, merge requests, the repository, CI/CD, wiki, packages and
registries, plus gitlab_api_request — a generic escape hatch to any REST API v4 endpoint
that has no dedicated tool.
The headline feature: the server reads attachments. A screenshot pasted into an issue is downloaded with authentication and handed to the model as an image — not as a link it cannot open.
Two ways to run it
Mode | Transport | Configuration comes from | Use it when |
Hosted | Streamable HTTP on | request headers, per call | clients should connect to a URL with nothing installed |
Local | stdio | environment variables | the instance is private, or credentials must not leave the machine |
The public deployment lives at https://gitlab-mcp.webapace.ink — the landing page on /,
the MCP endpoint on /mcp. Connecting to it needs no install:
claude mcp add --transport http gitlab \
https://gitlab-mcp.webapace.ink/mcp \
--header "X-GitLab-Url: https://gitlab.example.com" \
--header "X-GitLab-Token: glpat-xxxxxxxxxxxx"Or write the configuration yourself. The same object goes into .mcp.json in a repository,
~/.claude.json, claude_desktop_config.json or .cursor/mcp.json — fill in the blanks:
{
"mcpServers": {
"gitlab": {
"type": "http",
"url": "https://gitlab-mcp.webapace.ink/mcp",
"headers": {
"X-GitLab-Url": "",
"X-GitLab-Token": "",
"X-GitLab-Project-Id": ""
}
}
}
}Header | Fill in with | If left empty |
| the address as typed in the browser, |
|
| the | the request is refused — this one is required |
| default project: | tools ask for a project each time |
X-GitLab-Project-Lock and X-GitLab-Read-Only can be added the same way; see
Hosted mode for the full header list.
Keep the file out of version control, or write "X-GitLab-Token": "${GITLAB_TOKEN}" — Claude Code
and Cursor substitute environment variables, so the secret stays in your shell.
Two things worth knowing when a change appears to do nothing:
In Claude Code an entry added to the local scope (kept in
~/.claude.json) takes precedence over the project's.mcp.json.claude mcp listshows what is actually in use, andclaude mcp remove <name> -s localdrops a stale one.Client configuration is read at startup, so restart the app after editing the file.
The rest of this document is for running your own copy in either mode.
Related MCP server: gitlab-mcp
Install
git clone <repo> gitlab-mcp
cd gitlab-mcp
npm install
npm run buildRequires Node.js 20 or newer.
Token
GitLab → Settings → Access Tokens. A personal, group or project access token all work.
Scope | What it allows |
| everything read-only: issues, MRs, files, pipelines, attachments |
| the above plus writes: comments, commits, merge requests, issue edits |
A project access token is the safest choice when the work is confined to one project.
Configuration
In stdio mode everything is configured through environment variables.
Variable | Required | Default | Description |
| yes |
| Instance URL. A trailing |
| yes | — | Access token |
| no | — | Default project: |
| no |
|
|
| no |
|
|
| no |
|
|
| no |
|
|
| no |
| Per-request timeout |
| no |
| Size cap for downloaded attachments |
Aliases that are also read: GITLAB_API_URL / CI_SERVER_URL for the URL,
GITLAB_PERSONAL_ACCESS_TOKEN / GITLAB_ACCESS_TOKEN for the token.
Connecting from Claude Code
claude mcp add gitlab \
--env GITLAB_URL=https://gitlab.velocorner.ch \
--env GITLAB_TOKEN=glpat-xxxxxxxxxxxx \
-- node /absolute/path/gitlab-mcp/dist/index.jsOr drop a .mcp.json into the root of your working repository, so the configuration travels
with the project:
{
"mcpServers": {
"gitlab": {
"command": "node",
"args": ["/absolute/path/gitlab-mcp/dist/index.js"],
"env": {
"GITLAB_URL": "https://dev.tectiers.com",
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxx",
"GITLAB_PROJECT_ID": "team/backend",
"GITLAB_PROJECT_LOCK": "true"
}
}
}
}The same block works in Claude Desktop (claude_desktop_config.json) and Cursor (.cursor/mcp.json).
Several instances at once — just several entries with different names:
{
"mcpServers": {
"gitlab-com": { "command": "node", "args": ["…/dist/index.js"], "env": { "GITLAB_URL": "https://gitlab.com", "GITLAB_TOKEN": "glpat-…" } },
"gitlab-work": { "command": "node", "args": ["…/dist/index.js"], "env": { "GITLAB_URL": "https://gitlab.velocorner.ch", "GITLAB_TOKEN": "glpat-…" } }
}
}To verify the connection, ask for gitlab_whoami — it returns the authenticated user, the
instance URL and the GitLab version.
Hosted mode
Start the HTTP transport with MCP_TRANSPORT=http (or --http). It serves:
Route | Purpose |
| the landing page ( |
| the MCP endpoint, stateless — one server instance per request |
| liveness probe |
| generated per request from the |
| static files from |
GET /index.html redirects to / so the page has a single canonical address.
Every request carries its own credentials, so one deployment serves many users and many instances without holding state:
Header | Maps to | Notes |
|
| required unless the deployment sets a default |
|
|
|
|
| default project |
|
|
|
|
|
|
|
|
|
TLS verification, timeouts and the attachment size cap are deliberately not header-controlled: they are process-wide and belong to whoever runs the deployment.
Deployment settings:
Variable | Default | Description |
|
|
|
|
| listen address |
|
| page served at |
|
| directory holding the icons, the manifest and the preview image |
| — | comma-separated hostnames; when set, only these instances may be targeted |
| — | fallbacks used when the corresponding header is absent |
Without an allowlist the server refuses private addresses (localhost, RFC 1918 ranges,
169.254.*, *.internal, *.local) so a public deployment cannot be used to probe the network
it runs in. A GitLab on a private network therefore needs a local or internal deployment — which
is the correct answer anyway.
docker build -t gitlab-mcp .
docker run -p 8080:8080 -e GITLAB_ALLOWED_INSTANCES=gitlab.example.com gitlab-mcpDeploying
deploy.sh does the whole cycle on the server — pull, build, swap the container, verify,
purge the CDN cache:
./deploy.sh # the usual deploy
./deploy.sh --page-only # only replace landing.html in the running container, no rebuild
./deploy.sh --no-pull # deploy the working tree as it is
./deploy.sh --logs # follow the container log afterwardsCopy deploy.env.example to deploy.env on the server and set the port, container name,
PUBLIC_URL and, if the site sits behind Cloudflare, a zone id and an API token with the
Cache Purge permission. deploy.env is git-ignored, so server-specific values stay there.
The previous image is tagged :previous before every build, and a failed health check
restores it automatically and exits non-zero, so a broken build never stays deployed.
Because the server reads landing.html from disk on every request, --page-only publishes
a page change in a second without rebuilding anything — but the file is baked into the image
by COPY landing.html, so the next full deploy is what makes it permanent.
Landing page assets
assets/ holds everything the page references: favicon.svg (the source of every raster icon),
favicon.ico, the touch and PWA icons, site.webmanifest, and og-image.png — the 1200×630
preview used by link unfurlers, rendered from assets/og-card.html.
The PNG and ICO files are committed, so a normal build needs nothing extra. Regenerate them only
after editing favicon.svg or og-card.html:
npm run assets # headless Chrome does the rasterising; set CHROME=… if it is not foundThe page carries a description, canonical URL, Open Graph and Twitter cards, and JSON-LD
(SoftwareApplication, WebSite, FAQPage). Those absolute URLs point at
gitlab-mcp.webapace.ink; a self-hosted copy that should be indexed under its own name needs
them replaced in landing.html — robots.txt and sitemap.xml already follow the request host
on their own.
Because the endpoint accepts tokens from callers, put it behind TLS and treat access logs accordingly. The server itself keeps nothing: no sessions, no storage, one throwaway server instance per request.
Single-project isolation
Two modes, and they stack.
Soft — a default project. Only GITLAB_PROJECT_ID is set: the project_id argument becomes
optional, but any other project is still reachable.
Hard — GITLAB_PROJECT_LOCK=true. The server resolves the project at startup and from then on:
any
project_idother than the configured one is refused — the id, the full path and the URL-encoded path are all compared, so42,group/projectandgroup%2Fprojectcount as the same project;group-wide and instance-wide operations (listing groups, group members, group search) are refused;
gitlab_api_requestonly accepts paths under/projects/<your project>/plus the harmless/user,/version,/markdown,/todos;gitlab_download_urlandgitlab_read_attachmentonly fetch URLs that belong to the project;gitlab_list_projectsreturns exactly one project and an unscopedgitlab_searchsearches inside it.
GITLAB_PROJECT_LOCK=true + GITLAB_READ_ONLY=true + a project access token with read_api
gives a server that can neither reach outside the project nor change anything inside it.
Screenshots and attachments
This is the part worth its own section. GitLab stores attachments at URLs like
/uploads/<hash>/screenshot.png — unreachable without authentication, and written into the issue
body as a relative link.
How it works:
1. Fetch the issue. gitlab_get_issue returns the description, every comment, and a separate
attachments block with absolute URLs already resolved:
{
"attachments": [
{
"title": "Screenshot_2026-08-04",
"url": "https://gitlab.velocorner.ch/team/web/uploads/a1b2…/Screenshot.png",
"is_image_embed": true,
"source": "description"
},
{ "title": "log", "url": "https://…/uploads/c3d4…/error.log", "is_image_embed": false, "source": "note #98123" }
]
}2. Look at the image. gitlab_read_attachment with that url — PNG/JPEG/GIF/WebP come back as
an image the model can actually see. Text files come back as text, anything else as a base64 resource.
Relative paths work too: {"url": "/uploads/a1b2…/Screenshot.png", "project_id": "team/web"}.
Related tools:
gitlab_list_attachments— scan an issue/MR/epic and get only the attachment list, without the body;gitlab_get_filewithas_image: true— view an image committed to the repository;gitlab_get_job_artifact— pull a screenshot out of CI artifacts (Playwright, Cypress);gitlab_download_url— authenticated GET of any URL on the instance;gitlab_upload_file— upload a local file and get the markdown snippet to embed in a comment.
Downloads try several routes in order: the API first (/projects/:id/uploads/:secret/:file,
GitLab 16.6+), then the web URL with a PRIVATE-TOKEN header, then with ?private_token=. A
response that turns out to be a login page counts as a failure and the next route is tried — which
is what makes this work on older self-hosted instances.
Tools
Identifiers: a project is a numeric id or a full path group/subgroup/project; issues and merge
requests are addressed by their iid (the number shown in the UI), not the global id.
Every list tool shares the arguments page, per_page (max 100), all_pages (walk the whole
pagination) and limit.
Instance and utility
Tool | Description |
| Current user, instance URL, GitLab version — the configuration smoke test |
| Search across projects, issues, MRs, code, commits, wiki, comments |
| The user's todo list and marking items done |
| Activity stream of the current user or a project |
| User lookup (project members only in locked mode) |
| Render markdown through GitLab (expands relative links) |
| Any REST API v4 endpoint: epics, feature flags, admin, instance specifics |
Projects and groups
Tool | Description |
| Project list and details |
| Project management |
| Project members |
| Groups |
| Languages, contributors, repository size |
Issues
Tool | Description |
| Issues of a project, a group or the whole instance, with filters |
| The whole issue: body, comments, attachments |
| Issue CRUD, including close/reopen |
| Comments on issues, MRs, snippets, epics, commits |
| Discussion threads |
| Links between issues (relates to / blocks / blocked by) |
| Estimate and spent time |
| Merge requests connected to the issue |
| Labels, milestones, boards |
| Emoji reactions |
Merge requests
Tool | Description |
| MR list and details (with attachments) |
| Per-file diff, truncated to a readable size |
| MR lifecycle |
| approve, unapprove, rebase, cancel auto-merge, commits, participants, pipelines |
| Comment on a specific line of a file under review |
| Resolve or unresolve a thread |
Repository
Tool | Description |
| File tree and contents ( |
| Write one file, or commit several operations at once |
| Branches: create, delete, prune merged |
| History, commit diff, ref comparison, blame |
| Tags and releases |
| Branch protection rules |
CI/CD
Tool | Description |
| Pipelines: inspect, run, retry, cancel |
| Jobs: retry, cancel, play, erase |
| Log of a failed job (tail by default) |
| Job artifact; images come back as images |
| CI/CD variables |
| Validate |
| Environments, deployments, schedules |
Attachments
Tool | Description |
| Download an attachment with authentication; images arrive as images |
| Every attachment of an issue/MR/epic in one list |
| Upload a local file, get the markdown snippet back |
| Authenticated GET of any instance URL |
Everything else
Tool | Description |
| Wiki pages: read and edit |
| Project snippets |
| Webhooks |
| Package registry and container registry |
When no tool fits
gitlab_api_request calls any REST API v4
endpoint:
{
"method": "GET",
"path": "/groups/42/epics",
"query": { "state": "opened" },
"all_pages": true
}Slashes inside identifiers are encoded as %2F: /projects/group%2Fproject/issues.
In read-only mode only GET passes; in locked mode only paths inside the locked project do.
Development
npm run build # compile
npm run dev # tsc --watch
npm run typecheck # types only
npm run start # stdio mode
npm run start:http # hosted mode on PORT (default 8080)Layout:
src/
index.ts entry point, picks stdio or HTTP
server.ts builds a configured MCP server with every tool registered
http.ts hosted mode: landing page, stateless /mcp, per-request credentials
config.ts configuration from environment or headers
gitlab-client.ts HTTP client: pagination, errors, downloads, project lock
tools/
helpers.ts shared argument schemas, result formatting, guards
core.ts instance, search, todos, raw API
projects.ts projects and groups
issues.ts issues, comments, labels, milestones
merge-requests.ts merge requests, diffs, reviews
repository.ts files, branches, commits, tags, releases
ci.ts pipelines, jobs, artifacts, variables
attachments.ts attachments and screenshots
misc.ts wiki, snippets, hooks, registriesTo add a tool, call defineTool in the appropriate module: error handling, the read-only guard
and the project lock are wired in automatically.
Troubleshooting
Symptom | Cause |
| The token expired, was revoked, or belongs to a different instance |
| Missing scope ( |
| Wrong project id/path, or the token cannot see it. The path must be complete: |
Attachment will not download | Check that the URL belongs to the same instance; for a private CA set |
| Raise |
Server will not start | Logs go to stderr with the |
A visual walkthrough with examples lives in landing.html at the repository root.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseBqualityDmaintenanceA Model Context Protocol server that enables AI assistants to interact with self-hosted GitLab instances through 30 specialized tools. It supports managing projects, merge requests, CI/CD pipelines, and repository operations via the GitLab REST API v4.Last updated30MIT
- Alicense-qualityBmaintenanceMCP 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.Last updated634MIT
- Alicense-qualityAmaintenanceMCP server for the GitLab REST API providing tools to manage projects, merge requests, pipelines, CI/CD variables, approvals, issues, and code reviews.Last updated5MIT
- Alicense-qualityAmaintenanceMCP server for interacting with GitLab API, supporting dynamic tool selection and enterprise-grade security.Last updated11MIT
Related MCP Connectors
A MCP server built for developers enabling Git based project management with project and personal…
GitLab Public MCP — wraps the GitLab REST API v4 (public endpoints, no auth)
The MCP server for Azure DevOps, bringing the power of Azure DevOps directly to your agents.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/amalychev/gitlab-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server