Skip to content

Troubleshooting

GITLAB_TOKEN required

Invalid GITLAB_URL

401 Unauthorized

403 Forbidden

Connection refused

No

Yes

Yes

Yes

No

No

Server won't start

Error message?

Set GITLAB_TOKEN
in env or ~/.gitlab-mcp-server.env

Fix self-managed
GITLAB_URL

Regenerate token
with api scope

Check token has
api scope

Is GitLab reachable?

Check GITLAB_URL
and network

TLS error?

Can install CA cert?

Add CA cert to system
trust store

Set GITLAB_MCP_SKIP_TLS_VERIFY=true
as last resort

Enable debug logging
GITLAB_MCP_LOG_LEVEL=debug

SymptomCauseSolution
GITLAB_TOKEN is required at startupToken not setSet GITLAB_TOKEN in the environment or in ~/.gitlab-mcp-server.env; a working-directory .env is not read, and one that exists is named at WARN on startup
GITLAB_URL is not a valid URL at startupURL has invalid syntaxFix GITLAB_URL or omit it to use https://gitlab.com in stdio mode
401 Unauthorized from GitLab APIInvalid or expired PATGenerate a new token with api scope at GitLab → Preferences → Access Tokens
403 Forbidden on specific operationsToken lacks required scopeEnsure the token has api scope (not just read_api)
Connection refused or timeoutGitLab instance unreachableVerify GITLAB_URL is reachable: curl -s $GITLAB_URL/api/v4/version
SymptomCauseSolution
x509: certificate signed by unknown authoritySelf-signed certificateFirst try adding the CA certificate to your system trust store. If that’s not possible, set GITLAB_MCP_SKIP_TLS_VERIFY=true in the environment or in ~/.gitlab-mcp-server.env, or --skip-tls-verify in HTTP mode; --auth-mode=oauth refuses it for a non-loopback instance, so install the CA (or point SSL_CERT_FILE at a bundle) there
x509: certificate has expiredExpired TLS certificateRenew the certificate on the GitLab server, or use GITLAB_MCP_SKIP_TLS_VERIFY=true temporarily

If the server cannot resolve your GitLab hostname:

Terminal window
# Verify DNS from the machine running the MCP server
nslookup gitlab.example.com
# or
dig gitlab.example.com +short

In Docker containers, ensure your compose file or docker run command uses --dns or a custom network with proper DNS configuration. Inside Kubernetes, check CoreDNS logs and resolv.conf in the pod.

Go’s net/http honours the standard proxy environment variables. Set them before launching the server:

Terminal window
export HTTPS_PROXY=http://proxy.corp.example.com:8080
export HTTP_PROXY=http://proxy.corp.example.com:8080
export NO_PROXY=localhost,127.0.0.1,.internal.corp
SymptomCauseSolution
Connection timeout behind corporate networkProxy not configuredSet HTTPS_PROXY / HTTP_PROXY
Proxy works for curl but not for the serverEnv vars not exported into the server processPass them in the client’s env block, ~/.gitlab-mcp-server.env, Docker environment:, or your platform’s secrets
Internal GitLab routed through external proxyNO_PROXY missingAdd your GitLab hostname to NO_PROXY

When running the MCP server behind nginx, Caddy, or a cloud load balancer:

SymptomCauseSolution
429 after a few failed logins from different peopleThe authentication-failure limit (10 per address per minute) sees the proxy’s address, not the client’sSet --trusted-proxy-header to the header your proxy sets (e.g. X-Forwarded-For, CF-Connecting-IP) and --trusted-proxies to the proxy’s address; the per-call rate limiter is keyed by token and unaffected
SSE stream disconnectedProxy read or idle timeout shorter than the stream’s silenceRaise the proxy read timeout; the server’s 25-second SSE keep-alive holds an idle stream open across nginx’s 60-second default, so a tighter edge is what cuts it
502 Bad GatewayServer not listening yetAdd a startup probe or retry; the server needs a few seconds to initialize on first request
SymptomCauseSolution
Only two tools appear: gitlab_find_action and gitlab_execute_actionExpected — dynamic is the default surfaceNot a failure. Ask for what you want in plain language; the assistant finds the action and its schema, then executes it. Set GITLAB_MCP_TOOL_SURFACE=meta or GITLAB_MCP_TOOL_SURFACE=individual if you want a browsable tool list
MCP client shows hundreds of individual tools instead of 34Individual surface selectedSet GITLAB_MCP_TOOL_SURFACE=meta to consolidate into domain meta-tools
Tool not found in tools/listTool surface mismatchIndividual mode uses gitlab_issue_create, meta mode uses gitlab_issue with action: create, and dynamic mode exposes gitlab_find_action and gitlab_execute_action
unknown action in meta-tool callInvalid action parameterCheck valid actions in the Tools Overview
json: unknown field "<name>" from a meta-toolMisspelled or stale parameter in paramsMeta-tools reject unknown keys. Use the exact parameter names for the chosen action (e.g. merge_request_iid, issue_iid, epic_iid, work_item_iid, snippet_id)
Enterprise tools missingEnterprise catalog disabledIn stdio mode, set GITLAB_MCP_TIER=premium or GITLAB_MCP_TIER=ultimate. In HTTP mode, set --tier=premium/--tier=ultimate to force the catalog or let license detection enable it when the instance reports a Premium or Ultimate plan. The deprecated GITLAB_ENTERPRISE=true variable still works when GITLAB_MCP_TIER is unset; there is no --enterprise flag — use --tier
SymptomCauseSolution
the @jmrp.io/gitlab-mcp-server-linux-x64 package is not installedOn Alpine this is deliberate: the linux packages declare libc: glibc and the released binaries need the glibc dynamic loader, so npm skips them on musl. On a glibc distro it means the install skipped optional dependencies (--no-optional, or a lockfile resolved on another OS)On musl, use the container image ghcr.io/jmrplens/gitlab-mcp-server (built against musl) or build from source — the publish is not broken. On glibc, reinstall without --no-optional, or delete node_modules and the lockfile and install again
Updates never arrive under npxThe server never updates itself, and npm owns the binaryExpected. Upgrade with npm, or add @latest to the npx spec so it resolves afresh instead of reusing its cache

The server never updates itself, so an upgrade is whatever your install channel does plus a restart.

SymptomCauseSolution
Still running the old version after upgradingThe old process was never restartedRestart the server, or run gitlab-mcp-server --shutdown to terminate every running instance first
Cannot replace the binary (file locked)Running instances hold the file openRun gitlab-mcp-server --shutdown to terminate every instance, then replace it
SymptomCauseSolution
401 Unauthorized with WWW-Authenticate: BearerMissing or empty token headerSend PRIVATE-TOKEN or Authorization: Bearer <token> header with every request. The JSON body names both accepted headers
400 Bad Request with no server available in plain textA build older than 2.6.6 received a request it could not routeNot a crash and not emitted by this project — it comes from the MCP SDK when server selection finds no server. Upgrade: those cases are now 401, 400, 429 and 503 with a JSON-RPC body
400 Bad Request naming GITLAB-URLThe per-request GITLAB-URL header is not a parseable URLSend an absolute URL such as https://gitlab.example.com, or omit the header when the server runs with --gitlab-url
400/403 saying the deployment does not serve that GitLab instance--gitlab-url names more than one instance, so it publishes an allow-listSend a GITLAB-URL the server publishes; omitting the header is refused too (400), never resolved to the first instance. In OAuth mode the error names the published instances — the same list its RFC 9728 metadata already serves unauthenticated — and answers 403 before the token is used anywhere. Legacy mode answers 400 and does not name them: it publishes no metadata document and reaches this rejection before the credential is validated, so ask the operator
429 Too Many Requests with Retry-AfterMore than 10 failed authentications from one client IP within a minuteWait for the window to pass and retry with a valid token. Behind a reverse proxy, set --trusted-proxy-header and --trusted-proxies so the limit counts real client IPs
503 Service UnavailableThe GitLab instance was unreachable while building the session for that tokenCheck GITLAB-URL / --gitlab-url and instance reachability; the server logs the underlying error
Pool eviction too frequentToo many unique tokensIncrease --max-http-clients (default: 100)
Sessions expiring unexpectedlyMCP idle timeout too shortIncrease --session-timeout (default: 30m); it applies to --stateless=false only, the default stateless transport has no session to expire
MCP sessions drop every ~2 min / keepalive ping failed; closing sessionA low --http-idle-timeout (or a proxy timeout) is closing long-lived SSE streamsDefault --http-idle-timeout=0 disables HTTP-layer idle closure; if you set a low value, raise it or use 0. Behind a reverse proxy, also raise its read/idle timeout
SymptomCauseSolution
401 Unauthorized with valid OAuth tokenToken expired or GitLab rejected itRe-authorize through the OAuth flow; check that the GitLab OAuth app is still active
High latency on first request after cache expiryToken re-validation against GitLab APIExpected behavior — increase --oauth-cache-ttl (default: 15m, max: 2h) to reduce validation frequency
404 on /.well-known/oauth-protected-resourceOAuth mode not enabled, or --public-url carries a pathStart the server with --auth-mode=oauth. When --public-url has a path, the document moves to the derived path (see HTTP Server Mode) and the bare path answers 404 on purpose
Client doesn’t start OAuth flowClient lacks OAuth 2.1 supportSend a personal access token as Authorization: Bearer <glpat-...> — verified the same way as an OAuth token
401 when sending PRIVATE-TOKEN in OAuth modeOAuth mode is Bearer-only, by designMove the token to Authorization: Bearer <token>; PRIVATE-TOKEN is accepted only in --auth-mode=legacy
Operations fail with insufficient mcp scopeDCR fallback assigned mcp instead of apiConfigure clientId explicitly in the MCP client config. See HTTP Server Mode
SymptomCauseSolution
List results truncatedDefault per_page limitPass per_page (max 100) and page parameters to paginate
nextPage missing in responseLast page reachedNo more results — this is expected behavior
SymptomSolution
“Tool not found” in Copilot ChatCheck Output panel → MCP Logs for errors. Verify .vscode/mcp.json path
Server not appearing in MCP statusRun Ctrl+Shift+PMCP: List Servers to verify configuration
“Permission denied” on startupRun chmod +x /path/to/gitlab-mcp-server (Linux/macOS)
Server restarts repeatedlyCheck MCP Logs for missing GITLAB_URL or GITLAB_TOKEN
Waiting for initialize with Docker logs showing HTTP modeAdd -i to the docker run args so the container gets a pipe to speak on and infers stdio (JSON-RPC over stdin/stdout, no port needed), and docker pull the image if it predates the release that made --transport auto its command. Alternatively, run the container in HTTP mode with -p 8080:8080 and an instance to serve (-e GITLAB_URL=https://gitlab.com, without which it exits with --gitlab-url is required in HTTP mode), then configure the MCP client as HTTP pointing to http://host:8080/mcp — this only works if the server is running and the port is reachable.

Enable verbose logging to diagnose issues:

  1. Set the log level to debug:

    Terminal window
    # Stdio mode
    GITLAB_MCP_LOG_LEVEL=debug ./gitlab-mcp-server 2>debug.log
    # HTTP mode (logs interleaved with server output)
    GITLAB_MCP_LOG_LEVEL=debug ./gitlab-mcp-server --http --gitlab-url=https://gitlab.com 2>debug.log
  2. Reproduce the issue by running the same operation that failed.

  3. Examine the logs — debug logs include:

    • Every tool call with input parameters
    • GitLab API request/response details
    • Token validation events (last 4 characters only)
    • Session pool operations (HTTP mode)

If you cannot resolve an issue:

  1. Enable debug logging (GITLAB_MCP_LOG_LEVEL=debug) and capture the output
  2. Check the GitHub Issues for known problems
  3. Open a new issue with:
    • Server version (gitlab-mcp-server --version or check startup logs)
    • Operating system and architecture
    • MCP client name and version
    • Redacted debug logs (remove any tokens or sensitive data)
    • Steps to reproduce the issue

Frequently asked questions

Why does my MCP client show hundreds of tools?

The client is using the individual tool surface, which registers one tool per GitLab operation. Set GITLAB_MCP_TOOL_SURFACE=meta to consolidate them into 34 domain-level meta-tools, or use the default dynamic surface, which exposes only gitlab_find_action and gitlab_execute_action. Tool names also differ by surface: individual mode uses gitlab_issue_create, meta mode uses gitlab_issue with action: create, and dynamic mode uses find and execute.

How do I fix 401 or 403 errors from GitLab?

A 401 Unauthorized means the Personal Access Token is invalid or expired — generate a new token with the api scope at GitLab → Preferences → Access Tokens. A 403 Forbidden on specific operations means the token lacks the required scope; ensure it has the api scope, not just read_api. If the server will not start with GITLAB_TOKEN is required, set GITLAB_TOKEN in the client's environment or in ~/.gitlab-mcp-server.env (a .env in the working directory is not read).

Why are Enterprise tools missing?

The Enterprise/Premium catalog is disabled. In stdio mode, set GITLAB_MCP_TIER=premium or GITLAB_MCP_TIER=ultimate. In HTTP mode, set --tier=premium or --tier=ultimate to force the catalog, or let license detection enable it when the instance reports a Premium or Ultimate plan. The deprecated GITLAB_ENTERPRISE=true variable still works when GITLAB_MCP_TIER is unset; there is no --enterprise flag — use --tier. Enterprise tools also require a Premium or Ultimate license on the connected instance.

How do I enable debug logging?

Set GITLAB_MCP_LOG_LEVEL=debug and redirect stderr to a file — for example GITLAB_MCP_LOG_LEVEL=debug ./gitlab-mcp-server 2>debug.log — then reproduce the failing operation. Debug logs include every tool call with input parameters, GitLab API request and response details, token validation events (last 4 characters only), and HTTP session pool operations. Logs go to stderr and JSON-RPC messages go to stdout, so always redirect stderr to avoid mixing the two.