A Model Context Protocol (MCP) server that provides GitLab integration tools for Claude Code.
- List Issues: List issues for a GitLab project using project path (namespace/project-name)
- Create Issues: Create new issues with title, description, labels, and assignees
- Update Issues: Update existing issues (title, description, state, labels, assignees)
- List Labels: List project labels with optional filtering and counts
- Add Issue Notes: Add comments/notes to existing issues
- Create Merge Requests: Create new merge requests with source/target branches, title, description, assignees, reviewers, and labels
- Get Project Description: Retrieve the current description of a GitLab project
- Update Project Description: Update the description of a GitLab project
- Get Project Topics: Retrieve the current topics/tags of a GitLab project
- Update Project Topics: Update the topics/tags of a GitLab project (replaces all existing topics)
- Direct project path access - no need to resolve project IDs
- Compatible with Claude Code's MCP architecture
- Go 1.21 or later
- Task for build automation
- Claude Code CLI
- Access to GitLab repositories
# Add the tap and install
brew tap sgaunet/homebrew-tools
brew install sgaunet/tools/gitlab-mcp-
Download the latest release:
Visit the releases page and download the appropriate binary for your platform:
- macOS:
gitlab-mcp_VERSION_darwin_amd64(Intel) orgitlab-mcp_VERSION_darwin_arm64(Apple Silicon) - Linux:
gitlab-mcp_VERSION_linux_amd64(x86_64) orgitlab-mcp_VERSION_linux_arm64(ARM64) - Windows:
gitlab-mcp_VERSION_windows_amd64.exe
- macOS:
-
Make it executable (macOS/Linux):
chmod +x gitlab-mcp_* -
Move to a location in your PATH:
# Example for macOS/Linux sudo mv gitlab-mcp_* /usr/local/bin/gitlab-mcp
-
Clone the repository:
git clone https://github.com/sgaunet/gitlab-mcp.git cd gitlab-mcp -
Build the project:
task build
Or manually:
go build -o gitlab-mcp
-
Install to your PATH:
sudo mv gitlab-mcp /usr/local/bin/
The MCP server requires a GitLab personal access token (PAT) with appropriate scopes (e.g., api, read_api, write_api) to interact with the GitLab API.
Set the GITLAB_TOKEN environment variable with your PAT:
export GITLAB_TOKEN=your_personal_access_tokenYou can also set the GITLAB_URI environment variable if you're using a self-hosted GitLab instance (default is https://gitlab.com):
export GITLAB_URI=https://your.gitlab.instanceYou can also configure label validation behavior with the GITLAB_VALIDATE_LABELS environment variable:
export GITLAB_VALIDATE_LABELS=true # Default: Enable label validation
export GITLAB_VALIDATE_LABELS=false # Disable label validation for backward compatibilityThe easiest way to set these variables permanently is to add them to your shell profile (e.g., ~/.bashrc, ~/.zshrc). It avoids issues with environment variables not being available when Claude Code starts the MCP server.
After installation, add the MCP server to Claude Code:
# If installed via Homebrew (Apple Silicon)
claude mcp add gitlab-mcp -s user -- /opt/homebrew/bin/gitlab-mcp
# If installed via Homebrew (Intel Mac) or manually to /usr/local/bin
claude mcp add gitlab-mcp -s user -- /usr/local/bin/gitlab-mcp
# If installed elsewhere, adjust the path accordingly
claude mcp add gitlab-mcp -s user -- /path/to/gitlab-mcpOnce installed, the MCP server provides the following tools in Claude Code:
Lists issues for a GitLab project using the project path.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URLstate(string, optional): Filter by issue state (opened,closed,all) - defaults toopenedlabels(string, optional): Comma-separated list of labels to filter bylimit(number, optional): Maximum number of issues to return (default: 100, max: 100)
Examples:
List all open issues for project namespace/project_name
List all issues (open and closed) for project namespace/project_name
List issues with state=all and limit=50 for project namespace/project_name
Response Format: Returns a JSON array of issue objects, each containing:
id: Issue IDiid: Internal issue IDtitle: Issue titledescription: Issue descriptionstate: Issue state (openedorclosed)labels: Array of label namesassignees: Array of assignee objectscreated_at: Creation timestampupdated_at: Last update timestamp
Creates a new issue for a GitLab project.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URLtitle(string, required): Issue titledescription(string, optional): Issue descriptionlabels(array, optional): Array of labels to assign to the issueassignees(array, optional): Array of user IDs to assign to the issue
Examples:
Create an issue with title "Bug fix needed" for project namespace/project_name
Create an issue with title "Feature request", description "Add new functionality", and labels ["enhancement", "feature"] for project namespace/project_name
Label Validation: By default, the server validates that labels exist in the project before creating issues. If any labels don't exist, the issue creation fails with a helpful error message listing missing labels and all available labels in the project.
Example validation error:
The following labels do not exist in project 'namespace/project':
- 'nonexistent-label'
- 'typo-label'
Available labels in this project:
- bug, enhancement, documentation, priority-high, priority-medium, priority-low
To disable label validation, set GITLAB_VALIDATE_LABELS=false
To see available labels before creating issues, use the list_labels tool. Label validation can be disabled by setting GITLAB_VALIDATE_LABELS=false in your environment.
Response Format: Returns a JSON object of the created issue with the same structure as list_issues.
Updates an existing issue for a GitLab project.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URLissue_iid(number, required): Issue internal ID (IID) to updatetitle(string, optional): Updated issue titledescription(string, optional): Updated issue descriptionstate(string, optional): Issue state ('opened' or 'closed')labels(array, optional): Array of labels to assign to the issueassignees(array, optional): Array of user IDs to assign to the issue
Examples:
Update the title of issue #5 for project namespace/project_name
Close issue #10 and update its description for project namespace/project_name
Response Format: Returns a JSON object of the updated issue with the same structure as list_issues.
Lists labels for a GitLab project with optional filtering.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URLwith_counts(boolean, optional): Include issue and merge request counts (default: false)include_ancestor_groups(boolean, optional): Include labels from ancestor groups (default: false)search(string, optional): Filter labels by search keywordlimit(number, optional): Maximum number of labels to return (default: 100, max: 100)
Examples:
List all labels for project namespace/project_name
List labels with counts for project namespace/project_name
Search for labels containing "bug" in project namespace/project_name
Response Format: Returns a JSON array of label objects, each containing:
id: Label IDname: Label namecolor: Label color (hex code)text_color: Text color for the labeldescription: Label descriptionopen_issues_count: Number of open issues (if with_counts=true)closed_issues_count: Number of closed issues (if with_counts=true)open_merge_requests_count: Number of open merge requests (if with_counts=true)
Adds a note/comment to an existing issue for a GitLab project.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URLissue_iid(number, required): Issue internal ID (IID) to add note tobody(string, required): Note/comment body text
Examples:
Add a comment "This looks good to me!" to issue #5 for project namespace/project_name
Add a note "Fixed in latest commit" to issue #12 for project namespace/project_name
Response Format: Returns a JSON object of the created note containing:
id: Note IDbody: Note body textauthor: Author object with id, username, and namecreated_at: Creation timestampupdated_at: Last update timestampsystem: Boolean indicating if this is a system-generated notenoteable: Object containing information about the issue this note belongs to
Creates a new merge request for a GitLab project.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URLsource_branch(string, required): Source branch nametarget_branch(string, required): Target branch nametitle(string, required): MR titledescription(string, optional): MR descriptionassignee_ids(array, optional): Array of assignee user IDsreviewer_ids(array, optional): Array of reviewer user IDslabels(array, optional): Array of labelsmilestone_id(number, optional): Milestone IDremove_source_branch(boolean, optional): Auto-remove source branch after merge (default: true)draft(boolean, optional): Create as draft MR (default: false)
Examples:
Create a merge request from feature-branch to main for project namespace/project_name
Create a merge request with assignees and labels from feature-branch to main for project namespace/project_name
Create a draft merge request with description "New feature implementation" from feature-branch to develop for project namespace/project_name
Response Format: Returns a JSON object of the created merge request containing:
id: Merge request IDiid: Internal merge request IDtitle: MR titledescription: MR descriptionstate: MR state (opened, closed, merged)source_branch: Source branch nametarget_branch: Target branch nameauthor: Author object with id, username, and nameassignees: Array of assignee objectsreviewers: Array of reviewer objectslabels: Array of label namesmilestone: Milestone object (if assigned)web_url: Web URL to the merge requestdraft: Boolean indicating if this is a draft MRcreated_at: Creation timestampupdated_at: Last update timestamp
Retrieves the description of a GitLab project.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URL
Examples:
Get the project description for namespace/project_name
Response Format: Returns a JSON object containing:
id: Project IDname: Project namepath: Project pathdescription: Project description
Updates the description of a GitLab project.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URLdescription(string, required): The new description for the project
Examples:
Update the description of namespace/project_name to "A new and improved project description"
Response Format: Returns a JSON object containing:
id: Project IDname: Project namepath: Project pathdescription: Updated project descriptiontopics: Array of project topics
Retrieves the topics/tags of a GitLab project.
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URL
Examples:
Get the topics for namespace/project_name
Response Format: Returns a JSON object containing:
id: Project IDname: Project namepath: Project pathtopics: Array of topic strings
Updates the topics/tags of a GitLab project (replaces all existing topics).
Parameters:
project_path(string, required): GitLab project path including all namespaces (e.g., 'namespace/project-name' or 'company/department/team/project'). Run 'git remote -v' to find the full path from the repository URLtopics(array, required): Array of topic strings to set for the project (replaces all existing topics)
Examples:
Update the topics of namespace/project_name to ["golang", "mcp", "gitlab", "api"]
Remove all topics from namespace/project_name by setting an empty array []
Response Format: Returns a JSON object containing:
id: Project IDname: Project namepath: Project pathdescription: Project descriptiontopics: Updated array of topic strings
This project uses Task for build automation. Available tasks:
# List all available tasks
task --list
# Build the binary
task build
# Build with coverage support
task build:coverage
# Run linter
task linter
# Run unit tests
task test
# Show test coverage percentage
task coverageUnit Tests:
task testCoverage Testing:
# Show coverage percentage
task coverageThe unit tests use interface abstractions and mocking to provide fast, reliable testing without external dependencies. Current test coverage is 78.8%.
See MANUAL_TEST.md for step-by-step manual testing instructions.
Run the test script:
./test_working.shThe MCP server runs as a subprocess and communicates via JSON-RPC over stdin/stdout. Configuration is done through environment variables:
GITLAB_TOKEN(required): GitLab personal access token with appropriate scopes (api,read_api,write_api)GITLAB_URI(optional): GitLab instance URI (default:https://gitlab.com/)GITLAB_VALIDATE_LABELS(optional): Enable/disable label validation for issue creation (default:true)true: Validates that labels exist in the project before creating issuesfalse: Allows creating issues with non-existent labels (GitLab's default behavior)
This server uses stdio (stdin/stdout) for communication, which is the standard and recommended approach for MCP servers:
โ Why stdio is optimal:
- Standard MCP pattern - Most MCP servers use stdio communication
- Simple process model - Started by Claude Code as a subprocess
- No port conflicts - No network port management needed
- Security - Process isolation, no network exposure
- Resource efficiency - Direct pipe communication with minimal overhead
- Cross-platform compatibility - Works everywhere Go works
- Easy configuration - Just specify the executable path in Claude Code
๐ Alternative transports (HTTP/SSE): While MCP supports HTTP and Server-Sent Events, these are better suited for:
- Multi-client scenarios (serving multiple agents simultaneously)
- Containerized environments where process spawning is restricted
- Remote access across network boundaries
- Web-based MCP clients
For GitLab integration with Claude Code, stdio provides the best user experience with simple configuration and reliable performance.
- Parse Error (-32700): Ensure you're using the server through Claude Code's MCP interface, not directly inputting raw URLs
- Invalid URL: Verify the GitLab URL format is correct (SSH or HTTPS)
- Connection Issues: Check network connectivity to GitLab
The server outputs debug information to stderr, which can be helpful for troubleshooting:
go run . < input.txt 2> debug.log- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
MIT License. See LICENSE for details.
For issues and questions, please create an issue in the repository.