gitlab-mcp
MCP server that acts as a thin proxy over GitLab REST API v4, enabling users to list merge requests, view diffs, and leave inline comments without leaving their MCP client.
README
@vinihcrosa/gitlab-mcp
MCP server (stdio) que funciona como proxy fino sobre a REST API v4 de uma instância GitLab CE self-hosted.
MVP com um objetivo só: navegar merge requests e deixar review inline sem abrir o browser. Ler o estado da CI faz parte disso — pipeline vermelha é justamente o momento em que o review para. Escrever em CI não faz: nada dispara, cancela ou re-roda pipeline, e nada lê variável ou secret. Fora de escopo também: issues, criar/mergear MR, aprovações, recursos Premium/Ultimate.
Como funciona
- 13 tools: 10 de leitura, 3 de escrita.
- Toda resposta passa por whitelist explícita de campos — a API do GitLab devolve objetos com 40+ campos e nenhum deles chega cru no contexto do modelo.
- Toda listagem tem
per_pagecom default 20 (máximo 100) e informa se há mais páginas. - Read-only por default. As tools de escrita só funcionam com
GITLAB_READ_ONLY=false. - O diff sai parseado, com os números de linha de cada lado impressos (
old=/new=), porque é isso que torna o comentário em linha confiável.
Instalação
Não precisa instalar nada: o client MCP executa o pacote via npx e o npm cuida do download.
npx -y @vinihcrosa/gitlab-mcp
Rodar esse comando na mão só serve para conferir que sobe — ele fica esperando o protocolo em stdin. A configuração de verdade está em Configuração no client.
Requer Node >= 20 (usa fetch nativo e AbortSignal.timeout).
A partir do código-fonte
Para desenvolver ou rodar um fork:
git clone https://github.com/vinihcrosa/gitlab-mcp.git
cd gitlab-mcp
npm install # o script `prepare` já compila
O client passa a apontar para dist/index.js com caminho absoluto, em vez de npx.
Configuração
| Variável | Obrigatória | Default | Descrição |
|---|---|---|---|
GITLAB_URL |
sim | — | Base da instância, ex.: https://gitlab.empresa.com. Barra final e sufixo /api/v4 são removidos automaticamente. |
GITLAB_TOKEN |
sim | — | Personal Access Token. |
GITLAB_READ_ONLY |
não | true |
Só o literal false habilita as tools de escrita. |
GITLAB_CA_CERT |
não | — | Caminho para CA privada / cert self-signed em PEM. |
GITLAB_TIMEOUT_MS |
não | 20000 |
Timeout por request, em ms. |
Falta GITLAB_URL ou GITLAB_TOKEN → o server escreve o erro em stderr e sai com código 1. Não sobe quebrado.
Veja .env.example.
Escopos do token — leia antes de gerar
| Tools | Escopo mínimo |
|---|---|
1–7 (whoami, list_my_projects, list_my_authored_mrs, list_mrs_awaiting_my_review, get_mr, get_mr_diff, list_mr_discussions) |
read_api |
11, 13 (get_mr_pipeline, list_pipelines) |
read_api |
12 (get_job_log) |
api — ver nota abaixo |
8–10 (comment_on_mr, comment_on_mr_line, reply_to_mr_discussion) |
api |
get_job_log pede api por precaução, não por medida: não foi verificado se read_api alcança /jobs/:id/trace. Se você testar com um token read_api e funcionar, esta linha da tabela muda e nenhum código muda junto.
read_api não escreve. Se você gerar o token com read_api e tentar comentar, o GitLab devolve 403 — o server traduz isso para uma mensagem dizendo exatamente que provavelmente é esse o caso, mas o conserto é regerar o token com escopo api.
Configuração no client
MCP stdio não é daemon: você não sobe o servidor, você registra um comando. O client executa esse comando, conversa por stdin/stdout e mata o processo ao fim da sessão.
Claude Code
Instale global e aponte para o arquivo, com caminhos absolutos:
npm i -g @vinihcrosa/gitlab-mcp
claude mcp add gitlab -s user \
-e GITLAB_URL=https://gitlab.empresa.com \
-e GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx \
-e GITLAB_READ_ONLY=true \
-- "$(which node)" "$(npm root -g)/@vinihcrosa/gitlab-mcp/dist/index.js"
-s user vale em todos os projetos. Use -s project só se aceitar que o arquivo .mcp.json gerado é commitável — e aí não coloque o token nele.
Por que não npx aqui. Duas armadilhas, as duas silenciosas — o sintoma é sempre Connection closed:
- O bloco
envsubstitui o ambiente do processo em vez de estender. SemPATH, onpxnão acha onodee morre comenv: node: No such file or directory. Se insistir nonpx, passe-e PATH=/opt/homebrew/bin:/usr/bin:/binjunto. - O client roda o servidor com
cwdno diretório do projeto. Se esse projeto for este repositório, onpxresolve o nome para o pacote local em vez do publicado e falha comcommand not found. Só afeta quem desenvolve o próprio pacote, mas custa meia hora para descobrir.
Caminho absoluto para o node e para o dist/index.js não depende de PATH nem de cwd, e ainda corta a resolução do npx a cada spawn.
Claude Desktop
claude_desktop_config.json:
{
"mcpServers": {
"gitlab": {
"command": "/opt/homebrew/bin/node",
"args": ["/opt/homebrew/lib/node_modules/@vinihcrosa/gitlab-mcp/dist/index.js"],
"env": {
"GITLAB_URL": "https://gitlab.empresa.com",
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx",
"GITLAB_READ_ONLY": "true"
}
}
}
}
Descubra os dois caminhos da sua máquina com which node e npm root -g — variam entre Homebrew, nvm e Linux. As mesmas duas armadilhas da seção do Claude Code valem aqui.
Rodando a partir do código-fonte, aponte args para o dist/index.js do seu clone.
Para habilitar review inline, troque para "GITLAB_READ_ONLY": "false" (e use um token com escopo api).
Com CA privada:
"env": {
"GITLAB_URL": "https://gitlab.empresa.com",
"GITLAB_TOKEN": "glpat-...",
"GITLAB_CA_CERT": "/etc/ssl/certs/empresa-ca.pem"
}
Não existe opção de desabilitar verificação TLS. De propósito.
Testar antes de plugar no client
npm run build
GITLAB_URL=https://gitlab.empresa.com \
GITLAB_TOKEN=glpat-xxx \
npx @modelcontextprotocol/inspector node dist/index.js
O Inspector abre no browser, lista as 13 tools e deixa você chamar cada uma com os argumentos na mão. Se algo falhar aqui, falha no client também — e aqui você vê a mensagem de erro inteira.
Logs do server saem em stderr (aba de logs do Inspector). stdout é exclusivo do protocolo MCP.
Checklist de validação manual
Nesta ordem. Cada passo alimenta o seguinte.
-
whoami— devolve seuusername? Se derToken inválido ou expirado., pare aqui. -
list_my_projects— anote opath_with_namespacede um projeto com MR aberto. -
list_mrs_awaiting_my_review— deve listar MRs onde você é reviewer. Se vier vazio e você sabe que tem MR esperando: confira que você está como reviewer e não como assignee (são campos diferentes no GitLab). -
get_mrcomproject+iid(o número da URL,/-/merge_requests/123) — confira quediff_refsnão énull. -
get_mr_diffcom o mesmoproject+iid— deve sair o diff comold=/new=em cada linha e osdiff_refsno rodapé. Anote uma linhaadde uma linhactx.A partir daqui precisa de
GITLAB_READ_ONLY=falsee token com escopoapi. -
comment_on_mr— comentário geral. Abra oweb_urlretornado e confirme que apareceu. -
comment_on_mr_linenuma linhaadd:side="new",line= o númeronew=daquela linha. -
comment_on_mr_linenuma linhactx:side="context",line= onew=,context_old_line= oold=da mesma linha. Os dois são obrigatórios — é o erro mais comum. -
list_mr_discussions— as duas threads criadas devem aparecer compositionediscussion_id. -
reply_to_mr_discussioncom um dosdiscussion_iddo passo 9.
Se o passo 7 ou 8 falhar, a mensagem de erro diz quais linhas de fato existem naquele lado do diff. Não é preciso adivinhar.
As 13 tools
| # | Tool | Escrita | Resumo |
|---|---|---|---|
| 1 | whoami |
Identidade do token. Cacheada no processo. | |
| 2 | list_my_projects |
Projetos onde você é membro, por atividade recente. | |
| 3 | list_my_authored_mrs |
MRs que você criou, em todos os projetos. | |
| 4 | list_mrs_awaiting_my_review |
MRs abertos onde você é reviewer. | |
| 5 | get_mr |
Detalhe do MR, incluindo diff_refs. |
|
| 6 | get_mr_diff |
Diff parseado com numeração de linha explícita. | |
| 7 | list_mr_discussions |
Threads de comentário, com discussion_id e posição. |
|
| 8 | comment_on_mr |
sim | Comentário geral no MR. |
| 9 | comment_on_mr_line |
sim | Thread ancorada numa linha do diff. |
| 10 | reply_to_mr_discussion |
sim | Resposta numa thread existente. |
| 11 | get_mr_pipeline |
Pipeline mais recente do MR e seus jobs. Nomeia o que falhou. | |
| 12 | get_job_log |
Log do job, limpo e cortado pelo fim. | |
| 13 | list_pipelines |
Pipelines do projeto, com filtro de branch e status. |
Notas de implementação que importam
iid, nãoid. Todas as tools de MR usam oiid— o número que aparece na URL. Oidglobal existe e a API aceita em outros contextos; usar o errado pega o MR de outro projeto ou dá 404.- Resolução de projeto. O path (
grupo/subgrupo/projeto) é URL-encoded (%2F) e resolvido para id numérico, com cache em memória. comment_on_mr_linebuscadiff_refsfresco com um GET do MR imediatamente antes do POST, e nunca aceita os shas como parâmetro: se alguém deu push, os shas velhos invalidam a posição.- Validação local antes do POST. A tool confere que o arquivo está no MR e que a linha existe no lado pedido. Se não existir, falha localmente listando as linhas válidas, em vez de mandar pro GitLab e devolver um 400 opaco. Se mesmo assim vier 400, a mensagem do GitLab volta na íntegra junto com o payload enviado.
- Linha de contexto exige os dois números.
side="context"semcontext_old_lineé rejeitado localmente, com o valor correto na mensagem. - Prompt injection.
descriptionde MR ebodyde comentário são conteúdo escrito por qualquer pessoa com acesso ao GitLab. Vêm envelopados em<untrusted source="gitlab:...">e a resposta carrega uma nota dizendo que aquilo é dado, não instrução. Não é blindagem; é o mínimo defensável. - Comentário multi-linha está fora de escopo. Só linha única.
Testes
npm test
Todos offline. Cobrem a lógica pura — onde saída errada parece plausível:
src/diff.ts— hunk misto, múltiplos hunks, arquivo novo/deletado/renomeado,\ No newline at end of file, truncamento em 400 linhas, binário. É o que quebracomment_on_mr_linequando erra.src/trace.ts— ANSI (CSI e OSC), marcador de seção, prefixo de timestamp e de stream, colapso de barra de progresso, corte pela cauda em limite de linha.src/pipelines.ts— whitelist de campos, escolha da pipeline mais recente, precedência entre "nunca começou" e "log apagado", envelope<untrusted>.src/tools/index.ts— a superfície registrada é exatamente 13 tools, o que pega tanto tool nova que não registrou quanto tool existente derrubada por engano.
Sem testes de integração e sem mock de HTTP: a camada de I/O não tem cobertura, e o que isso deixa de fora está declarado em docs/features/001-ci-pipelines/tests.md em vez de subentendido.
Estrutura
src/
├── index.ts # entrypoint stdio. NUNCA escreve em stdout.
├── config.ts # env, validação no boot, normalização da URL
├── gitlab.ts # único ponto de saída HTTP: token, timeout, CA, paginação, 429, erros
├── errors.ts # GitLabError / ToolError
├── projects.ts # resolveProject + cache path <-> id
├── diff.ts # parser de diff unificado (puro, testado)
├── trace.ts # limpeza e corte pela cauda de log de job (puro, testado)
├── pipelines.ts # projeção, decisão e renderização de CI (puro, testado)
├── format.ts # whitelist, truncamento, blocos <untrusted>
└── tools/ # as 13 tools, agrupadas por domínio
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
E2B
Using MCP to run code via e2b.