@zereight/mcp-gitlab 2.0.36 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,13 +6,34 @@
6
6
 
7
7
  ## @zereight/mcp-gitlab
8
8
 
9
- GitLab MCP(Model Context Protocol) Server. **Includes bug fixes and improvements over the original GitLab MCP server.**
9
+ A comprehensive GitLab MCP server for AI clients. Manage projects, merge requests, issues, pipelines, wiki, releases, milestones, and more through stdio, SSE, and Streamable HTTP.
10
10
 
11
- ## Usage
11
+ Supports PAT, OAuth, read-only mode, dynamic API URLs, and remote authorization for VS Code, Claude, Cursor, Copilot, and other MCP clients.
12
+
13
+ ### Why use this GitLab MCP?
14
+
15
+ - Broad GitLab coverage — projects, repository browsing, merge requests, issues, pipelines, wiki, releases, labels, milestones, and more
16
+ - Flexible auth — Personal Access Token, local OAuth2 browser flow, MCP OAuth proxy, and per-request remote authorization
17
+ - Multiple transports — stdio for local clients, SSE for legacy clients, and Streamable HTTP for modern remote deployments
18
+ - Client-friendly setup — examples for Claude Code, Codex, Antigravity, OpenCode, Copilot, Cline, Roo Code, Cursor, Kilo Code, and Amp Code
19
+ - Self-hosted ready — works with custom GitLab instances, proxy settings, and dynamic API URL routing
20
+
21
+ Quick start: choose either Personal Access Token or OAuth2 setup below and use `@zereight/mcp-gitlab` in your MCP client configuration.
12
22
 
13
- ### Using with Claude Code, Codex, Antigravity, OpenCode, Copilot, Cline, Roo Code, Cursor, Kilo Code, Amp Code
23
+ ### Client Setup Guides
24
+
25
+ - [Claude Code Setup Guide](./docs/claude-code-setup.md)
26
+ - [VS Code Setup Guide](./docs/vscode-setup.md)
27
+ - [GitHub Copilot Setup Guide](./docs/copilot-setup.md)
28
+ - [Codex Setup Guide](./docs/codex-setup.md)
29
+ - [Cursor Setup Guide](./docs/cursor-setup.md)
30
+ - [JSON-Based MCP Clients Setup Guide](./docs/json-mcp-clients-setup.md) - for Factory AI Droid, OpenClaw, and OpenCode style clients
31
+ - [OAuth2 Authentication Setup Guide](./docs/oauth-setup.md)
32
+ - [Environment Variables Reference](./docs/environment-variables.md)
33
+
34
+ ## Usage
14
35
 
15
- When using with the Claude App, you need to set up your API key and URLs directly.
36
+ ### Setup Overview
16
37
 
17
38
  #### Authentication Methods
18
39
 
@@ -28,74 +49,17 @@ The server supports four authentication methods:
28
49
  3. **OAuth2 — MCP Proxy** (`GITLAB_MCP_OAUTH`) — for remote MCP clients such as Claude.ai
29
50
  4. **Remote Authorization** (`REMOTE_AUTHORIZATION`) — multi-user deployments where each caller provides their own token
30
51
 
31
- #### Using OAuth2 Authentication
32
-
33
- OAuth2 provides a more secure authentication flow using browser-based authentication. When enabled, the server will:
34
-
35
- 1. Open your browser to GitLab's authorization page
36
- 2. Wait for you to approve the access
37
- 3. Store the token securely for future use
38
- 4. Automatically refresh the token when it expires
39
-
40
- For detailed OAuth2 setup instructions, see [OAuth Setup Guide](./docs/oauth-setup.md).
41
-
42
- Quick setup - first create a GitLab OAuth application:
43
-
44
- 1. Go to your GitLab instance: `Admin area` → `Applications`
45
- 2. Create a new application with:
46
- - **Name**: `GitLab MCP Server` (or any name you prefer)
47
- - **Redirect URI**: `http://127.0.0.1:8888/callback`
48
- - **Scopes**: Select `api` (provides complete read/write access to the API)
49
- 3. Copy the **Application ID** (this is your Client ID)
50
-
51
- Then configure the MCP server with OAuth:
52
-
53
- ```json
54
- {
55
- "mcpServers": {
56
- "gitlab": {
57
- "command": "npx",
58
- "args": ["-y", "@zereight/mcp-gitlab"],
59
- "env": {
60
- "GITLAB_USE_OAUTH": "true",
61
- "GITLAB_OAUTH_CLIENT_ID": "your_oauth_client_id",
62
- "GITLAB_OAUTH_CLIENT_SECRET": "your_oauth_client_secret", // Required for Confidential apps only
63
- "GITLAB_OAUTH_REDIRECT_URI": "http://127.0.0.1:8888/callback",
64
- "GITLAB_API_URL": "your_gitlab_api_url",
65
- "GITLAB_PROJECT_ID": "your_project_id", // Optional: default project
66
- "GITLAB_ALLOWED_PROJECT_IDS": "", // Optional: comma-separated list of allowed project IDs
67
- "GITLAB_READ_ONLY_MODE": "false",
68
- "USE_GITLAB_WIKI": "false", // use wiki api?
69
- "USE_MILESTONE": "false", // use milestone api?
70
- "USE_PIPELINE": "false" // use pipeline api?
71
- }
72
- }
73
- }
74
- }
75
- ```
52
+ #### Quick setup paths
76
53
 
77
- #### Using Personal Access Token (traditional)
54
+ - **Claude Code**: see [Claude Code Setup Guide](./docs/claude-code-setup.md)
55
+ - **VS Code**: see [VS Code Setup Guide](./docs/vscode-setup.md)
56
+ - **GitHub Copilot**: see [GitHub Copilot Setup Guide](./docs/copilot-setup.md)
57
+ - **Codex**: see [Codex Setup Guide](./docs/codex-setup.md)
58
+ - **Cursor**: see [Cursor Setup Guide](./docs/cursor-setup.md)
59
+ - **Factory AI Droid / OpenClaw / OpenCode style clients**: see [JSON-Based MCP Clients Setup Guide](./docs/json-mcp-clients-setup.md)
60
+ - **OAuth browser flow details**: see [OAuth2 Authentication Setup Guide](./docs/oauth-setup.md)
78
61
 
79
- ```json
80
- {
81
- "mcpServers": {
82
- "gitlab": {
83
- "command": "npx",
84
- "args": ["-y", "@zereight/mcp-gitlab"],
85
- "env": {
86
- "GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
87
- "GITLAB_API_URL": "your_gitlab_api_url",
88
- "GITLAB_PROJECT_ID": "your_project_id", // Optional: default project
89
- "GITLAB_ALLOWED_PROJECT_IDS": "", // Optional: comma-separated list of allowed project IDs
90
- "GITLAB_READ_ONLY_MODE": "false",
91
- "USE_GITLAB_WIKI": "false", // use wiki api?
92
- "USE_MILESTONE": "false", // use milestone api?
93
- "USE_PIPELINE": "false" // use pipeline api?
94
- }
95
- }
96
- }
97
- }
98
- ```
62
+ For the simplest local setup, start with a Personal Access Token. For browser-based local auth, use OAuth2. For remote or multi-user deployments, continue to the MCP OAuth and Remote Authorization sections later in this README.
99
63
 
100
64
  #### Using CLI Arguments (for clients with env var issues)
101
65
 
@@ -129,155 +93,6 @@ Some MCP clients (like GitHub Copilot CLI) have issues with environment variable
129
93
 
130
94
  CLI arguments take precedence over environment variables.
131
95
 
132
- #### vscode .vscode/mcp.json
133
-
134
- **Using OAuth2 (Non-Confidential - Recommended):**
135
-
136
- ```json
137
- {
138
- "servers": {
139
- "GitLab-MCP": {
140
- "type": "stdio",
141
- "command": "npx",
142
- "args": ["-y", "@zereight/mcp-gitlab"],
143
- "env": {
144
- "GITLAB_USE_OAUTH": "true",
145
- "GITLAB_OAUTH_CLIENT_ID": "your_oauth_client_id",
146
- "GITLAB_OAUTH_REDIRECT_URI": "http://127.0.0.1:8888/callback",
147
- "GITLAB_API_URL": "https://gitlab.com/api/v4",
148
- "GITLAB_READ_ONLY_MODE": "false",
149
- "USE_GITLAB_WIKI": "false",
150
- "USE_MILESTONE": "false",
151
- "USE_PIPELINE": "false"
152
- }
153
- }
154
- }
155
- }
156
- ```
157
-
158
- **Using OAuth2 (Confidential):**
159
-
160
- ```json
161
- {
162
- "inputs": [
163
- {
164
- "type": "promptString",
165
- "id": "gitlab-oauth-secret",
166
- "description": "GitLab OAuth Client Secret",
167
- "password": true
168
- }
169
- ],
170
- "servers": {
171
- "GitLab-MCP": {
172
- "type": "stdio",
173
- "command": "npx",
174
- "args": ["-y", "@zereight/mcp-gitlab"],
175
- "env": {
176
- "GITLAB_USE_OAUTH": "true",
177
- "GITLAB_OAUTH_CLIENT_ID": "your_oauth_client_id",
178
- "GITLAB_OAUTH_CLIENT_SECRET": "${input:gitlab-oauth-secret}",
179
- "GITLAB_OAUTH_REDIRECT_URI": "http://127.0.0.1:8888/callback",
180
- "GITLAB_API_URL": "https://gitlab.com/api/v4",
181
- "GITLAB_READ_ONLY_MODE": "false"
182
- }
183
- }
184
- }
185
- }
186
- ```
187
-
188
- **Using Personal Access Token:**
189
-
190
- ```json
191
- {
192
- "inputs": [
193
- {
194
- "type": "promptString",
195
- "id": "gitlab-token",
196
- "description": "GitLab Personal Access Token",
197
- "password": true
198
- }
199
- ],
200
- "servers": {
201
- "GitLab-MCP": {
202
- "type": "stdio",
203
- "command": "npx",
204
- "args": ["-y", "@zereight/mcp-gitlab"],
205
- "env": {
206
- "GITLAB_PERSONAL_ACCESS_TOKEN": "${input:gitlab-token}",
207
- "GITLAB_API_URL": "https://gitlab.com/api/v4",
208
- "GITLAB_READ_ONLY_MODE": "false",
209
- "USE_GITLAB_WIKI": "false",
210
- "USE_MILESTONE": "false",
211
- "USE_PIPELINE": "false"
212
- }
213
- }
214
- }
215
- }
216
- ```
217
-
218
- #### Strands Agents SDK (MCP Tools)
219
-
220
- ```python
221
- env_vars = {
222
- "GITLAB_PERSONAL_ACCESS_TOKEN": gitlab_access_token,
223
- "GITLAB_API_URL": gitlab_api_url,
224
- "USE_GITLAB_WIKI": use_gitlab_wiki
225
- # ......the rest of the optional parameters
226
- }
227
-
228
- stdio_gitlab_mcp_client = MCPClient(
229
- lambda: stdio_client(
230
- StdioServerParameters(
231
- command="npx",
232
- args=["-y", "@zereight/mcp-gitlab"],
233
- env=env_vars,
234
- )
235
- )
236
- )
237
- ```
238
-
239
- #### Docker
240
-
241
- > **Note**: For Docker deployments, **Personal Access Token is recommended**. OAuth requires browser-based authentication and a local callback server, which does not work properly in containerized environments.
242
-
243
- **Using Personal Access Token (stdio) - Recommended:**
244
-
245
- ```json
246
- {
247
- "mcpServers": {
248
- "gitlab": {
249
- "command": "docker",
250
- "args": [
251
- "run",
252
- "-i",
253
- "--rm",
254
- "-e",
255
- "GITLAB_PERSONAL_ACCESS_TOKEN",
256
- "-e",
257
- "GITLAB_API_URL",
258
- "-e",
259
- "GITLAB_READ_ONLY_MODE",
260
- "-e",
261
- "USE_GITLAB_WIKI",
262
- "-e",
263
- "USE_MILESTONE",
264
- "-e",
265
- "USE_PIPELINE",
266
- "zereight050/gitlab-mcp"
267
- ],
268
- "env": {
269
- "GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
270
- "GITLAB_API_URL": "https://gitlab.com/api/v4",
271
- "GITLAB_READ_ONLY_MODE": "false",
272
- "USE_GITLAB_WIKI": "true",
273
- "USE_MILESTONE": "true",
274
- "USE_PIPELINE": "true"
275
- }
276
- }
277
- }
278
- }
279
- ```
280
-
281
96
  - sse
282
97
 
283
98
  ```shell
@@ -417,119 +232,32 @@ Authorization: Bearer glpat-xxxxxxxxxxxxxxxxxxxx
417
232
 
418
233
  ### Environment Variables
419
234
 
420
- #### Authentication Configuration
421
-
422
- - `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token. **Required in standard mode**; not used when `REMOTE_AUTHORIZATION=true` or when using OAuth.
423
- - `GITLAB_USE_OAUTH`: Set to `true` to enable OAuth2 authentication instead of personal access token.
424
- - `GITLAB_OAUTH_CLIENT_ID`: The Client ID from your GitLab OAuth application. Required when using OAuth.
425
- - `GITLAB_OAUTH_CLIENT_SECRET`: The Client Secret from your GitLab OAuth application. Required only for Confidential applications.
426
- - `GITLAB_OAUTH_REDIRECT_URI`: The OAuth callback URL. Default: `http://127.0.0.1:8888/callback`
427
- - `GITLAB_OAUTH_TOKEN_PATH`: Custom path to store the OAuth token. Default: `~/.gitlab-mcp-token.json`
428
- - `REMOTE_AUTHORIZATION`: When set to 'true', enables remote per-session authorization via HTTP headers. In this mode:
429
- - The server accepts GitLab PAT tokens from HTTP headers (`Authorization: Bearer <token>`, `Private-Token: <token>` or `Job-Token: <token>`) on a per-session basis
430
- - `GITLAB_PERSONAL_ACCESS_TOKEN` environment variable is **not required** and ignored
431
- - Only works with **Streamable HTTP transport** (`STREAMABLE_HTTP=true`) because session management was already handled by the transport layer
432
- - **SSE transport is disabled** - attempting to use SSE with remote authorization will cause the server to exit with an error
433
- - Each client session can use a different token, enabling multi-user support with secure session isolation
434
- - Tokens are stored per session and automatically cleaned up when sessions close or timeout
435
- - `GITLAB_MCP_OAUTH`: Set to `true` to enable the server-side MCP OAuth proxy mode. See [MCP OAuth Setup](#mcp-oauth-setup-claudeai-native-oauth) for details.
436
- - `GITLAB_OAUTH_APP_ID`: Client ID of the pre-registered GitLab OAuth application. Required when `GITLAB_MCP_OAUTH=true`.
437
- - `GITLAB_OAUTH_SCOPES`: Comma-separated list of GitLab scopes to request during the MCP OAuth flow (e.g. `api,read_user`). Defaults to `api` (or `read_api` when `GITLAB_READ_ONLY_MODE=true`). Only used when `GITLAB_MCP_OAUTH=true`. The pre-registered application must be configured with at least these scopes.
438
- - `SESSION_TIMEOUT_SECONDS`: Session auth token timeout in seconds. Default: `3600` (1 hour). Valid range: 1-86400 seconds (recommended: 60+). After this period of inactivity, the auth token is removed but the transport session remains active. The client must provide auth headers again on the next request. Only applies when `REMOTE_AUTHORIZATION=true`.
439
-
440
- #### General Configuration
441
-
442
- - `GITLAB_API_URL`: Your GitLab API URL. (Default: `https://gitlab.com/api/v4`)
443
- - `GITLAB_PROJECT_ID`: Default project ID. If set, Overwrite this value when making an API request.
444
- - `GITLAB_ALLOWED_PROJECT_IDS`: Optional comma-separated list of allowed project IDs. When set with a single value, acts as a default project (like the old "lock" mode). When set with multiple values, restricts access to only those projects. Examples:
445
- - Single value `123`: MCP server can only access project 123 and uses it as default
446
- - Multiple values `123,456,789`: MCP server can access projects 123, 456, and 789 but requires explicit project ID in requests
447
- - `GITLAB_READ_ONLY_MODE`: When set to 'true', restricts the server to only expose read-only operations. Useful for enhanced security or when write access is not needed. Also useful for using with Cursor and it's 40 tool limit.
448
- - `GITLAB_DENIED_TOOLS_REGEX`: When set as a regular expression, it excludes the matching tools.
449
- - `USE_GITLAB_WIKI`: Legacy flag. Wiki features are now enabled by default. When set to 'true', ensures wiki-related tools are included even if the `wiki` toolset is not explicitly listed in `GITLAB_TOOLSETS`.
450
- - `USE_MILESTONE`: Legacy flag. Milestone features are now enabled by default. When set to 'true', ensures milestone-related tools are included even if the `milestones` toolset is not explicitly listed in `GITLAB_TOOLSETS`.
451
- - `USE_PIPELINE`: Legacy flag. Pipeline features are now enabled by default. When set to 'true', ensures pipeline-related tools are included even if the `pipelines` toolset is not explicitly listed in `GITLAB_TOOLSETS`.
452
- - `GITLAB_TOOLSETS`: Comma-separated list of toolset IDs to enable. When empty or unset, default toolsets are used. Set to `"all"` to enable every toolset. Available toolsets (default toolsets marked with `*`):
453
-
454
- - `merge_requests`\* — MR operations, notes, discussions, draft notes, threads, versions, file diffs, conflicts (34 tools)
455
- - `issues`\* — Issue CRUD, notes, links, discussions (14 tools)
456
- - `repositories`\* — Search, create, file contents, push, fork, tree (7 tools)
457
- - `branches`\* — Branch creation, commits, diffs (4 tools)
458
- - `projects`\* — Project/namespace info, group projects, iterations (8 tools)
459
- - `labels`\* — Label CRUD (5 tools)
460
- - `pipelines`\* — Pipeline, job, deployment, environment, and artifact operations (19 tools)
461
- - `milestones`\* — Milestone CRUD, issues, MRs, burndown (9 tools)
462
- - `wiki`\* — Wiki page CRUD for projects and groups (10 tools)
463
- - `releases`\* — Release CRUD, evidence, asset download (7 tools)
464
- - `users`\* — User info, events, markdown upload, attachments (5 tools)
465
- - `workitems` — Work item CRUD via GraphQL, type conversion, statuses, custom fields, notes, timeline events (12 tools, opt-in)
466
- - `webhooks` — Webhook listing and event inspection (3 tools, opt-in)
467
- - `search` — Code search across projects, groups, or globally (3 tools, requires advanced search or exact code search enabled)
468
-
469
- Note: `execute_graphql` is not in any toolset and must be added individually via `GITLAB_TOOLS` if needed.
470
- Exposing arbitrary GraphQL would allow bypassing toolset boundaries (e.g. querying data that the user intentionally disabled via toolsets like wiki or pipelines), which is a security and permission-containment concern. Keeping `execute_graphql` out of all toolsets and requiring explicit opt-in via `GITLAB_TOOLS=execute_graphql` is intentional, to align with that principle rather than for backward compatibility.
471
- CLI arg: `--toolsets`
472
-
473
- - `GITLAB_TOOLS`: Comma-separated list of individual tool names to add on top of the enabled toolsets (additive). Useful for cherry-picking specific tools without enabling an entire toolset. Example: `GITLAB_TOOLS="list_pipelines,execute_graphql"`. CLI arg: `--tools`
474
-
475
- Combined logic: `final tools = (tools from enabled toolsets) ∪ (GITLAB_TOOLS) ∪ (legacy flag overrides)`
476
-
477
- Examples:
478
-
479
- ```bash
480
- # Default behavior (unchanged)
481
- GITLAB_PERSONAL_ACCESS_TOKEN=xxx npx @zereight/mcp-gitlab
482
-
483
- # Only issues and repositories
484
- GITLAB_TOOLSETS="issues,repositories" npx @zereight/mcp-gitlab
485
-
486
- # All toolsets
487
- GITLAB_TOOLSETS="all" npx @zereight/mcp-gitlab
488
-
489
- # Default toolsets + one extra pipeline tool
490
- GITLAB_TOOLS="list_pipelines" npx @zereight/mcp-gitlab
491
-
492
- # Specific toolsets + individual tools
493
- GITLAB_TOOLSETS="issues,merge_requests" GITLAB_TOOLS="list_pipelines,get_pipeline" npx @zereight/mcp-gitlab
494
-
495
- # Legacy flags still work (backward compatible)
496
- USE_PIPELINE=true npx @zereight/mcp-gitlab
497
- ```
498
-
499
- - `GITLAB_AUTH_COOKIE_PATH`: Path to an authentication cookie file for GitLab instances that require cookie-based authentication. When provided, the cookie will be included in all GitLab API requests.
500
- - `SSE`: When set to 'true', enables the Server-Sent Events transport.
501
- - `STREAMABLE_HTTP`: When set to 'true', enables the Streamable HTTP transport. If both **SSE** and **STREAMABLE_HTTP** are set to 'true', the server will prioritize Streamable HTTP over SSE transport.
502
- - `GITLAB_COMMIT_FILES_PER_PAGE`: The number of files per page that GitLab returns for commit diffs. This value should match the server-side GitLab setting. Adjust this if your GitLab instance uses a custom per-page value for commit diffs.
503
- - `GITLAB_REPO_FILE_ENCODING`: Encoding for repository file create/update and related commit payloads sent to the GitLab API. Use `text` (default) or `base64`. Equivalent CLI: `--repo-file-encoding=text|base64`.
504
-
505
- #### Performance & Security Configuration
506
-
507
- - `HOST`: Server host address. Default: `127.0.0.1` (localhost only). Set to `0.0.0.0` to allow external connections (required for Docker with port forwarding).
508
- - `MAX_SESSIONS`: Maximum number of concurrent sessions allowed. Default: `1000`. Valid range: 1-10000. When limit is reached, new connections are rejected with HTTP 503.
509
- - `MAX_REQUESTS_PER_MINUTE`: Rate limit per session in requests per minute. Default: `60`. Valid range: 1-1000. Exceeded requests return HTTP 429.
510
- - `PORT`: Server port. Default: `3002`. Valid range: 1-65535.
511
- - `HTTP_PROXY`: HTTP proxy server URL for outgoing requests. Example: `http://proxy.example.com:8080`. Supports HTTP/HTTPS and SOCKS proxies (URLs starting with `socks://` or `socks5://`). CLI arg: `--http-proxy`
512
- - `HTTPS_PROXY`: HTTPS proxy server URL for outgoing requests. Example: `https://proxy.example.com:8080`. Supports HTTP/HTTPS and SOCKS proxies. CLI arg: `--https-proxy`
513
- - `NO_PROXY`: Comma-separated list of hosts that should bypass the proxy. Supports:
514
- - Exact hostname matches (e.g., `localhost`, `gitlab.internal.com`)
515
- - Domain suffix matches (e.g., `.internal.com` matches any subdomain)
516
- - IP addresses (e.g., `127.0.0.1`, `192.168.1.1`)
517
- - Port-specific matches (e.g., `example.com:443`)
518
- - Wildcard `*` to bypass proxy for all hosts
519
- - Example: `NO_PROXY=localhost,127.0.0.1,.internal.com`
520
- - CLI arg: `--no-proxy`
521
-
522
- #### Monitoring Endpoints
523
-
524
- When using Streamable HTTP transport, the following endpoints are available:
525
-
526
- - `/health`: Health check endpoint returning server status, active sessions count, and uptime.
527
- - `/metrics`: Detailed metrics including:
528
- - Active and total session counts
529
- - Authentication metrics (failures, expirations)
530
- - Rate limiting statistics
531
- - Resource usage (memory, uptime)
532
- - Configuration summary
235
+ Use the dedicated reference for the full environment variable list:
236
+
237
+ - [Environment Variables Reference](./docs/environment-variables.md)
238
+
239
+ Most users only need one of these starting sets:
240
+
241
+ - **Local PAT**: `GITLAB_PERSONAL_ACCESS_TOKEN`, `GITLAB_API_URL`
242
+ - **Local OAuth**: `GITLAB_USE_OAUTH=true`, `GITLAB_OAUTH_CLIENT_ID`, `GITLAB_OAUTH_REDIRECT_URI`, `GITLAB_API_URL`
243
+ - **Remote multi-user HTTP**: `STREAMABLE_HTTP=true`, `REMOTE_AUTHORIZATION=true`, `HOST`, `PORT`
244
+
245
+ Commonly referenced variables:
246
+
247
+ - `GITLAB_API_URL`
248
+ - `GITLAB_PERSONAL_ACCESS_TOKEN`
249
+ - `GITLAB_USE_OAUTH`
250
+ - `REMOTE_AUTHORIZATION`
251
+ - `GITLAB_MCP_OAUTH`
252
+
253
+ The reference document also covers:
254
+
255
+ - auth and OAuth variables
256
+ - MCP OAuth proxy variables
257
+ - project and tool filtering variables
258
+ - dynamic tool discovery via `discover_tools` (on-demand toolset activation)
259
+ - transport and session variables
260
+ - proxy and TLS variables
533
261
 
534
262
  ### Remote Authorization Setup (Multi-User Support)
535
263
 
@@ -686,6 +414,15 @@ No `headers` field is needed — Claude.ai obtains the token via OAuth automatic
686
414
  the same server instance. `Authorization: Bearer` is always treated as an OAuth
687
415
  token — use `Private-Token` for PAT-based header auth.
688
416
 
417
+ ## Agent Skill Files
418
+
419
+ Pre-built skill files are available in [`skills/gitlab-mcp/`](./skills/gitlab-mcp/) for AI agents that support skill/instruction loading (Claude Code, GitHub Copilot, Cursor, etc.).
420
+
421
+ - **[SKILL.md](./skills/gitlab-mcp/SKILL.md)** — Core guide (~800 tokens) with toolset overview, key workflows, and parameter hints
422
+ - **[reference/](./skills/gitlab-mcp/reference/)** — Detailed workflow docs for code review, merge requests, issues, and pipelines
423
+
424
+ Register the skill directory in your AI client to get optimal tool usage guidance without relying solely on the full ListTools response.
425
+
689
426
  ## Tools 🛠️
690
427
 
691
428
  <details>
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Pure helper functions for OAuth 401 auto-retry logic.
3
+ *
4
+ * Extracted into a separate module so they can be unit-tested without
5
+ * importing index.ts (which has heavy side effects: starts the MCP server,
6
+ * reads env vars, etc.).
7
+ */
8
+ import { Headers } from "node-fetch";
9
+ /**
10
+ * Convert various header representations to a plain Record<string, string>.
11
+ */
12
+ export function headersToPlainObject(headers) {
13
+ if (!headers)
14
+ return {};
15
+ if (headers instanceof Headers) {
16
+ const obj = {};
17
+ headers.forEach((value, key) => { obj[key] = value; });
18
+ return obj;
19
+ }
20
+ if (Array.isArray(headers)) {
21
+ return Object.fromEntries(headers);
22
+ }
23
+ return headers;
24
+ }
25
+ /**
26
+ * Detect request bodies that cannot be replayed (streams, FormData).
27
+ */
28
+ export function isNonReplayableBody(body) {
29
+ if (!body)
30
+ return false;
31
+ // Stream-like objects (has .pipe or .read)
32
+ if (typeof body.pipe === "function" || typeof body.read === "function")
33
+ return true;
34
+ // form-data instances (duck-type check since FormData is dynamically imported)
35
+ if (typeof body.getBuffer === "function" && typeof body.getBoundary === "function")
36
+ return true;
37
+ return false;
38
+ }
39
+ /**
40
+ * Wrap a fetch function with automatic OAuth token refresh on 401 responses.
41
+ * On a 401, force-refreshes the OAuth token and retries the request once.
42
+ * The retry calls baseFetch directly (not the wrapper), so infinite loops are impossible.
43
+ * In non-OAuth mode, the wrapper is a transparent pass-through.
44
+ *
45
+ * When called without `config`, falls back to module globals in index.ts.
46
+ * When called with `config` (tests), uses injected dependencies.
47
+ */
48
+ export function wrapWithAuthRetry(baseFetch, config) {
49
+ let refreshLock = null;
50
+ const log = config.logger ?? { info: () => { }, error: () => { } };
51
+ return (async (url, options) => {
52
+ const response = await baseFetch(url, options);
53
+ if (response.status === 401 && config.isOAuthEnabled()) {
54
+ // Skip retry for non-replayable bodies (streams, FormData) since the first request consumed them
55
+ if (isNonReplayableBody(options?.body)) {
56
+ log.info("Received 401 but request body is not replayable (stream/FormData), skipping retry.");
57
+ return response;
58
+ }
59
+ log.info("Received 401, force-refreshing OAuth token and retrying...");
60
+ try {
61
+ // Mutex: coalesce concurrent refresh attempts into a single in-flight request
62
+ if (!refreshLock) {
63
+ refreshLock = config.refreshToken(true).finally(() => {
64
+ refreshLock = null;
65
+ });
66
+ }
67
+ const token = await refreshLock;
68
+ config.onTokenRefreshed(token);
69
+ const retryOptions = {
70
+ ...options,
71
+ headers: { ...headersToPlainObject(options?.headers), ...config.buildAuthHeaders() },
72
+ };
73
+ return await baseFetch(url, retryOptions);
74
+ }
75
+ catch (refreshError) {
76
+ log.error("OAuth token refresh failed, returning original 401 response:", refreshError);
77
+ }
78
+ }
79
+ return response;
80
+ });
81
+ }
@@ -0,0 +1,75 @@
1
+ // Parse CLI arguments before anything else
2
+ const args = process.argv.slice(2);
3
+ export const cliArgs = {};
4
+ for (let i = 0; i < args.length; i++) {
5
+ const arg = args[i];
6
+ if (arg.startsWith("--")) {
7
+ const [key, value] = arg.slice(2).split("=");
8
+ if (value) {
9
+ cliArgs[key] = value;
10
+ }
11
+ else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
12
+ cliArgs[key] = args[++i];
13
+ }
14
+ }
15
+ }
16
+ export function getConfig(cliKey, envKey, defaultValue) {
17
+ return cliArgs[cliKey] || process.env[envKey] || defaultValue;
18
+ }
19
+ // ---------------------------------------------------------------------------
20
+ // Authentication
21
+ // ---------------------------------------------------------------------------
22
+ export const GITLAB_PERSONAL_ACCESS_TOKEN = getConfig("token", "GITLAB_PERSONAL_ACCESS_TOKEN");
23
+ export const GITLAB_JOB_TOKEN = getConfig("job-token", "GITLAB_JOB_TOKEN");
24
+ export const GITLAB_AUTH_COOKIE_PATH = getConfig("cookie-path", "GITLAB_AUTH_COOKIE_PATH");
25
+ export const USE_OAUTH = getConfig("use-oauth", "GITLAB_USE_OAUTH") === "true";
26
+ export const IS_OLD = getConfig("is-old", "GITLAB_IS_OLD") === "true";
27
+ // ---------------------------------------------------------------------------
28
+ // Behavior flags
29
+ // ---------------------------------------------------------------------------
30
+ export const GITLAB_READ_ONLY_MODE = getConfig("read-only", "GITLAB_READ_ONLY_MODE") === "true";
31
+ export const USE_GITLAB_WIKI = getConfig("use-wiki", "USE_GITLAB_WIKI") === "true";
32
+ export const USE_MILESTONE = getConfig("use-milestone", "USE_MILESTONE") === "true";
33
+ export const USE_PIPELINE = getConfig("use-pipeline", "USE_PIPELINE") === "true";
34
+ // ---------------------------------------------------------------------------
35
+ // Tool filtering
36
+ // ---------------------------------------------------------------------------
37
+ export const GITLAB_TOOLSETS_RAW = getConfig("toolsets", "GITLAB_TOOLSETS");
38
+ export const GITLAB_TOOLS_RAW = getConfig("tools", "GITLAB_TOOLS");
39
+ // Tool policy: comma-separated tool names
40
+ // approve = exposed but requires confirmation; hidden = not exposed at all
41
+ export const GITLAB_TOOL_POLICY_APPROVE_RAW = getConfig("tool-policy-approve", "GITLAB_TOOL_POLICY_APPROVE");
42
+ export const GITLAB_TOOL_POLICY_HIDDEN_RAW = getConfig("tool-policy-hidden", "GITLAB_TOOL_POLICY_HIDDEN");
43
+ // ---------------------------------------------------------------------------
44
+ // Transport
45
+ // ---------------------------------------------------------------------------
46
+ export const SSE = getConfig("sse", "SSE") === "true";
47
+ export const STREAMABLE_HTTP = getConfig("streamable-http", "STREAMABLE_HTTP") === "true";
48
+ export const REMOTE_AUTHORIZATION = getConfig("remote-auth", "REMOTE_AUTHORIZATION") === "true";
49
+ export const GITLAB_MCP_OAUTH = getConfig("mcp-oauth", "GITLAB_MCP_OAUTH") === "true";
50
+ // ---------------------------------------------------------------------------
51
+ // OAuth / MCP OAuth
52
+ // ---------------------------------------------------------------------------
53
+ export const MCP_SERVER_URL = getConfig("mcp-server-url", "MCP_SERVER_URL");
54
+ export const GITLAB_OAUTH_APP_ID = getConfig("oauth-app-id", "GITLAB_OAUTH_APP_ID");
55
+ export const GITLAB_OAUTH_SCOPES_RAW = getConfig("oauth-scopes", "GITLAB_OAUTH_SCOPES");
56
+ export const GITLAB_OAUTH_SCOPES = GITLAB_OAUTH_SCOPES_RAW
57
+ ? GITLAB_OAUTH_SCOPES_RAW.split(",").map((s) => s.trim()).filter(Boolean)
58
+ : undefined;
59
+ export const ENABLE_DYNAMIC_API_URL = getConfig("enable-dynamic-api-url", "ENABLE_DYNAMIC_API_URL") === "true";
60
+ // ---------------------------------------------------------------------------
61
+ // Session / server settings
62
+ // ---------------------------------------------------------------------------
63
+ export const SESSION_TIMEOUT_SECONDS = Number.parseInt(getConfig("session-timeout", "SESSION_TIMEOUT_SECONDS", "3600"), 10);
64
+ export const HOST = getConfig("host", "HOST") || "127.0.0.1";
65
+ export const PORT = Number.parseInt(getConfig("port", "PORT", "3002"), 10);
66
+ // ---------------------------------------------------------------------------
67
+ // Proxy configuration
68
+ // ---------------------------------------------------------------------------
69
+ export const HTTP_PROXY = getConfig("http-proxy", "HTTP_PROXY");
70
+ export const HTTPS_PROXY = getConfig("https-proxy", "HTTPS_PROXY");
71
+ export const NO_PROXY = getConfig("no-proxy", "NO_PROXY");
72
+ export const NODE_TLS_REJECT_UNAUTHORIZED = getConfig("tls-reject-unauthorized", "NODE_TLS_REJECT_UNAUTHORIZED");
73
+ export const GITLAB_CA_CERT_PATH = getConfig("ca-cert-path", "GITLAB_CA_CERT_PATH");
74
+ const _poolMaxSizeRaw = getConfig("pool-max-size", "GITLAB_POOL_MAX_SIZE");
75
+ export const GITLAB_POOL_MAX_SIZE = _poolMaxSizeRaw ? Number.parseInt(_poolMaxSizeRaw, 10) : 100;
@@ -1,17 +1,5 @@
1
1
  import { z } from "zod";
2
- import { pino } from 'pino';
3
2
  const DEFAULT_NULL = process.env.DEFAULT_NULL === "true";
4
- const logger = pino({
5
- level: process.env.LOG_LEVEL || 'info',
6
- transport: {
7
- target: 'pino-pretty',
8
- options: {
9
- colorize: true,
10
- levelFirst: true,
11
- destination: 2,
12
- },
13
- },
14
- });
15
3
  export const flexibleBoolean = z.preprocess(val => {
16
4
  if (typeof val === "boolean") {
17
5
  return val;
@@ -82,7 +82,6 @@ export class GitLabClientPool {
82
82
  */
83
83
  createAgentsForUrl(apiUrl) {
84
84
  const { httpProxy, httpsProxy, noProxy, rejectUnauthorized, caCertPath } = this.options;
85
- const url = new URL(apiUrl);
86
85
  let sslOptions = {};
87
86
  if (rejectUnauthorized === false) {
88
87
  sslOptions.rejectUnauthorized = false;