Skip to content
Conduit

mcp server

gitlab-mcp

Community GitLab MCP Server — works with any GitLab tier (Free/Premium/Ultimate), no Duo required. GraphQL schema discovery, repo browsing, multi-client support (Claude Code, LibreChat).

ttpears/gitlab-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • Node.js 20 or newer
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "ttpears-gitlab-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "@ttpears/gitlab-mcp-server"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 17, 2026

GitLab MCP Server

npm version npm downloads CI Container License: MIT

A community MCP server for GitLab — works with any GitLab tier (Free, Premium, Ultimate), no GitLab Duo required. PAT-based auth. Streamable HTTP and stdio transports.

npx @ttpears/gitlab-mcp-server

Field notes: Two session bugs that broke my GitLab MCP server explains the recursive close callback and stale HTTP session behavior behind the current lifecycle handling.


Choose your deployment

You're runningGo to
Claude Code or another IDE/AI tool, just for youSolo IDE
LibreChat for a team with a service-account read tokenLibreChat — service-account reads
LibreChat where every operation should use the calling user's tokenLibreChat — strict per-user

Solo IDE

Claude Code (recommended)

claude mcp add gitlab \
  --env GITLAB_URL=https://gitlab.com \
  --env GITLAB_TOKEN=glpat-your-pat \
  -- npx -y @ttpears/gitlab-mcp-server

--scope controls where the configuration lives:

  • --scope local (default) — only this user, only this project
  • --scope user — shared across all of your projects
  • --scope project — written to .mcp.json in the project root, intended for team check-in

For team check-in, use a ${VAR} placeholder so the PAT itself stays out of git:

{
  "mcpServers": {
    "gitlab": {
      "command": "npx",
      "args": ["-y", "@ttpears/gitlab-mcp-server"],
      "env": {
        "GITLAB_URL": "https://gitlab.com",
        "GITLAB_TOKEN": "${GITLAB_TOKEN}"
      }
    }
  }
}

Each contributor exports GITLAB_TOKEN in their shell; Claude Code expands it on launch.

Cold start

If launching via npx adds noticeable latency, install once:

npm install -g @ttpears/gitlab-mcp-server

…and replace npx -y @ttpears/gitlab-mcp-server with gitlab-mcp-server in your config.

Claude Desktop

Option A — MCPB one-click install: download gitlab-mcp-community-${VERSION}.mcpb from the latest GitHub Release and drag it onto the Claude Desktop window. Fill in your GitLab URL and a Personal Access Token when prompted; the token is stored in your OS keychain. Use a read_api-scoped PAT for read-only access, or an api-scoped PAT to also allow writes (create_issue, create_merge_request, etc.).

Option B — manual config: add to claude_desktop_config.json:

{
  "mcpServers": {
    "gitlab": {
      "command": "npx",
      "args": ["-y", "@ttpears/gitlab-mcp-server"],
      "env": {
        "GITLAB_URL": "https://gitlab.com",
        "GITLAB_TOKEN": "glpat-your-pat"
      }
    }
  }
}

LibreChat — service-account reads

A read-only service token covers reads (so anyone in the workspace can ask questions). Each user supplies their own PAT for writes through LibreChat's customUserVars.

LibreChat's documented extension point is docker-compose.override.yml — the fragments below assume you're adding gitlab-mcp there alongside LibreChat's own api, mongodb, etc.

.env

GITLAB_URL=https://gitlab.example.com
GITLAB_READ_TOKEN=glpat-readonly-service-token   # read_api scope only

Use https://gitlab.com for SaaS GitLab; use your own host for self-hosted.

docker-compose.override.yml

services:
  gitlab-mcp:
    image: ghcr.io/ttpears/gitlab-mcp:1.14.0
    env_file:
      - .env
    networks:
      - librechat
    restart: unless-stopped

env_file reads GITLAB_URL and GITLAB_READ_TOKEN from .env. No port mapping needed — LibreChat's api container reaches gitlab-mcp over the shared librechat network. Add ports: ["8008:8008"] only if you need to hit it from the host (e.g. for curl http://localhost:8008/health).

Then:

docker compose -f docker-compose.yml -f docker-compose.override.yml up -d gitlab-mcp api

api is restarted alongside so it re-reads librechat.yaml.

librechat.yaml

mcpServers:
  gitlab:
    type: streamable-http
    url: http://gitlab-mcp:8008/
    startup: false                # don't connect until the user supplies their PAT
    initTimeout: 30000
    timeout: 120000
    headers:
      Authorization: "Bearer {{GITLAB_PAT}}"
      X-GitLab-Url: "{{GITLAB_URL_OVERRIDE}}"   # only honored when GITLAB_PIN_HOST=false (default pins to GITLAB_URL)
    customUserVars:
      GITLAB_PAT:
        title: "GitLab Personal Access Token"
        description: "Your GitLab PAT with api scope. Used for issues, MRs, and comments you create or edit."
      GITLAB_URL_OVERRIDE:
        title: "GitLab URL (optional)"
        description: "Leave blank to use the workspace default. Override only if your account is on a different GitLab instance."

initTimeout / timeout are forgiving defaults for slower internal networks and large schemas — drop them if SaaS GitLab feels snappy.

Server-side env vars use ${VAR}. Per-user vars use {{VAR}}. They are not interchangeable.


LibreChat — strict per-user

Every call must carry a user PAT. No service-account fallback. Reads as well as writes are gated on the user's token.

Add MCP Server (UI path — recommended)

  1. Host gitlab-mcp somewhere LibreChat can reach. For a quick standalone host:

    docker run -d --restart unless-stopped \
      -p 8008:8008 \
      -e GITLAB_URL=https://gitlab.example.com \
      --name gitlab-mcp \
      ghcr.io/ttpears/gitlab-mcp:1.14.0
    

    Or, when running alongside LibreChat in the same compose project, drop it into docker-compose.override.yml (no token env vars):

    services:
      gitlab-mcp:
        image: ghcr.io/ttpears/gitlab-mcp:1.14.0
        environment:
          GITLAB_URL: https://gitlab.example.com
        networks:
          - librechat
        restart: unless-stopped
    
  2. In LibreChat → MCP Servers → Add MCP Server:

    • URL: https://your-host/ (or http://gitlab-mcp:8008/ if same network)
    • Authentication: API Key → check User provides key → header format Bearer
    • Save
  3. Each user fills in their PAT through the MCP Tool Select Dialog when configuring an agent.

librechat.yaml equivalent

mcpServers:
  gitlab:
    type: streamable-http
    url: http://gitlab-mcp:8008/
    startup: false
    initTimeout: 30000
    timeout: 120000
    headers:
      Authorization: "Bearer {{GITLAB_PAT}}"
    customUserVars:
      GITLAB_PAT:
        title: "GitLab Personal Access Token"
        description: "Your GitLab PAT with api scope."

The container needs no env-configured token at all in this mode — every request is rejected unless Authorization: Bearer … is present.


Hosting for a remote LibreChat

The server speaks plain HTTP. For LibreChat-as-a-service or any cross-network deployment, terminate TLS at a reverse proxy. Common patterns:

  • Caddy — auto-TLS via Let's Encrypt:

    mcp.example.com {
      reverse_proxy gitlab-mcp:8008
    }
    
  • Traefik — Docker labels on the gitlab-mcp service.

  • Cloudflare Tunnel — no public IP needed; expose gitlab-mcp:8008 through the tunnel.

The Authorization and Mcp-Session-Id headers must pass through unchanged. Most defaults handle this fine.


OAuth for remote users

For a publicly hosted instance where each user signs in with their own GitLab identity — and modern MCP clients (Claude.ai, Claude Code) connect with no manual token setup — enable brokered OAuth 2.1. The server then acts as its own OAuth Authorization Server in front of GitLab:

  • advertises Protected Resource Metadata (RFC 9728) and Authorization Server Metadata (RFC 8414) at the standard .well-known endpoints,
  • supports Dynamic Client Registration (RFC 7591), so clients self-register,
  • runs authorization-code + PKCE against both the MCP client and GitLab (dual-PKCE) behind one fixed GitLab callback,
  • gates the MCP endpoints with bearer validation — unauthenticated requests get 401 + WWW-Authenticate pointing at the resource metadata (the discovery trigger),
  • mints its own opaque tokens and keeps each user's GitLab token server-side (no token passthrough).

Setup

  1. Register one GitLab application (instance/group/user → Applications):

    • Redirect URI: https://<your-host>/gitlab/callback
    • Scopes: api (read + write) or read_api
    • Confidential: yes (recommended) → you get a client secret; or mark it public for PKCE-only.
  2. Run the server (HTTP mode) with no GITLAB_TOKEN — identity comes from each user's OAuth login:

    MCP_TRANSPORT=http GITLAB_MCP_PORT=8008 \
    GITLAB_URL=https://gitlab.example.com \
    GITLAB_MCP_OAUTH=true \
    MCP_SERVER_URL=https://gitlab-mcp.example.com \
    GITLAB_OAUTH_CLIENT_ID=<application id> \
    GITLAB_OAUTH_CLIENT_SECRET=<application secret> \
    GITLAB_OAUTH_SCOPES=api \
    npx -y @ttpears/gitlab-mcp-server
    
  3. Point an MCP client at https://gitlab-mcp.example.com/ — it discovers the metadata, registers, and walks the user through GitLab sign-in automatically.

MCP_SERVER_URL must be HTTPS (terminate TLS at your reverse proxy) and must match the host the GitLab redirect URI is registered under. Token/registration state is in-memory, so run a single instance (or add a shared store) per issuer.


How this differs from GitLab's official MCP server

GitLab ships an official MCP server (Beta) that requires Premium/Ultimate and GitLab Duo.

This serverGitLab official
GitLab tierFree, Premium, UltimatePremium / Ultimate only
GitLab Duo requiredNoYes
AuthPAT, or brokered OAuth 2.1 + Dynamic Client RegistrationOAuth 2.0 Dynamic Client Registration
Transportstdio + streamable HTTPstdio + HTTP
Multi-userPer-call PAT, OAuth per-user, or service-account fallbackOAuth per-user
GraphQL schema discoveryYes — introspect & run custom queriesNo
Repository browsing & file readingYesNo
Update issues / MRs / notesYesNo (create only)
Delete issues / notesYesNo
CI/CD pipeline managementYesYes
MR diffs & commitsYesYes
Time tracking & timelogsYesNo
MR reviewer & approval statusYesNo
Iteration / milestone trackingYesNo
Project statistics dashboardYesNo
Group member listingYesNo
Semantic code searchNoYes (requires additional setup)

Choose this server for Free/CE, GraphQL flexibility, LibreChat multi-user, or brokered OAuth without GitLab Duo. Choose the official server for Premium+Duo with semantic code search.


Tools

Search & Discovery

ToolDescription
search_gitlabGlobal search across projects, issues, and merge requests
search_projectsFind repositories by name or description
search_issuesSearch issues globally or within a project (filter by assignee, author, labels, state)
search_merge_requestsFind merge requests by username or within a project
search_usersFind team members and contributors
search_groupsDiscover groups and organizations
search_labelsSearch labels in a project or group
list_group_membersList group members with access levels
browse_repositoryExplore directory structure and files
get_file_contentRead file contents for code analysis

Read Operations

ToolDescription
get_projectDetailed project information
get_issuesList project issues with pagination
get_merge_requestsList project merge requests with pagination
get_merge_request_pipelinesGet CI/CD pipelines for a merge request
get_pipeline_jobsGet jobs for a specific pipeline
get_merge_request_diffsGet diff statistics for a merge request
get_merge_request_commitsGet commits for a merge request
get_notesGet notes/comments on an issue or merge request
list_milestonesList milestones with progress statistics
list_iterationsList iterations/sprints (Premium/Ultimate)
get_time_trackingGet time estimate, spent, and timelogs
get_merge_request_reviewersGet MR approval and reviewer status
get_project_statisticsAggregate project stats dashboard
get_user_issuesGet all issues assigned to a user
get_user_merge_requestsGet MRs authored by or assigned to a user
get_work_itemFetch a work item (issue, task, epic, incident, OKR) by ID with full widget data
list_work_itemsList work items in a group or project, filtered by type and state
list_broadcast_messagesList instance-wide broadcast messages
get_broadcast_messageGet a specific broadcast message by ID
list_my_todosAuthenticated user's to-do inbox — notifications about issues, MRs, mentions, reviews requested
list_my_eventsAuthenticated user's activity feed — pushes, MRs, comments, approvals
list_user_eventsAnother user's public activity feed by username or ID
list_project_eventsActivity events for a specific project
resolve_pathResolve a path to a project or group
get_available_queriesDiscover available GraphQL operations
execute_custom_queryRun custom GraphQL queries

Achievements

GitLab Achievements are available on all tiers when enabled on the instance (introduced behind a feature flag in 15.10, enabled by default in 19.2, generally available in 19.3). Writes require a full-access token and group Maintainer/Owner permissions.

ToolDescription
list_achievementsList group achievements by cursor, optionally filter IDs and preview active recipients
create_achievementCreate a named group achievement with an optional description
update_achievementUpdate name or description; an empty description clears it
delete_achievementDelete an achievement and all associated awards
award_achievementAward to an exact username with an optional message
revoke_achievementRevoke a specific award ID or look up a unique active award by group, achievement, and username

IDs can be numeric or GitLab global IDs. list_achievements uses first, after, and fetchAll as above; fetchAll stops at first items. includeRecipients adds one page of active awards per achievement with its own pageInfo; it does not fetch every recipient. Use the returned award ID (userAchievementId) for precise revocation. The username lookup scans recipient pages, stops with an explicit error if the scan limit is reached, and requires an award ID when multiple active awards match.

GitLab may require recipient acceptance before an award appears on a profile and sends an award notification. Avatar uploads and user profile display/reordering are managed in GitLab. See GitLab Achievements documentation and the GraphQL mutation reference.

Write Operations (require user authentication)

ToolDescription
create_issueCreate new issues
create_merge_requestCreate new merge requests
create_noteAdd a comment/note to an issue or merge request
update_issueUpdate title, description, assignees, labels, due date
update_merge_requestUpdate title, description, assignees, reviewers, labels
update_noteEdit the body of an existing comment
delete_issueDelete an issue (issue author or maintainer required)
delete_noteDelete a comment (note author or maintainer required)
manage_pipelineRetry or cancel a CI/CD pipeline
create_broadcast_messageCreate a broadcast message (instance admin)
update_broadcast_messageUpdate a broadcast message (instance admin)
delete_broadcast_messageDelete a broadcast message (instance admin)
mark_todo_doneMark a single to-do item as done
mark_all_todos_doneMark all pending to-do items as done for the authenticated user
restore_todoRestore a previously-done to-do item back to pending

Configuration

Environment variables

VariableDescriptionDefault
GITLAB_URLGitLab instance URLhttps://gitlab.com
GITLAB_TOKENFull-access fallback token (reads + writes)—
GITLAB_READ_TOKENRead-only fallback token (writes always rejected)—
GITLAB_PIN_HOSTForce every request to GITLAB_URL, ignoring per-call/header gitlabUrl (SSRF guard). Set false to serve multiple instancestrue
GITLAB_ALLOW_SHARED_ESCAPE_HATCHAllow execute_custom_query / execute_rest_read / execute_rest_write to run on the shared token (otherwise they require per-call credentials)false
GITLAB_MAX_PAGE_SIZEMaximum items per page (1–100)50
GITLAB_TIMEOUTRequest timeout in milliseconds30000
GITLAB_MAX_CONCURRENCYMax concurrent in-flight requests to GitLab (excess queue, never rejected). 0 = unlimited16
GITLAB_ANALYTICS_MAX_PROJECTSCap on projects scanned per group-analytics fan-out (logs when bounded)500
GITLAB_MCP_PORTHTTP server port8008
MCP_TRANSPORTTransport mode (http for LibreChat)stdio
MCP_ALLOWED_ORIGINSComma-separated browser origins allowed for HTTP requests. Defaults to localhost/127.0.0.1 on the configured port plus the origin of MCP_SERVER_URL. Requests without Origin remain supported—
TRUST_PROXYExpress trust proxy when behind a reverse proxy (hop count like 1, boolean, or IP/subnet list). Needed for correct per-IP OAuth rate limiting behind traefik/nginxunset
GITLAB_MCP_OAUTHEnable brokered OAuth 2.1 (HTTP mode) — see OAuth for remote usersfalse
MCP_SERVER_URLPublic HTTPS URL of this server (OAuth issuer/resource id) — required when OAuth is on—
GITLAB_OAUTH_CLIENT_IDGitLab application id — required when OAuth is on—
GITLAB_OAUTH_CLIENT_SECRETGitLab application secret (omit for a public/PKCE-only app)—
GITLAB_OAUTH_SCOPESSpace-separated GitLab scopes to requestapi
GITLAB_OAUTH_CALLBACK_PATHPath of the fixed GitLab redirect URI/gitlab/callback
GITLAB_OAUTH_ALLOWED_GROUPSRestrict the connector to members of these GitLab groups (comma/space separated full-paths; subgroups included). Empty = any authenticated userunset
REDIS_URLBack OAuth broker state with Redis (survives redeploys, enables >1 replica). Unset = in-memoryunset
REDIS_KEY_PREFIXBase namespace for Redis keys (issuer host is appended)gitlab-mcp

GITLAB_TOKEN and GITLAB_READ_TOKEN are mutually exclusive; setting both is a startup error.

Removed in 1.14.0

  • GITLAB_AUTH_MODE — the three-way enum is gone. Pick your deployment shape by which env var you set; see Choose your deployment.
  • GITLAB_SHARED_ACCESS_TOKEN — rename to GITLAB_TOKEN (full access) or GITLAB_READ_TOKEN (read-only).

Old env vars trigger a deprecation warning at startup and are otherwise ignored.


Troubleshooting

"This operation requires authentication" / "Write operation requires a user token":

  • For LibreChat: check the user filled in their PAT in the MCP Tool Select Dialog (or in their customUserVars).
  • For Claude Code: confirm GITLAB_TOKEN is set in the MCP server's env block.
  • If you set GITLAB_READ_TOKEN, write operations against it are rejected by design — supply per-call user creds for writes.

Connection issues with LibreChat:

  • type: streamable-http (not sse).
  • URL is the Docker service name (http://gitlab-mcp:8008/), not localhost, when LibreChat and gitlab-mcp share a Docker network.
  • docker logs gitlab-mcp shows session init and request lines.

Schema introspection failed:

  • GitLab 12.0+ with GraphQL API enabled.
  • Verify GITLAB_URL is reachable from the container.

Debug logging:

NODE_ENV=development GITLAB_TOKEN=glpat-... npm start

Health check (HTTP mode):

curl http://localhost:8008/health

Changelog

See CHANGELOG.md. Releases before 1.14.0 are in GitHub releases.

Runtime and MCP compatibility

Node.js 22 or later is required; the container uses Node.js 24 LTS. HTTP browser clients must use an allowed origin (MCP_ALLOWED_ORIGINS); public deployments should set MCP_SERVER_URL. Native clients without an Origin header continue to work. Tool execution and input-validation failures return isError: true with a text explanation. Object results include structuredContent alongside the JSON text for older clients.

License

MIT

↑↓ navigate openesc close