Skip to content

Configuration

  • One required variable
  • Read-only aware
  • Safe-mode previews
  • Tier detected from the licence

GitLab MCP Server needs almost nothing to start. In stdio mode — the default used by IDE integrations — you set just two things: which GitLab instance to talk to (GITLAB_URL) and a personal access token to talk with (GITLAB_TOKEN). On GitLab.com even the URL is optional, because GITLAB_URL defaults to https://gitlab.com, so a single GITLAB_TOKEN is enough; point GITLAB_URL at your own host for self-managed instances. In HTTP mode the server holds no credentials at all — each client sends its own token (and optionally its own GitLab URL) with every request. Everything else on this page is optional and ships with a safe default.

Stdio mode reads its configuration from environment variables, falling back to ~/.gitlab-mcp-server.env and to any file named in GITLAB_MCP_ENV_FILE; HTTP mode reads CLI flags. This page covers the options most users need; see the repository environment reference and CLI reference for the exhaustive tables.

Settings this project defines are read as GITLAB_MCP_<NAME> from 2.8.0.

A stdio MCP server runs in whatever shell its client was started from, next to every other tool that person uses. Names as generic as LOG_LEVEL, AUTH_MODE or RATE_LIMIT_RPS may already be owned by something else there, and the collision is silent: the server reads a value nobody gave it and behaves in a way nobody configured.

The unprefixed spelling still works and is removed in 3.1.0. When both are set the prefixed one wins, and a warning at startup names the one being ignored.

Some names stay bare on purpose:

NamesWhy they were not renamed
GITLAB_URL, GITLAB_TOKENGitLab’s own convention. Every existing configuration sets them, and they are the two most likely to be written into a client configuration from memory
GITLAB_MCP_SKIP_TLS_VERIFY, GITLAB_MCP_TIER, GITLAB_MCP_READ_ONLY, GITLAB_MCP_SAFE_MODE, GITLAB_MCP_IGNORE_SCOPESAlready namespaced by GITLAB_. A second prefix would churn every existing configuration and protect against nothing
OTEL_*Owned by the OpenTelemetry specification. The exporters read those names themselves and would never see a prefixed spelling
GITLAB_MCP_YOLO_MODE, AUTOPILOTConventions other agent tooling sets. Honoring the name another tool already uses is the entire point of reading them
MODELEVAL_*The model evaluation’s own variables, set by make targets in this repository. They configure a test harness the server never links, so they never appear beside another tool’s variables in a user’s shell

The tables below always give the name to set, so read the name rather than deriving it.


GitLab MCP Server requires exactly one variable to start in stdio mode — the rest are optional and default to safe values:

VariableDescriptionExample
GITLAB_TOKENPersonal Access Token with api scopeglpat-xxxxxxxxxxxxxxxxxxxx
VariableDefaultDescription
GITLAB_URLhttps://gitlab.comGitLab instance base URL. Set this for self-managed instances
GITLAB_MCP_SKIP_TLS_VERIFYfalseSkip TLS certificate verification for self-signed certs
GITLAB_MCP_TOOL_SURFACEdynamicCanonical tool catalog selector: dynamic, meta, or individual
GITLAB_MCP_CAPABILITY_SURFACEfullResource and prompt catalog selector: full keeps the complete catalog; minimal keeps the gitlab://tools manifest, and disables optional resources, prompts, workflow guides, and resource subscriptions
GITLAB_MCP_META_PARAM_SCHEMAopaqueMeta-tool input-schema strategy: opaque (default), compact (8.7x the opaque schema), or full (18.3x). Applies to meta-tool tools/list schemas only; measure the current ratios with go run ./cmd/audit_tokens --compare-schemas
GITLAB_MCP_TIER(auto-detect)GitLab edition selector: free/ce, premium, or ultimate. When set, used verbatim; when unset, detected from GET /license (fallback free). Tier gates Enterprise/Premium tools and prunes per-field schema entries via pruneSchemaFieldsByTier (see internal/tools/action_catalog.go)
GITLAB_MCP_READ_ONLYfalseDisable all mutating tools (create, update, delete)
GITLAB_MCP_SAFE_MODEfalseReturn structured JSON preview instead of executing mutating tools (dry-run mode)
GITLAB_MCP_EMBEDDED_RESOURCEStrueEmbed the canonical gitlab:// MCP resource URI in the get results that carry one (twenty-two get actions, from projects and groups to snippets and wiki pages); set false for clients that do not tolerate duplicate content blocks
GITLAB_MCP_EXCLUDE_TOOLSComma-separated tool names, group names or action IDs (e.g., gitlab_project_delete,gitlab_admin), excluded from the tool surface and from the resources, subscriptions, prompts and argument completions that return the same objects, so the removal holds on every request path
GITLAB_MCP_IGNORE_SCOPESfalseSkip automatic PAT scope detection — register all tools regardless of token scopes
GITLAB_MCP_LOG_LEVELinfoLog verbosity: debug, info, warn, error. The --log-level flag sets the same variable and wins over it
GITLAB_MCP_PPROF_ADDR(empty)Serve Go’s profiling handlers (net/http/pprof) on this loopback address (127.0.0.1:6060), on a listener of their own that starts before the transport; a host that is not loopback is refused at startup, since a heap profile is a copy of the process’s memory. Empty serves nothing. The --pprof-addr flag sets the same variable
VariableDefaultDescription
GITLAB_MCP_CLIENT_COMPATautoPer-client response compatibility: Codex sessions get fractional annotation priorities rounded to 0/1; off disables. Both transports; the --client-compat flag sets the same variable
GITLAB_MCP_DESCRIPTION_SUBSTITUTIONSemptyRewrite listed descriptions and titles for strict MCP gateway validators: comma-separated old=new pairs applied in order (backslash escapes \, \= \\); malformed values refuse startup. Both transports; the --description-substitutions flag sets the same variable
GITLAB_MCP_UPLOAD_MAX_FILE_SIZE2GBSize cap for upload and file tools, including raw file reads (streamed and stopped at the limit); supports KB/MB/GB suffixes, 1 TB ceiling. Both transports; the --upload-max-file-size flag sets the same variable
GITLAB_MCP_YOLO_MODEfalseSkip destructive action confirmations (not recommended). A non-empty value wins over AUTOPILOT; the --yolo-mode flag sets the same variable
AUTOPILOTfalseSame as GITLAB_MCP_YOLO_MODE — skip destructive confirmations. No flag of its own
GITLAB_MCP_ALLOWED_IMPORT_DIRSAdditional OS path-list-separated directories allowed for local project/group import archives
GITLAB_MCP_ALLOWED_UPLOAD_DIRSAdditional OS path-list-separated directories a tool may read a local file from (every file_path and directory_path input). The working directory (unless it is the filesystem root or the user’s home directory, which are dropped as implicit roots) and the OS temp directory are always allowed, and a path is resolved through symlinks first
GITLAB_MCP_ALLOWED_DOWNLOAD_DIRSAdditional OS path-list-separated directories a tool may write a downloaded file into (output_path). Same syntax and same always-allowed roots
GITLAB_MCP_ENV_FILEOne dotenv file to load besides ~/.gitlab-mcp-server.env. Read from the process environment only, so a file the server loads cannot nominate another. Give an absolute path; a relative one follows the client into every workspace it opens, and the server warns when it sees one
GITLAB_MCP_STDIO_MAX_LINE_BYTES4 MiBLongest stdio message accepted, in bytes. A longer line is refused and answered, not buffered. It matches the SDK’s own HTTP body default, so both transports refuse the same messages; raise it only for a client that inlines large base64 payloads
GITLAB_MCP_MAX_LISTEN_STREAMS64Concurrent subscriptions/listen streams one credential may hold open; 0 removes that ceiling. A further 512 per process is not configurable, since the per-credential figure multiplies by however many tokens a caller holds. Applies to both transports
GITLAB_MCP_ACTION_TIMEOUT65mCancel an action still running after this long; 0 disables it (upper bound 24h). Above the longest wait any action offers. Both transports; HTTP mode also has --action-timeout
GITLAB_MCP_DRAIN_DELAY0HTTP mode: after SIGTERM, keep the listener open and answer /health with 503 draining for this long before closing it, so a balancer that polls /health removes the instance before the close (upper bound 5m); 0 closes at once. Also --drain-delay
GITLAB_MCP_RATE_LIMIT_RPS0Per-credential rate limit, in req/s, on every call that reaches GitLab: tools/call, resources/read, resources/subscribe, subscriptions/listen, prompts/get (0 disables it), plus tools/list on a bucket of its own refilled a tenth as fast and holding the same burst, charged because it spends the processor every tenant shares rather than because it reaches GitLab. Both transports: the default is 0 in stdio and 10 in HTTP mode, where --rate-limit-rps overrides it
GITLAB_MCP_RATE_LIMIT_BURST40Token-bucket burst size when GITLAB_MCP_RATE_LIMIT_RPS > 0

Write it to ~/.gitlab-mcp-server.env, or to any path you then name in GITLAB_MCP_ENV_FILE. A .env in the working directory is not loaded.

~/.gitlab-mcp-server.env
# Required
GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
# Optional
GITLAB_MCP_SKIP_TLS_VERIFY=false
GITLAB_MCP_TOOL_SURFACE=dynamic
GITLAB_MCP_TIER=free
GITLAB_MCP_READ_ONLY=false
GITLAB_MCP_SAFE_MODE=false
GITLAB_MCP_EXCLUDE_TOOLS=
GITLAB_MCP_IGNORE_SCOPES=false
GITLAB_MCP_LOG_LEVEL=info

For self-managed GitLab, add GITLAB_URL=https://gitlab.example.com.

Create .vscode/mcp.json in your workspace:

{
"servers": {
"gitlab": {
"type": "stdio",
"command": "/path/to/gitlab-mcp-server",
"env": {
"GITLAB_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx"
}
}
}
}

Secure token configuration using VS Code input variables:

{
"inputs": [
{
"id": "gitlab-token",
"type": "promptString",
"description": "GitLab Personal Access Token",
"password": true
}
],
"servers": {
"gitlab": {
"type": "stdio",
"command": "/path/to/gitlab-mcp-server",
"env": {
"GITLAB_TOKEN": "${input:gitlab-token}"
}
}
}
}

In HTTP mode (--http), settings resolve in three layers, highest first: a CLI flag passed explicitly, then the matching environment variable, then the built-in default. A few flags — the listener ones below among them — have no environment counterpart:

FlagDefaultDescription
--httpfalseEnable HTTP transport mode
--http-addr:8080Listen address. host:port binds TCP; a value containing a path separator (e.g. /run/gitlab-mcp.sock) binds a unix socket instead. No environment counterpart
--http-socket-mode0660Octal permission mode for a unix socket named by --http-addr — owner and group may connect, nobody else. No environment counterpart
--tls-cert / --tls-key(empty)PEM certificate and key. Serves HTTPS on the listener itself (TLS 1.2 floor, 1.3 negotiated). Both or neither, loaded at startup. No environment counterpart
--gitlab-urlGitLab instance URL. Required unless --allow-any-gitlab-url is passed; repeat it (or comma-separate) to publish several instances, among which the GITLAB-URL header is then required
--allow-any-gitlab-urlfalseStart with no instance published and let GITLAB-URL name any host. Single-user local deployments only; it warns at startup
--skip-tls-verifyfalseSkip TLS verification
--tool-surfacedynamicCanonical tool catalog selector: dynamic, meta, or individual
--meta-param-schemaopaqueMeta-tool params schema mode: opaque, compact, or full; applies to meta-tool schemas only
--capability-surfacefullResource and prompt catalog selector: full or minimal; minimal keeps the gitlab://tools manifest, and omits optional resources, workflow guides, and prompts
--tier(auto-detect)Force GitLab edition: free/ce, premium, or ultimate; omit to auto-detect CE/EE per token+URL entry. Replaces the deprecated GITLAB_ENTERPRISE toggle; there is no --enterprise flag
--read-onlyfalseRead-only mode
--safe-modefalseIntercept mutating tools and return a JSON preview instead of executing them
--embedded-resourcestrueEmbed the canonical MCP resource URI in the get results that carry one (twenty-two get actions)
--exclude-toolsComma-separated tool names, group names or canonical action IDs, excluded from the tool surface and from the resources, subscriptions, prompts and argument completions that return the same objects
--ignore-scopesfalseSkip PAT scope detection
--max-http-clients100Maximum unique (token, GitLab URL) server entries kept in the pool; bounds pooled entries, not sessions or concurrent requests
--session-timeout30mIdle MCP session timeout; applies to --stateless=false only — under the default stateless transport each POST’s session ends with its response
--http-idle-timeout0 (disabled)HTTP server idle connection timeout. Default 0 disables idle closure, so --session-timeout is the effective lifetime; set a positive duration to recycle idle connections sooner
--statelesstrueSessionless streamable HTTP (protocol 2026-07-28): no Mcp-Session-Id tracking, every POST is self-contained, GET and DELETE answer 405. --stateless=false restores legacy stateful sessions. No environment counterpart
--json-responsefalseReturn application/json response bodies instead of text/event-stream (SSE). No environment counterpart
--max-request-body-bytes0Maximum streamable HTTP request body size in bytes; 0 uses the SDK default (4 MiB). Oversized bodies are rejected with 413. No environment counterpart
--auth-modelegacyAuthentication mode: legacy or oauth
--oauth-cache-ttl15mOAuth token identity cache TTL (1m–2h)
--oauth-client-uid(empty)Comma-separated GitLab OAuth application uids whose tokens are admitted. Empty admits any credential the instance accepts; setting it also refuses personal access tokens, which belong to no application
--public-url(empty)Externally reachable origin (scheme://host[:port][/path]). Required with --auth-mode=oauth; its origin is also trusted for cross-origin browser requests. Env: GITLAB_MCP_PUBLIC_URL
--resource-documentation(empty)https URL published as RFC 9728 resource_documentation; point it at a page describing your own OAuth application (its client ID and registered redirect URIs). Empty publishes this project’s HTTP server mode page
--resource-policy-uri(empty)https URL published as RFC 9728 resource_policy_uri; your own page on what this deployment does with the data reached through it. Empty omits the field
--resource-tos-uri(empty)https URL published as RFC 9728 resource_tos_uri; your own terms of service. Empty omits the field
--trusted-origins(empty)Comma-separated origins allowed to make cross-origin browser requests; * accepts any origin. Empty adds none, though a configured --public-url origin is trusted regardless. Env: GITLAB_MCP_TRUSTED_ORIGINS
--action-timeout65mCancel an action still running after this long; 0 disables it (upper bound: 24h). Falls back to GITLAB_MCP_ACTION_TIMEOUT
--drain-delay0After SIGTERM, keep the listener open and answer /health with 503 draining for this long before closing it (upper bound: 5m); 0 closes at once. Falls back to GITLAB_MCP_DRAIN_DELAY
--pool-idle-timeout1hReclaim a pooled per-token-and-URL credential entry after this long unused; 0 keeps entries until the pool size bound evicts them (upper bound: 24h). An entry with a live subscription is never idle by this measure
--revalidate-interval15mToken re-validation interval (upper bound: 24h). 0 stops the periodic check, but an entry whose credential is older than 1h is still rebuilt, which re-runs the probe
--rate-limit-rps10Per-credential rate limit, in req/s, on every call that reaches GitLab: tools/call, resources/read, resources/subscribe, subscriptions/listen, prompts/get (0 disables it), plus tools/list on a bucket of its own refilled a tenth as fast and holding the same burst, charged for spending the shared processor rather than for reaching GitLab; on by default because an HTTP deployment is shared
--rate-limit-burst40Token-bucket burst size when --rate-limit-rps > 0
--trusted-proxiesAddresses or CIDR ranges of the reverse proxies whose --trusted-proxy-header is believed (e.g. 127.0.0.1,10.0.0.0/8); from any other peer the header is ignored. Required with --trusted-proxy-header
--trusted-proxy-headerHTTP header with real client IP for rate limiting behind proxies (e.g. CF-Connecting-IP, X-Forwarded-For); believed only from --trusted-proxies

General flags (both stdio and HTTP modes):

FlagDefaultDescription
--transport(empty)stdio, http or auto. Empty defers to --http; auto serves HTTP only when stdin is the null device (a container started without -i) and stdio for the pipe an MCP client connects
--env-file(empty)Dotenv file to load besides ~/.gitlab-mcp-server.env; the same setting as GITLAB_MCP_ENV_FILE, and wins over it
--log-level(empty)debug, info (the effective default), warn or error. Sets GITLAB_MCP_LOG_LEVEL
--client-compat(empty)auto (the effective default) or off. Sets GITLAB_MCP_CLIENT_COMPAT
--upload-max-file-size(empty)Size cap for upload and file-read tools, KB/MB/GB suffixes accepted (2GB when unset). Sets GITLAB_MCP_UPLOAD_MAX_FILE_SIZE
--yolo-mode(empty)true skips the confirmation prompt on destructive actions. Sets GITLAB_MCP_YOLO_MODE
--description-substitutions(empty)Comma-separated old=new pairs applied to every listed description and title. Sets GITLAB_MCP_DESCRIPTION_SUBSTITUTIONS; a malformed value refuses startup
--pprof-addr(empty)Serve Go’s profiling handlers on this loopback address (127.0.0.1:6060), on a listener of their own; any other host is refused at startup. Sets GITLAB_MCP_PPROF_ADDR
--tool-search(empty)Search the action catalog by name, alias, tag or description, then exit; prints each canonical action ID beside the tool the configured surface names it. Reads --tool-surface and --tier, and otherwise GITLAB_MCP_TOOL_SURFACE and GITLAB_MCP_TIER
--versionfalsePrint the version and commit, then exit
--shutdownfalseTerminate all running instances and exit
--probefalseAsk the running instance’s /health and exit 0 when it answers; the image’s HEALTHCHECK. Reads the listener off the instance’s own flags, or probes the URL, unix:<path> or host:port given after the flag

The six flags that set a variable (--log-level through --pprof-addr) exist so one command line can configure the whole server; an explicitly passed flag beats an exported variable. GITLAB_TOKEN deliberately has no flag, since a token on a command line is visible to every user on the machine. The telemetry flags (--telemetry, --telemetry-identity, --telemetry-identity-rotation, --telemetry-tool-name) and the full --help text are in the repository CLI reference.

Example:

Terminal window
# Single GitLab.com instance (fixed URL for all clients; replace for self-managed GitLab)
./gitlab-mcp-server \
--http \
--http-addr=0.0.0.0:8080 \
--gitlab-url=https://gitlab.com \
--max-http-clients=200 \
--session-timeout=1h
# Several instances (the GITLAB-URL header is required, and must name one of them)
./gitlab-mcp-server \
--http \
--http-addr=0.0.0.0:8080 \
--gitlab-url=https://gitlab.com,https://gitlab.example.com

The server loads configuration in the following order (later sources override earlier ones):

  1. ~/.gitlab-mcp-server.env — User-level defaults (home directory)
  2. The file GITLAB_MCP_ENV_FILE names — One extra dotenv file, if the environment names one
  3. System environment variables — What the MCP client passed, or the shell exported
  4. CLI flags — Command-line arguments (highest priority)

For GitLab instances with self-signed TLS certificates:

Terminal window
GITLAB_MCP_SKIP_TLS_VERIFY=true

Enable GITLAB_MCP_READ_ONLY=true to restrict the server to read-only operations. All tools that create, update, or delete resources are disabled. This is useful for:

  • Audit and compliance environments
  • Shared servers where users should only query data
  • Tokens with read_api scope
What it exposes

every read operation, on every surface: list, get and search keep working exactly as before.

Requires
GITLAB_MCP_READ_ONLY=true in stdio mode, --read-only in HTTP mode.
What it does not do

anything that writes: create, update and delete actions are removed from the catalog per action, so on the dynamic and meta surfaces they stop being reachable rather than merely erroring. When safe mode is also set, read-only wins — mutations disappear instead of previewing.

Enable GITLAB_MCP_SAFE_MODE=true to intercept mutating tools and return a structured JSON preview of what would be executed, without actually performing the operation. Read-only tools work normally. This is useful for:

  • Reviewing operations before execution (dry-run)
  • Training environments where you want to see tool behavior
  • Debugging tool parameters without side effects
What it exposes

every operation’s shape: mutating actions stay callable and return a structured JSON preview naming the canonical action (for example issue.create) and the parameters it would send; reads execute normally.

Requires
GITLAB_MCP_SAFE_MODE=true in stdio mode, --safe-mode in HTTP mode.
What it does not do

actual writes: nothing mutating reaches GitLab. The preview is not a validation either — GitLab-side errors (permissions, conflicts) only surface on a real execution.

Both safe mode and read-only act per action, not per tool. On the dynamic and meta surfaces a single tool serves many actions — gitlab_execute_action routes everything, and gitlab_issue covers list and create alike — so the policy is applied to the underlying action catalog. Reads keep working on every surface, and safe-mode previews name the canonical action (for example issue.create) rather than the dispatcher tool.

Frequently asked questions

What is the minimum configuration?

In stdio mode, a single GITLAB_TOKEN with the api scope is enough to start — every other variable is optional and defaults to a safe value. GITLAB_URL defaults to https://gitlab.com, so you set it only when connecting to a self-managed instance. For a read-only setup, use a read_api token: the server serves it a read-only surface on its own, and GITLAB_MCP_READ_ONLY=true does the same for an api token. In HTTP mode, no token is configured on the server at all; each client sends its own token with every request.

stdio vs HTTP — which mode do I use?

Use stdio mode (the default) for local, single-user setups such as IDE integrations like VS Code, Cursor, and Claude Desktop; it is configured through environment variables, plus ~/.gitlab-mcp-server.env or a file named in GITLAB_MCP_ENV_FILE for what the environment does not carry. Use HTTP mode (--http) for shared or remote deployments such as Docker or Kubernetes, where configuration uses CLI flags and each client authenticates with its own GitLab token per request.

How do I point at a self-managed GitLab instance?

Set GITLAB_URL to your instance base URL, for example GITLAB_URL=https://gitlab.example.com (in HTTP mode, use the --gitlab-url flag instead). If the instance uses a self-signed or internal CA certificate, also set GITLAB_MCP_SKIP_TLS_VERIFY=true (or --skip-tls-verify in HTTP mode), but only on a trusted network — it disables certificate verification for all connections to GitLab.

How do I pick a tool surface (dynamic, meta, or individual)?

Set the GITLAB_MCP_TOOL_SURFACE variable (or the --tool-surface flag in HTTP mode). dynamic is the default and lowest-token catalog: it exposes gitlab_find_action and gitlab_execute_action while keeping every GitLab action reachable through the canonical action catalog. Choose meta when a client prefers consolidated domain dispatchers with an action parameter, and individual to register one MCP tool per GitLab operation.