gitlab-mcp 0.1.5 → 1.1.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.
- package/.dockerignore +7 -0
- package/.editorconfig +9 -0
- package/.env.example +75 -0
- package/.github/workflows/nodejs.yml +31 -0
- package/.github/workflows/npm-publish.yml +31 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc.json +6 -0
- package/Dockerfile +20 -0
- package/README.md +416 -251
- package/docker-compose.yml +10 -0
- package/docs/architecture.md +310 -0
- package/docs/authentication.md +299 -0
- package/docs/configuration.md +149 -0
- package/docs/deployment.md +336 -0
- package/docs/tools.md +294 -0
- package/eslint.config.js +23 -0
- package/package.json +78 -32
- package/scripts/get-oauth-token.example.sh +15 -0
- package/src/config/env.ts +171 -0
- package/src/http.ts +620 -0
- package/src/index.ts +77 -0
- package/src/lib/auth-context.ts +19 -0
- package/src/lib/gitlab-client.ts +1810 -0
- package/src/lib/logger.ts +17 -0
- package/src/lib/network.ts +45 -0
- package/src/lib/oauth.ts +287 -0
- package/src/lib/output.ts +51 -0
- package/src/lib/policy.ts +78 -0
- package/src/lib/request-runtime.ts +376 -0
- package/src/lib/sanitize.ts +25 -0
- package/src/lib/session-capacity.ts +14 -0
- package/src/server/build-server.ts +17 -0
- package/src/tools/gitlab.ts +3135 -0
- package/src/tools/health.ts +27 -0
- package/src/tools/mr-code-context.ts +473 -0
- package/src/types/context.ts +13 -0
- package/tests/auth-context.test.ts +102 -0
- package/tests/gitlab-client.test.ts +672 -0
- package/tests/graphql-guard.test.ts +121 -0
- package/tests/integration/agent-loop.integration.test.ts +558 -0
- package/tests/integration/server.integration.test.ts +543 -0
- package/tests/mr-code-context.test.ts +600 -0
- package/tests/oauth.test.ts +43 -0
- package/tests/output.test.ts +186 -0
- package/tests/policy.test.ts +324 -0
- package/tests/request-runtime.test.ts +252 -0
- package/tests/sanitize.test.ts +123 -0
- package/tests/session-capacity.test.ts +49 -0
- package/tests/upload-reference.test.ts +88 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +12 -0
- package/LICENSE +0 -21
- package/build/index.js +0 -1642
- package/build/schemas.js +0 -684
- package/build/test-note.js +0 -54
package/docs/tools.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Tools Reference
|
|
2
|
+
|
|
3
|
+
This document lists all MCP tools provided by gitlab-mcp. Each tool is prefixed with `gitlab_` (except `health_check`). Tools marked as **mutating** are disabled when `GITLAB_READ_ONLY_MODE=true`.
|
|
4
|
+
|
|
5
|
+
All project-scoped tools accept an optional `project_id` parameter. When `GITLAB_ALLOWED_PROJECT_IDS` is configured with a single project, `project_id` is automatically inferred.
|
|
6
|
+
|
|
7
|
+
Most list endpoints support `page` and `per_page`. Notable exceptions are `gitlab_list_merge_request_versions` and `gitlab_list_draft_notes`.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Health
|
|
12
|
+
|
|
13
|
+
| Tool | Mutating | Description |
|
|
14
|
+
| -------------- | -------- | --------------------------------------------- |
|
|
15
|
+
| `health_check` | No | Return server liveness and current timestamp. |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Projects & Organization
|
|
20
|
+
|
|
21
|
+
| Tool | Mutating | Description |
|
|
22
|
+
| ------------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| `gitlab_get_project` | No | Get project details by ID or path. |
|
|
24
|
+
| `gitlab_list_projects` | No | List projects available to the current user. Supports `search`, `visibility`, `membership`, `owned`, `archived`, `order_by`, `sort`. |
|
|
25
|
+
| `gitlab_create_repository` | **Yes** | Create a new GitLab project. Params: `name`, `description`, `visibility`, `initialize_with_readme`, `path`, `namespace_id`, `default_branch`. |
|
|
26
|
+
| `gitlab_fork_repository` | **Yes** | Fork a project to another namespace. Params: `namespace`, `namespace_id`, `path`, `name`, `description`, `visibility`, `default_branch`. |
|
|
27
|
+
| `gitlab_list_project_members` | No | List members of a project. Supports `query`, `user_ids`, `skip_users`, `include_inheritance`. |
|
|
28
|
+
| `gitlab_list_group_projects` | No | List projects under a group. Params: `group_id` (required). Supports `include_subgroups`, `search`, filters. |
|
|
29
|
+
| `gitlab_list_group_iterations` | No | List iterations for a group. Params: `group_id` (required). Supports `state`, `search`, date filters. |
|
|
30
|
+
| `gitlab_search_repositories` | No | Search repositories by keyword. Params: `search` (required). |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Users & Namespaces
|
|
35
|
+
|
|
36
|
+
| Tool | Mutating | Description |
|
|
37
|
+
| ------------------------- | -------- | -------------------------------------------------------------------------------- |
|
|
38
|
+
| `gitlab_get_users` | No | Search users. Supports `username`, `search`, `active`, `extern_uid`, `provider`. |
|
|
39
|
+
| `gitlab_list_namespaces` | No | List namespaces visible to user. Supports `search`, `owned`. |
|
|
40
|
+
| `gitlab_get_namespace` | No | Get namespace by ID or path. Params: `namespace_id_or_path` or `namespace_id`. |
|
|
41
|
+
| `gitlab_verify_namespace` | No | Verify if a namespace path exists. Params: `path` (required). |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Events
|
|
46
|
+
|
|
47
|
+
| Tool | Mutating | Description |
|
|
48
|
+
| --------------------------- | -------- | ----------------------------------------------------------------------------------------------- |
|
|
49
|
+
| `gitlab_list_events` | No | List current user events. Supports `action`, `target_type`, `before`, `after`, `scope`, `sort`. |
|
|
50
|
+
| `gitlab_get_project_events` | No | List events for a specific project. Same filters as `list_events`. |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Repository & Files
|
|
55
|
+
|
|
56
|
+
| Tool | Mutating | Description |
|
|
57
|
+
| ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `gitlab_get_repository_tree` | No | List files and directories. Supports `path`, `ref`, `recursive`. |
|
|
59
|
+
| `gitlab_get_file_contents` | No | Get file by path and ref. Params: `file_path` (required), `ref` (defaults to project's default branch). |
|
|
60
|
+
| `gitlab_create_or_update_file` | **Yes** | Create or update a single file. Params: `file_path`, `branch`, `content`, `commit_message` (all required). Supports `encoding`, `author_email`, `author_name`, `start_branch`, `last_commit_id`. |
|
|
61
|
+
| `gitlab_push_files` | **Yes** | Create a commit with multiple file actions. Params: `branch`, `commit_message` (required), `actions` array (each with `action`, `file_path`, `content`, etc.). Also accepts legacy `files` format. |
|
|
62
|
+
| `gitlab_create_branch` | **Yes** | Create a new branch. Params: `branch` (required), `ref` (defaults to default branch). |
|
|
63
|
+
| `gitlab_get_branch_diffs` | No | Compare two branches/refs and return diffs. Params: `from`, `to` (required), `straight`, `excluded_file_patterns`. |
|
|
64
|
+
| `gitlab_search_code_blobs` | No | Search code in a project. Params: `search` (required), `ref`. |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Commits
|
|
69
|
+
|
|
70
|
+
| Tool | Mutating | Description |
|
|
71
|
+
| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `gitlab_list_commits` | No | List commits. Supports `ref_name`, `since`, `until`, `path`, `author`, `all`, `with_stats`, `order`. |
|
|
73
|
+
| `gitlab_get_commit` | No | Get one commit by SHA. Params: `sha` (required), `stats`. |
|
|
74
|
+
| `gitlab_get_commit_diff` | No | Get diff for one commit. Params: `sha` (required), `full_diff`. |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Merge Requests
|
|
79
|
+
|
|
80
|
+
| Tool | Mutating | Description |
|
|
81
|
+
| ------------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `gitlab_list_merge_requests` | No | List MRs. When `project_id` is omitted, lists globally. Supports `assignee_id`, `author_id`, `reviewer_id`, `state`, `labels`, `milestone`, `source_branch`, `target_branch`, `scope`, `order_by`, `sort`, date filters, `search`, `wip`. |
|
|
83
|
+
| `gitlab_get_merge_request` | No | Get one MR. Params: `merge_request_iid` or `source_branch`. |
|
|
84
|
+
| `gitlab_create_merge_request` | **Yes** | Create an MR. Params: `source_branch`, `target_branch`, `title` (required). Supports `description`, `assignee_ids`, `reviewer_ids`, `labels`, `draft`, `squash`, `remove_source_branch`. |
|
|
85
|
+
| `gitlab_update_merge_request` | **Yes** | Update MR fields. Params: `merge_request_iid` (required). Supports `title`, `description`, `target_branch`, `state_event`, `labels`, `assignee_ids`, `reviewer_ids`, `draft`, `squash`. |
|
|
86
|
+
| `gitlab_merge_merge_request` | **Yes** | Merge an MR. Accepts `merge_request_iid` or `source_branch`. Supports `merge_commit_message`, `squash_commit_message`, `squash`, `should_remove_source_branch`, `merge_when_pipeline_succeeds`. |
|
|
87
|
+
| `gitlab_get_merge_request_diffs` | No | Get MR diffs with changed files. Supports `view` (`inline`/`parallel`), `excluded_file_patterns`. |
|
|
88
|
+
| `gitlab_list_merge_request_diffs` | No | List detailed MR diffs (versions/changes view). Supports `unidiff`. |
|
|
89
|
+
| `gitlab_list_merge_request_versions` | No | List MR diff versions. |
|
|
90
|
+
| `gitlab_get_merge_request_version` | No | Get one MR diff version. Params: `version_id` (required), `unidiff`. |
|
|
91
|
+
|
|
92
|
+
### MR Code Context
|
|
93
|
+
|
|
94
|
+
| Tool | Mutating | Description |
|
|
95
|
+
| --------------------------------------- | -------- | -------------------------------------------------------------- |
|
|
96
|
+
| `gitlab_get_merge_request_code_context` | No | High-signal MR code context with filtering and budget control. |
|
|
97
|
+
|
|
98
|
+
**Parameters:**
|
|
99
|
+
|
|
100
|
+
| Parameter | Type | Default | Description |
|
|
101
|
+
| ------------------- | -------- | --------------- | ------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `merge_request_iid` | string | — | **Required.** MR IID. |
|
|
103
|
+
| `include_paths` | string[] | — | Glob patterns for files to include. |
|
|
104
|
+
| `exclude_paths` | string[] | — | Glob patterns for files to exclude. |
|
|
105
|
+
| `extensions` | string[] | — | File extension filter (e.g. `.ts`, `.py`). |
|
|
106
|
+
| `languages` | string[] | — | Language filter (e.g. `typescript`, `python`, `go`). Maps to extensions automatically. |
|
|
107
|
+
| `max_files` | number | `30` | Maximum number of files to process (1–500). |
|
|
108
|
+
| `max_total_chars` | number | `120000` | Character budget (500–2,000,000). Stops fetching when budget is exhausted. |
|
|
109
|
+
| `context_lines` | number | `20` | Lines of context around changes (0–200). Used in `surrounding` mode. |
|
|
110
|
+
| `mode` | enum | `patch` | Content mode: `patch` (raw diff), `surrounding` (changed lines with context), `fullfile` (entire file). |
|
|
111
|
+
| `sort` | enum | `changed_lines` | Sort files by: `changed_lines`, `path`, `file_size`. |
|
|
112
|
+
| `list_only` | boolean | `false` | If `true`, returns file list without content (for two-stage retrieval). |
|
|
113
|
+
|
|
114
|
+
**Supported languages:** typescript, javascript, python, go, rust, java, kotlin, csharp, cpp, c, ruby, php, swift, scala, shell, yaml, json, markdown.
|
|
115
|
+
|
|
116
|
+
### MR Approvals
|
|
117
|
+
|
|
118
|
+
| Tool | Mutating | Description |
|
|
119
|
+
| ----------------------------------------- | -------- | --------------------------------------------------- |
|
|
120
|
+
| `gitlab_approve_merge_request` | **Yes** | Approve an MR. Supports `sha`, `approval_password`. |
|
|
121
|
+
| `gitlab_unapprove_merge_request` | **Yes** | Remove current user's approval from an MR. |
|
|
122
|
+
| `gitlab_get_merge_request_approval_state` | No | Get approval state for an MR. |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## MR Discussions
|
|
127
|
+
|
|
128
|
+
| Tool | Mutating | Description |
|
|
129
|
+
| --------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
130
|
+
| `gitlab_list_merge_request_discussions` | No | List MR discussions. |
|
|
131
|
+
| `gitlab_mr_discussions` | No | Alias of `list_merge_request_discussions`. |
|
|
132
|
+
| `gitlab_create_merge_request_thread` | **Yes** | Create a new discussion thread. Params: `body` (required). Supports `position` (for diff comments), `created_at`. |
|
|
133
|
+
| `gitlab_create_merge_request_discussion_note` | **Yes** | Reply to an existing discussion thread. Params: `discussion_id`, `body` (required). |
|
|
134
|
+
| `gitlab_update_merge_request_discussion_note` | **Yes** | Update a discussion note. Provide either `body` or `resolved` (not both). |
|
|
135
|
+
| `gitlab_delete_merge_request_discussion_note` | **Yes** | Delete a note from a discussion thread. |
|
|
136
|
+
| `gitlab_resolve_merge_request_thread` | **Yes** | Resolve/unresolve a discussion note. Params: `discussion_id`, `note_id`, `resolved` (default `true`). |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## MR Notes (Comments)
|
|
141
|
+
|
|
142
|
+
| Tool | Mutating | Description |
|
|
143
|
+
| ---------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `gitlab_list_merge_request_notes` | No | List top-level MR notes. Supports `sort`, `order_by`. |
|
|
145
|
+
| `gitlab_get_merge_request_notes` | No | Alias of `list_merge_request_notes`. |
|
|
146
|
+
| `gitlab_get_merge_request_note` | No | Get a single MR note by ID. |
|
|
147
|
+
| `gitlab_create_merge_request_note` | **Yes** | Create a top-level MR comment. Params: `body` (required). |
|
|
148
|
+
| `gitlab_update_merge_request_note` | **Yes** | Update MR note body. Params: `note_id`, `body` (required). |
|
|
149
|
+
| `gitlab_delete_merge_request_note` | **Yes** | Delete an MR note. |
|
|
150
|
+
| `gitlab_create_note` | **Yes** | Create a note on an issue or MR. Params: `noteable_type` (`issue`/`merge_request`), `noteable_iid`, `body` (required). |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Draft Notes
|
|
155
|
+
|
|
156
|
+
| Tool | Mutating | Description |
|
|
157
|
+
| --------------------------------- | -------- | ------------------------------------------------------------------------------------------ |
|
|
158
|
+
| `gitlab_get_draft_note` | No | Get a single draft note. |
|
|
159
|
+
| `gitlab_list_draft_notes` | No | List draft notes on an MR. |
|
|
160
|
+
| `gitlab_create_draft_note` | **Yes** | Create a draft note. Params: `body` (required). Supports `position`, `resolve_discussion`. |
|
|
161
|
+
| `gitlab_update_draft_note` | **Yes** | Update a draft note. At least one of `body`, `position`, or `resolve_discussion` required. |
|
|
162
|
+
| `gitlab_delete_draft_note` | **Yes** | Delete a draft note. |
|
|
163
|
+
| `gitlab_publish_draft_note` | **Yes** | Publish one draft note. |
|
|
164
|
+
| `gitlab_bulk_publish_draft_notes` | **Yes** | Publish all draft notes on an MR. |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Issues
|
|
169
|
+
|
|
170
|
+
| Tool | Mutating | Description |
|
|
171
|
+
| ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
172
|
+
| `gitlab_list_issues` | No | List issues. When `project_id` is omitted, lists globally. Supports `assignee_id`, `author_id`, `state`, `labels`, `milestone`, `scope`, `search`, `issue_type`, `confidential`, date filters. |
|
|
173
|
+
| `gitlab_my_issues` | No | List issues assigned to the current user. Supports `state`, `labels`, `search`, date filters. |
|
|
174
|
+
| `gitlab_get_issue` | No | Get issue by IID. |
|
|
175
|
+
| `gitlab_create_issue` | **Yes** | Create an issue. Params: `title` (required). Supports `description`, `labels`, `milestone_id`, `due_date`, `confidential`, `issue_type`, `assignee_ids`. |
|
|
176
|
+
| `gitlab_update_issue` | **Yes** | Update issue fields. Supports `title`, `description`, `state_event`, `labels`, `assignee_ids`, `weight`, `issue_type`, `discussion_locked`. |
|
|
177
|
+
| `gitlab_delete_issue` | **Yes** | Delete an issue. |
|
|
178
|
+
| `gitlab_list_issue_discussions` | No | List issue discussions. |
|
|
179
|
+
| `gitlab_create_issue_note` | **Yes** | Create issue comment. Params: `body` (required). Supports `discussion_id` (to reply to thread), `created_at`. |
|
|
180
|
+
| `gitlab_update_issue_note` | **Yes** | Update an issue note. Provide either `body` or `resolved` (not both). |
|
|
181
|
+
|
|
182
|
+
### Issue Links
|
|
183
|
+
|
|
184
|
+
| Tool | Mutating | Description |
|
|
185
|
+
| -------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
186
|
+
| `gitlab_list_issue_links` | No | List related issue links. |
|
|
187
|
+
| `gitlab_get_issue_link` | No | Get a single issue link by ID. |
|
|
188
|
+
| `gitlab_create_issue_link` | **Yes** | Create a relation between two issues. Params: `target_project_id`, `target_issue_iid` (required). Supports `link_type` (`relates_to`, `blocks`, `is_blocked_by`). |
|
|
189
|
+
| `gitlab_delete_issue_link` | **Yes** | Delete a relation between issues. |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Wiki
|
|
194
|
+
|
|
195
|
+
Requires `USE_GITLAB_WIKI=true` (default).
|
|
196
|
+
|
|
197
|
+
| Tool | Mutating | Description |
|
|
198
|
+
| ------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
199
|
+
| `gitlab_list_wiki_pages` | No | List wiki pages. Supports `with_content`. |
|
|
200
|
+
| `gitlab_get_wiki_page` | No | Get wiki page by slug. Supports `version`. |
|
|
201
|
+
| `gitlab_create_wiki_page` | **Yes** | Create a wiki page. Params: `title`, `content` (required). Supports `format` (`markdown`, `rdoc`, `asciidoc`, `org`). |
|
|
202
|
+
| `gitlab_update_wiki_page` | **Yes** | Update wiki page by slug. Params: `slug`, `content` (required). Supports `title`, `format`. |
|
|
203
|
+
| `gitlab_delete_wiki_page` | **Yes** | Delete wiki page by slug. |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Pipelines & Jobs
|
|
208
|
+
|
|
209
|
+
Requires `USE_PIPELINE=true` (default).
|
|
210
|
+
|
|
211
|
+
| Tool | Mutating | Description |
|
|
212
|
+
| ----------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
213
|
+
| `gitlab_list_pipelines` | No | List pipelines. Supports `scope`, `status`, `ref`, `sha`, `username`, `source`, `order_by`, `sort`, date filters. |
|
|
214
|
+
| `gitlab_get_pipeline` | No | Get one pipeline by ID. |
|
|
215
|
+
| `gitlab_list_pipeline_jobs` | No | List jobs in a pipeline. Supports `scope`, `include_retried`. |
|
|
216
|
+
| `gitlab_list_pipeline_trigger_jobs` | No | List downstream/bridge trigger jobs in a pipeline. |
|
|
217
|
+
| `gitlab_get_pipeline_job` | No | Get one job by ID. |
|
|
218
|
+
| `gitlab_get_pipeline_job_output` | No | Get raw job trace/log output. |
|
|
219
|
+
| `gitlab_create_pipeline` | **Yes** | Trigger a new pipeline. Params: `ref` (required). Supports `variables` array (`key`, `value`, `variable_type`). |
|
|
220
|
+
| `gitlab_retry_pipeline` | **Yes** | Retry failed jobs in a pipeline. |
|
|
221
|
+
| `gitlab_cancel_pipeline` | **Yes** | Cancel a running pipeline. |
|
|
222
|
+
| `gitlab_retry_pipeline_job` | **Yes** | Retry one failed job. |
|
|
223
|
+
| `gitlab_cancel_pipeline_job` | **Yes** | Cancel one running job. |
|
|
224
|
+
| `gitlab_play_pipeline_job` | **Yes** | Play/trigger a manual job. |
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Milestones
|
|
229
|
+
|
|
230
|
+
Requires `USE_MILESTONE=true` (default).
|
|
231
|
+
|
|
232
|
+
| Tool | Mutating | Description |
|
|
233
|
+
| -------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
|
|
234
|
+
| `gitlab_list_milestones` | No | List project milestones. Supports `iids`, `state`, `title`, `search`, `include_ancestors`, date filters. |
|
|
235
|
+
| `gitlab_get_milestone` | No | Get a milestone by ID. |
|
|
236
|
+
| `gitlab_create_milestone` | **Yes** | Create a milestone. Params: `title` (required). Supports `description`, `due_date`, `start_date`. |
|
|
237
|
+
| `gitlab_update_milestone` | **Yes** | Update milestone fields. |
|
|
238
|
+
| `gitlab_edit_milestone` | **Yes** | Alias of `update_milestone`. |
|
|
239
|
+
| `gitlab_delete_milestone` | **Yes** | Delete a milestone. |
|
|
240
|
+
| `gitlab_get_milestone_issue` | No | List issues assigned to a milestone. |
|
|
241
|
+
| `gitlab_get_milestone_merge_requests` | No | List MRs assigned to a milestone. |
|
|
242
|
+
| `gitlab_promote_milestone` | **Yes** | Promote a project milestone to a group milestone. |
|
|
243
|
+
| `gitlab_get_milestone_burndown_events` | No | List burndown events for a milestone. |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Releases
|
|
248
|
+
|
|
249
|
+
Requires `USE_RELEASE=true` (default).
|
|
250
|
+
|
|
251
|
+
| Tool | Mutating | Description |
|
|
252
|
+
| -------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
253
|
+
| `gitlab_list_releases` | No | List project releases. Supports `order_by`, `sort`, `include_html_description`. |
|
|
254
|
+
| `gitlab_get_release` | No | Get one release by tag name. |
|
|
255
|
+
| `gitlab_create_release` | **Yes** | Create a release. Params: `tag_name` (required). Supports `name`, `tag_message`, `description`, `ref`, `released_at`, `milestones`, `assets`. |
|
|
256
|
+
| `gitlab_update_release` | **Yes** | Update existing release. |
|
|
257
|
+
| `gitlab_delete_release` | **Yes** | Delete a release by tag. |
|
|
258
|
+
| `gitlab_create_release_evidence` | **Yes** | Create evidence for an existing release. |
|
|
259
|
+
| `gitlab_download_release_asset` | No | Download a release asset. Params: `tag_name`, `direct_asset_path` (required). |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Labels
|
|
264
|
+
|
|
265
|
+
| Tool | Mutating | Description |
|
|
266
|
+
| --------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
|
|
267
|
+
| `gitlab_list_labels` | No | List project labels. Supports `with_counts`, `include_ancestor_groups`, `search`. |
|
|
268
|
+
| `gitlab_get_label` | No | Get one label by ID. Supports `include_ancestor_groups`. |
|
|
269
|
+
| `gitlab_create_label` | **Yes** | Create a label. Params: `name`, `color` (required). Supports `description`, `priority`. |
|
|
270
|
+
| `gitlab_update_label` | **Yes** | Update a label. Identify by `name` or `label_id`. Supports `new_name`, `color`, `description`, `priority`. |
|
|
271
|
+
| `gitlab_delete_label` | **Yes** | Delete a label. Identify by `name` or `label_id`. |
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Uploads & Attachments
|
|
276
|
+
|
|
277
|
+
| Tool | Mutating | Description |
|
|
278
|
+
| ---------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
279
|
+
| `gitlab_upload_markdown` | **Yes** | Upload markdown file/attachment to project. Provide either `file_path` (local file) or `content` with `filename`. |
|
|
280
|
+
| `gitlab_download_attachment` | No | Download attachment. Provide either `url_or_path` or both `secret` and `filename`. Absolute `url_or_path` must be same-origin with configured GitLab API URL. In project-scoped mode (`GITLAB_ALLOWED_PROJECT_IDS`), `url_or_path` must be a GitLab upload URL/path and `project_id` must be provided (or inferred from a single allowed project). Returns base64 content. |
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## GraphQL
|
|
285
|
+
|
|
286
|
+
| Tool | Mutating | Description |
|
|
287
|
+
| --------------------------------- | -------- | -------------------------------------------------------------------------------------------- |
|
|
288
|
+
| `gitlab_execute_graphql_query` | No | Execute a read-only GraphQL query. Rejects mutations. |
|
|
289
|
+
| `gitlab_execute_graphql_mutation` | **Yes** | Execute a GraphQL mutation. Disabled in read-only mode. |
|
|
290
|
+
| `gitlab_execute_graphql` | No\* | Backward-compatible executor. Automatically detects mutations and enforces read-only policy. |
|
|
291
|
+
|
|
292
|
+
\* `gitlab_execute_graphql` is registered as non-mutating but dynamically checks mutation content against the policy engine at execution time.
|
|
293
|
+
|
|
294
|
+
When `GITLAB_ALLOWED_PROJECT_IDS` is configured, GraphQL tools are disabled by default. Set `GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE=true` to enable them explicitly.
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import tseslint from "typescript-eslint";
|
|
3
|
+
import prettier from "eslint-config-prettier";
|
|
4
|
+
|
|
5
|
+
export default tseslint.config(
|
|
6
|
+
{
|
|
7
|
+
ignores: ["dist/**", "coverage/**", "node_modules/**", "other/**"]
|
|
8
|
+
},
|
|
9
|
+
js.configs.recommended,
|
|
10
|
+
...tseslint.configs.recommended,
|
|
11
|
+
{
|
|
12
|
+
files: ["**/*.ts"],
|
|
13
|
+
rules: {
|
|
14
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
15
|
+
"error",
|
|
16
|
+
{
|
|
17
|
+
prefer: "type-imports"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
prettier
|
|
23
|
+
);
|
package/package.json
CHANGED
|
@@ -1,44 +1,90 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitlab-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "./build/index.js",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "A MCP server for GitLab",
|
|
6
5
|
"type": "module",
|
|
7
|
-
"
|
|
8
|
-
|
|
6
|
+
"packageManager": "pnpm@10.28.1",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.11.0"
|
|
9
9
|
},
|
|
10
|
-
"files": [
|
|
11
|
-
"build"
|
|
12
|
-
],
|
|
13
10
|
"publishConfig": {
|
|
14
|
-
"access": "public"
|
|
11
|
+
"access": "public",
|
|
12
|
+
"provenance": true
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/mcpland/gitlab-mcp.git"
|
|
15
17
|
},
|
|
16
18
|
"scripts": {
|
|
17
|
-
"build": "tsc
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"dev": "tsx watch src/index.ts",
|
|
22
|
+
"dev:http": "tsx watch src/http.ts",
|
|
23
|
+
"start": "node dist/index.js",
|
|
24
|
+
"start:http": "node dist/http.js",
|
|
25
|
+
"lint": "eslint .",
|
|
26
|
+
"lint:fix": "eslint . --fix",
|
|
27
|
+
"format": "prettier --check .",
|
|
28
|
+
"format:fix": "prettier --write .",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest",
|
|
31
|
+
"lint-staged": "lint-staged",
|
|
32
|
+
"commit": "git-cz",
|
|
33
|
+
"inspector": "pnpm exec @modelcontextprotocol/inspector node dist/index.js",
|
|
34
|
+
"prepare": "husky"
|
|
21
35
|
},
|
|
22
|
-
"
|
|
23
|
-
"
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
38
|
+
"dotenv": "^17.3.1",
|
|
39
|
+
"express": "^5.2.1",
|
|
40
|
+
"fetch-cookie": "^3.2.0",
|
|
41
|
+
"open": "^11.0.0",
|
|
42
|
+
"picomatch": "^4.0.3",
|
|
43
|
+
"pino": "^10.3.1",
|
|
44
|
+
"pkce-challenge": "^6.0.0",
|
|
45
|
+
"tough-cookie": "^6.0.0",
|
|
46
|
+
"undici": "^7.21.0",
|
|
47
|
+
"yaml": "^2.8.2",
|
|
48
|
+
"zod": "^4.3.6"
|
|
24
49
|
},
|
|
25
|
-
"author": "unadlib",
|
|
26
|
-
"license": "MIT",
|
|
27
50
|
"devDependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@
|
|
30
|
-
"@types/
|
|
31
|
-
"@types/
|
|
32
|
-
"
|
|
33
|
-
"
|
|
51
|
+
"@eslint/js": "^9.39.2",
|
|
52
|
+
"@modelcontextprotocol/inspector": "^0.20.0",
|
|
53
|
+
"@types/express": "^5.0.6",
|
|
54
|
+
"@types/node": "^25.2.3",
|
|
55
|
+
"@types/picomatch": "^4.0.2",
|
|
56
|
+
"commitizen": "^4.3.1",
|
|
57
|
+
"eslint": "^9.39.2",
|
|
58
|
+
"eslint-config-prettier": "^10.1.8",
|
|
59
|
+
"husky": "^9.1.7",
|
|
60
|
+
"lint-staged": "^16.2.7",
|
|
61
|
+
"prettier": "^3.8.1",
|
|
62
|
+
"tsx": "^4.21.0",
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"typescript-eslint": "^8.55.0",
|
|
65
|
+
"vitest": "^4.0.18"
|
|
34
66
|
},
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
"lint-staged": {
|
|
68
|
+
"*.{ts,js,json,md,yml,yaml}": [
|
|
69
|
+
"prettier --write"
|
|
70
|
+
],
|
|
71
|
+
"*.ts": [
|
|
72
|
+
"eslint --fix"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"config": {
|
|
76
|
+
"commitizen": {
|
|
77
|
+
"path": "cz-conventional-changelog"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"keywords": [
|
|
81
|
+
"gitlab",
|
|
82
|
+
"mcp",
|
|
83
|
+
"ai",
|
|
84
|
+
"agent",
|
|
85
|
+
"tool",
|
|
86
|
+
"automation"
|
|
87
|
+
],
|
|
88
|
+
"author": "unadlib",
|
|
89
|
+
"license": "MIT"
|
|
44
90
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Example external token script for GITLAB_TOKEN_SCRIPT.
|
|
5
|
+
# The MCP server accepts either:
|
|
6
|
+
# 1) Raw token on stdout
|
|
7
|
+
# 2) JSON: {"access_token":"..."} or {"token":"..."}
|
|
8
|
+
|
|
9
|
+
if [[ -n "${GITLAB_OAUTH_ACCESS_TOKEN:-}" ]]; then
|
|
10
|
+
printf '{"access_token":"%s"}\n' "${GITLAB_OAUTH_ACCESS_TOKEN}"
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
echo "GITLAB_OAUTH_ACCESS_TOKEN is not set" >&2
|
|
15
|
+
exit 1
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
const logLevelSchema = z.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"]);
|
|
6
|
+
|
|
7
|
+
const responseModeSchema = z.enum(["json", "compact-json", "yaml"]);
|
|
8
|
+
const errorDetailModeSchema = z.enum(["safe", "full"]);
|
|
9
|
+
|
|
10
|
+
function parseBoolean(value: string | undefined, fallback: boolean): boolean {
|
|
11
|
+
if (value === undefined) {
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return value.toLowerCase() === "true";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseCsv(value: string | undefined): string[] {
|
|
19
|
+
if (!value) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return value
|
|
24
|
+
.split(",")
|
|
25
|
+
.map((item) => item.trim())
|
|
26
|
+
.filter((item) => item.length > 0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const envSchema = z.object({
|
|
30
|
+
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
|
|
31
|
+
LOG_LEVEL: logLevelSchema.default("info"),
|
|
32
|
+
MCP_SERVER_NAME: z.string().min(1).default("gitlab-mcp"),
|
|
33
|
+
MCP_SERVER_VERSION: z.string().min(1).default("0.1.0"),
|
|
34
|
+
GITLAB_API_URL: z.string().min(1).default("https://gitlab.com/api/v4"),
|
|
35
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: z.string().min(1).optional(),
|
|
36
|
+
GITLAB_USE_OAUTH: z.enum(["true", "false"]).default("false"),
|
|
37
|
+
GITLAB_OAUTH_CLIENT_ID: z.string().optional(),
|
|
38
|
+
GITLAB_OAUTH_CLIENT_SECRET: z.string().optional(),
|
|
39
|
+
GITLAB_OAUTH_GITLAB_URL: z.string().optional(),
|
|
40
|
+
GITLAB_OAUTH_REDIRECT_URI: z.string().url().optional(),
|
|
41
|
+
GITLAB_OAUTH_SCOPES: z.string().default("api"),
|
|
42
|
+
GITLAB_OAUTH_TOKEN_PATH: z.string().optional(),
|
|
43
|
+
GITLAB_OAUTH_AUTO_OPEN_BROWSER: z.enum(["true", "false"]).default("true"),
|
|
44
|
+
GITLAB_READ_ONLY_MODE: z
|
|
45
|
+
.enum(["true", "false"])
|
|
46
|
+
.default("false")
|
|
47
|
+
.transform((value) => value === "true"),
|
|
48
|
+
GITLAB_ALLOWED_PROJECT_IDS: z.string().optional(),
|
|
49
|
+
GITLAB_ALLOWED_TOOLS: z.string().optional(),
|
|
50
|
+
GITLAB_DENIED_TOOLS_REGEX: z.string().optional(),
|
|
51
|
+
GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE: z.enum(["true", "false"]).default("false"),
|
|
52
|
+
GITLAB_RESPONSE_MODE: responseModeSchema.default("json"),
|
|
53
|
+
GITLAB_MAX_RESPONSE_BYTES: z.coerce.number().int().min(1024).max(2_000_000).default(200_000),
|
|
54
|
+
GITLAB_HTTP_TIMEOUT_MS: z.coerce.number().int().min(1_000).max(120_000).default(20_000),
|
|
55
|
+
GITLAB_ERROR_DETAIL_MODE: errorDetailModeSchema.optional(),
|
|
56
|
+
GITLAB_AUTH_COOKIE_PATH: z.string().optional(),
|
|
57
|
+
GITLAB_COOKIE_WARMUP_PATH: z.string().default("/user"),
|
|
58
|
+
GITLAB_CLOUDFLARE_BYPASS: z.enum(["true", "false"]).default("false"),
|
|
59
|
+
GITLAB_USER_AGENT: z.string().optional(),
|
|
60
|
+
GITLAB_ACCEPT_LANGUAGE: z.string().optional(),
|
|
61
|
+
GITLAB_TOKEN_SCRIPT: z.string().optional(),
|
|
62
|
+
GITLAB_TOKEN_SCRIPT_TIMEOUT_MS: z.coerce.number().int().min(500).max(120_000).default(10_000),
|
|
63
|
+
GITLAB_TOKEN_CACHE_SECONDS: z.coerce.number().int().min(0).max(86_400).default(300),
|
|
64
|
+
GITLAB_TOKEN_FILE: z.string().optional(),
|
|
65
|
+
GITLAB_ALLOW_INSECURE_TOKEN_FILE: z.enum(["true", "false"]).default("false"),
|
|
66
|
+
GITLAB_ALLOW_INSECURE_TLS: z.enum(["true", "false"]).default("false"),
|
|
67
|
+
NODE_TLS_REJECT_UNAUTHORIZED: z.string().optional(),
|
|
68
|
+
GITLAB_CA_CERT_PATH: z.string().optional(),
|
|
69
|
+
HTTP_PROXY: z.string().optional(),
|
|
70
|
+
HTTPS_PROXY: z.string().optional(),
|
|
71
|
+
USE_GITLAB_WIKI: z.enum(["true", "false"]).default("true"),
|
|
72
|
+
USE_MILESTONE: z.enum(["true", "false"]).default("true"),
|
|
73
|
+
USE_PIPELINE: z.enum(["true", "false"]).default("true"),
|
|
74
|
+
USE_RELEASE: z.enum(["true", "false"]).default("true"),
|
|
75
|
+
REMOTE_AUTHORIZATION: z.enum(["true", "false"]).default("false"),
|
|
76
|
+
ENABLE_DYNAMIC_API_URL: z.enum(["true", "false"]).default("false"),
|
|
77
|
+
SESSION_TIMEOUT_SECONDS: z.coerce.number().int().min(1).max(86_400).default(3_600),
|
|
78
|
+
MAX_SESSIONS: z.coerce.number().int().min(1).max(10_000).default(1_000),
|
|
79
|
+
MAX_REQUESTS_PER_MINUTE: z.coerce.number().int().min(1).max(10_000).default(300),
|
|
80
|
+
HTTP_HOST: z.string().min(1).default("127.0.0.1"),
|
|
81
|
+
HTTP_PORT: z.coerce.number().int().min(1).max(65_535).default(3333),
|
|
82
|
+
HTTP_JSON_ONLY: z.enum(["true", "false"]).default("false"),
|
|
83
|
+
SSE: z.enum(["true", "false"]).default("false")
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const parsed = envSchema.safeParse(process.env);
|
|
87
|
+
|
|
88
|
+
if (!parsed.success) {
|
|
89
|
+
const issues = parsed.error.issues
|
|
90
|
+
.map((issue) => `- ${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
91
|
+
.join("\n");
|
|
92
|
+
|
|
93
|
+
throw new Error(`Invalid environment variables:\n${issues}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const data = parsed.data;
|
|
97
|
+
const rawApiUrls = parseCsv(data.GITLAB_API_URL);
|
|
98
|
+
|
|
99
|
+
if (rawApiUrls.length === 0) {
|
|
100
|
+
throw new Error("GITLAB_API_URL must contain at least one URL");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const normalizedApiUrls = rawApiUrls.map((item) => {
|
|
104
|
+
try {
|
|
105
|
+
return normalizeApiUrl(item);
|
|
106
|
+
} catch {
|
|
107
|
+
throw new Error(`Invalid GITLAB_API_URL entry: '${item}'`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (data.ENABLE_DYNAMIC_API_URL === "true" && data.REMOTE_AUTHORIZATION !== "true") {
|
|
112
|
+
throw new Error("ENABLE_DYNAMIC_API_URL=true requires REMOTE_AUTHORIZATION=true");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (data.GITLAB_USE_OAUTH === "true" && !data.GITLAB_OAUTH_CLIENT_ID) {
|
|
116
|
+
throw new Error("GITLAB_USE_OAUTH=true requires GITLAB_OAUTH_CLIENT_ID");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (data.SSE === "true" && data.REMOTE_AUTHORIZATION === "true") {
|
|
120
|
+
throw new Error("SSE=true is not compatible with REMOTE_AUTHORIZATION=true");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (data.NODE_TLS_REJECT_UNAUTHORIZED === "0" && data.GITLAB_ALLOW_INSECURE_TLS !== "true") {
|
|
124
|
+
throw new Error(
|
|
125
|
+
"NODE_TLS_REJECT_UNAUTHORIZED=0 requires GITLAB_ALLOW_INSECURE_TLS=true acknowledgment"
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const env = {
|
|
130
|
+
...data,
|
|
131
|
+
GITLAB_READ_ONLY_MODE: data.GITLAB_READ_ONLY_MODE,
|
|
132
|
+
GITLAB_ERROR_DETAIL_MODE:
|
|
133
|
+
data.GITLAB_ERROR_DETAIL_MODE ?? (data.NODE_ENV === "production" ? "safe" : "full"),
|
|
134
|
+
GITLAB_USE_OAUTH: parseBoolean(data.GITLAB_USE_OAUTH, false),
|
|
135
|
+
GITLAB_OAUTH_AUTO_OPEN_BROWSER: parseBoolean(data.GITLAB_OAUTH_AUTO_OPEN_BROWSER, true),
|
|
136
|
+
GITLAB_CLOUDFLARE_BYPASS: parseBoolean(data.GITLAB_CLOUDFLARE_BYPASS, false),
|
|
137
|
+
GITLAB_ALLOW_INSECURE_TOKEN_FILE: parseBoolean(data.GITLAB_ALLOW_INSECURE_TOKEN_FILE, false),
|
|
138
|
+
GITLAB_ALLOW_INSECURE_TLS: parseBoolean(data.GITLAB_ALLOW_INSECURE_TLS, false),
|
|
139
|
+
USE_GITLAB_WIKI: parseBoolean(data.USE_GITLAB_WIKI, true),
|
|
140
|
+
USE_MILESTONE: parseBoolean(data.USE_MILESTONE, true),
|
|
141
|
+
USE_PIPELINE: parseBoolean(data.USE_PIPELINE, true),
|
|
142
|
+
USE_RELEASE: parseBoolean(data.USE_RELEASE, true),
|
|
143
|
+
REMOTE_AUTHORIZATION: parseBoolean(data.REMOTE_AUTHORIZATION, false),
|
|
144
|
+
ENABLE_DYNAMIC_API_URL: parseBoolean(data.ENABLE_DYNAMIC_API_URL, false),
|
|
145
|
+
HTTP_JSON_ONLY: parseBoolean(data.HTTP_JSON_ONLY, false),
|
|
146
|
+
SSE: parseBoolean(data.SSE, false),
|
|
147
|
+
GITLAB_ALLOWED_PROJECT_IDS: parseCsv(data.GITLAB_ALLOWED_PROJECT_IDS),
|
|
148
|
+
GITLAB_ALLOWED_TOOLS: parseCsv(data.GITLAB_ALLOWED_TOOLS),
|
|
149
|
+
GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE: parseBoolean(
|
|
150
|
+
data.GITLAB_ALLOW_GRAPHQL_WITH_PROJECT_SCOPE,
|
|
151
|
+
false
|
|
152
|
+
),
|
|
153
|
+
GITLAB_API_URLS: normalizedApiUrls,
|
|
154
|
+
GITLAB_API_URL: normalizedApiUrls[0] ?? normalizeApiUrl("https://gitlab.com/api/v4")
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export type AppEnv = typeof env;
|
|
158
|
+
|
|
159
|
+
function normalizeApiUrl(rawUrl: string): string {
|
|
160
|
+
const url = new URL(rawUrl);
|
|
161
|
+
const pathname = url.pathname.replace(/\/+$/, "");
|
|
162
|
+
|
|
163
|
+
if (pathname.endsWith("/api/v4")) {
|
|
164
|
+
url.pathname = pathname;
|
|
165
|
+
return url.toString();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
url.pathname = `${pathname}/api/v4`.replace(/\/\//g, "/");
|
|
169
|
+
|
|
170
|
+
return url.toString();
|
|
171
|
+
}
|