GitLab MCP

GitLab MCP

MCP server for GitLab that provides 85 tools covering issues, merge requests, repositories, CI/CD, wiki, packages, and attachments, with support for both gitlab.com and self-hosted instances. It can read attachments so models can see screenshots and other files.

Category
访问服务器

README

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 /mcp 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"

The rest of this document is for running your own copy in either mode.


Install

git clone <repo> gitlab-mcp
cd gitlab-mcp
npm install
npm run build

Requires Node.js 20 or newer.

Token

GitLab → Settings → Access Tokens. A personal, group or project access token all work.

Scope What it allows
read_api everything read-only: issues, MRs, files, pipelines, attachments
api 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
GITLAB_URL yes https://gitlab.com Instance URL. A trailing /api/v4 is accepted and stripped
GITLAB_TOKEN yes — Access token
GITLAB_PROJECT_ID no — Default project: 42 or group/subgroup/project
GITLAB_PROJECT_LOCK no false true — hard isolation inside GITLAB_PROJECT_ID
GITLAB_READ_ONLY no false true — every mutating tool is refused
GITLAB_AUTH_TYPE no pat oauth — send the token as Authorization: Bearer
GITLAB_TLS_REJECT_UNAUTHORIZED no true false — skip TLS verification (private CA)
GITLAB_TIMEOUT_MS no 60000 Per-request timeout
GITLAB_MAX_ATTACHMENT_BYTES no 8388608 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.js

Or 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 three routes:

Route Purpose
GET / the landing page (landing.html, or LANDING_PATH)
POST /mcp the MCP endpoint, stateless — one server instance per request
GET /health liveness probe

Every request carries its own credentials, so one deployment serves many users and many instances without holding state:

Header Maps to Notes
X-GitLab-Url GITLAB_URL required unless the deployment sets a default
X-GitLab-Token GITLAB_TOKEN Authorization: Bearer <token> is accepted instead
X-GitLab-Project-Id GITLAB_PROJECT_ID default project
X-GitLab-Project-Lock GITLAB_PROJECT_LOCK true locks the session to that project
X-GitLab-Read-Only GITLAB_READ_ONLY true refuses every mutating tool
X-GitLab-Auth-Type GITLAB_AUTH_TYPE oauth to send a Bearer token to GitLab

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
MCP_TRANSPORT stdio http to start the HTTP server
PORT / HOST 8080 / 0.0.0.0 listen address
LANDING_PATH ./landing.html page served at /
GITLAB_ALLOWED_INSTANCES — comma-separated hostnames; when set, only these instances may be targeted
GITLAB_URL, GITLAB_TOKEN, … — 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-mcp

Deploying

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 afterwards

Copy 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.

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_id other than the configured one is refused — the id, the full path and the URL-encoded path are all compared, so 42, group/project and group%2Fproject count as the same project;
  • group-wide and instance-wide operations (listing groups, group members, group search) are refused;
  • gitlab_api_request only accepts paths under /projects/<your project>/ plus the harmless /user, /version, /markdown, /todos;
  • gitlab_download_url and gitlab_read_attachment only fetch URLs that belong to the project;
  • gitlab_list_projects returns exactly one project and an unscoped gitlab_search searches 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_file with as_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
gitlab_whoami Current user, instance URL, GitLab version — the configuration smoke test
gitlab_search Search across projects, issues, MRs, code, commits, wiki, comments
gitlab_list_todos / gitlab_mark_todo_done The user's todo list and marking items done
gitlab_list_events Activity stream of the current user or a project
gitlab_list_users / gitlab_get_user User lookup (project members only in locked mode)
gitlab_markdown_render Render markdown through GitLab (expands relative links)
gitlab_api_request Any REST API v4 endpoint: epics, feature flags, admin, instance specifics

Projects and groups

Tool Description
gitlab_list_projects / gitlab_get_project Project list and details
gitlab_create_project / gitlab_update_project / gitlab_delete_project / gitlab_fork_project Project management
gitlab_list_project_members / gitlab_add_project_member Project members
gitlab_list_groups / gitlab_get_group / gitlab_list_group_projects / gitlab_list_group_members Groups
gitlab_project_statistics Languages, contributors, repository size

Issues

Tool Description
gitlab_list_issues Issues of a project, a group or the whole instance, with filters
gitlab_get_issue The whole issue: body, comments, attachments
gitlab_create_issue / gitlab_update_issue / gitlab_delete_issue Issue CRUD, including close/reopen
gitlab_list_notes / gitlab_create_note / gitlab_update_note Comments on issues, MRs, snippets, epics, commits
gitlab_list_discussions / gitlab_reply_to_discussion Discussion threads
gitlab_issue_links Links between issues (relates to / blocks / blocked by)
gitlab_issue_time_tracking Estimate and spent time
gitlab_issue_related_merge_requests Merge requests connected to the issue
gitlab_list_labels / gitlab_create_label / gitlab_list_milestones / gitlab_list_boards Labels, milestones, boards
gitlab_award_emoji Emoji reactions

Merge requests

Tool Description
gitlab_list_merge_requests / gitlab_get_merge_request MR list and details (with attachments)
gitlab_get_merge_request_diff Per-file diff, truncated to a readable size
gitlab_create_merge_request / gitlab_update_merge_request / gitlab_merge_merge_request MR lifecycle
gitlab_merge_request_action approve, unapprove, rebase, cancel auto-merge, commits, participants, pipelines
gitlab_create_merge_request_thread Comment on a specific line of a file under review
gitlab_resolve_merge_request_thread Resolve or unresolve a thread

Repository

Tool Description
gitlab_list_repo_tree / gitlab_get_file File tree and contents (as_image for images)
gitlab_create_or_update_file / gitlab_commit_files Write one file, or commit several operations at once
gitlab_list_branches / gitlab_branch_action Branches: create, delete, prune merged
gitlab_list_commits / gitlab_get_commit / gitlab_compare / gitlab_blame_file History, commit diff, ref comparison, blame
gitlab_list_tags / gitlab_tag_action / gitlab_list_releases / gitlab_create_release Tags and releases
gitlab_list_protected_branches Branch protection rules

CI/CD

Tool Description
gitlab_list_pipelines / gitlab_get_pipeline / gitlab_pipeline_action Pipelines: inspect, run, retry, cancel
gitlab_list_jobs / gitlab_job_action Jobs: retry, cancel, play, erase
gitlab_get_job_log Log of a failed job (tail by default)
gitlab_get_job_artifact Job artifact; images come back as images
gitlab_ci_variables CI/CD variables
gitlab_lint_ci_config Validate .gitlab-ci.yml before committing it
gitlab_list_environments / gitlab_list_deployments / gitlab_list_pipeline_schedules Environments, deployments, schedules

Attachments

Tool Description
gitlab_read_attachment Download an attachment with authentication; images arrive as images
gitlab_list_attachments Every attachment of an issue/MR/epic in one list
gitlab_upload_file Upload a local file, get the markdown snippet back
gitlab_download_url Authenticated GET of any instance URL

Everything else

Tool Description
gitlab_wiki Wiki pages: read and edit
gitlab_snippets Project snippets
gitlab_project_hooks Webhooks
gitlab_list_packages / gitlab_list_registry_repositories 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, registries

To 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
401 Unauthorized The token expired, was revoked, or belongs to a different instance
403 Forbidden Missing scope (read_api instead of api) or insufficient role in the project
404 Not Found Wrong project id/path, or the token cannot see it. The path must be complete: group/subgroup/project
Attachment will not download Check that the URL belongs to the same instance; for a private CA set GITLAB_TLS_REJECT_UNAUTHORIZED=false
Attachment is N bytes, above… Raise GITLAB_MAX_ATTACHMENT_BYTES
Server will not start Logs go to stderr with the [gitlab-mcp] prefix; usually GITLAB_TOKEN is missing

A visual walkthrough with examples lives in landing.html at the repository root.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选