A step-by-step tutorial to get gitlab-mcp-server running and make your first GitLab query through an AI assistant.
Diátaxis type: Tutorial Audience: New users Time: ~5 minutes 📖 User documentation: See the Getting Started on the documentation site for a user-friendly version.
- A GitLab instance (self-hosted or gitlab.com)
- A Personal Access Token with
apiscope (how to create one) - An MCP-compatible AI client (VS Code with GitHub Copilot, Claude Desktop, Cursor, etc.)
Headless / CI usage? See the CI/CD Usage guide for running in pipelines without an interactive client.
If you just want it running, pick one of these and skip the manual steps below. Every channel has a full section in the Installation guide: what it needs, where the binary lands, how to verify a download, and how to upgrade or uninstall.
Each registers a Docker-based server (auto-pulls the image on first run; needs Docker). VS Code prompts for your token; Cursor / LM Studio / Kiro add a YOUR_GITLAB_TOKEN placeholder you replace.
Docker (no install — pulls the image on first run):
export GITLAB_TOKEN=glpat-xxxx
claude mcp add gitlab --transport stdio \
-- docker run -i --rm -e GITLAB_TOKEN ghcr.io/jmrplens/gitlab-mcp-server:latestThe registration command carries no token: -e GITLAB_TOKEN with no value forwards the
variable from the environment Claude Code hands docker, so export it where you launch
the client. A container never reads ~/.gitlab-mcp-server.env, which is where the
native-binary paths below put it.
The server is published as @jmrp.io/gitlab-mcp-server. npm downloads only the prebuilt binary for your platform — nothing compiles, nothing runs at install time.
npx -y @jmrp.io/gitlab-mcp-server # zero install; clients launch it directly
npm install -g @jmrp.io/gitlab-mcp-server # or install globally (npm)
pnpm add -g @jmrp.io/gitlab-mcp-server # or globally (pnpm)Point any MCP client at npx with no install at all:
{
"mcpServers": {
"gitlab": {
"command": "npx",
"args": ["-y", "@jmrp.io/gitlab-mcp-server"],
"env": { "GITLAB_URL": "https://gitlab.com", "GITLAB_TOKEN": "glpat-…" }
}
}
}Update with npm update -g @jmrp.io/gitlab-mcp-server: npm owns the binary, as every distribution channel does, and the server never replaces itself on disk. The Linux packages declare libc: ["glibc"] because the prebuilt binaries need the glibc dynamic loader, so npm skips them on musl systems such as Alpine; there, run the Docker image (docker run -i --rm -e GITLAB_TOKEN ghcr.io/jmrplens/gitlab-mcp-server:latest) or build from source.
The server is also published as jmrplens-gitlab-mcp-server, using the same model as uv and ruff: each platform wheel carries the native binary, the installer places it on the scripts path as the gitlab-mcp-server command itself, and no Python runs when the server does. (The unprefixed PyPI name is an empty registration by an unrelated account, under PEP 541 reclamation.)
uvx jmrplens-gitlab-mcp-server # zero install; clients launch it directly
pipx install jmrplens-gitlab-mcp-server # or install globally (pipx)
pip install jmrplens-gitlab-mcp-server # or into the active environment (pip)
# Linux wheels need glibc; on musl systems such as Alpine use the Docker image instead{
"mcpServers": {
"gitlab": {
"command": "uvx",
"args": ["jmrplens-gitlab-mcp-server"],
"env": { "GITLAB_URL": "https://gitlab.com", "GITLAB_TOKEN": "glpat-…" }
}
}
}Like npm, the Linux wheels need glibc (manylinux); on musl systems such as Alpine use the Docker image instead.
The server is also published on NuGet.org as gitlab-mcp-server, a .NET tool in the layout the .NET 10 SDK uses for tools that ship a native executable: a pointer package names one package per runtime identifier, each carrying the same binary, and the SDK runs it directly. It needs the .NET 10 SDK or newer; no .NET code runs once the server is up.
dnx gitlab-mcp-server # zero install; clients launch it directly
dotnet tool install -g gitlab-mcp-server # or install globally (dotnet tool)
# Linux packages need glibc; on musl systems such as Alpine use the Docker image instead{
"mcpServers": {
"gitlab": {
"command": "dnx",
"args": ["gitlab-mcp-server"],
"env": { "GITLAB_URL": "https://gitlab.com", "GITLAB_TOKEN": "glpat-…" }
}
}
}Two dnx habits worth knowing: it parses its own options anywhere on the line, so arguments meant for the server go after -- (dnx gitlab-mcp-server -- --version; without the separator --version is dnx's own option and prints its usage), and it installs the tool without asking when its standard input is not a terminal, which is how a client starts it, so the configuration above needs no extra flag.
# macOS/Linux (Homebrew)
brew install jmrplens/tap/gitlab-mcp-server
# Linux/macOS (script)
curl -fsSL https://raw.githubusercontent.com/jmrplens/gitlab-mcp-server/main/scripts/install.sh | sh
# Windows (winget)
winget install --id jmrplens.gitlab-mcp-server -e
# Windows (PowerShell)
irm https://raw.githubusercontent.com/jmrplens/gitlab-mcp-server/main/scripts/install.ps1 | iex
echo 'GITLAB_TOKEN=glpat-xxxx' > ~/.gitlab-mcp-server.env
claude mcp add gitlab -- gitlab-mcp-serverClaude Desktop users can skip all of the above: download
gitlab-mcp-server.mcpb
and open it with Claude Desktop — see the
Claude Desktop Extension guide.
The server reads ~/.gitlab-mcp-server.env for values its environment does not already carry, one KEY=value per line, which is why the registration command names no token. Self-managed GitLab? Add GITLAB_URL=https://gitlab.example.com to that same file (and GITLAB_MCP_SKIP_TLS_VERIFY=true for self-signed certs). The per-client JSON for every supported client is in Step 2.
A public instance runs at https://mcp.jmrp.io/gitlab — nothing to install, no account beyond your own GitLab.com token. It runs in OAuth mode: a client that speaks the OAuth flow authorizes in the browser and stores its own token, and anything that cannot open a browser sends a personal access token as Authorization: Bearer instead:
{
"mcpServers": {
"gitlab": {
"type": "http",
"url": "https://mcp.jmrp.io/gitlab",
"headers": { "Authorization": "Bearer glpat-xxxxxxxxxxxx" }
}
}
}The server card carries copy-paste config for Claude Code, Cursor and VS Code, including the OAuth client ID those clients need — without it they fall back to dynamic registration and receive a scope this server cannot use. The browser inspector signs in with OAuth and calls the same endpoint read-only, with nothing to install.
It is a personal service run best-effort, with no SLA: your token and every request pass through a machine you do not control, and the instance is fixed to https://gitlab.com. For a self-managed GitLab, or for anything you would rather keep on your own computer, use one of the paths above. The full property table is in HTTP Server Mode — Public Hosted Endpoint.
Prefer the fast paths above? Skip to Step 3. This section is the manual walkthrough.
Download the latest release for your platform from the project Releases page:
| Platform | Binary |
|---|---|
| Linux amd64 | gitlab-mcp-server-linux-amd64 |
| Linux arm64 | gitlab-mcp-server-linux-arm64 |
| Windows amd64 | gitlab-mcp-server-windows-amd64.exe |
| Windows arm64 | gitlab-mcp-server-windows-arm64.exe |
| macOS Intel | gitlab-mcp-server-darwin-amd64 |
| macOS Apple Silicon | gitlab-mcp-server-darwin-arm64 |
| macOS universal | gitlab-mcp-server-darwin-all |
On Linux/macOS, make it executable:
chmod +x gitlab-mcp-server-linux-amd64Every release also ships checksums.txt and a keyless Cosign signature (checksums.txt.sigstore.json); releases after v2.7.5 add one SPDX SBOM per binary and a SLSA build provenance attestation you can check with gh attestation verify <file> -R jmrplens/gitlab-mcp-server (steps in release integrity, and the per-release detail in the Installation guide). The install.sh and install.ps1 scripts verify the SHA-256 against checksums.txt before installing and abort on a mismatch.
Configuration lives in your MCP client's own file, not in this binary. Each client has its own path and JSON shape; Manual Configuration below has the snippet for yours, and IDE Configuration has the full set.
You need two values:
- GitLab URL — your instance base URL, for example
https://gitlab.example.com. On GitLab.com you can leave it out. - Personal Access Token — a
glpat-...token with theapiscope, created under User settings > Access tokens. Aread_apitoken also works for reads: the server detects the scope at startup and serves a read-only tool surface for it, on stdio as in HTTP mode, andgitlab_find_actionreports a write action as withheld by the token rather than missing.
If you would rather not paste a token into a config file at all, the HTTP mode supports OAuth against your GitLab instance instead: see OAuth App Setup.
There is no interactive setup to run. Started in a terminal, or double-clicked on Windows, with neither value set, the server prints what it is and what it needs and waits for you to read it before closing. That is a signpost, not a configuration step: the settings still go in your client's JSON.
Open your configured AI client (e.g., VS Code with GitHub Copilot). The MCP server starts automatically when the client connects.
You should see the GitLab MCP server listed in your client's MCP server panel. In VS Code, check the MCP Servers section in the Copilot chat sidebar.
Type a natural language request in the AI chat:
"List my GitLab projects"
In the default dynamic mode, the AI assistant finds the project-list action with gitlab_find_action, executes it with gitlab_execute_action, and returns a formatted list of your projects with names, URLs, and descriptions. In meta-tool mode it calls gitlab_project; in individual mode it calls gitlab_project_list.
The response includes a Markdown table like:
| ID | Name | Path | Visibility | ⭐ |
|----|------|------|------------|---|
| 12 | [my-app](https://gitlab.example.com/user/my-app) | user/my-app | private | 3 |
| 15 | [api-service](https://gitlab.example.com/user/api-service) | user/api-service | internal | 1 |
Plus structured JSON data for the AI to process programmatically.
Here are some things to try next:
Browse a project:
"Show me the branches in project my-app"
Check merge requests:
"List open merge requests in project 42"
Read a file:
"Show me the contents of README.md in project my-app"
Create an issue:
"Create an issue in project my-app titled 'Fix login bug' with label 'bug'"
Pipeline status:
"What's the latest pipeline status for project my-app?"
The server handles all GitLab API calls. You do not need to know project IDs, endpoints, or JSON syntax — the AI figures that out.
By default, the server registers the dynamic find/execute surface: gitlab_find_action and gitlab_execute_action. The same canonical GitLab action catalog remains reachable, and gitlab_find_action returns exact schemas before execution. Set GITLAB_MCP_TOOL_SURFACE=meta to use 34 meta-tools on Free/CE (40 on Premium, 51 on self-managed Ultimate, 52 on GitLab.com Ultimate with Orbit).
To register the complete individual tool set instead (one tool per GitLab operation; 866 on Free/CE, 1019 on Premium, 1085 on self-managed Ultimate and 1091 on GitLab.com Ultimate with Orbit), set:
GITLAB_MCP_TOOL_SURFACE=individualTo switch away from the default dynamic surface and register the consolidated meta-tool catalog instead, set:
GITLAB_MCP_TOOL_SURFACE=metaFor the smallest startup surface with the default dynamic mode, also set GITLAB_MCP_CAPABILITY_SURFACE=minimal. This keeps the gitlab://tools manifest, and omits optional GitLab data resources, prompts, and workflow guides. Dynamic action find and execute remain available because dynamic discovery returns action schemas inline.
See Dynamic Tools for the default find/execute workflow and Meta-Tools for the explicit meta-tool catalog reference.
The repository ships an Agent Plugins 1.0 manifest (root plugin.json + mcp.json), and keeps the legacy Open Plugins manifest (.plugin/plugin.json) for older hosts, so the server can be installed in a single step on conformant hosts:
/plugin install jmrplens/gitlab-mcp-serverThe bundled mcp.json runs the published Docker image ghcr.io/jmrplens/gitlab-mcp-server:latest, so Docker must be installed. It is configured for stdio MCP clients and needs no transport flag: the image reads the transport off stdin, and docker run -i is what puts a pipe there. Keep the -i if you copy the Docker configuration into VS Code or another stdio client; without it the container is handed /dev/null, reads that as nobody speaking to it, starts an HTTP listener, and the client waits forever for a stdio initialize response.
The bundled config forwards these environment variables into the container, but
the host has to put them in the plugin's environment first. Agent Plugins §9.1
lets a client "inherit, omit, or sanitize" ambient variables, and the spec
defines no portable way for a plugin to reference a secret, so GITLAB_TOKEN
cannot travel inside mcp.json itself. If the server reports an authorization
failure, set it the way your host documents, or add it to the env block of the
installed plugin's local mcp.json.
Note that Agent Plugins expands only ${PLUGIN_ROOT} and ${PLUGIN_DATA}: any
other ${...} reaches the process literally, so a placeholder such as
"GITLAB_TOKEN": "${GITLAB_TOKEN}" in a local mcp.json hands the server that
exact string rather than the token.
| Variable | Required | Description |
|---|---|---|
GITLAB_URL |
No | GitLab instance URL. Defaults to https://gitlab.com; set for self-managed instances |
GITLAB_TOKEN |
Yes | Personal Access Token |
GITLAB_MCP_SKIP_TLS_VERIFY |
No | true for self-signed certs (default false) |
GITLAB_MCP_TOOL_SURFACE |
No | Canonical tool catalog selector: dynamic, meta, or individual (default dynamic) |
GITLAB_MCP_CAPABILITY_SURFACE |
No | Resource and prompt catalog selector: full or minimal (default full) |
GITLAB_MCP_META_PARAM_SCHEMA |
No | Meta-tool input schema detail: opaque, compact, or full (default opaque) |
GITLAB_MCP_TIER |
No | Licensing tier: free/ce, premium, ultimate; unset detects from license (fallback free) |
GITLAB_MCP_READ_ONLY |
No | Disable mutating tools (default false) |
GITLAB_MCP_SAFE_MODE |
No | Preview mutating tool inputs (default false) |
EMBEDDED_RESOURCES |
No | Append embedded MCP resource links to get-style tool results (default true). Deprecated spelling of GITLAB_MCP_EMBEDDED_RESOURCES, still read with a startup warning |
GITLAB_MCP_EXCLUDE_TOOLS |
No | Comma-separated tool names to exclude from registration |
GITLAB_MCP_IGNORE_SCOPES |
No | Skip token scope detection and register all tools (default false) |
GITLAB_MCP_UPLOAD_MAX_FILE_SIZE |
No | Maximum attachment upload size in bytes (default 2147483648) |
GITLAB_MCP_ALLOWED_IMPORT_DIRS |
No | Extra path-list-separated directories allowed for local GitLab import archives |
GITLAB_MCP_RATE_LIMIT_RPS |
No | Per-credential rate limit, in requests per second, on every call that reaches GitLab (tools/call, resources/read, resources/subscribe, subscriptions/listen, prompts/get), plus tools/list on a bucket of its own refilled a tenth as fast, charged for spending the processor every tenant shares rather than for reaching GitLab; 0 disables it (default 0) |
GITLAB_MCP_RATE_LIMIT_BURST |
No | Token-bucket burst size when GITLAB_MCP_RATE_LIMIT_RPS is greater than 0 (default 40) |
GITLAB_MCP_CLIENT_COMPAT |
No | Per-client response compatibility (default auto) |
GITLAB_MCP_LOG_LEVEL |
No | debug, info, warn, error (default info) |
GITLAB_MCP_TOOL_SURFACE selects the catalog: dynamic is the default two-tool low-token find/execute surface, meta exposes consolidated domain dispatchers, and individual exposes every tool separately. It is the variable the plugin config forwards.
The Agent Plugins spec starts every entry in the referenced MCP config automatically and does not support runtime variants, so the manifest ships with a single Docker stdio entry. To use the native binary instead, locate the installed gitlab-mcp-server plugin directory from your host's plugin UI or installation output, then edit its local mcp.json (commonly under .agents/plugins/gitlab-mcp-server/) and replace command / args with the path to the binary downloaded from GitHub Releases.
For detached HTTP deployments, do not use a stdio client entry. Run the Docker image in HTTP mode and configure the MCP client with type: "http" and a URL such as http://localhost:8080/mcp. See HTTP Server Mode.
To keep credentials out of client config files, create ~/.gitlab-mcp-server.env (or any file you then name in GITLAB_MCP_ENV_FILE). A .env in the working directory is not read, because that directory is whatever workspace the client opened:
GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxxFor self-managed GitLab, add GITLAB_URL=https://gitlab.example.com.
Then add the server to your MCP client config manually.
Add to .vscode/mcp.json in your project:
{
"servers": {
"gitlab": {
"type": "stdio",
"command": "/path/to/gitlab-mcp-server",
"env": {
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx"
}
}
}
}Add to claude_desktop_config.json:
{
"mcpServers": {
"gitlab": {
"command": "/path/to/gitlab-mcp-server",
"env": {
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx"
}
}
}
}Add to .cursor/mcp.json:
{
"mcpServers": {
"gitlab": {
"command": "/path/to/gitlab-mcp-server",
"env": {
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx"
}
}
}
}See Configuration for all supported clients and HTTP mode setup.
For shared server deployments, run in HTTP mode:
# Use https://gitlab.com for GitLab.com, or replace it with your self-managed URL.
gitlab-mcp-server --http --gitlab-url=https://gitlab.com --http-addr=:8080Each client provides its own token via HTTP header:
{
"servers": {
"gitlab": {
"type": "http",
"url": "http://your-server:8080/mcp",
"headers": {
"PRIVATE-TOKEN": "glpat-your-token"
}
}
}
}For production deployments, enable server-side token verification with OAuth mode:
gitlab-mcp-server --http \
--gitlab-url=https://gitlab.com \
--auth-mode=oauth \
--public-url=https://mcp.example.com \
--oauth-cache-ttl=15mOAuth mode validates every Bearer token against GitLab's /api/v4/user endpoint and serves an RFC 9728 metadata endpoint that allows MCP clients with OAuth 2.1 support to discover the GitLab authorization server automatically.
MCP clients that support OAuth can connect by providing the clientId from the GitLab OAuth Application:
{
"servers": {
"gitlab": {
"type": "http",
"url": "http://your-server:8080/mcp",
"oauth": {
"clientId": "YOUR_GITLAB_APPLICATION_ID",
"scopes": ["api"]
}
}
}
}The client discovers the GitLab authorization server via /.well-known/oauth-protected-resource and handles token acquisition through the standard OAuth 2.1 PKCE flow.
Important: Without
clientId, clients fall back to Dynamic Client Registration (DCR). GitLab's DCR assigns themcpscope instead ofapi, causing most operations to fail. Always configureclientIdexplicitly.Prerequisite: A GitLab OAuth Application must be created. See OAuth App Setup for a step-by-step guide and IDE Configuration for per-client examples.
See HTTP Server Mode for the full architecture and deployment details.
- Configuration — all environment variables and client setup options
- Meta-Tools — domain meta-tool reference with action mappings
- Usage Examples — real-world scenarios
- Tools Reference — all individual tools, including GitLab.com-only Orbit
- Troubleshooting — common issues and solutions