Skip to main content
Glama
README.md
# @wanadev/mcp-gitlab

A Model Context Protocol (MCP) server that gives project managers **and developers** full control over GitLab **epics**, **issues**, **milestones**, **iterations**, **merge requests**, **code review**, **pipelines**, **branches**, **repository content**, **labels**, **boards**, **members**, and **governance** from Claude Desktop, Claude Code, or any MCP-compatible client.

## Why this MCP server?

Existing tools like **glab** are developer-oriented and CLI-only. `@wanadev/mcp-gitlab` covers both lenses -- planning *and* code -- through a conversational interface:

- **Epics, milestones & iterations** -- create, update, close, reopen, and link issues to epics. Track sprints with iterations (CRUD). Set health status (on track / needs attention / at risk) and view progress via Work Items API. Attach issues under epics via the modern hierarchy widget.
- **Full MR lifecycle** -- create, update, merge, rebase, approve, diff, and comment on merge requests.
- **Code review** -- create line-positioned discussions on MR diffs and resolve them.
- **CI/CD** -- list and inspect pipelines, fetch job logs, retry or cancel runs.
- **Repository content** -- list/create branches, browse the tree, read files, list commits, *and* write files (create / update / delete / atomic multi-file commits).
- **Search** -- full-text search across issues, MRs, and code blobs at project or group scope.
- **Members management** -- add, update, and remove group/project members with access levels.
- **Governance** -- list and manage protected branches and MR approval rules.
- **Uploads** -- upload images/binaries to embed in issues, MRs, and epics.
- **Labels & users** -- CRUD on labels, search users to assign work.
- **Cross-group visibility** -- query multiple GitLab groups in the same conversation (no hardcoded group ID).
- **Time tracking** -- see estimated vs. spent time on issues at a glance.
- **Comments (notes)** -- read, add, edit, and delete notes on issues, epics, and MRs.
- **Dry-run by default** -- every write operation previews what it will do before touching GitLab.

## Quick setup

### Prerequisites

- **Node.js >= 20**
- A GitLab **Personal Access Token** (PAT) with the `api` scope (or `read_api` for read-only access)
- **GitLab Premium/Ultimate** for epics and iterations (issues, milestones, MRs, labels, and boards work with all editions)

### 1. Generate a GitLab token

1. Go to **GitLab > Settings > Access Tokens**
2. Create a token with the `api` scope
3. Copy the token

### 2. Install

#### Claude Code (plugin)

```
/plugin marketplace add wanadev/gitlab-mcp
/plugin install wanadev-gitlab@wanadev-gitlab
```

Set `GITLAB_TOKEN` in your environment (`.bashrc`, `.zshrc`, or system variable). The plugin will guide you on first launch if it's missing.

#### Claude Desktop (manual config)

Add the following to your `claude_desktop_config.json`:

**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`

```json
{
  "mcpServers": {
    "gitlab": {
      "command": "npx",
      "args": ["-y", "@wanadev/mcp-gitlab"],
      "env": {
        "GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx",
        "GITLAB_BASE_URL": "https://gitlab.com",
        "GITLAB_READ_ONLY": "false"
      }
    }
  }
}
```

### 3. Restart and test

The MCP server will be available immediately. Test with: *"List my GitLab groups"*

## Environment variables

| Variable | Required | Description |
|----------|:--------:|-------------|
| `GITLAB_TOKEN` | Yes | GitLab Personal Access Token |
| `GITLAB_BASE_URL` | No | GitLab instance URL (default: `https://gitlab.com`) |
| `GITLAB_READ_ONLY` | No | Set to `true` to block all write operations |

> **Note:** There is no `GITLAB_GROUP_ID` environment variable. Every group-scoped tool takes a `group_id` parameter. Use `list_groups` to discover accessible groups -- the LLM does this automatically.

## Dry-run safety

All write tools (`create_*`, `update_*`, `close_*`, `set_*`, `add_*`) include a `dry_run` parameter that defaults to **`true`**.

| Mode | Behavior |
|------|----------|
| `dry_run: true` (default) | Returns a summary of the planned action **without** executing anything on GitLab. |
| `dry_run: false` | Executes the action for real, after the user confirms. |

This prevents accidental changes: the LLM always shows what it intends to do first and only proceeds after your approval.

## All 100 tools

### Epics (12 tools -- requires GitLab Premium/Ultimate)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `list_epics` | List epics (filter by state, search, labels) | group | -- |
| `get_epic` | Get epic details by number | group | -- |
| `create_epic` | Create an epic | group | dry_run |
| `update_epic` | Update an epic (title, description, labels, dates) | group | dry_run |
| `close_epic` | Close an epic | group | dry_run |
| `reopen_epic` | Reopen a closed epic | group | dry_run |
| `list_epic_issues` | List issues linked to an epic | group | -- |
| `add_issue_to_epic` | Link an issue to an epic | group | dry_run |
| `list_epic_notes` | List comments on an epic | group | -- |
| `add_epic_note` | Add a comment to an epic | group | dry_run |
| `update_epic_note` | Edit an existing epic comment | group | dry_run |
| `delete_epic_note` | Delete an epic comment | group | dry_run |

### Work Items (9 tools -- requires GitLab Premium/Ultimate)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `get_epic_widgets` | Get epic widgets: health status, progress, milestone, iteration, linked items | group | -- |
| `set_epic_milestone` | Associate a milestone with an epic | group | dry_run |
| `set_epic_health_status` | Set health status on an epic (onTrack / needsAttention / atRisk) | group | dry_run |
| `set_issue_health_status` | Set health status on an issue (onTrack / needsAttention / atRisk) | project | dry_run |
| `set_epic_iteration` | Associate an iteration (sprint) with an epic | group | dry_run |
| `add_linked_item` | Link work items (RELATED / BLOCKS / BLOCKED_BY) | group/project | dry_run |
| `get_work_item_id` | Resolve the WorkItem global ID for an epic or issue (required by link/unlink) | group/project | -- |
| `link_work_items` | Attach children (issues/epics) under a parent epic via hierarchy widget | group/project | dry_run |
| `unlink_work_items` | Detach children from their current parent | group/project | dry_run |

### Issues (11 tools)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `list_issues` | List issues for a group (filter by state, labels, milestone, assignee, iteration) | group | -- |
| `list_project_issues` | List issues for a single project (with iteration filter) | project | -- |
| `get_issue` | Get issue details (with time tracking) | project | -- |
| `create_issue` | Create an issue | project | dry_run |
| `update_issue` | Update an issue | project | dry_run |
| `close_issue` | Close an issue | project | dry_run |
| `reopen_issue` | Reopen a closed issue | project | dry_run |
| `list_issue_notes` | List comments on an issue | project | -- |
| `add_issue_note` | Add a comment to an issue | project | dry_run |
| `update_issue_note` | Edit an existing issue comment | project | dry_run |
| `delete_issue_note` | Delete an issue comment | project | dry_run |

### Milestones (10 tools)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `list_milestones` | List milestones for a group | group | -- |
| `get_milestone` | Get milestone details (group) | group | -- |
| `create_milestone` | Create a group milestone | group | dry_run |
| `update_milestone` | Update a group milestone | group | dry_run |
| `close_milestone` | Close a group milestone | group | dry_run |
| `list_project_milestones` | List milestones at the project level (the only way on GitLab CE/Free) | project | -- |
| `get_project_milestone` | Get a project milestone | project | -- |
| `create_project_milestone` | Create a project milestone | project | dry_run |
| `update_project_milestone` | Update a project milestone | project | dry_run |
| `close_project_milestone` | Close a project milestone | project | dry_run |

### Merge Requests (18 tools)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `list_merge_requests` | List merge requests for a group | group | -- |
| `get_merge_request` | Get merge request details | project | -- |
| `create_merge_request` | Open a new MR (source/target branch, title, description) | project | dry_run |
| `update_merge_request` | Update an MR (title, description, labels, assignees, reviewers) | project | dry_run |
| `merge_merge_request` | Merge an MR (optionally squash or delete source branch) | project | dry_run |
| `approve_merge_request` | Approve an MR | project | dry_run |
| `rebase_merge_request` | Asynchronously rebase the MR's source branch on top of target | project | dry_run |
| `get_mr_diff` | Get the diff/changes for an MR (also returns diff_refs for line-positioned comments) | project | -- |
| `list_mr_notes` | List comments on an MR | project | -- |
| `add_mr_note` | Add a comment to an MR | project | dry_run |
| `update_mr_note` | Edit an existing MR comment | project | dry_run |
| `delete_mr_note` | Delete an MR comment | project | dry_run |
| `create_mr_discussion` | Code-review comment on a specific diff line (requires diff_refs from get_mr_diff) | project | dry_run |
| `resolve_mr_discussion` | Mark an MR discussion as resolved or unresolved | project | dry_run |
| `list_mr_approval_rules` | List project-level MR approval rules | project | -- |
| `create_mr_approval_rule` | Create a project-level approval rule (eligible users/groups, branches) | project | dry_run |
| `update_mr_approval_rule` | Update an existing approval rule | project | dry_run |
| `delete_mr_approval_rule` | Delete an approval rule | project | dry_run |

### CI/CD (5 tools)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `list_pipelines` | List pipelines for a project (filter by ref, status, etc.) | project | -- |
| `get_pipeline` | Get pipeline details and jobs | project | -- |
| `get_job_log` | Fetch the trace/log of a CI job | project | -- |
| `retry_pipeline` | Retry a failed pipeline | project | dry_run |
| `cancel_pipeline` | Cancel a running pipeline | project | dry_run |

### Branches & Repository (12 tools)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `list_branches` | List branches in a project | project | -- |
| `create_branch` | Create a branch from a ref | project | dry_run |
| `list_repository_tree` | List the files/folders in a repo path | project | -- |
| `get_file` | Read a file's contents at a given ref | project | -- |
| `list_commits` | List commits (filter by ref, author, date) | project | -- |
| `create_file` | Create a new file via the API (commit included) | project | dry_run |
| `update_file` | Update a file (optimistic concurrency via last_commit_id) | project | dry_run |
| `delete_file` | Delete a file in a single commit | project | dry_run |
| `commit_files` | Atomic multi-file commit (create/update/delete/move/chmod) | project | dry_run |
| `list_protected_branches` | List protected branches with their access levels | project | -- |
| `protect_branch` | Protect a branch with push/merge/unprotect access levels | project | dry_run |
| `unprotect_branch` | Remove branch protection | project | dry_run |

### Iterations (3 tools -- requires GitLab Premium/Ultimate)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `list_iterations` | List iterations/sprints (filter by state: upcoming, current, closed) | group | -- |
| `create_iteration` | Create an iteration | group | dry_run |
| `update_iteration` | Update an iteration (title, dates, state) | group | dry_run |

### Utilities (20 tools)

| Tool | Description | Scope | Write |
|------|-------------|:-----:|:-----:|
| `list_groups` | Discover accessible groups | -- | -- |
| `list_projects` | List projects in a group | group | -- |
| `list_group_members` | List members of a group | group | -- |
| `add_group_member` | Add a user to a group with an access level | group | dry_run |
| `add_project_member` | Add a user to a project with an access level | project | dry_run |
| `update_member_access_level` | Change a member's access level (group or project) | group/project | dry_run |
| `remove_member` | Remove a user from a group or project | group/project | dry_run |
| `list_labels` | List labels for a group | group | -- |
| `create_label` | Create a label | group | dry_run |
| `update_label` | Update a label (name, color, description) | group | dry_run |
| `delete_label` | Delete a label | group | dry_run |
| `list_boards` | List issue boards for a group | group | -- |
| `list_workitem_statuses` | List available work item statuses (for health filtering) | group | -- |
| `get_work_item_type_id` | Resolve the global ID of a Work Item type (EPIC, ISSUE, TASK, …) for a namespace | group/project | -- |
| `upload_file` | Upload a file to a project and return the Markdown snippet to embed | project | dry_run |
| `search_issues` | Full-text search issues at project or group scope | group/project | -- |
| `search_merge_requests` | Full-text search MRs at project or group scope | group/project | -- |
| `search_blobs` | Full-text search code/file content at project or group scope | group/project | -- |
| `search_users` | Search GitLab users by name or username | -- | -- |
| `get_current_user` | Check connection (current user info) | -- | -- |

> A `gitlab_setup` helper tool is also registered automatically when `GITLAB_TOKEN` is missing or `GITLAB_BASE_URL` is invalid — it guides the user through configuration and is not counted in the 100 above.

## Example prompts

- *"List my GitLab groups"*
- *"Show open epics in group 42"*
- *"What's the health status and progress of epic #5?"*
- *"Set epic #12 health status to 'needs attention'"*
- *"Link epic #3 as blocking issue #45"*
- *"List issues labeled `bug` in the wanadev group"*
- *"Create an epic called 'Homepage Redesign' in group 42 with a deadline of April 30"*
- *"Which issues are linked to epic #5 in the wanadev group?"*
- *"Close issue #15 in project 789"*
- *"Who are the members of the wanadev group?"*
- *"Show me all open merge requests in group 42"*
- *"How much time has been spent on issue #23 in project 456?"*
- *"Add a comment on epic #3 in group 42: Specs validated, ready for dev"*
- *"What milestones are coming up in the wanadev group?"*
- *"List the boards for group 42"*
- *"Open an MR from `feat/new-login` to `main` in project 789, then request review from @alice"*
- *"Show me the diff of MR !42 and summarize the changes"*
- *"List the last 5 failed pipelines on `main` in project 789 and fetch the log of the failing job"*
- *"Retry pipeline 12345 in project 789"*
- *"Create a `release/2026.05` branch from `main` in project 789"*
- *"Read the contents of `package.json` on branch `main` in project 789"*
- *"Find a user named 'Jean Dupont' and assign them issue #15"*
- *"Rebase MR !42 on top of main"*
- *"Comment on line 73 of `src/auth.ts` in MR !42 — this should be wrapped in a try/catch"*
- *"Search the wanadev group for issues mentioning 'CVE-2024'"*
- *"Show me where `processPayment` is used across project 789"*
- *"Add @alice as Developer (level 30) to the kp1/api project"*
- *"Protect `main` on project 789 — only maintainers can merge, no direct pushes"*
- *"Set up a 2-approval rule named 'security' on project 789, applies to all protected branches"*
- *"Attach issues #12, #14, and #18 as children of epic #5 in the wanadev group"*
- *"Upload `screenshot.png` to project 789 and embed it in issue #15"*

## Use cases for project managers

### Sprint planning

> *"List the active milestones in the wanadev group. For each one, show me the open issues and how many are unassigned."*

Claude calls `list_milestones` → `list_issues` per milestone → summarizes the gaps. You see at a glance what's on track and what needs attention.

### Daily standup prep

> *"Show me all open MRs in the [project] group that have been waiting for review for more than 3 days. Also list any issues that were closed yesterday."*

Claude calls `list_merge_requests` (state: opened) → `list_issues` (state: closed, sort: updated_at) → gives you a ready-made standup brief.

### Epic progress review

> *"Give me a status report on epic #12 in group wanadev: how many issues are done vs. open, what's the total time spent, and list the latest comments."*

Claude chains `get_epic` → `list_epic_issues` → reads `time_stats` from each issue → `list_epic_notes` → returns a structured progress report.

### Cross-group dashboard

> *"Compare the open issue count across my three groups: wanadev, [project], and infra. Which group has the most overdue issues?"*

Claude calls `list_groups` → `list_issues` for each group with due date filtering → builds a comparison table.

### Quick issue triage

> *"In the [project] group, find all issues labeled 'urgent' with no assignee. Assign them to @jean and add a comment saying 'Triaged in weekly review'."*

Claude calls `list_issues` (labels: urgent) → for each unassigned issue, dry-runs `update_issue` (assignee) + `add_issue_note` → shows you the plan → you confirm → done.

### Milestone closure

> *"Close milestone 'Sprint 14' in group wanadev. Before that, show me any issues still open in it."*

Claude calls `list_issues` (milestone: Sprint 14, state: opened) → warns you about remaining items → dry-runs `close_milestone` → you confirm.

## Development

```bash
git clone https://github.com/wanadev/gitlab-mcp.git
cd gitlab-mcp
npm install
npm run build       # Build ESM (tsc)
npm run typecheck   # TypeScript type checking
npm run dev         # Build in watch mode
```

## Notes

- **`issue_id` vs `issue_iid`** -- `add_issue_to_epic` requires the global issue ID (not the `#iid` displayed in the project). Both `list_issues` and `list_epic_issues` return both values.
- **Read-only mode** -- With `GITLAB_READ_ONLY=true`, any create/update/close attempt returns a clear error.
- **403 on epics** -- Epic endpoints require a GitLab Premium or Ultimate license.
- **Multi-group workflow** -- You can work across several groups in a single conversation. The LLM will call `list_groups` to discover them, then pass the right `group_id` to each tool.
- **GitLab CE / Free support** -- The server introspects your GitLab instance at startup and detects whether `Issue.weight` / `Issue.epic` are available (Premium/Ultimate-only fields). On Free/CE, those fields are silently omitted from queries and mutations, so all issue tools work — only epic-specific features remain Premium. The detected tier is logged at startup: `[..., Premium/Ultimate]` or `[..., Free/CE]`.

TDQS

B3.4/5.0

Scored across 100 tools

Disambiguation4/5

Most tools follow a clear resource+action pattern, so group vs project variants and note/diff/discussion tools are generally distinguishable. However, add_issue_to_epic and link_work_items explicitly overlap (the latter is described as the replacement), and list_issues/list_project_issues plus several similar work-item helpers add some ambiguity.

Naming Consistency4/5

The vast majority use consistent snake_case verb_noun names (list_*, get_*, create_*, update_*, close_*, delete_*). Minor inconsistencies: `mr` abbreviations vs `merge_request`, and `workitem` vs `work_item` in list_workitem_statuses, prevent a perfect score.

Tool Count1/5

100 tools is an extreme count for an MCP surface, far beyond typical scoped servers, and includes redundant group/project variants plus legacy/new Work Item linking (add_issue_to_epic vs link_work_items). Even though GitLab is broad, this bloated surface is expensive for agents to navigate and many tools feel duplicative rather than earning their place.

Completeness3/5

Core lifecycles for epics, issues, milestones, MRs, notes, labels, members, branches/files, and pipelines are well covered. Notable gaps remain: no delete issue/epic/milestone, no close/reopen/delete MR, no project member listing, no branch deletion, and no project/group creation, so agents will hit dead ends in some administrative workflows.

Maintenance

ActivityInactive
ResponsivenessResponsive