gitlab-mcp 0.1.5 → 1.0.0

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.
Files changed (55) hide show
  1. package/.dockerignore +7 -0
  2. package/.editorconfig +9 -0
  3. package/.env.example +75 -0
  4. package/.github/workflows/nodejs.yml +31 -0
  5. package/.github/workflows/npm-publish.yml +31 -0
  6. package/.husky/pre-commit +1 -0
  7. package/.nvmrc +1 -0
  8. package/.prettierrc.json +6 -0
  9. package/Dockerfile +20 -0
  10. package/README.md +416 -251
  11. package/docker-compose.yml +10 -0
  12. package/docs/architecture.md +310 -0
  13. package/docs/authentication.md +299 -0
  14. package/docs/configuration.md +149 -0
  15. package/docs/deployment.md +336 -0
  16. package/docs/tools.md +294 -0
  17. package/eslint.config.js +23 -0
  18. package/package.json +70 -32
  19. package/scripts/get-oauth-token.example.sh +15 -0
  20. package/src/config/env.ts +171 -0
  21. package/src/http.ts +605 -0
  22. package/src/index.ts +77 -0
  23. package/src/lib/auth-context.ts +19 -0
  24. package/src/lib/gitlab-client.ts +1810 -0
  25. package/src/lib/logger.ts +17 -0
  26. package/src/lib/network.ts +45 -0
  27. package/src/lib/oauth.ts +287 -0
  28. package/src/lib/output.ts +51 -0
  29. package/src/lib/policy.ts +78 -0
  30. package/src/lib/request-runtime.ts +376 -0
  31. package/src/lib/sanitize.ts +25 -0
  32. package/src/server/build-server.ts +17 -0
  33. package/src/tools/gitlab.ts +3128 -0
  34. package/src/tools/health.ts +27 -0
  35. package/src/tools/mr-code-context.ts +473 -0
  36. package/src/types/context.ts +13 -0
  37. package/tests/auth-context.test.ts +102 -0
  38. package/tests/gitlab-client.test.ts +674 -0
  39. package/tests/graphql-guard.test.ts +121 -0
  40. package/tests/integration/agent-loop.integration.test.ts +552 -0
  41. package/tests/integration/server.integration.test.ts +543 -0
  42. package/tests/mr-code-context.test.ts +600 -0
  43. package/tests/oauth.test.ts +43 -0
  44. package/tests/output.test.ts +186 -0
  45. package/tests/policy.test.ts +324 -0
  46. package/tests/request-runtime.test.ts +252 -0
  47. package/tests/sanitize.test.ts +123 -0
  48. package/tests/upload-reference.test.ts +84 -0
  49. package/tsconfig.build.json +11 -0
  50. package/tsconfig.json +21 -0
  51. package/vitest.config.ts +12 -0
  52. package/LICENSE +0 -21
  53. package/build/index.js +0 -1642
  54. package/build/schemas.js +0 -684
  55. package/build/test-note.js +0 -54
package/README.md CHANGED
@@ -1,276 +1,441 @@
1
- # GitLab MCP Server
1
+ # gitlab-mcp
2
2
 
3
- Model Context Protocol (MCP) server for GitLab integration. This server allows communication between GitLab and MCP-compatible AI assistants.
3
+ ![Node CI](https://github.com/mcpland/gitlab-mcp/workflows/Node%20CI/badge.svg)
4
+ [![npm](https://img.shields.io/npm/v/gitlab-mcp.svg)](https://www.npmjs.com/package/gitlab-mcp)
5
+ ![license](https://img.shields.io/npm/l/gitlab-mcp)
6
+
7
+ A production-ready [MCP](https://modelcontextprotocol.io/) server for GitLab. Provides **80+ tools** that let AI assistants read and manage GitLab projects, merge requests, issues, pipelines, wikis, releases, and more through a unified, policy-controlled interface.
8
+
9
+ ## Highlights
10
+
11
+ - **Comprehensive GitLab coverage** — projects, merge requests (with code-context analysis), issues, pipelines, wikis, milestones, releases, labels, commits, branches, GraphQL, and file management
12
+ - **Multiple transports** — stdio for local CLI usage, Streamable HTTP for remote deployments, optional SSE
13
+ - **Flexible authentication** — personal access tokens, OAuth 2.0 PKCE, external token scripts, token files, cookie-based auth, and per-request remote authorization
14
+ - **Policy engine** — read-only mode, tool allowlist/denylist, feature toggles, and project-scoped restrictions
15
+ - **Enterprise networking** — HTTP/HTTPS proxy, custom CA certificates, Cloudflare bypass, multi-instance API rotation
16
+ - **Output control** — JSON, compact JSON, or YAML formatting with configurable response size limits
4
17
 
5
18
  ## Usage
6
19
 
7
- ### Using with Claude App, Cline, Roo Code, Cursor
20
+ ### Supported clients
21
+
22
+ Claude Desktop, Claude Code, VS Code, GitHub Copilot Chat (VS Code), Cursor, JetBrains AI Assistant, GitLab Duo, and any MCP client that supports stdio or streamable HTTP.
23
+
24
+ Current client format references:
25
+
26
+ - [MCP transports and protocol](https://modelcontextprotocol.io/docs/concepts/transports)
27
+ - [Claude Code MCP](https://docs.anthropic.com/en/docs/claude-code/mcp)
28
+ - [VS Code MCP servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers)
29
+ - [Cursor MCP](https://docs.cursor.com/context/model-context-protocol)
30
+ - [JetBrains AI Assistant MCP](https://www.jetbrains.com/help/ai-assistant/configure-an-mcp-server.html)
31
+
32
+ ### Authentication methods
33
+
34
+ The server supports three auth patterns:
35
+
36
+ 1. Personal Access Token (PAT)
37
+ 2. OAuth 2.0 PKCE (recommended for local interactive use)
38
+ 3. Remote per-request auth (`REMOTE_AUTHORIZATION=true`, HTTP mode)
8
39
 
9
- When using with the Claude App, you need to set up your API key and URLs directly.
40
+ ### OAuth2 setup (stdio, recommended for local interactive use)
41
+
42
+ 1. Create a GitLab OAuth application in `Settings -> Applications`.
43
+ 2. Set redirect URI to `http://127.0.0.1:8765/callback` (or your custom callback).
44
+ 3. Set scope to `api`.
45
+ 4. Copy the Application ID as `GITLAB_OAUTH_CLIENT_ID`.
10
46
 
11
47
  ```json
12
48
  {
13
49
  "mcpServers": {
14
- "GitLab communication server": {
50
+ "gitlab": {
15
51
  "command": "npx",
16
- "args": ["-y", "mcp-gitlab"],
52
+ "args": ["-y", "gitlab-mcp@latest"],
53
+ "env": {
54
+ "GITLAB_USE_OAUTH": "true",
55
+ "GITLAB_OAUTH_CLIENT_ID": "your_oauth_client_id",
56
+ "GITLAB_OAUTH_REDIRECT_URI": "http://127.0.0.1:8765/callback",
57
+ "GITLAB_API_URL": "https://gitlab.com/api/v4",
58
+ "GITLAB_ALLOWED_PROJECT_IDS": "",
59
+ "GITLAB_READ_ONLY_MODE": "false",
60
+ "USE_GITLAB_WIKI": "true",
61
+ "USE_MILESTONE": "true",
62
+ "USE_PIPELINE": "true"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ If your OAuth app is confidential, also set `GITLAB_OAUTH_CLIENT_SECRET`.
70
+
71
+ ### Personal Access Token setup (stdio)
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "gitlab": {
77
+ "command": "npx",
78
+ "args": ["-y", "gitlab-mcp@latest"],
79
+ "env": {
80
+ "GITLAB_PERSONAL_ACCESS_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx",
81
+ "GITLAB_API_URL": "https://gitlab.com/api/v4",
82
+ "GITLAB_ALLOWED_PROJECT_IDS": "",
83
+ "GITLAB_READ_ONLY_MODE": "false",
84
+ "USE_GITLAB_WIKI": "true",
85
+ "USE_MILESTONE": "true",
86
+ "USE_PIPELINE": "true"
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### VS Code `.vscode/mcp.json` examples
94
+
95
+ PAT with secure prompt input:
96
+
97
+ ```json
98
+ {
99
+ "inputs": [
100
+ {
101
+ "type": "promptString",
102
+ "id": "gitlab_token",
103
+ "description": "GitLab Personal Access Token",
104
+ "password": true
105
+ }
106
+ ],
107
+ "servers": {
108
+ "gitlab": {
109
+ "type": "stdio",
110
+ "command": "node",
111
+ "args": ["/absolute/path/to/gitlab-mcp/dist/index.js"],
17
112
  "env": {
18
- "GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
19
- "GITLAB_API_URL": "your_gitlab_api_url",
20
- "GITLAB_READ_ONLY_MODE": "true"
113
+ "GITLAB_PERSONAL_ACCESS_TOKEN": "${input:gitlab_token}",
114
+ "GITLAB_API_URL": "https://gitlab.com/api/v4",
115
+ "GITLAB_READ_ONLY_MODE": "false"
21
116
  }
22
117
  }
23
118
  }
24
119
  }
25
120
  ```
26
121
 
27
- ### Environment Variables
28
-
29
- - `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token.
30
- - `GITLAB_API_URL`: Your GitLab API URL. (Default: `https://gitlab.com/api/v4`)
31
- - `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.
32
-
33
- ## Tools 🛠️
34
-
35
- 1. `create_or_update_file`
36
-
37
- - Create or update a single file in a GitLab project. 📝
38
- - Inputs:
39
- - `project_id` (string): Project ID or namespace/project_path
40
- - `file_path` (string): Path to create/update the file
41
- - `content` (string): File content
42
- - `commit_message` (string): Commit message
43
- - `branch` (string): Branch to create/update the file in
44
- - `previous_path` (optional string): Previous file path when renaming a file
45
- - Returns: File content and commit details
46
-
47
- 2. `push_files`
48
-
49
- - Push multiple files in a single commit. 📤
50
- - Inputs:
51
- - `project_id` (string): Project ID or namespace/project_path
52
- - `branch` (string): Branch to push to
53
- - `files` (array): Array of files to push, each with `file_path` and `content` properties
54
- - `commit_message` (string): Commit message
55
- - Returns: Updated branch reference
56
-
57
- 3. `search_repositories`
58
-
59
- - Search for GitLab projects. 🔍
60
- - Inputs:
61
- - `search` (string): Search query
62
- - `page` (optional number): Page number (default: 1)
63
- - `per_page` (optional number): Results per page (default: 20, max: 100)
64
- - Returns: Project search results
65
-
66
- 4. `create_repository`
67
-
68
- - Create a new GitLab project. ➕
69
- - Inputs:
70
- - `name` (string): Project name
71
- - `description` (optional string): Project description
72
- - `visibility` (optional string): Project visibility level (public, private, internal)
73
- - `initialize_with_readme` (optional boolean): Initialize with README
74
- - Returns: Details of the created project
75
-
76
- 5. `get_file_contents`
77
-
78
- - Get the contents of a file or directory. 📂
79
- - Inputs:
80
- - `project_id` (string): Project ID or namespace/project_path
81
- - `file_path` (string): Path to the file/directory
82
- - `ref` (optional string): Branch, tag, or commit SHA (default: default branch)
83
- - Returns: File/directory content
84
-
85
- 6. `create_issue`
86
-
87
- - Create a new issue. 🐛
88
- - Inputs:
89
- - `project_id` (string): Project ID or namespace/project_path
90
- - `title` (string): Issue title
91
- - `description` (string): Issue description
92
- - `assignee_ids` (optional number[]): Array of assignee IDs
93
- - `milestone_id` (optional number): Milestone ID
94
- - `labels` (optional string[]): Array of labels
95
- - Returns: Details of the created issue
96
-
97
- 7. `create_merge_request`
98
-
99
- - Create a new merge request. 🚀
100
- - Inputs:
101
- - `project_id` (string): Project ID or namespace/project_path
102
- - `title` (string): Merge request title
103
- - `description` (string): Merge request description
104
- - `source_branch` (string): Branch with changes
105
- - `target_branch` (string): Branch to merge into
106
- - `allow_collaboration` (optional boolean): Allow collaborators to push commits to the source branch
107
- - `draft` (optional boolean): Create as a draft merge request
108
- - Returns: Details of the created merge request
109
-
110
- 8. `fork_repository`
111
-
112
- - Fork a project. 🍴
113
- - Inputs:
114
- - `project_id` (string): Project ID or namespace/project_path to fork
115
- - `namespace` (optional string): Namespace to fork into (default: user namespace)
116
- - Returns: Details of the forked project
117
-
118
- 9. `create_branch`
119
-
120
- - Create a new branch. 🌿
121
- - Inputs:
122
- - `project_id` (string): Project ID or namespace/project_path
123
- - `name` (string): New branch name
124
- - `ref` (optional string): Ref to create the branch from (branch, tag, commit SHA, default: default branch)
125
- - Returns: Created branch reference
126
-
127
- 10. `get_merge_request`
128
-
129
- - Get details of a merge request. ℹ️
130
- - Inputs:
131
- - `project_id` (string): Project ID or namespace/project_path
132
- - `merge_request_iid` (number): Merge request IID
133
- - Returns: Merge request details
134
-
135
- 11. `get_merge_request_diffs`
136
-
137
- - Get changes (diffs) of a merge request. diff
138
- - Inputs:
139
- - `project_id` (string): Project ID or namespace/project_path
140
- - `merge_request_iid` (number): Merge request IID
141
- - `view` (optional string): Diff view type ('inline' or 'parallel')
142
- - Returns: Array of merge request diff information
143
-
144
- 12. `update_merge_request`
145
-
146
- - Update a merge request. 🔄
147
- - Inputs:
148
- - `project_id` (string): Project ID or namespace/project_path
149
- - `merge_request_iid` (number): Merge request IID
150
- - `title` (optional string): New title
151
- - `description` (string): New description
152
- - `target_branch` (optional string): New target branch
153
- - `state_event` (optional string): Merge request state change event ('close', 'reopen')
154
- - `remove_source_branch` (optional boolean): Remove source branch after merge
155
- - `allow_collaboration` (optional boolean): Allow collaborators to push commits to the source branch
156
- - Returns: Updated merge request details
157
-
158
- 13. `create_note`
159
-
160
- - Create a new note (comment) to an issue or merge request. 💬
161
- - Inputs:
162
- - `project_id` (string): Project ID or namespace/project_path
163
- - `noteable_type` (string): Type of noteable ("issue" or "merge_request")
164
- - `noteable_iid` (number): IID of the issue or merge request
165
- - `body` (string): Note content
166
- - Returns: Details of the created note
167
-
168
- 14. `list_projects`
169
- - List accessible projects with rich filtering options 📊
170
- - Inputs:
171
- - Search/filtering:
172
- - `search`
173
- - `owned`
174
- - `membership`
175
- - `archived`
176
- - `visibility`
177
- - Features filtering:
178
- - `with_issues_enabled`
179
- - `with_merge_requests_enabled`
180
- - Sorting:
181
- - `order_by`
182
- - `sort`
183
- - Access control:
184
- - `min_access_level`
185
- - Pagination:
186
- - `page`
187
- - `per_page`
188
- - `simple`
189
- - Returns: Array of projects
190
- 15. `list_labels`
191
- - List all labels for a project with filtering options 🏷️
192
- - Inputs:
193
- - `project_id` (string): Project ID or path
194
- - `with_counts` (optional): Include issue and merge request counts
195
- - `include_ancestor_groups` (optional): Include ancestor groups
196
- - `search` (optional): Filter labels by keyword
197
- - Returns: Array of labels
198
- 16. `get_label`
199
- - Get a single label from a project
200
- - Inputs:
201
- - `project_id` (string): Project ID or path
202
- - `label_id` (number/string): Label ID or name
203
- - `include_ancestor_groups` (optional): Include ancestor groups
204
- - Returns: label details
205
- 17. `create_label`
206
- - Create a new label in an object 🏷️➕
207
- - Inputs:
208
- - `project_id` (string): Project ID or path
209
- - `name` (string): Label name
210
- - `color` (string): Color in hex format (e.g., "#FF0000")
211
- - `description` (optional): Label description
212
- - `priority` (optional): Label priority
213
- - Returns: Created label details
214
- 18. `update_label`
215
- - Update an existing label in a project 🏷️✏️
216
- - Inputs:
217
- - `project_id` (string): Project ID or path
218
- - `label_id` (number/string): Label ID or name
219
- - `new_name` (optional): New label name
220
- - `color` (optional): New color in hex format
221
- - `description` (optional): New description
222
- - `priority` (optional): New priority
223
- - Returns: Updated label details
224
- 19. `delete_label`
225
-
226
- - Delete a label from a project 🏷️❌
227
- - Inputs:
228
- - `project_id` (string): Project ID or path
229
- - `label_id` (number/string): Label ID or name
230
- - Returns: Success message
231
-
232
- 20. `list_group_projects`
233
-
234
- - List all projects in a GitLab group. 📂
235
- - Inputs:
236
- - `group_id` (string): Project ID or namespace/project_path
237
- - Filtering options:
238
- - `include_subgroups` (optional boolean): Include projects from subgroups
239
- - `search` (optional string): Search term to filter projects
240
- - `archived` (optional boolean): Filter for archived projects
241
- - `visibility` (optional string): Filter by project visibility (public/internal/private)
242
- - `with_programming_language` (optional string): Filter by programming language
243
- - `starred` (optional boolean): Filter by starred projects
244
- - Feature filtering:
245
- - `with_issues_enabled` (optional boolean): Filter projects with issues feature enabled
246
- - `with_merge_requests_enabled` (optional boolean): Filter projects with merge requests feature enabled
247
- - `min_access_level` (optional number): Filter by minimum access level
248
- - Pagination:
249
- - `page` (optional number): Page number
250
- - `per_page` (optional number): Results per page
251
- - Sorting:
252
- - `order_by` (optional string): Field to sort by
253
- - `sort` (optional string): Sort direction (asc/desc)
254
- - Additional data:
255
- - `statistics` (optional boolean): Include project statistics
256
- - `with_custom_attributes` (optional boolean): Include custom attributes
257
- - `with_security_reports` (optional boolean): Include security reports
258
- - Returns: List of projects
259
-
260
- ## Environment Variable Configuration
261
-
262
- Before running the server, you need to set the following environment variables:
122
+ OAuth (confidential app) with secure prompt input:
263
123
 
124
+ ```json
125
+ {
126
+ "inputs": [
127
+ {
128
+ "type": "promptString",
129
+ "id": "gitlab_oauth_secret",
130
+ "description": "GitLab OAuth Client Secret",
131
+ "password": true
132
+ }
133
+ ],
134
+ "servers": {
135
+ "gitlab": {
136
+ "type": "stdio",
137
+ "command": "node",
138
+ "args": ["/absolute/path/to/gitlab-mcp/dist/index.js"],
139
+ "env": {
140
+ "GITLAB_USE_OAUTH": "true",
141
+ "GITLAB_OAUTH_CLIENT_ID": "your_oauth_client_id",
142
+ "GITLAB_OAUTH_CLIENT_SECRET": "${input:gitlab_oauth_secret}",
143
+ "GITLAB_OAUTH_REDIRECT_URI": "http://127.0.0.1:8765/callback",
144
+ "GITLAB_API_URL": "https://gitlab.com/api/v4"
145
+ }
146
+ }
147
+ }
148
+ }
264
149
  ```
265
- GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token
266
- GITLAB_API_URL=your_gitlab_api_url # Default: https://gitlab.com/api/v4
267
- GITLAB_READ_ONLY_MODE=true # Optional: Enable read-only mode
150
+
151
+ GitHub Copilot Chat in VS Code uses the same `.vscode/mcp.json` format.
152
+
153
+ ### Claude Desktop / Claude Code / Cursor
154
+
155
+ Claude Desktop reads `claude_desktop_config.json`.
156
+ Claude Code supports project-level `.mcp.json` and `claude mcp add-json`.
157
+ Cursor uses `.cursor/mcp.json`.
158
+
159
+ ```json
160
+ {
161
+ "mcpServers": {
162
+ "gitlab": {
163
+ "command": "node",
164
+ "args": ["/absolute/path/to/gitlab-mcp/dist/index.js"],
165
+ "env": {
166
+ "GITLAB_PERSONAL_ACCESS_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx",
167
+ "GITLAB_API_URL": "https://gitlab.com/api/v4"
168
+ }
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ ### GitLab Duo (`~/.gitlab/duo/mcp.json`)
175
+
176
+ ```json
177
+ {
178
+ "mcpServers": {
179
+ "gitlab": {
180
+ "command": "node",
181
+ "args": ["/absolute/path/to/gitlab-mcp/dist/index.js"],
182
+ "env": {
183
+ "GITLAB_PERSONAL_ACCESS_TOKEN": "glpat-xxxxxxxxxxxxxxxxxxxx",
184
+ "GITLAB_API_URL": "https://gitlab.com/api/v4"
185
+ }
186
+ }
187
+ },
188
+ "approvedTools": ["gitlab_get_project", "gitlab_list_merge_requests"]
189
+ }
268
190
  ```
269
191
 
270
- ## Credits
192
+ ### JetBrains AI Assistant
193
+
194
+ JetBrains can import an existing MCP JSON config or register the server manually.
195
+ Use stdio command `node /absolute/path/to/gitlab-mcp/dist/index.js`, or HTTP endpoint `http://127.0.0.1:3333/mcp` with required headers.
196
+
197
+ ### Remote authorization (multi-user HTTP)
198
+
199
+ Start server:
200
+
201
+ ```bash
202
+ REMOTE_AUTHORIZATION=true \
203
+ HTTP_HOST=0.0.0.0 \
204
+ HTTP_PORT=3333 \
205
+ node dist/http.js
206
+ ```
207
+
208
+ Client config:
209
+
210
+ ```json
211
+ {
212
+ "mcpServers": {
213
+ "gitlab": {
214
+ "url": "http://127.0.0.1:3333/mcp",
215
+ "headers": {
216
+ "Authorization": "Bearer glpat-xxxxxxxxxxxxxxxxxxxx"
217
+ }
218
+ }
219
+ }
220
+ }
221
+ ```
222
+
223
+ Dynamic per-request API URL:
224
+
225
+ ```bash
226
+ REMOTE_AUTHORIZATION=true \
227
+ ENABLE_DYNAMIC_API_URL=true \
228
+ HTTP_HOST=0.0.0.0 \
229
+ HTTP_PORT=3333 \
230
+ node dist/http.js
231
+ ```
232
+
233
+ Add header in client requests:
234
+
235
+ ```json
236
+ {
237
+ "headers": {
238
+ "Authorization": "Bearer glpat-xxxxxxxxxxxxxxxxxxxx",
239
+ "X-GitLab-API-URL": "https://gitlab.example.com/api/v4"
240
+ }
241
+ }
242
+ ```
243
+
244
+ Remote auth behavior matrix:
245
+
246
+ | Server Mode | Required Request Headers | Token Fallback Chain |
247
+ | ----------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------- |
248
+ | `REMOTE_AUTHORIZATION=false` | none | enabled |
249
+ | `REMOTE_AUTHORIZATION=true` | `Authorization: Bearer <token>` or `Private-Token: <token>` | disabled |
250
+ | `REMOTE_AUTHORIZATION=true` + `ENABLE_DYNAMIC_API_URL=true` | `Authorization` or `Private-Token`, and `X-GitLab-API-URL: https://host/api/v4` | disabled |
251
+
252
+ ### Docker
253
+
254
+ For containerized deployments, PAT or remote auth is recommended.
255
+ OAuth interactive callback flow is usually less convenient in containers.
256
+
257
+ ```bash
258
+ docker compose up --build -d
259
+ ```
260
+
261
+ or:
262
+
263
+ ```bash
264
+ docker build -t gitlab-mcp .
265
+
266
+ docker run -d \
267
+ --name gitlab-mcp \
268
+ -p 3333:3333 \
269
+ -e GITLAB_API_URL=https://gitlab.com/api/v4 \
270
+ -e GITLAB_PERSONAL_ACCESS_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx \
271
+ gitlab-mcp
272
+ ```
273
+
274
+ ### Compatibility notes
275
+
276
+ - `GITLAB_PROJECT_ID` is not a supported environment variable in this repository.
277
+ - To set an effective default project, use `GITLAB_ALLOWED_PROJECT_IDS` with one project ID, or pass `project_id` in tool arguments.
278
+ - CLI argument overrides such as `--token` or `--api-url` are not implemented.
279
+ - JSON config files do not support comments (`//`).
280
+
281
+ ## MCP Server Configuration
282
+
283
+ ## HTTP server
284
+
285
+ ```bash
286
+ pnpm install
287
+ cp .env.example .env
288
+ pnpm build
289
+
290
+ # stdio (local MCP)
291
+ pnpm start
292
+
293
+ # streamable HTTP server (http://127.0.0.1:3333/mcp)
294
+ pnpm start:http
295
+ ```
296
+
297
+ ### Transport and entrypoint
298
+
299
+ | Transport | Entry Point | Endpoint | Best For |
300
+ | ------------------- | -------------------- | ---------------------------- | ------------------------------------ |
301
+ | **stdio** | `node dist/index.js` | stdin/stdout | Local single-user MCP clients |
302
+ | **Streamable HTTP** | `node dist/http.js` | `POST/GET/DELETE /mcp` | Remote/shared deployments |
303
+ | **SSE (legacy)** | `node dist/http.js` | `GET /sse`, `POST /messages` | Legacy SSE-only clients (`SSE=true`) |
304
+ | **Health** | `node dist/http.js` | `GET /healthz` | Liveness/readiness checks |
305
+
306
+ `SSE=true` is not compatible with `REMOTE_AUTHORIZATION=true`.
307
+
308
+ ## Tool Categories
309
+
310
+ Tools are organized into these categories. All GitLab tools use the `gitlab_` prefix, except `health_check`.
311
+
312
+ | Category | Examples | Count |
313
+ | ------------------- | ------------------------------------------------------------------------- | ----- |
314
+ | **Projects** | `get_project`, `list_projects`, `create_repository`, `fork_repository` | 8 |
315
+ | **Repository** | `get_repository_tree`, `get_file_contents`, `push_files`, `create_branch` | 7 |
316
+ | **Merge Requests** | `list_merge_requests`, `create_merge_request`, `merge_merge_request` | 12 |
317
+ | **MR Code Context** | `get_merge_request_code_context` (advanced code review) | 1 |
318
+ | **MR Discussions** | `list_merge_request_discussions`, `create_merge_request_thread` | 7 |
319
+ | **MR Notes** | `list_merge_request_notes`, `create_merge_request_note` | 7 |
320
+ | **Draft Notes** | `list_draft_notes`, `create_draft_note`, `bulk_publish_draft_notes` | 7 |
321
+ | **Issues** | `list_issues`, `create_issue`, `update_issue`, issue links | 13 |
322
+ | **Pipelines** | `list_pipelines`, `get_pipeline_job_output`, `create_pipeline` | 12 |
323
+ | **Commits** | `list_commits`, `get_commit`, `get_commit_diff` | 3 |
324
+ | **Labels** | `list_labels`, `create_label`, `update_label` | 5 |
325
+ | **Milestones** | `list_milestones`, `create_milestone`, burndown events | 10 |
326
+ | **Releases** | `list_releases`, `create_release`, `download_release_asset` | 7 |
327
+ | **Wiki** | `list_wiki_pages`, `create_wiki_page`, `update_wiki_page` | 5 |
328
+ | **Uploads** | `upload_markdown`, `download_attachment` | 2 |
329
+ | **GraphQL** | `execute_graphql_query`, `execute_graphql_mutation` | 3 |
330
+ | **Users & Groups** | `get_users`, `list_namespaces`, `list_events` | 6 |
331
+ | **Health** | `health_check` | 1 |
332
+
333
+ See [docs/tools.md](docs/tools.md) for the complete reference.
334
+
335
+ ## Policy & Security
336
+
337
+ The policy engine controls which tools are available at registration time:
338
+
339
+ ```bash
340
+ # Read-only mode — disables all mutating tools
341
+ GITLAB_READ_ONLY_MODE=true
342
+
343
+ # Only expose specific tools (supports with or without gitlab_ prefix)
344
+ GITLAB_ALLOWED_TOOLS=get_project,list_merge_requests,get_merge_request
345
+
346
+ # Block tools by regex pattern
347
+ GITLAB_DENIED_TOOLS_REGEX=^gitlab_(delete|create)_
348
+
349
+ # Restrict to specific projects
350
+ GITLAB_ALLOWED_PROJECT_IDS=123,456,789
351
+
352
+ # Keep GraphQL tools enabled in project-scoped mode (disabled by default)
353
+ GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE=true
354
+
355
+ # Disable feature groups
356
+ USE_PIPELINE=false
357
+ USE_GITLAB_WIKI=false
358
+ ```
359
+
360
+ ## Configuration
361
+
362
+ All configuration is done through environment variables. Key settings:
363
+
364
+ | Area | Variable | Default | Description |
365
+ | --------------- | ----------------------------------------- | --------------------------- | ----------------------------------------------------------------------------------- |
366
+ | GitLab API | `GITLAB_API_URL` | `https://gitlab.com/api/v4` | Base API URL. Supports comma-separated multi-instance URLs. |
367
+ | GitLab API | `GITLAB_PERSONAL_ACCESS_TOKEN` | — | Static default token used when `REMOTE_AUTHORIZATION=false`. |
368
+ | Remote Auth | `REMOTE_AUTHORIZATION` | `false` | Require per-request token headers in HTTP mode (disables fallback token chain). |
369
+ | Remote Auth | `ENABLE_DYNAMIC_API_URL` | `false` | Require `X-GitLab-API-URL` per request. Requires `REMOTE_AUTHORIZATION=true`. |
370
+ | HTTP Server | `HTTP_HOST` | `127.0.0.1` | HTTP bind host (`0.0.0.0` for external access). |
371
+ | HTTP Server | `HTTP_PORT` | `3333` | HTTP server port. |
372
+ | HTTP Server | `HTTP_JSON_ONLY` | `false` | Force JSON-only responses (no streaming framing). |
373
+ | HTTP Server | `SSE` | `false` | Enable legacy SSE endpoints (`/sse`, `/messages`). Not compatible with remote auth. |
374
+ | Sessions | `SESSION_TIMEOUT_SECONDS` | `3600` | Idle session timeout in HTTP mode. |
375
+ | Sessions | `MAX_SESSIONS` | `1000` | Maximum concurrent sessions (`503` when reached). |
376
+ | Sessions | `MAX_REQUESTS_PER_MINUTE` | `300` | Per-session rate limit (`429` when exceeded). |
377
+ | Policy | `GITLAB_READ_ONLY_MODE` | `false` | Disable mutating tools at registration time. |
378
+ | Policy | `GITLAB_ALLOWED_PROJECT_IDS` | — | Restrict access to specific GitLab project IDs. |
379
+ | Policy | `GITLAB_ALLOWED_TOOLS` | — | Tool allowlist (supports names with or without `gitlab_` prefix). |
380
+ | Policy | `GITLAB_DENIED_TOOLS_REGEX` | — | Regex denylist for tool names. |
381
+ | Policy | `GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE` | `false` | Keep GraphQL tools enabled when project scope restriction is active. |
382
+ | Auth Extensions | `GITLAB_USE_OAUTH` | `false` | Enable OAuth 2.0 PKCE flow. |
383
+ | Auth Extensions | `GITLAB_TOKEN_SCRIPT` | — | Resolve token from an external script. |
384
+ | Auth Extensions | `GITLAB_TOKEN_FILE` | — | Resolve token from a local file. |
385
+ | Auth Extensions | `GITLAB_AUTH_COOKIE_PATH` | — | Enable cookie-jar based session auth from Netscape cookie file. |
386
+ | Output | `GITLAB_RESPONSE_MODE` | `json` | Response format: `json`, `compact-json`, `yaml`. |
387
+ | Output | `GITLAB_MAX_RESPONSE_BYTES` | `200000` | Max response payload (1KB–2MB), oversized payloads are truncated safely. |
388
+ | Output | `GITLAB_HTTP_TIMEOUT_MS` | `20000` | Upstream GitLab HTTP timeout (1s–120s). |
389
+ | Output | `GITLAB_ERROR_DETAIL_MODE` | `safe/full` | Error verbosity (`safe` by default in production, `full` otherwise). |
390
+ | Network/TLS | `HTTP_PROXY`, `HTTPS_PROXY` | — | Proxy settings for outbound GitLab requests. |
391
+ | Network/TLS | `GITLAB_CA_CERT_PATH` | — | Custom CA certificate path (PEM). |
392
+ | Network/TLS | `GITLAB_CLOUDFLARE_BYPASS` | `false` | Add browser-like headers for Cloudflare-protected instances. |
393
+ | Network/TLS | `GITLAB_USER_AGENT` | — | Custom User-Agent for GitLab requests. |
394
+
395
+ See [docs/configuration.md](docs/configuration.md) for the complete reference.
396
+
397
+ ## Authentication Methods
398
+
399
+ Authentication behavior depends on mode:
400
+
401
+ 1. **`REMOTE_AUTHORIZATION=true` (HTTP strong mode)**
402
+ Each request must include `Authorization: Bearer <token>` or `Private-Token: <token>`.
403
+ When `ENABLE_DYNAMIC_API_URL=true`, each request must also include `X-GitLab-API-URL`.
404
+ 2. **`REMOTE_AUTHORIZATION=false` (default mode)**
405
+ The server resolves credentials in this order:
406
+ `GITLAB_PERSONAL_ACCESS_TOKEN` -> OAuth PKCE (`GITLAB_USE_OAUTH=true`) -> `GITLAB_TOKEN_SCRIPT` -> `GITLAB_TOKEN_FILE`.
407
+
408
+ Cookie-based auth (`GITLAB_AUTH_COOKIE_PATH`) is applied independently via a cookie jar and can work with or without a token.
409
+
410
+ See [docs/authentication.md](docs/authentication.md) for setup guides.
411
+
412
+ ## Development
413
+
414
+ ```bash
415
+ pnpm dev # stdio mode with hot-reload
416
+ pnpm dev:http # HTTP mode with hot-reload
417
+ pnpm test # Run tests
418
+ pnpm test:watch # Run tests in watch mode
419
+ pnpm lint # Lint
420
+ pnpm typecheck # Type check
421
+ pnpm inspector # Launch MCP Inspector
422
+ ```
423
+
424
+ ### Project Structure
425
+
426
+ See [docs/architecture.md](docs/architecture.md) for detailed design documentation.
427
+
428
+ ## Documentation
429
+
430
+ - [Configuration Reference](docs/configuration.md) — All environment variables
431
+ - [Tools Reference](docs/tools.md) — Complete list of MCP tools
432
+ - [Authentication Guide](docs/authentication.md) — Auth methods and setup
433
+ - [Deployment Guide](docs/deployment.md) — Docker, production, and multi-instance
434
+ - [Architecture](docs/architecture.md) — Internal design and patterns
435
+
436
+ ## Acknowledgements
271
437
 
272
- - [Model Context Protocol](https://modelcontextprotocol.io/)
273
- - [GitLab MCP](https://github.com/zereight/gitlab-mcp)
438
+ This repository references and learns from parts of the implementation in [zereight/gitlab-mcp](https://github.com/zereight/gitlab-mcp). Thanks to the maintainers and contributors for their work.
274
439
 
275
440
  ## License
276
441