# Reported issues for GitLab MCP Server

Pod holds 12 of 12 GitHub reports that passed its relevance review. This can include external user reports, maintainer-confirmed bugs, and concrete feature gaps. Treat them as evidence to inspect, not a count of distinct defects.

Back to [GitLab MCP Server](/mcp/gitlab-mcp-server).

## Most discussed

### A rejected GraphQL document is reported to the model as an empty result, and five tools are in that state today

A GraphQL document this server sends can be rejected by GitLab and the caller is told the project has nothing, not that the question was malformed. Two registered tools are in that state today, and one of them reports on vulnerabilities.

## The mechanism

GitLab answers a document that fails validation with **HTTP 200**, `data: null` and a top-level `errors` array. client-go's `GraphQL.Do` only builds a `GraphQLResponseError` when the underlying HTTP call itself returned an error…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/568) · 2026-09-06 · closed · 3 comments

### GraphQL mocks answer what GitLab would refuse, so four broken documents shipped with green tests

Our GraphQL tests cannot fail for the reason that matters. Every domain test answers the request from an `httptest` handler that returns whatever the test wrote, so the document itself is never judged by anything. GitLab is the only party that would refuse it, and no test ever asks GitLab.

That is not a theoretical gap. I posted all 30 raw GraphQL documents this server sends to `https://gitlab.com/api/graphql` and four are rejected outright, so four registered tools cannot work on any current…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/569) · 2026-09-06 · closed · 1 comment

### cmd/server's stdio shutdown test hangs the Windows suite for thirty minutes, intermittently

The `🖥️ Cross-platform (windows-latest)` job fails by timing out the whole `cmd/server` package, not by an assertion. The panic names what was still running:

```
panic: test timed out after 30m0s
	running tests:
		TestServeStdio_TheTwoDocumentedShutdowns_ExitCleanly (29m29s)
		TestServeStdio_TheTwoDocumentedShutdowns_ExitCleanly/a_signal_cancels_the_context (29m29s)
FAIL	github.com/jmrplens/gitlab-mcp-server/v2/cmd/server	1800s
```

Every other test in the package is then reported as failed…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/638) · 2026-09-08 · open · 0 comments

### A blocked address is refused before its credential is read, so one token sprayer behind a shared IP cuts off every legitimate neighbour

In HTTP mode an address is refused before its credential is read, so a client spraying invalid tokens from behind a shared public IP takes every legitimate neighbour down with it. `cmd/server/auth_gate.go:449` checks `blockedByBudget` first and only then extracts the token; `bearer_guard.go` does the same in OAuth mode. The budget is `authFailureLimit`, ten failed authentications per address inside `authFailureWindow`, one minute, and the block answers 429 to everything from that address for…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/619) · 2026-09-08 · open · 0 comments

### Read-only and safe mode reach the tools outside the catalog only inside cmd/server, so the evaluator and the suite keep their real handlers

Read-only mode, safe mode, the token-scope narrowing and `--exclude-tools` act in two places. The catalog-backed tools receive them per action from `FilterActionCatalog`, inside the catalog that every assembler shares. The tools registered outside the catalog receive them from `applyToolVisibilityConfig` at `cmd/server/main.go:2048`, a pass that runs after registration over the tools the server holds: it removes the names `--exclude-tools` lists, removes what the token's scopes cannot call…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/617) · 2026-09-08 · open · 0 comments

### The e2e suite assembles the meta surface on its own and drives one tool fewer than the binary serves

`test/e2e/suite/setup_test.go:203` builds the meta session with `tools.RegisterAllMeta`, which asks the catalog for no MCP group, so the surface the suite drives has no `gitlab_server`. That is 33 tools in the suite against 34 in the binary on Free, 39 against 40 on Premium, 50 against 51 on self-managed Ultimate and 51 against 52 on GitLab.com. The doc comment on `RegisterAllMeta` in `internal/tools/register_meta.go:12` says exactly this, and the suite is now the function's only caller outside…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/616) · 2026-09-08 · open · 0 comments

### approval_config publishes nine fields a live GitLab never sends, and 1:1 parity with the SDK is why nothing notices

`gitlab_merge_request approval_config` publishes an output schema promising nine fields that a live GitLab CE never sends, and nothing in the repository can currently notice.

## What is declared and what arrives

`mrapprovals.ConfigOutput` in `internal/tools/mrapprovals/mr_approvals.go` mirrors client-go's `MergeRequestApprovals` field for field, which is the 1:1 norm working as intended: 24 fields, 11 of them carrying `tier:"premium"` so a Free client never sees them in the model-facing…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/580) · 2026-09-07 · closed · 0 comments

### A meta-tool description is checked only against a snapshot regenerated from itself, so it cannot go stale loudly

The description a model reads for a meta-tool is prose that enumerates the tool's parameters, and nothing connects it to the parameters. It cannot go stale loudly, because its only source is a file regenerated from itself.

`internal/tools/action_catalog.go:386` fills a group's description from `catalogGroupDescription`, whose signature is:

```go
func catalogGroupDescription(toolName string, _ toolutil.ActionMap) string
```

The actions are passed in and discarded. The text comes from…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/574) · 2026-09-06 · closed · 0 comments

## Most recent

### Eight GraphQL domains advertise backward pagination and silently discard it

`toolutil.GraphQLPaginationInput` offers a model four cursor parameters and describes two of them as backward pagination:

```go
Last   *int   `json:"last,omitempty"   jsonschema:"Number of items from the end (backward pagination)"`
Before string `json:"before,omitempty" jsonschema:"Cursor for backward pagination (from previous response start_cursor)"`
```

`Variables()` duly puts them in the variables map. But not one of the eight domains that use the helper declares `$before` or `$last` in…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/562) · 2026-09-06 · closed · 0 comments

### Pool eviction can take a subscribed credential, and what that costs an attacker is undocumented

When the pool is at `--max-http-clients` and a new credential arrives, `lruVictimLocked` walks the LRU from the tail looking for an entry that `WithInUse` does not report as busy, and **returns the tail anyway when every entry is busy**. Eviction is not gentle: `dropEntry` fires `WithOnEvict` under the pool's write lock, the credential's session IDs stop being accepted at once, and on another goroutine its watchers stop, its `subscriptions/listen` streams are cancelled and the sessions no…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/561) · 2026-09-06 · closed · 0 comments

### The integration read path drifted from the shared renderer, so a titleless integration reads as an empty heading

`internal/tools/integrations/markdown.go` renders a single integration twice, from two places that were once the same code and no longer are.

`formatIntegrationItemString`, which the set and upsert paths use, writes its heading as `fallback(i.Title, i.Slug)`: an integration whose title is empty is named by its slug. `formatGetMarkdownString`, which `gitlab_get_integration` uses, writes `i.Title` raw. The rest of the two bodies is line for line identical: the id, the slug, the active badge, the…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/559) · 2026-09-06 · open · 0 comments

### Two markdown formatters register for one type, so a runner authentication token renders as a registration token

`internal/tools/runners/markdown.go` registers two formatters for the same Go type:

```go
toolutil.RegisterMarkdown(FormatAuthTokenMarkdown) // func(AuthTokenOutput) string
toolutil.RegisterMarkdown(FormatRegTokenMarkdown)  // func(AuthTokenOutput) string
```

`RegisterMarkdown` keys a `sync.Map` on `reflect.TypeOf` and calls `Store`, so the second registration replaces the first. Every value of that type therefore renders through `FormatRegTokenMarkdown`, and the result of resetting a…

[Read the thread](https://github.com/jmrplens/gitlab-mcp-server/issues/558) · 2026-09-06 · open · 0 comments

The remaining reports are on [the project's issue tracker](https://github.com/jmrplens/gitlab-mcp-server/issues).
