Skip to content

GitLab MCP Environment Variables ​

Complete reference for all environment variables.

Multi-Instance Configuration ​

VariableDescriptionDefault
GITLAB_INSTANCES_FILEPath to YAML/JSON config file—
GITLAB_INSTANCESInstance list (URL, array, or JSON)—

See Multi-Instance Setup for detailed configuration.

Configuration priority (first match wins):

  1. GITLAB_INSTANCES_FILE - Configuration file
  2. GITLAB_INSTANCES - Environment variable
  3. GITLAB_API_URL + GITLAB_TOKEN - Legacy single-instance

Authentication ​

VariableDescriptionDefault
GITLAB_TOKENGitLab personal access token—
GITLAB_API_URLGitLab instance URL (legacy single-instance)https://gitlab.com
GITLAB_AUTH_COOKIE_PATHPath to auth cookie file (cookie-based auth)—

Project Scope ​

VariableDescriptionDefault
GITLAB_PROJECT_IDDefault project ID for requests—
GITLAB_ALLOWED_PROJECT_IDSComma-separated allowed project IDs—

When GITLAB_ALLOWED_PROJECT_IDS is set:

  • Single value (e.g., 123): Acts as default project, restricts to that project only
  • Multiple values (e.g., 123,456,789): Restricts access to listed projects, requires explicit project ID

The restriction is enforced before tool execution. Project IDs, full paths and URL-encoded paths are resolved to the same GitLab project. Account settings and the chat's working scope may narrow this list; selecting everywhere cannot widen it. Targetless listings and searches use only permitted projects. Group operations that cannot be confined to those projects are refused. The scope picker offers only allowed projects. Explicit targets take precedence over GITLAB_PROJECT_ID; defaults are authorized by the same check as explicit targets.

Global work item, container repository, vulnerability and todo IDs require ownership evidence from the connected GitLab instance, even when a permitted project is also supplied. Relationship targets are checked too. Timelog and suggestion operations require matching project metadata, with bounded verification; missing evidence, excessive pagination or an expired request fails closed. Older GitLab versions that do not expose suggestion ownership metadata cannot apply suggestions with an allowlist enabled. Without an allowlist, these checks add no ownership requests.

This is a server-side restriction on tool arguments and results, not a replacement for GitLab token permissions. Use a project access token when GitLab itself must enforce the project boundary. The caller's GitLab token remains authoritative for all requests and project identity lookups.

Runner lists retain only project runners whose complete set of associated projects is allowed. The upstream cursor is preserved, so a filtered page may be empty while a later page contains allowed runners.

Operations by runner_id require the same complete association check before reading details, listing jobs, changing settings, deleting, or rotating a token. Hidden associations, missing ownership evidence, or more than 100 associated projects fail closed. Jobs are additionally filtered by their own project, including historical jobs from before a runner was reassigned. Group and instance runners cannot be confined to this project allowlist; their registration and ID-based operations are refused. Project runner registration checks its project first. Without an allowlist, runner tools make no additional ownership requests.

Access Control ​

VariableDescriptionDefault
GITLAB_READ_ONLY_MODERestrict to read-only operationsfalse
GITLAB_CROSS_REFSInclude "Related:" cross-references in tool descriptionstrue
GITLAB_DENIED_TOOLS_REGEXRegex to exclude matching tools—
GITLAB_DENIED_ACTIONSDisable specific CQRS actions—

API Settings ​

VariableDescriptionDefault
GITLAB_API_CONNECT_TIMEOUT_MSTCP connect timeout in ms2000
GITLAB_API_HEADERS_TIMEOUT_MSResponse headers timeout in ms10000
GITLAB_API_BODY_TIMEOUT_MSResponse body timeout in ms30000
GITLAB_TOOL_TIMEOUT_MSTotal tool execution timeout in ms120000
GITLAB_API_RETRY_ENABLEDEnable retry for idempotent ops (GET/HEAD/OPTIONS)true
GITLAB_API_RETRY_MAX_ATTEMPTSMax retry attempts3
GITLAB_API_RETRY_BASE_DELAY_MSBase delay for exponential backoff1000
GITLAB_API_RETRY_MAX_DELAY_MSMax delay cap for backoff4000
SKIP_TLS_VERIFYSkip SSL cert verification (dev only)false

WARNING

SKIP_TLS_VERIFY=true bypasses SSL validation. Use only for testing with self-signed certificates.

Outbound proxy and GitLab CA ​

VariableDescriptionDefault
HTTP_PROXYHTTP proxy URL for HTTP GitLab instances; also used for HTTPS when HTTPS_PROXY is unsetUnset
HTTPS_PROXYHTTP or HTTPS proxy URL for HTTPS GitLab instancesUnset
NO_PROXYComma- or space-separated hosts to reach directly, including domain suffixes, optional ports and *Unset
GITLAB_CA_CERT_PATHPath to a PEM CA bundle for verifying GitLab server certificatesUnset

These process-level settings apply to REST and GraphQL requests for every configured instance, including the default single instance, and to startup requests before the instance registry is ready. HTTPS_PROXY alone does not proxy HTTP requests. NO_PROXY=localhost,127.0.0.1,.example.com:443 bypasses those hosts and the matching domain on port 443; NO_PROXY=* bypasses every proxy. Proxy credentials in URLs are not included in diagnostics.

The CA bundle is loaded once per process. Restart after changing it or the proxy configuration. GitLab CA and per-instance insecureSkipVerify settings apply to the GitLab TLS handshake, including inside a CONNECT tunnel. They do not disable the separate certificate check of an HTTPS proxy. Configure proxy trust through Node's NODE_EXTRA_CA_CERTS when its CA is not already trusted.

Direct instances retain their connection Pool counters. Environment proxy agents do not expose these counters, so those instances are omitted from Pool statistics. See connection troubleshooting.

Feature Flags ​

Enable or disable tool groups:

VariableDescriptionDefault
USE_LABELSLabel management toolstrue
USE_MRSMerge request toolstrue
USE_FILESFile browsing and managementtrue
USE_VARIABLESCI/CD variable toolstrue
USE_WORKITEMSWork items (GraphQL API)true
USE_WEBHOOKSWebhook managementtrue
USE_SNIPPETSCode snippet toolstrue
USE_INTEGRATIONSProject integration toolstrue
USE_GITLAB_WIKIWiki page toolstrue
USE_MILESTONEMilestone toolstrue
USE_PIPELINEPipeline and CI/CD job toolstrue
USE_RELEASESRelease management toolstrue
USE_REFSBranch and tag toolstrue
USE_MEMBERSTeam member toolstrue
USE_SEARCHCross-project searchtrue
USE_ITERATIONSIteration planning (sprints)true
USE_CI_TOKENSCI access credential tools: job token scope/allowlist and deploy keystrue
USE_ENVIRONMENTSEnvironments and deploymentstrue
USE_RUNNERSCI runner managementtrue
USE_REGISTRYContainer registry repositories and tagstrue
USE_ACCESS_TOKENSProject/group/personal access token managementtrue
USE_AUDIT_EVENTSAudit events (Premium+)true
USE_VULNERABILITIESVulnerability management (Ultimate)true
USE_DUODuo session discovery, progress and explicit CI executiontrue

Server Configuration ​

VariableDescriptionDefault
PORTHTTP server port (enables HTTP mode)—
HOSTServer bind address127.0.0.1
MCP_REQUEST_BODY_LIMITMaximum JSON body size for MCP HTTP POST requests, using Express size syntax (512kb, 10mb, or a byte count)10mb (10 MiB)

The body limit applies to POST /mcp, POST / and legacy SSE POST /messages. Use an integer byte count or an explicit unit (b, kb, mb, gb, tb, pb); larger units also accept decimal values. Invalid values stop startup. It counts serialized JSON bytes, including escaping and the JSON-RPC envelope; for compressed requests, it counts bytes after decompression. Requests above it receive HTTP 413 with a JSON-RPC -32600 error naming the limit and setting. Rate limiting and OAuth authentication run before parsing; legacy SSE messages also require an existing session owned by the authenticated account. OAuth registration and token endpoints retain their own parsers and limits. The setting does not affect stdio or change GitLab's own limits for notes, descriptions or files.

A reverse proxy has an independent request body limit. Raise it too when needed, for example nginx's client_max_body_size, otherwise the proxy may reject a request before it reaches the MCP server.

Security note: The default 127.0.0.1 binds only to localhost, preventing network access to the server and your GITLAB_TOKEN. If you run inside Docker, set HOST=0.0.0.0 explicitly — containers need it for port mapping to work. The Dockerfile and docker-compose examples already include this setting.

TLS Configuration ​

VariableDescriptionDefault
SSL_CERT_PATHPEM certificate file path—
SSL_KEY_PATHPEM private key file path—
SSL_CA_PATHCA certificate chain path—
SSL_PASSPHRASEPrivate key passphrase—
TRUST_PROXYExpress trust proxy setting—

See TLS/HTTPS Configuration for detailed setup guides.

OAuth Configuration ​

VariableDescriptionDefault
OAUTH_ENABLEDEnable OAuth modefalse
OAUTH_ISSUERPublic URL clients connect to; required in OAuth mode (https, or http for localhost)—
OAUTH_SESSION_SECRETSession secret (min 32 chars)—
OAUTH_CLIENT_IDGitLab OAuth application ID—
OAUTH_CLIENT_SECRETClient secret (if confidential app)—
OAUTH_SCOPESOAuth scopesapi,read_user
OAUTH_TOKEN_TTLToken lifetime in seconds3600
OAUTH_REFRESH_TOKEN_TTLRefresh token lifetime in seconds604800
OAUTH_DEVICE_POLL_INTERVALMinimum device flow poll interval in seconds; GitLab's interval and slow_down raise it5
OAUTH_DEVICE_TIMEOUTMaximum device flow lifetime in seconds; GitLab's expires_in can shorten it300

See OAuth Authentication for setup guide.

Session Storage ​

OAuth sessions can be kept in memory, in a file, or in PostgreSQL.

VariableDescriptionDefault
OAUTH_STORAGE_TYPEmemory, file, or postgresqlmemory
OAUTH_STORAGE_FILE_PATHPath for file storage./data/oauth-sessions.json
OAUTH_STORAGE_POSTGRESQL_URLPostgreSQL connection string (or DATABASE_URL)—
  • memory (default) — fastest, zero footprint; sessions are lost on restart and not shared between replicas.
  • file — single-instance persistence across restarts.
  • postgresql — persistent, multi-instance. Requires the optional @structured-world/gitlab-mcp-db package (or the gitlab-mcp-db Docker image), which carries the Prisma-based backend. The default gitlab-mcp package/image does not include PostgreSQL — selecting postgresql without the db package fails with a clear error telling you to install it.

See Docker + PostgreSQL for a full deployment.

Schema Configuration ​

VariableDescriptionDefault
GITLAB_SCHEMA_MODESchema output format (flat, discriminated, or per-session auto)flat

See Customization for schema mode details.

Logging Configuration ​

VariableDefaultDescription
LOG_LEVELinfoLog level: debug, info, warn, error
LOG_JSONfalseOutput logs as JSON (NDJSON) for log aggregators
LOG_FORMAT%msgWhich fields appear in plain-text logs: %time, %level, %name. Tokens toggle field visibility only; the line always renders in pino-pretty's fixed [time] LEVEL (name): msg order, and the message itself is always included regardless of %msg (e.g. [%time] %level (%name): %msg enables all fields)
LOG_DESTINATIONautostdout or stderr. HTTP mode only; see Log Destinations
LOG_FILTERClaude Code filterJSON array of filter rules to skip access logging

Log Destinations (stdout vs stderr) ​

Where log lines are written depends on the transport mode:

ModePlain text (default)LOG_JSON=true
stdio (no PORT)stderr (always)stderr (always)
HTTP (PORT set)stderrstdout

In stdio mode all logs always go to stderr. stdout is reserved for MCP JSON-RPC protocol frames (MCP spec), so any log line on stdout would corrupt the client connection. LOG_DESTINATION is ignored in stdio mode.

In HTTP mode the defaults follow the container convention: JSON logs go to stdout for log-driver ingestion, human-readable logs go to stderr. Set LOG_DESTINATION=stdout or LOG_DESTINATION=stderr to override. Docker's docker logs and journald capture both streams either way.

Plain Text Mode (default) ​

Human-readable single-line format suitable for journald, systemd, and console viewing:

bash
# Default - no configuration needed
gitlab-mcp

Output:

[12:34:56.789] INFO: 192.168.1.100 abc123.. mygroup/proj - POST /mcp 200 142ms | browse_projects list | GL:200 98ms | items=15
[12:34:57.123] INFO: Session created

JSON Mode ​

Full JSON output for log aggregation systems (Loki, Elasticsearch, Datadog):

bash
LOG_JSON=true gitlab-mcp

Output:

json
{"level":30,"time":1737781833989,"name":"gitlab-mcp","msg":"192.168.1.100 abc123.. - - POST /mcp 200 142ms | browse_projects list | GL:200 98ms | items=15","accessLog":{"timestamp":"2026-01-25T04:10:33.989Z","clientIp":"192.168.1.100","session":"abc123..","method":"POST","path":"/mcp","status":200,"durationMs":142,"tool":"browse_projects","action":"list"}}

Each line is a valid JSON object (NDJSON format), suitable for ingestion by log aggregators.

Docker example:

bash
docker run -e LOG_JSON=true -e GITLAB_TOKEN=xxx ghcr.io/structured-world/gitlab-mcp:latest

systemd example:

ini
[Service]
Environment="LOG_JSON=true"
Environment="GITLAB_TOKEN=xxx"
ExecStart=/usr/bin/node /opt/gitlab-mcp/dist/server.js

Access Log Filtering ​

Use LOG_FILTER to exclude specific requests from access logs. This is useful for reducing noise from health checks, polling, and monitoring tools.

VariableDefaultDescription
LOG_FILTERClaude Code filterJSON array of filter rules

Filter rule properties (all optional):

PropertyTypeDescription
methodstringHTTP method (exact match, case-insensitive)
pathstringRequest path (exact match, or prefix match if ends with *)
userAgentstringSubstring match in User-Agent header (case-insensitive)

A request is skipped if ALL conditions in a rule match. If a property is omitted, it matches any value.

Examples:

bash
# Skip Claude Code polling (GET / with claude-code user agent)
LOG_FILTER='[{"method":"GET","path":"/","userAgent":"claude-code"}]'

# Skip all HEAD requests (e.g., uptime monitoring)
LOG_FILTER='[{"method":"HEAD","path":"/*"}]'

# Skip requests from monitoring tools
LOG_FILTER='[{"userAgent":"prometheus"},{"userAgent":"datadog"}]'

# Multiple rules combined
LOG_FILTER='[
  {"method":"GET","path":"/","userAgent":"claude-code"},
  {"userAgent":"prometheus"}
]'

Note: The /health endpoint is registered before access logging middleware and is never logged, so filtering it is unnecessary.

Default behavior:

By default, LOG_FILTER is set to filter Claude Code polling requests:

bash
# Default (implicit)
LOG_FILTER='[{"method":"GET","path":"/","userAgent":"claude-code"}]'

To log all requests (disable filtering):

bash
LOG_FILTER='[]'

INFO

Filtered requests are still processed normally — only access logging is disabled. Invalid JSON or schema errors log a warning at startup and fall back to logging all requests.

Released under the Apache 2.0 License.