# gitlab-mcp-server: GitLab's API, held to a running GitLab

> One page from jmrp.io, published as markdown. Index: https://jmrp.io/llms.txt

Canonical: https://jmrp.io/blog/series/gitlab-mcp-server/
Language: en
Alternate: https://jmrp.io/es/blog/series/gitlab-mcp-server/index.md
Updated: 2026-10-10
License: https://jmrp.io/license/

Two deep dives from gitlab-mcp-server: checking GitLab's API against a booted GitLab, and deriving what a fine-grained token needs for each action.
Build-Date: 2026-10-10

Two articles from gitlab-mcp-server, the Model Context Protocol (MCP) server I maintain that exposes GitLab's REST and GraphQL APIs one operation at a time. The first makes a booted GitLab the referee of what the API accepts and returns; the second asks that record what a fine-grained personal access token needs for each action.

2 articles

## Why these two belong together

Both rest on one decision: when the documentation, the SDK and a running instance disagree about GitLab's API, the running instance wins. The server boots a released GitLab image, asks the loaded application what its routes, entities, GraphQL types and permissions are, and commits the answer. The audits of what GitLab's REST API returns, and the per-action permission table, read that record instead of the docs or the source. The first article explains why booting beats parsing; the second uses the same record to answer what GitLab's permission reference pages, which list one route or GraphQL type at a time, leave open: what each of the server's actions needs from a fine-grained token as a whole, and which actions no grant can reach at all.

They also share a vantage point that few projects have. A server that maps a thousand GitLab operations one by one has to send a request GitLab accepts and publish what GitLab returns for every one of them, so the gaps between what GitLab says and what GitLab does keep reaching it: a 401 that means 403, a write answered as a success that saved nothing, a route documented with the wrong entity, a GraphQL type that answers null to a fine-grained token with no error. Each article is about what that vantage point exposed, and about how the server copes until GitLab changes.

The last thread is what happens to a finding afterwards. Whatever is not the server's to fix, whether it belongs to GitLab, to client-go (GitLab's Go SDK) or to another upstream project, goes into a public register with its evidence and what it costs a client, and from there, as a rule, into a merge request upstream. The first article describes that loop for the API as a whole; the second applies the same habit to GitLab's token API, where it produced the merge requests that let a fine-grained token read its own grant from the self endpoint and make creating or rotating your own token return the new grant. Read together, they show one method applied twice, with the upstream fixes as its by-product.

## Read in this order

The first article introduces the record and the checks built around it; the second needs only the record, which it recaps in a sentence, goes deep on one question, and can be read alone by anyone who only needs the token answer. Each entry below says what it settles.

### Part 1: What building a GitLab MCP server taught me about GitLab's API

URL: https://jmrp.io/blog/013-gitlab-mcp-server-api-lessons/
Markdown: https://jmrp.io/blog/013-gitlab-mcp-server-api-lessons/index.md
Published: 2026-10-10

Start here, because it builds what the second article relies on: five checks that hold the server to a running GitLab, the first of them a record taken from a booted GitLab, and the rule that a declaration answering a finding fails the build once it matches nothing. The first three checks come with the blind spot each cannot see, and those blind spots are where the most instructive findings were. It settles which of the three descriptions of GitLab's API to believe when they disagree, and it ends with the register that turned the findings into merge requests upstream.

### Part 2: GitLab fine-grained personal access tokens, one action at a time

URL: https://jmrp.io/blog/014-gitlab-fine-grained-tokens-per-action/
Markdown: https://jmrp.io/blog/014-gitlab-fine-grained-tokens-per-action/index.md
Published: 2026-10-10

Then the same record applied to one question: what a fine-grained personal access token needs for each action. Read it if you create these tokens or write a client that receives one. A grant cannot be changed once the token exists, and GraphQL can answer outside it with null and no error where REST names the missing permission, so trial and error costs a new token per miss. It derives each action's requirement from what GitLab 19.4.1 declares, shows how the server tells a client before anything reaches GitLab, and ends with the token to create.

## Where this goes next

The record is pinned to a GitLab release, so what comes next is mechanical: each time it is re-taken from a newer release, the declarations that answered defects fixed in that release stop matching anything, the build fails on them, and they are deleted with the workarounds they explained. The permission table moves the same way, as GitLab declares the GraphQL types still on its own pending list.

The current state of anything these articles date lives outside them. The server's documentation carries a generated reference of what every action needs from a fine-grained token, and the register of upstream defects, public in the server's GitHub repository, records each merge request's state and the workaround it costs until the fix ships.

## What this series does not cover

Beyond creating a token for it, this is not a guide to installing or configuring the server; its documentation does that. Nor is it about the Model Context Protocol itself or the clients that call the server: the subject is GitLab's API and what checking it against GitLab revealed. And it does not cover GitLab CI/CD, runners or the GitLab UI, except where a token is created there.

## The software

The series documents one program, gitlab-mcp-server. Its source, documentation and project card:

- Repository: https://github.com/jmrplens/gitlab-mcp-server
- Documentation: https://jmrp.io/docs/gitlab-mcp-server/
- On the projects page: https://jmrp.io/projects/


Questions answered:

**What is the "gitlab-mcp-server: GitLab's API, held to a running GitLab" series about?**

Two articles from gitlab-mcp-server, the Model Context Protocol (MCP) server I maintain that exposes GitLab's REST and GraphQL APIs one operation at a time. The first makes a booted GitLab the referee of what the API accepts and returns; the second asks that record what a fine-grained personal access token needs for each action.

## Other series

- [Hardening Nginx, edge inward](https://jmrp.io/blog/series/nginx-hardening/): Five Nginx guides in reading order: mTLS, CSP, HTTP/3, virtual files and a tarpit, one edge hardened decision by decision. ([markdown](https://jmrp.io/blog/series/nginx-hardening/index.md))
- [Dual-stack MikroTik, from the ISP to the firewall](https://jmrp.io/blog/series/mikrotik-dual-stack/): Three RouterOS guides: a dual-stack WireGuard VPN, PPPoE with DHCPv6 prefix delegation, and a honeypot that auto-blocks scanners. ([markdown](https://jmrp.io/blog/series/mikrotik-dual-stack/index.md))
- [Kleidos firmware: three decisions under hard constraints](https://jmrp.io/blog/series/kleidos-firmware/): Three deep dives from a hardware password manager on ESP32-S3: packed i18n strings, an encrypt-then-MAC vault, and device-bound key derivation. ([markdown](https://jmrp.io/blog/series/kleidos-firmware/index.md))
