@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 +69 -332
- package/build/auth-retry.js +81 -0
- package/build/config.js +75 -0
- package/build/customSchemas.js +0 -12
- package/build/gitlab-client-pool.js +0 -1
- package/build/index.js +472 -1650
- package/build/oauth.js +4 -4
- package/build/schemas.js +160 -48
- package/build/test/mcp-oauth-tests.js +49 -0
- package/build/test/schema-tests.js +59 -3
- package/build/test/test-auth-retry.js +188 -0
- package/build/test/test-search-code.js +7 -4
- package/build/test/test-token-optimizations.js +691 -0
- package/build/test/test-toolset-filtering.js +39 -33
- package/build/tools/registry.js +1295 -0
- package/build/utils/helpers.js +57 -0
- package/build/utils/schema.js +49 -0
- package/build/utils/url.js +19 -0
- package/package.json +24 -5
package/README.md
CHANGED
|
@@ -6,13 +6,34 @@
|
|
|
6
6
|
|
|
7
7
|
## @zereight/mcp-gitlab
|
|
8
8
|
|
|
9
|
-
GitLab MCP
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
####
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
-
|
|
427
|
-
-
|
|
428
|
-
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
- `
|
|
436
|
-
- `
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
-
|
|
443
|
-
-
|
|
444
|
-
-
|
|
445
|
-
|
|
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
|
+
}
|
package/build/config.js
ADDED
|
@@ -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;
|
package/build/customSchemas.js
CHANGED
|
@@ -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;
|