Verified MCP server: GitLab

The GitLab MCP server, enumerated live on a real Mac.

  • Tools enumerated2026-08-03macOS 15.7.7
  • Package@zereight/mcp-gitlab 2.1.46
  • Self-reportedzereight-gitlab-mcp-server 2.1.46
  • MCP protocol2025-06-18
  • Runtimev24.18.0

The tool list below is a live capture from the running server; an authenticated tool call is still pending credentials.

The short answer

Last verified 2026-08-03

The GitLab MCP server connects an AI assistant to GitLab, so it can read and act on your projects, merge requests, issues, branches and pipelines instead of you clicking through the web interface. It is a community server rather than a GitLab product: it is published to npm as @zereight/mcp-gitlab by the developer zereight, it is MIT licensed, and it runs on your Mac over stdio. There was an official reference server once, @modelcontextprotocol/server-gitlab, but npm marks it as no longer supported and its repository is archived, while this package shipped a release the day before we ran it and is downloaded roughly twenty times more often, which is why it is the one on this page. Its credential is a GitLab personal access token in one environment variable, and the server will not even start with that variable empty. What we proved on the wire, and what still waits on a real token, is laid out below. We started it with a placeholder in that variable rather than a real token: the server started, completed the MCP handshake, and answered tools/list with all 116 tools, which is the table below, word for word. The call we made to ask GitLab who we were signed in as, whoami, left the Mac and came back with GitLab's own 401 Unauthorized, because a placeholder is not a token. So the tool table is enumerated live and every error on this page is output we captured, while a successful authenticated call still waits for a token you create yourself. [1][3][2][8][9]

  • Tools

    116, enumerated live

  • Credentials

    GitLab personal access token [5][10]

  • Transport

    stdio, runs on your Mac [1][4]

  • Maintainer

    zereight, community [1][3]

  • Downloads

    114,673 last week [2]

  • License

    MIT [1][3]

Verification

How this MCP server was verified.

Method

We spawned the server with npx over stdio with two environment variables and nothing else: GITLAB_PERSONAL_ACCESS_TOKEN holding a dummy glpat-style placeholder rather than a real token, and GITLAB_API_URL set to https://gitlab.com/api/v4, which the server logged straight back to us as the API URL it had configured. It completed the MCP initialize handshake, introduced itself as zereight-gitlab-mcp-server 2.1.46 on protocol 2025-06-18, and answered tools/list with all 116 tools, and the table below is that answer, word for word. Before any of it, the server printed the two notices quoted as the startup banner, which every start of this version prints because two branch tools belong to two toolsets at once. That run made no tool call. Four more runs of the same command followed on the same Mac within two minutes. In the first we called whoami, the tool that asks GitLab who the token belongs to, and GitLab answered 401 Unauthorized after 294 milliseconds: the call left the Mac and was refused at the other end, because a placeholder is not a token, and that reply is the sample call below. In the second we called health_check, which came back as an ordinary reply whose payload reported authentication false. In the third we sent search_repositories a number where it expects text, and the server refused it in 6 milliseconds without contacting GitLab. The fourth was started with no token in the environment at all, and stopped instead of starting. The tools are therefore enumerated live and every error on this page is real output, while a successful authenticated call still waits for a GitLab personal access token you create yourself.

Startup banner

Tool "get_branch" is defined in multiple toolsets: "merge_requests" and "branches"
Tool "list_branches" is defined in multiple toolsets: "merge_requests" and "branches"

One real tool call

tools/call whoami {}
GitLab API error: 401 Unauthorized
{"message":"401 Unauthorized"}

Real lines from the captured result: enough to prove the call answered.

Tools

Every tool, enumerated from the running server.

The server answered tools/list with 116 tools on 2026-08-03. The names, descriptions and parameters below are its own words, copied from that response and never edited.

ToolWhat it does
merge_merge_requestproject_id*merge_request_iidauto_mergemerge_commit_messagemerge_when_pipeline_succeedsshould_remove_source_branchsquash_commit_messagesquash

Merge a merge request

approve_merge_requestproject_id*merge_request_iid*shaapproval_password

Approve a merge request

unapprove_merge_requestproject_id*merge_request_iid*

Unapprove a merge request

get_merge_request_approval_stateproject_id*merge_request_iid*

Get merge request approval details including approvers

get_merge_request_conflictsproject_id*merge_request_iid*

Get the conflicts of a merge request

list_merge_request_pipelinesproject_id*merge_request_iid*pageper_page

List pipelines for a merge request with pagination

create_or_update_fileproject_id*file_path*content*commit_message*branch*previous_pathlast_commit_idcommit_id

Create or update a file in a GitLab project

search_repositoriessearchquerypageper_page

Search for GitLab projects

create_repositoryname*namespace_iddescriptionvisibilityinitialize_with_readme

Create a new GitLab project

create_groupname*path*descriptionvisibilityparent_id

Create new group or subgroup

get_file_contentsproject_idfile_pathpathref

Get contents of a file or directory from a GitLab project

push_filesproject_id*branch*files*commit_message*

Push multiple files in a single commit

create_issueproject_id*title*descriptionassignee_idslabelsmilestone_idissue_typeweight

Create a new issue

create_merge_requestproject_id*title*descriptionsource_branch*target_branch*target_project_idassignee_idsreviewer_idslabelsdraftallow_collaborationremove_source_branchsquash

Create a new merge request

fork_repositoryproject_id*namespace

Fork a project to your account or specified namespace

create_branchproject_id*branch*ref

Create a new branch

get_branchproject_id*branch_name*

Get branch details (commit, protection status)

list_branchesproject_id*searchpageper_page

List branches in project with search filter

delete_branchproject_id*branch_name*

Delete branch from project

list_protected_branchesproject_id*searchpageper_page

List protected branches in a project, supports search filter

get_protected_branchproject_id*branch_name*

Get details of a single protected branch (access levels, force push settings)

protect_branchproject_idbranch_name*namepush_access_levelmerge_access_levelunprotect_access_levelallow_force_pushcode_owner_approval_required

Protect a repository branch (set push/merge/unprotect access levels)

unprotect_branchproject_id*branch_name*

Remove protection from a previously protected branch

update_default_branchproject_id*default_branch*

Change the default branch of a project

get_merge_requestproject_id*merge_request_iidsource_branchinclude_summaries

Get details of a merge request (mergeRequestIid or branchName required). Set include_summaries=true for deployment/commit/approval summaries

get_merge_request_diffsproject_id*merge_request_iidsource_branchviewexcluded_file_patterns

Get the changes/diffs of a merge request (mergeRequestIid or branchName required)

list_merge_request_changed_filesproject_id*merge_request_iidsource_branchexcluded_file_patterns

List changed file paths in a merge request without diff content (mergeRequestIid or branchName required)

list_merge_request_diffsproject_id*merge_request_iidsource_branchpageper_pageunidiff

List merge request diffs with pagination (mergeRequestIid or branchName required)

get_merge_request_file_diffproject_id*merge_request_iidsource_branchfile_paths*unidiff

Get diffs for specific files from a merge request (mergeRequestIid or branchName required)

list_merge_request_versionsproject_id*merge_request_iid*

List all versions of a merge request

get_merge_request_versionproject_id*merge_request_iid*version_id*unidiff

Get a specific version of a merge request

get_branch_diffsproject_id*from*to*straightexcluded_file_patterns

Get diffs between two branches or commits

update_merge_requestproject_id*merge_request_iidsource_branchtitledescriptiontarget_branchassignee_idsreviewer_idslabelsstate_eventremove_source_branchsquashdraftmilestone_id

Update a merge request (mergeRequestIid or branchName required)

create_noteproject_id*noteable_type*noteable_iid*body*

Create a new note (comment) to an issue or merge request

create_merge_request_threadproject_id*merge_request_iid*body*positioncreated_at

Create a new thread on a merge request

resolve_merge_request_threadproject_id*merge_request_iid*discussion_id*resolved*

Resolve a thread on a merge request

mr_discussionsproject_id*merge_request_iid*pageper_page

List discussion items for a merge request

delete_merge_request_discussion_noteproject_id*merge_request_iid*discussion_id*note_id*

Delete a discussion note on a merge request

update_merge_request_discussion_noteproject_idmerge_request_iiddiscussion_idnote_idbodyresolved

Update a discussion note on a merge request

create_merge_request_discussion_noteproject_id*merge_request_iid*discussion_id*body*created_at

Add a new discussion note to an existing merge request thread

create_merge_request_noteproject_id*merge_request_iid*body*

Add a new note to a merge request

delete_merge_request_noteproject_id*merge_request_iid*note_id*

Delete an existing merge request note

get_merge_request_noteproject_id*merge_request_iid*note_id*

Get a specific note for a merge request

get_merge_request_notesproject_id*merge_request_iid*sortorder_byper_pagepage

List notes for a merge request

update_merge_request_noteproject_id*merge_request_iid*note_id*body*

Modify an existing merge request note

get_draft_noteproject_id*merge_request_iid*draft_note_id*

Get a single draft note from a merge request

list_draft_notesproject_id*merge_request_iid*

List draft notes for a merge request

create_draft_noteproject_id*merge_request_iid*body*in_reply_to_discussion_idpositionresolve_discussion

Create a draft note for a merge request

update_draft_noteproject_id*merge_request_iid*draft_note_id*bodypositionresolve_discussion

Update an existing draft note

delete_draft_noteproject_id*merge_request_iid*draft_note_id*

Delete a draft note

publish_draft_noteproject_id*merge_request_iid*draft_note_id*

Publish a single draft note

bulk_publish_draft_notesproject_id*merge_request_iid*reviewer_statenoteinternal

Publish all draft notes for a merge request. Optionally sets reviewer_state and posts a summary note (GitLab 19.2+). Can set reviewer_state even with no drafts.

list_merge_request_emoji_reactionsproject_id*merge_request_iid*

List all emoji reactions on a merge request

list_merge_request_note_emoji_reactionsproject_id*merge_request_iid*note_id*discussion_id

List all emoji reactions on a merge request note. Pass discussion_id for discussion thread replies.

create_merge_request_emoji_reactionproject_id*merge_request_iid*name*

Add an emoji reaction to a merge request (e.g. thumbsup, rocket, eyes)

delete_merge_request_emoji_reactionproject_id*merge_request_iid*award_id*

Remove an emoji reaction from a merge request

create_merge_request_note_emoji_reactionproject_id*merge_request_iid*note_id*discussion_idname*

Add an emoji reaction to a merge request note. Pass discussion_id for discussion thread replies.

delete_merge_request_note_emoji_reactionproject_id*merge_request_iid*note_id*discussion_idaward_id*

Remove an emoji reaction from a merge request note. Pass discussion_id for discussion thread replies.

update_issue_noteproject_idissue_iiddiscussion_idnote_idbodyresolved

Modify an existing issue thread note

create_issue_noteproject_id*issue_iid*discussion_idbody*created_at

Add a note to an issue, optionally replying to a discussion thread

list_issue_emoji_reactionsproject_id*issue_iid*

List all emoji reactions on an issue

list_issue_note_emoji_reactionsproject_id*issue_iid*note_id*discussion_id

List all emoji reactions on an issue note. Pass discussion_id for discussion thread replies.

create_issue_emoji_reactionproject_id*issue_iid*name*

Add an emoji reaction to an issue (e.g. thumbsup, rocket, eyes)

delete_issue_emoji_reactionproject_id*issue_iid*award_id*

Remove an emoji reaction from an issue

create_issue_note_emoji_reactionproject_id*issue_iid*note_id*discussion_idname*

Add an emoji reaction to an issue note. Pass discussion_id for discussion thread replies.

delete_issue_note_emoji_reactionproject_id*issue_iid*note_id*discussion_idaward_id*

Remove an emoji reaction from an issue note. Pass discussion_id for discussion thread replies.

list_issuesproject_idassignee_idassignee_usernameauthor_idauthor_usernameconfidentialcreated_aftercreated_beforedue_datelabelsmilestoneissue_typeiteration_idscopesearchstateupdated_afterupdated_beforewith_labels_detailspageper_page

List issues (default: created by current user; use scope='all' for all)

my_issuesproject_idstatelabelsmilestonesearchcreated_aftercreated_beforeupdated_afterupdated_beforeper_pagepage

List issues assigned to the authenticated user

get_issueproject_id*issue_iid*full_response

Get details of a specific issue. Returns a slim milestone by default; set full_response=true for the complete milestone object

update_issueproject_id*issue_iid*titledescriptionassignee_idsconfidentialdiscussion_lockeddue_datelabelsmilestone_idstate_eventweightissue_typefull_response

Update an issue. Returns a slim confirmation by default; set full_response=true for the complete updated issue object

update_issue_description_patchproject_id*issue_iid*patch_type*patch*dry_runcreate_noteallow_multiple

Apply a patch (search/replace or unified diff) to an issue description. Reduces token usage by allowing small changes without sending the full description. Supports dry_run to preview changes and create_note to summarize updates.

delete_issueproject_id*issue_iid*

Delete an issue

list_todosactionauthor_idproject_idgroup_idstatetypepageper_page

List GitLab to-do items for the current user

mark_todo_doneid*

Mark a GitLab to-do item as done

mark_all_todos_done

Mark all pending GitLab to-do items as done for the current user

list_issue_linksproject_id*issue_iid*

List all issue links for a specific issue

list_issue_discussionsproject_id*issue_iid*pageper_page

List discussions for an issue

get_issue_linkproject_id*issue_iid*issue_link_id*

Get a specific issue link

create_issue_linkproject_id*issue_iid*target_project_id*target_issue_iid*link_type

Create an issue link between two issues

delete_issue_linkproject_id*issue_iid*issue_link_id*

Delete an issue link

list_namespacessearchownedpageper_page

List all namespaces (users and groups) available to the current user. Filter by kind='group' for groups only.

get_namespacenamespace_id*

Get details of a namespace (user or group) by ID or path. Groups are namespaces with kind='group'.

verify_namespacepath*parent_id

Verify if a namespace path exists. Use parent_id to scope the check to a specific parent namespace — required for nested namespaces where the same path may exist under different parents.

get_projectproject_id*

Get details of a specific project

list_projectssearchsearch_namespacesownedmembershipsimplearchivedvisibilityorder_bysortwith_issues_enabledwith_merge_requests_enabledmin_access_leveltopicpageper_page

List projects accessible by the current user

update_projectproject_idnamepathdescriptiondefault_branchvisibilitytopicsrequest_access_enabledremove_source_branch_after_mergeonly_allow_merge_if_pipeline_succeedsonly_allow_merge_if_all_discussions_are_resolvedsquash_optionmerge_methodissues_access_levelmerge_requests_access_levelbuilds_access_levelwiki_access_levelsnippets_access_levelcontainer_registry_access_levelenvironments_access_levelforking_access_levelpackage_registry_access_levelpages_access_level

Update project settings such as description, visibility, default branch, and feature access levels

list_project_membersproject_id*queryuser_idsskip_usersinclude_inheritanceper_pagepage

List members of a GitLab project

list_group_membersgroup_id*queryuser_idsskip_usersinclude_inheritanceper_pagepage

List members of a GitLab group with optional name or username search

list_labelsproject_id*with_countsinclude_ancestor_groupssearchpageper_page

List labels for a project

get_labelproject_id*label_id*include_ancestor_groups

Get a single label from a project

create_labelproject_id*name*color*descriptionpriority

Create a new label in a project

update_labelproject_id*label_id*new_namecolordescriptionpriority

Update an existing label in a project

delete_labelproject_id*label_id*

Delete a label from a project

list_group_projectsgroup_id*include_subgroupssearchorder_bysortarchivedvisibilitywith_issues_enabledwith_merge_requests_enabledmin_access_levelwith_programming_languagestarredstatisticswith_custom_attributeswith_security_reportstopicpageper_page

List projects in a group

get_repository_treeproject_id*pathrefrecursiveper_pagepage_tokenpagination

List files and directories in a repository

validate_ci_lintproject_id*content*dry_runinclude_jobsref

Validate provided GitLab CI/CD YAML content for a project

validate_project_ci_lintproject_id*content_refdry_rundry_run_refinclude_jobs

Validate an existing .gitlab-ci.yml configuration for a project

list_ci_catalog_resourcessearchfirstaftergroup_idsscopesorttopicsverification_level

List GitLab CI/CD Catalog resources/components visible to the user

get_ci_catalog_resourceversion_limitcomponent_limitcomponent_nameinclude_readmeidfull_path

Get details for a GitLab CI/CD Catalog resource, including versions and components

list_merge_requestsproject_idassignee_idassignee_usernameauthor_idauthor_usernamereviewer_idreviewer_usernameapproved_by_usernamescreated_aftercreated_beforeupdated_afterupdated_beforelabelsmilestonescopesearchstateorder_bysorttarget_branchsource_branchwipwith_labels_detailspageper_page

List merge requests (without project_id: user's MRs; with project_id: project MRs)

get_usersusernames*

Get GitLab user details by usernames

get_useruser_id*

Get user details by ID

whoami

Get current authenticated user details

list_commitsproject_id*ref_namesinceuntilpathauthorallwith_statsfirst_parentordertrailerspageper_page

List repository commits with filtering options

get_commitproject_id*sha*stats

Get details of a specific commit

get_commit_diffproject_id*sha*full_diff

Get changes/diffs of a specific commit

get_file_blameproject_idfile_path*ref*range_startrange_end

Get git blame for a file at a given ref. Each entry maps a contiguous range of source lines to the commit that last changed them (id, author, authored_date, message). Use range_start/range_end to limit blame to specific lines.

list_commit_statusesproject_id*sha*refstagenamepipeline_idorder_bysortallpageper_page

List statuses for a commit

create_commit_statusproject_id*sha*state*refnamecontexttarget_urldescriptioncoveragepipeline_id

Create or update the status of a commit

list_group_iterationsgroup_id*statesearchsearch_ininclude_ancestorsinclude_descendantsupdated_beforeupdated_afterpageper_page

List group iterations with filtering options

upload_markdownproject_id*file_path*

Upload a file for use in markdown content

download_attachmentproject_id*secret*filename*local_path

Download an uploaded file from a project (images returned as base64; use local_path to save to disk)

health_check

Verify server status and authentication. When authenticated, also reports the GitLab instance version from GET /api/v4/version (version, revision, enterprise). Version lookup failures do not fail the health check — those fields are omitted.

list_eventsactiontarget_typebeforeafterscopesortpageper_page

List events for the authenticated user (before/after: YYYY-MM-DD)

get_project_eventsproject_id*actiontarget_typebeforeaftersortpageper_page

List events for a project (before/after: YYYY-MM-DD)

discover_toolscategory

Discover and activate additional tool categories for this session. Available categories: merge_requests, issues, repositories, branches, projects, labels, ci, groups, pipelines, milestones, wiki, releases, tags, users, workitems, webhooks, search, variables, dependency_proxy, vulnerabilities. Already-active categories are listed in the response.

Parameters marked with * are required.

Setup

Working configs, one per app.

Copy the block for the app you use. Each one is the configuration this server was verified with.

Claude Desktop

Open the file ~/Library/Application Support/Claude/claude_desktop_config.json (in Claude Desktop: Settings, then Developer, then Edit Config) and add:

{
  "mcpServers": {
    "gitlab": {
      "command": "npx",
      "args": [
        "-y",
        "@zereight/mcp-gitlab"
      ],
      "env": {
        "GITLAB_PERSONAL_ACCESS_TOKEN": "glpat-your-token",
        "GITLAB_API_URL": "https://gitlab.com/api/v4"
      }
    }
  }
}

Paste your own token in place of glpat-your-token, and if your team hosts its own GitLab, change the URL to your instance with /api/v4 on the end. The command, the args and both variable names are exactly what we ran, and the server logged that API URL back to us as it started. If you install the package globally instead, the command becomes zereight-mcp-gitlab and the args disappear. Quit and reopen Claude Desktop afterwards. [12][5][4]

Claude Code

One command in your terminal:

claude mcp add gitlab --transport stdio --scope local \
  --env GITLAB_PERSONAL_ACCESS_TOKEN=glpat-your-token \
  --env GITLAB_API_URL=https://gitlab.com/api/v4 \
  -- npx -y @zereight/mcp-gitlab

This is the project's own Claude Code command with one change: it ends in npx -y @zereight/mcp-gitlab, which installs nothing, where the guide ends in the globally installed binary zereight-mcp-gitlab. Everything after the double dash is the exact command Claude Code will run. Check it registered with claude mcp list, and use /mcp inside Claude Code to see its status. [13][6]

Cursor

Add to ~/.cursor/mcp.json for every project, or to .cursor/mcp.json inside one project:

{
  "mcpServers": {
    "gitlab": {
      "command": "npx",
      "args": [
        "-y",
        "@zereight/mcp-gitlab"
      ],
      "env": {
        "GITLAB_PERSONAL_ACCESS_TOKEN": "glpat-your-token",
        "GITLAB_API_URL": "https://gitlab.com/api/v4",
        "GITLAB_READ_ONLY_MODE": "false"
      }
    }
  }
}

Cursor picks the file up on restart. The third variable is the one worth knowing: set GITLAB_READ_ONLY_MODE to true and the server offers only its read-only tools, which is a sensible way to start. Worth knowing too: the project's own Cursor guide describes itself as a best-effort community pattern rather than repository-verified Cursor documentation, so if Cursor changes its settings screen, take the wrapper from Cursor's own docs and keep the command, args and env values above. [14][7][5]

Routines

No JSON file and no terminal. In Routines: Settings, then Assistant, then Connections, then Add MCP Server. Switch the form to Command (stdio) and enter:

Name        GitLab
Command     npx
Arguments   -y @zereight/mcp-gitlab

Environment Variables
GITLAB_PERSONAL_ACCESS_TOKEN    glpat-your-token
GITLAB_API_URL                  https://gitlab.com/api/v4

The Arguments field splits on spaces, so keep it to exactly those three words. The token belongs in Environment Variables, never in Arguments. Change the API URL if your team hosts its own GitLab, keeping the /api/v4 ending. Click Test Connection first: a working server answers with its tool count, 116 for this one. A green test means the server started, not that GitLab accepted your token, so ask the assistant a small read-only question next. [15][5]

No terminal needed

Set the GitLab MCP server up without the terminal.

If you have never opened Terminal and never want to, this is your path. Routines is a Mac app that runs MCP servers for you: fill in a few fields once, and this server's tools are available to your AI in chat and in scheduled routines. Unlike a file server, this one needs a token from GitLab first, so that is step two.

  1. 01

    Install Routines

    Download the app from getroutines.ai/download, drag it to Applications, and sign in.

  2. 02

    Create a GitLab access token

    In GitLab, select your avatar in the upper-right corner, then Edit profile, then Access and Personal access tokens in the left sidebar, then Generate token. Give it a name and an expiry date you are comfortable with, and pick a scope: api grants complete read and write access to the API, read_api grants read access only, which is the one to start with if you want the assistant to look and not touch. GitLab shows the token once, so copy it before you close the page.

  3. 03

    Open the MCP settings

    Click your account at the bottom of the sidebar and choose Settings. Open the Assistant section, then the Connections tab, scroll to MCP Servers, and click Add MCP Server.

  4. 04

    Choose Command (stdio)

    Switch the form to Command (stdio): this server is a command your Mac runs, not a web address. Name: GitLab. Command: npx. Arguments: -y @zereight/mcp-gitlab, exactly those three words.

  5. 05

    Add the token and say which GitLab

    In Environment Variables add GITLAB_PERSONAL_ACCESS_TOKEN with the token you copied. Add a second one, GITLAB_API_URL, set to https://gitlab.com/api/v4 for the public service, or to your company instance with /api/v4 on the end. This step is not optional: with that first variable empty the server stops instead of starting, which we saw for ourselves.

  6. 06

    Test, add, and use it

    Click Test Connection: Routines starts the server and reports how many tools it found, one hundred and sixteen for this one. Then click Add Server. Ask a read-only question first, such as who you are signed in to GitLab as, because that is what proves the token itself works. To let a scheduled routine use these tools, open the routine, find the Tools & connections card, and tick the server under Apps.

Routine ideas

Routines worth scheduling.

Once the server is connected, a scheduled routine can use its tools while you are away. Copy a prompt, paste it into Routines, and pick a time.

A Monday review queue

Prompt

Every Monday at 9:00, list the open merge requests assigned to me in GitLab, note which ones have had no comment or new commit for more than three days and which still have unresolved discussion threads, and save the list as review-queue.md with the oldest first.

A daily build check

Prompt

Every weekday at 8:30, check the most recent commit statuses on the default branch of my main GitLab project and write one plain-English line telling me whether the last build passed or failed, naming the failing job and the commit title if it failed.

An issue triage sweep

Prompt

Every weekday at 17:00, list the GitLab issues opened in my project today that have no labels, summarise each one in a line, suggest a label for it, and save the result as triage.md. Do not change anything in GitLab.

Troubleshooting

The errors we hit, and what fixed them.

Real errors captured during the verification run, printed exactly as the server returned them.

The server stops the moment it starts

What you see

GITLAB_PERSONAL_ACCESS_TOKEN environment variable is not set
Either set GITLAB_PERSONAL_ACCESS_TOKEN or enable OAuth with GITLAB_USE_OAUTH=true

The fix

A token is not optional here, and this catches people out because plenty of other MCP servers start happily without their credential and only fail on the first call. Started with nothing in that variable, this one logged these two lines and stopped rather than waiting for your client, so the client reports a server that will not start. Put your token in GITLAB_PERSONAL_ACCESS_TOKEN, or set GITLAB_USE_OAUTH=true to use the browser sign-in flow instead, and start it again.

Every tool call comes back 401 Unauthorized

What you see

GitLab API error: 401 Unauthorized
{"message":"401 Unauthorized"}

The fix

This is the exact reply our own sample call received, a whoami call carrying a placeholder token we made up. In real use it means GitLab rejected the token: it expired, it was revoked, it was pasted with a character missing, or it belongs to a different GitLab than the one GITLAB_API_URL points at. Create a fresh token, check the URL ends in /api/v4, and restart the server so it reads the new value. One thing to know while you are debugging: this server checks the shape of a tool call on your Mac before it contacts GitLab, and answers a badly shaped one with the Invalid arguments complaint below instead, so a 401 is about the token rather than about the arguments.

A tool call comes back complaining about an argument

What you see

Invalid arguments: search: Expected string, received number

The fix

This one is the server talking, not GitLab. Every tool declares what kind of value each argument takes, and a call that does not match is refused on your Mac: our probe handed search_repositories a number where it expects text, and the refusal came back in 6 milliseconds, with nothing sent to gitlab.com and nothing changed there. In real use it means the assistant guessed the wrong kind of value, so name the tool and the value you want in plain words and ask it to try again. The table above lists every argument this server accepts.

Two warnings about branch tools appear at every start

What you see

Tool "get_branch" is defined in multiple toolsets: "merge_requests" and "branches"
Tool "list_branches" is defined in multiple toolsets: "merge_requests" and "branches"

The fix

Nothing is wrong. Every start of version 2.1.46 prints these two lines before anything else, because those two tools are listed in the merge_requests toolset and the branches toolset at once. They are the server describing its own catalogue, both tools work normally, and clients that paint server output red make them look worse than they are.

Your MCP client cannot start the server at all

The fix

The npx command belongs to Node.js. If Node is not installed on your Mac, every client on this page fails at the spawn step before the server can say anything. Install Node from nodejs.org, restart your MCP client, and try again.

FAQ

Questions people ask.

What is the GitLab MCP server?

It is a Model Context Protocol server that puts the GitLab API in front of an AI assistant, published to npm as @zereight/mcp-gitlab. It is community-maintained rather than an official GitLab product: the maintainer is the developer zereight, the repository is zereight/gitlab-mcp on GitHub, and it is MIT licensed. Once connected, an assistant can search projects, read and write files, open and merge merge requests, work through issues, manage branches and read pipeline and commit status, all through your own GitLab account. [1][3]

What tools does the GitLab MCP server include?

The running server exposed 116 tools, and the table above is that live tools/list output, word for word. Counted by name: 40 are merge request work, from create and merge and approve through diffs, versions, discussion notes and draft notes; 20 are issues, including links, discussions and my_issues; 10 are branches, including the protected-branch tools; 5 are commits and commit statuses; 5 are labels; 4 are CI lint and the CI catalog; 3 are to-do items and 3 are namespaces; and the remaining 26 cover projects, groups and members, repository files and the file tree, events, and utilities such as whoami, health_check and discover_tools. Twelve of the tools counted above are emoji reactions, which is a fair measure of how completely this server mirrors GitLab.

Is the GitLab MCP server safe?

Treat it as write access to your repositories, because by default it is: those 116 tools include merging, deleting branches, deleting issues and changing project settings. Two brakes are worth setting before you start. The token decides what is possible at GitLab's end, and the read_api scope grants read access to the API only, while api grants complete read and write access. The server has its own switch as well, GITLAB_READ_ONLY_MODE, which limits it to read-only tools when set to true. The credential itself is a token you paste into your MCP client config or the Routines connector form, so treat that config file the way you would treat any file holding a password, and give the token an expiry date. [11][5]

Does the GitLab MCP server need an API key or an account?

Yes: a GitLab personal access token, created in your GitLab profile under Access and Personal access tokens, pasted into the GITLAB_PERSONAL_ACCESS_TOKEN environment variable. It is not optional in the way it is for some servers, which is the one surprise here: with that variable empty the server logs a complaint and stops instead of starting, so even our verification run needed a placeholder value to reach the tool list. The second variable, GITLAB_API_URL, says which GitLab you mean, https://gitlab.com/api/v4 for the public service or your own address with /api/v4 on the end. The project also documents an OAuth browser flow as an alternative to a token. [10][5]

Do I need the terminal to set it up?

No. In Routines you fill in a command, two environment variables and a name, then click Test Connection; the walkthrough above shows every click. Claude Desktop and Cursor need a small JSON file edited once. Only Claude Code is terminal-first by nature. Creating the token happens in your browser, in GitLab's own settings. [15][12]

Why not the official @modelcontextprotocol/server-gitlab package?

Because it has been retired. npm carries a deprecation notice on it saying the package is no longer supported, its last release was 2025.4.25 in April 2025, and the repository it came from, modelcontextprotocol/servers-archived, is archived on GitHub and described as reference servers that are no longer maintained. The community package on this page had a release published the day before our run and drew 114,673 npm downloads in the week to 2026-08-02, against 5,632 for the retired one, which is roughly twenty times as many. [8][9][1][2]

Which apps can use the GitLab MCP server?

Any MCP client that can launch a local stdio server: Claude Desktop, Claude Code, Cursor and Routines all can, and the exact config for each is above. The project ships its own setup guides for several of them, and the npm listing advertises SSE and Streamable HTTP transports as well, for teams who would rather run one shared instance than one per Mac. The server itself is the same in every client; only the place you paste the config differs. [1][4][12][13][14][15]

Sources

Every external claim, receipted.

Anything on this page we did not observe in the run is linked here, with the date we read it. The run itself is the receipt for the rest.

  1. [1]

    npm registry: @zereight/mcp-gitlabaccessed 2026-08-03

    Latest version 2.1.46, published 2026-08-02 by GitHub Actions CI; license MIT; maintainer zereight; keywords include stdio, sse and streamable-http; binaries mcp-gitlab and zereight-mcp-gitlab.

  2. [2]

    npm downloads API: last weekaccessed 2026-08-03

    114,673 downloads for the week 2026-07-27 to 2026-08-02, against 5,632 for @modelcontextprotocol/server-gitlab in the same week.

  3. [3]

    GitHub: zereight/gitlab-mcpaccessed 2026-08-03

    1,870 stars, license MIT, archived false, last push 2026-08-03, owner type User, read from the GitHub REST API.

  4. [4]

    GitLab MCP Server READMEaccessed 2026-08-03

    Source for the recommended zereight-mcp-gitlab binary name over the older mcp-gitlab, and for the stdio, SSE and Streamable HTTP transport options.

  5. [5]

    GitLab MCP Server docs: environment variablesaccessed 2026-08-03

    GITLAB_PERSONAL_ACCESS_TOKEN, GITLAB_API_URL pointing at the API root ending in /api/v4, and GITLAB_READ_ONLY_MODE=true to restrict the server to read-only tools.

  6. [6]

    GitLab MCP Server docs: Claude Code setup guideaccessed 2026-08-03

    The claude mcp add command with --transport stdio, --scope local and the two --env values.

  7. [7]

    GitLab MCP Server docs: Cursor setup guideaccessed 2026-08-03

    The .cursor/mcp.json block, plus the guide's own note that it is a best-effort community pattern rather than repository-verified official Cursor documentation.

  8. [8]

    npm registry: @modelcontextprotocol/server-gitlabaccessed 2026-08-03

    The retired official reference server: latest version 2025.4.25, carrying the npm notice "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."

  9. [9]

    GitHub: modelcontextprotocol/servers-archivedaccessed 2026-08-03

    archived true, described as "Reference MCP servers that are no longer maintained", last push 2025-05-28, read from the GitHub REST API.

  10. [10]

    GitLab docs: Personal access tokensaccessed 2026-08-03

    The creation path quoted in the walkthrough: avatar, Edit profile, then Access and Personal access tokens in the left sidebar, then Generate token.

  11. [11]

    GitLab docs: Access token scopesaccessed 2026-08-03

    api "Grants complete read and write access to the API for the token's scope"; read_api "Grants read access to the API for the token's scope".

  12. [12]

    modelcontextprotocol.io: Connect to local MCP serversaccessed 2026-08-03

    The claude_desktop_config.json shape and where the file lives.

  13. [13]

    Claude Code docs: MCPaccessed 2026-08-03

    The claude mcp add syntax for local stdio servers, including --env and --scope.

  14. [14]

    Cursor docs: Model Context Protocolaccessed 2026-08-03

    The mcp.json shape and file locations.

  15. [15]

    Routines: Connectorsaccessed 2026-08-03

    How Routines runs one-click OAuth connectors and any MCP server.

This page describes GitLab as it behaved in one dated run on one Mac. Versions move: if something here no longer matches what you see, the capture date at the top says how old the reading is.

Behind this directory

Routines, the app behind this directory, runs MCP servers like this one without a terminal: see how connectors work. Your notes stay markdown files on your Mac, there is no cloud bill, and it works offline. Download Routines