@zereight/mcp-gitlab 2.1.7 → 2.1.8
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.ko.md +25 -0
- package/README.md +141 -96
- package/README.zh-CN.md +25 -0
- package/build/index.js +111 -6
- package/build/schemas.js +82 -0
- package/build/test/test-ci-lint.js +191 -0
- package/build/test/test-todos.js +195 -0
- package/build/test/test-toolset-filtering.js +11 -4
- package/build/tools/registry.js +39 -1
- package/package.json +2 -2
package/README.ko.md
CHANGED
|
@@ -155,6 +155,21 @@ docker run -i --rm \
|
|
|
155
155
|
|
|
156
156
|
MCP OAuth 사양을 지원하는 원격 MCP 클라이언트(예: Claude.ai)용입니다. 서버는 완전한 OAuth 2.0 인증 서버로 동작합니다. 인증되지 않은 요청은 `401 + WWW-Authenticate` 응답을 받고, 클라이언트 측 OAuth 브라우저 플로우가 자동으로 시작됩니다.
|
|
157
157
|
|
|
158
|
+
OpenCode, MCPJam, Claude.ai 같은 원격 MCP 클라이언트는 인증 중에 자체 callback URL을 보낼 수 있습니다. 모든 클라이언트 callback URL을 GitLab에 등록할 수 없다면 `GITLAB_OAUTH_CALLBACK_PROXY=true`를 켜세요. 콜백 프록시 모드에서는 GitLab에 `{MCP_SERVER_URL}/callback` 하나만 Redirect URI로 등록하면 됩니다.
|
|
159
|
+
|
|
160
|
+
`GITLAB_OAUTH_REDIRECT_URI`는 로컬 OAuth(`GITLAB_USE_OAUTH`) 전용입니다. 원격 MCP OAuth 클라이언트 callback URL을 덮어쓰지 않으며, 원격 `Unregistered redirect_uri` 오류 해결용으로 사용하면 안 됩니다.
|
|
161
|
+
|
|
162
|
+
이 변수가 존재하는 이유는 로컬 OAuth 플로우가 MCP 서버와 같은 머신에서 브라우저를 열고, `http://127.0.0.1:8888/callback` 같은 로컬 HTTP 서버로 callback을 받기 때문입니다.
|
|
163
|
+
|
|
164
|
+
원격 MCP OAuth는 다릅니다. `GITLAB_MCP_OAUTH=true` 모드에서는 MCP 클라이언트가 `/authorize` 요청 중에 자체 callback URL을 제공합니다. `GITLAB_OAUTH_REDIRECT_URI`는 그 클라이언트 제공 URL을 대체하지 않습니다.
|
|
165
|
+
|
|
166
|
+
| 모드 | 활성화 변수 | Callback 변수 | GitLab Redirect URI |
|
|
167
|
+
| --- | --- | --- | --- |
|
|
168
|
+
| 로컬 OAuth | `GITLAB_USE_OAUTH=true` | `GITLAB_OAUTH_REDIRECT_URI` | `http://127.0.0.1:8888/callback` 또는 로컬 callback |
|
|
169
|
+
| 원격 MCP OAuth | `GITLAB_MCP_OAUTH=true` | `GITLAB_OAUTH_CALLBACK_PROXY=true` | `{MCP_SERVER_URL}/callback` |
|
|
170
|
+
|
|
171
|
+
MCP 서버가 직접 로컬 브라우저 callback을 받을 때만 `GITLAB_OAUTH_REDIRECT_URI`를 사용하세요. 원격 MCP 클라이언트가 callback URL을 소유하는 경우에는 `GITLAB_OAUTH_CALLBACK_PROXY=true`를 사용하세요.
|
|
172
|
+
|
|
158
173
|
**동작 방식**: 공개 HTTPS URL을 가진 위치에 이 MCP 서버를 배포합니다. MCP 클라이언트는 `{MCP_SERVER_URL}/mcp`로 연결합니다. 서버는 OAuth 2.0 플로우를 처리하고 GitLab과 자격 증명을 교환합니다.
|
|
159
174
|
|
|
160
175
|
**사전 준비:**
|
|
@@ -173,6 +188,16 @@ MCP OAuth 사양을 지원하는 원격 MCP 클라이언트(예: Claude.ai)용
|
|
|
173
188
|
| `GITLAB_OAUTH_CALLBACK_PROXY` | 선택 | MCP 서버의 고정 `/callback` URL을 사용하려면 `true` |
|
|
174
189
|
| `GITLAB_OAUTH_SCOPES` | 선택 | 쉼표로 구분된 scope 목록(기본값: `api,read_api,read_user`) |
|
|
175
190
|
|
|
191
|
+
> **`Unregistered redirect_uri` 문제 해결**
|
|
192
|
+
>
|
|
193
|
+
> 브라우저 URL의 `redirect_uri`를 확인하세요. 값이 `http://127.0.0.1:xxxxx/.../callback` 같은 클라이언트 callback을 가리키면 다음 설정을 켜세요.
|
|
194
|
+
>
|
|
195
|
+
> ```env
|
|
196
|
+
> GITLAB_OAUTH_CALLBACK_PROXY=true
|
|
197
|
+
> ```
|
|
198
|
+
>
|
|
199
|
+
> 원격 MCP OAuth 문제를 `GITLAB_OAUTH_REDIRECT_URI` 변경으로 해결하려고 하지 마세요. 이 변수는 로컬 OAuth(`GITLAB_USE_OAUTH`) 전용입니다.
|
|
200
|
+
|
|
176
201
|
```shell
|
|
177
202
|
docker run -i --rm \
|
|
178
203
|
-e HOST=0.0.0.0 \
|
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ Quick start: choose either Personal Access Token or OAuth2 setup below and use `
|
|
|
33
33
|
- [OAuth2 Authentication Setup Guide](./docs/oauth-setup.md)
|
|
34
34
|
- [Environment Variables Reference](./docs/environment-variables.md)
|
|
35
35
|
- [Stateless Mode — Multi-Pod HPA](./docs/stateless-mode.md)
|
|
36
|
+
- [Custom Agents and Multiple PAT Setup](./docs/custom-agent-multiple-pat.md)
|
|
36
37
|
|
|
37
38
|
## Usage
|
|
38
39
|
|
|
@@ -159,6 +160,32 @@ The server acts as a full OAuth 2.0 authorization server — unauthenticated req
|
|
|
159
160
|
receive a `401 + WWW-Authenticate` response, which triggers the OAuth browser flow
|
|
160
161
|
automatically on the client side.
|
|
161
162
|
|
|
163
|
+
Remote MCP clients such as OpenCode, MCPJam, and Claude.ai can send their own
|
|
164
|
+
callback URL during authorization. If you cannot register every client callback
|
|
165
|
+
URL in GitLab, enable `GITLAB_OAUTH_CALLBACK_PROXY=true`. With callback proxy
|
|
166
|
+
mode, GitLab only needs one registered redirect URI: `{MCP_SERVER_URL}/callback`.
|
|
167
|
+
|
|
168
|
+
`GITLAB_OAUTH_REDIRECT_URI` is for local OAuth (`GITLAB_USE_OAUTH`) only. It does
|
|
169
|
+
not override remote MCP OAuth client callback URLs and should not be used to fix
|
|
170
|
+
remote `Unregistered redirect_uri` errors.
|
|
171
|
+
|
|
172
|
+
This variable exists because the local OAuth flow starts a browser on the same
|
|
173
|
+
machine as the MCP server and listens for the callback on a local HTTP server,
|
|
174
|
+
for example `http://127.0.0.1:8888/callback`.
|
|
175
|
+
|
|
176
|
+
Remote MCP OAuth is different. In `GITLAB_MCP_OAUTH=true` mode, the MCP client
|
|
177
|
+
provides its own callback URL during `/authorize`. `GITLAB_OAUTH_REDIRECT_URI`
|
|
178
|
+
does not replace that client-provided URL.
|
|
179
|
+
|
|
180
|
+
| Mode | Enable with | Callback variable | GitLab redirect URI |
|
|
181
|
+
| --- | --- | --- | --- |
|
|
182
|
+
| Local OAuth | `GITLAB_USE_OAUTH=true` | `GITLAB_OAUTH_REDIRECT_URI` | `http://127.0.0.1:8888/callback` or your local callback |
|
|
183
|
+
| Remote MCP OAuth | `GITLAB_MCP_OAUTH=true` | `GITLAB_OAUTH_CALLBACK_PROXY=true` | `{MCP_SERVER_URL}/callback` |
|
|
184
|
+
|
|
185
|
+
Use `GITLAB_OAUTH_REDIRECT_URI` only when the MCP server itself owns the local
|
|
186
|
+
browser callback. Use `GITLAB_OAUTH_CALLBACK_PROXY=true` when a remote MCP client
|
|
187
|
+
owns the callback URL.
|
|
188
|
+
|
|
162
189
|
**How it works**: You deploy this MCP server somewhere with a public HTTPS URL. MCP
|
|
163
190
|
clients connect to `{MCP_SERVER_URL}/mcp`. The server handles the OAuth 2.0 flow,
|
|
164
191
|
exchanging credentials with GitLab on behalf of the client.
|
|
@@ -179,6 +206,18 @@ exchanging credentials with GitLab on behalf of the client.
|
|
|
179
206
|
| `GITLAB_OAUTH_CALLBACK_PROXY` | optional | Set to `true` to use the MCP server's fixed `/callback` URL |
|
|
180
207
|
| `GITLAB_OAUTH_SCOPES` | optional | Comma-separated scopes (default: `api,read_api,read_user`) |
|
|
181
208
|
|
|
209
|
+
> **Troubleshooting `Unregistered redirect_uri`**
|
|
210
|
+
>
|
|
211
|
+
> Check the `redirect_uri` in the browser URL. If it points to a client callback
|
|
212
|
+
> such as `http://127.0.0.1:xxxxx/.../callback`, enable:
|
|
213
|
+
>
|
|
214
|
+
> ```env
|
|
215
|
+
> GITLAB_OAUTH_CALLBACK_PROXY=true
|
|
216
|
+
> ```
|
|
217
|
+
>
|
|
218
|
+
> Do not fix remote MCP OAuth by changing `GITLAB_OAUTH_REDIRECT_URI`. That
|
|
219
|
+
> variable is for local OAuth (`GITLAB_USE_OAUTH`) only.
|
|
220
|
+
|
|
182
221
|
```shell
|
|
183
222
|
docker run -i --rm \
|
|
184
223
|
-e HOST=0.0.0.0 \
|
|
@@ -497,102 +536,108 @@ Register the skill directory in your AI client to get optimal tool usage guidanc
|
|
|
497
536
|
48. `get_issue` - Get details of a specific issue in a GitLab project
|
|
498
537
|
49. `update_issue` - Update an issue in a GitLab project
|
|
499
538
|
50. `delete_issue` - Delete an issue from a GitLab project
|
|
500
|
-
51. `
|
|
501
|
-
52. `
|
|
502
|
-
53. `
|
|
503
|
-
54. `
|
|
504
|
-
55. `
|
|
505
|
-
56. `
|
|
506
|
-
57. `
|
|
507
|
-
58. `
|
|
508
|
-
59. `
|
|
509
|
-
60. `
|
|
510
|
-
61. `
|
|
511
|
-
62. `
|
|
512
|
-
63. `
|
|
513
|
-
64. `
|
|
514
|
-
65. `
|
|
515
|
-
66. `
|
|
516
|
-
67. `
|
|
517
|
-
68. `
|
|
518
|
-
69. `
|
|
519
|
-
70. `
|
|
520
|
-
71. `
|
|
521
|
-
72. `
|
|
522
|
-
73. `
|
|
523
|
-
74. `
|
|
524
|
-
75. `
|
|
525
|
-
76. `
|
|
526
|
-
77. `
|
|
527
|
-
78. `
|
|
528
|
-
79. `
|
|
529
|
-
80. `
|
|
530
|
-
81. `
|
|
531
|
-
82. `
|
|
532
|
-
83. `
|
|
533
|
-
84. `
|
|
534
|
-
85. `
|
|
535
|
-
86. `
|
|
536
|
-
87. `
|
|
537
|
-
88. `
|
|
538
|
-
89. `
|
|
539
|
-
90. `
|
|
540
|
-
91. `
|
|
541
|
-
92. `
|
|
542
|
-
93. `
|
|
543
|
-
94. `
|
|
544
|
-
95. `
|
|
545
|
-
96. `
|
|
546
|
-
97. `
|
|
547
|
-
98. `
|
|
548
|
-
99. `
|
|
549
|
-
100. `
|
|
550
|
-
101. `
|
|
551
|
-
102. `
|
|
552
|
-
103. `
|
|
553
|
-
104. `
|
|
554
|
-
105. `
|
|
555
|
-
106. `
|
|
556
|
-
107. `
|
|
557
|
-
108. `
|
|
558
|
-
109. `
|
|
559
|
-
110. `
|
|
560
|
-
111. `
|
|
561
|
-
112. `
|
|
562
|
-
113. `
|
|
563
|
-
114. `
|
|
564
|
-
115. `
|
|
565
|
-
116. `
|
|
566
|
-
117. `
|
|
567
|
-
118. `
|
|
568
|
-
119. `
|
|
569
|
-
120. `
|
|
570
|
-
121. `
|
|
571
|
-
122. `
|
|
572
|
-
123. `
|
|
573
|
-
124. `
|
|
574
|
-
125. `
|
|
575
|
-
126. `
|
|
576
|
-
127. `
|
|
577
|
-
128. `
|
|
578
|
-
129. `
|
|
579
|
-
130. `
|
|
580
|
-
131. `
|
|
581
|
-
132. `
|
|
582
|
-
133. `
|
|
583
|
-
134. `
|
|
584
|
-
135. `
|
|
585
|
-
136. `
|
|
586
|
-
137. `
|
|
587
|
-
138. `
|
|
588
|
-
139. `
|
|
589
|
-
140. `
|
|
590
|
-
141. `
|
|
591
|
-
142. `
|
|
592
|
-
143. `
|
|
593
|
-
144. `
|
|
594
|
-
145. `
|
|
595
|
-
146. `
|
|
539
|
+
51. `list_todos` - List GitLab to-do items for the current user
|
|
540
|
+
52. `mark_todo_done` - Mark a GitLab to-do item as done
|
|
541
|
+
53. `mark_all_todos_done` - Mark all pending GitLab to-do items as done for the current user
|
|
542
|
+
54. `list_issue_links` - List all issue links for a specific issue
|
|
543
|
+
55. `list_issue_discussions` - List discussions for an issue in a GitLab project
|
|
544
|
+
56. `get_issue_link` - Get a specific issue link
|
|
545
|
+
57. `create_issue_link` - Create an issue link between two issues
|
|
546
|
+
58. `delete_issue_link` - Delete an issue link
|
|
547
|
+
59. `list_namespaces` - List all namespaces available to the current user
|
|
548
|
+
60. `get_namespace` - Get details of a namespace by ID or path
|
|
549
|
+
61. `verify_namespace` - Verify if a namespace path exists
|
|
550
|
+
62. `get_project` - Get details of a specific project
|
|
551
|
+
63. `list_projects` - List projects accessible by the current user
|
|
552
|
+
64. `list_project_members` - List members of a GitLab project
|
|
553
|
+
65. `list_group_projects` - List projects in a GitLab group with filtering options
|
|
554
|
+
66. `list_group_iterations` - List group iterations with filtering options
|
|
555
|
+
67. `list_labels` - List labels for a project
|
|
556
|
+
68. `get_label` - Get a single label from a project
|
|
557
|
+
69. `create_label` - Create a new label in a project
|
|
558
|
+
70. `update_label` - Update an existing label in a project
|
|
559
|
+
71. `delete_label` - Delete a label from a project
|
|
560
|
+
72. `list_pipelines` - List pipelines in a GitLab project with filtering options
|
|
561
|
+
73. `get_pipeline` - Get details of a specific pipeline in a GitLab project
|
|
562
|
+
74. `list_pipeline_jobs` - List all jobs in a specific pipeline
|
|
563
|
+
75. `list_pipeline_trigger_jobs` - List all trigger jobs (bridges) in a specific pipeline that trigger downstream pipelines
|
|
564
|
+
76. `get_pipeline_job` - Get details of a GitLab pipeline job number
|
|
565
|
+
77. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job with optional pagination to limit context window usage
|
|
566
|
+
78. `validate_ci_lint` - Validate provided GitLab CI/CD YAML content for a project
|
|
567
|
+
79. `validate_project_ci_lint` - Validate an existing `.gitlab-ci.yml` configuration for a project
|
|
568
|
+
80. `create_pipeline` - Create a new pipeline for a branch or tag
|
|
569
|
+
81. `retry_pipeline` - Retry a failed or canceled pipeline
|
|
570
|
+
82. `cancel_pipeline` - Cancel a running pipeline
|
|
571
|
+
83. `play_pipeline_job` - Run a manual pipeline job
|
|
572
|
+
84. `retry_pipeline_job` - Retry a failed or canceled pipeline job
|
|
573
|
+
85. `cancel_pipeline_job` - Cancel a running pipeline job
|
|
574
|
+
86. `list_deployments` - List deployments in a GitLab project with filtering options
|
|
575
|
+
87. `get_deployment` - Get details of a specific deployment in a GitLab project
|
|
576
|
+
88. `list_environments` - List environments in a GitLab project
|
|
577
|
+
89. `get_environment` - Get details of a specific environment in a GitLab project
|
|
578
|
+
90. `list_job_artifacts` - List artifact files in a job's artifacts archive. Returns file names, paths, types, and sizes
|
|
579
|
+
91. `download_job_artifacts` - Download the entire artifact archive (zip) for a job to a local path. Returns the saved file path
|
|
580
|
+
92. `get_job_artifact_file` - Get the content of a single file from a job's artifacts by its path within the archive
|
|
581
|
+
93. `list_milestones` - List milestones in a GitLab project with filtering options
|
|
582
|
+
94. `get_milestone` - Get details of a specific milestone
|
|
583
|
+
95. `create_milestone` - Create a new milestone in a GitLab project
|
|
584
|
+
96. `edit_milestone` - Edit an existing milestone in a GitLab project
|
|
585
|
+
97. `delete_milestone` - Delete a milestone from a GitLab project
|
|
586
|
+
98. `get_milestone_issue` - Get issues associated with a specific milestone
|
|
587
|
+
99. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone
|
|
588
|
+
100. `promote_milestone` - Promote a milestone to the next stage
|
|
589
|
+
101. `get_milestone_burndown_events` - Get burndown events for a specific milestone
|
|
590
|
+
102. `list_wiki_pages` - List wiki pages in a GitLab project
|
|
591
|
+
103. `get_wiki_page` - Get details of a specific wiki page
|
|
592
|
+
104. `create_wiki_page` - Create a new wiki page in a GitLab project
|
|
593
|
+
105. `update_wiki_page` - Update an existing wiki page in a GitLab project
|
|
594
|
+
106. `delete_wiki_page` - Delete a wiki page from a GitLab project
|
|
595
|
+
107. `list_group_wiki_pages` - List wiki pages in a GitLab group
|
|
596
|
+
108. `get_group_wiki_page` - Get details of a specific group wiki page
|
|
597
|
+
109. `create_group_wiki_page` - Create a new wiki page in a GitLab group
|
|
598
|
+
110. `update_group_wiki_page` - Update an existing wiki page in a GitLab group
|
|
599
|
+
111. `delete_group_wiki_page` - Delete a wiki page from a GitLab group
|
|
600
|
+
112. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
|
|
601
|
+
113. `list_commits` - List repository commits with filtering options
|
|
602
|
+
114. `get_commit` - Get details of a specific commit
|
|
603
|
+
115. `get_commit_diff` - Get changes/diffs of a specific commit
|
|
604
|
+
116. `list_releases` - List all releases for a project
|
|
605
|
+
117. `get_release` - Get a release by tag name
|
|
606
|
+
118. `create_release` - Create a new release in a GitLab project
|
|
607
|
+
119. `update_release` - Update an existing release in a GitLab project
|
|
608
|
+
120. `delete_release` - Delete a release from a GitLab project (does not delete the associated tag)
|
|
609
|
+
121. `create_release_evidence` - Create release evidence for an existing release (GitLab Premium/Ultimate only)
|
|
610
|
+
122. `download_release_asset` - Download a release asset file by direct asset path
|
|
611
|
+
123. `list_tags` - List repository tags with filtering and pagination support
|
|
612
|
+
124. `get_tag` - Get details of a specific repository tag
|
|
613
|
+
125. `create_tag` - Create a new tag in the repository
|
|
614
|
+
126. `delete_tag` - Delete a tag from the repository
|
|
615
|
+
127. `get_tag_signature` - Get the signature of a signed tag
|
|
616
|
+
128. `get_users` - Get GitLab user details by usernames
|
|
617
|
+
129. `list_events` - List all events for the currently authenticated user
|
|
618
|
+
130. `get_project_events` - List all visible events for a specified project
|
|
619
|
+
131. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
|
|
620
|
+
132. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
|
|
621
|
+
133. `get_work_item` - Get a single work item with full details including status, hierarchy (parent/children), type, labels, assignees, and all widgets
|
|
622
|
+
134. `list_work_items` - List work items in a project with filters (type, state, search, assignees, labels). Returns items with status and hierarchy info
|
|
623
|
+
135. `create_work_item` - Create a new work item (issue, task, incident, test_case, epic, key_result, objective, requirement, ticket). Supports setting title, description, labels, assignees, weight, parent, health status, start/due dates, milestone, and confidentiality
|
|
624
|
+
136. `update_work_item` - Update a work item. Can modify title, description, labels, assignees, weight, state, status, parent hierarchy, children, health status, start/due dates, milestone, confidentiality, linked items, and custom fields
|
|
625
|
+
137. `convert_work_item_type` - Convert a work item to a different type (e.g. issue to task, task to incident)
|
|
626
|
+
138. `list_work_item_statuses` - List available statuses for a work item type in a project. Requires GitLab Premium/Ultimate with configurable statuses
|
|
627
|
+
139. `list_custom_field_definitions` - List available custom field definitions for a work item type in a project. Returns field names, types, and IDs needed for setting custom fields via update_work_item
|
|
628
|
+
140. `move_work_item` - Move a work item (issue, task, etc.) to a different project. Uses GitLab GraphQL issueMove mutation
|
|
629
|
+
141. `list_work_item_notes` - List notes and discussions on a work item. Returns threaded discussions with author, body, timestamps, and system/internal flags
|
|
630
|
+
142. `create_work_item_note` - Add a note/comment to a work item. Supports Markdown, internal notes, and threaded replies
|
|
631
|
+
143. `get_timeline_events` - List timeline events for an incident. Returns chronological events with notes, timestamps, and tags
|
|
632
|
+
144. `create_timeline_event` - Create a timeline event on an incident. Supports tags: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'
|
|
633
|
+
145. `list_webhooks` - List all configured webhooks for a GitLab project or group. Provide either project_id or group_id
|
|
634
|
+
146. `list_webhook_events` - List recent webhook events (past 7 days) for a project or group webhook. Use summary mode for overview, then get_webhook_event for full details
|
|
635
|
+
147. `get_webhook_event` - Get full details of a specific webhook event by ID, including request/response payloads
|
|
636
|
+
148. `search_code` - Search for code across all projects on the GitLab instance (requires advanced search or exact code search to be enabled)
|
|
637
|
+
149. `search_project_code` - Search for code within a specific GitLab project (requires advanced search or exact code search to be enabled)
|
|
638
|
+
150. `search_group_code` - Search for code within a specific GitLab group (requires advanced search or exact code search to be enabled)
|
|
639
|
+
151. `execute_graphql` - Execute a GitLab GraphQL query
|
|
640
|
+
|
|
596
641
|
<!-- TOOLS-END -->
|
|
597
642
|
|
|
598
643
|
</details>
|
package/README.zh-CN.md
CHANGED
|
@@ -155,6 +155,21 @@ docker run -i --rm \
|
|
|
155
155
|
|
|
156
156
|
适用于支持 MCP OAuth 规范的远程 MCP 客户端(例如 Claude.ai)。服务器会作为完整 OAuth 2.0 授权服务器运行。未认证请求会收到 `401 + WWW-Authenticate` 响应,从而触发客户端侧 OAuth 浏览器流程。
|
|
157
157
|
|
|
158
|
+
OpenCode、MCPJam、Claude.ai 等远程 MCP 客户端可能会在授权时发送自己的 callback URL。如果你无法在 GitLab 中注册每个客户端 callback URL,请启用 `GITLAB_OAUTH_CALLBACK_PROXY=true`。启用回调代理模式后,GitLab 只需要注册一个 Redirect URI:`{MCP_SERVER_URL}/callback`。
|
|
159
|
+
|
|
160
|
+
`GITLAB_OAUTH_REDIRECT_URI` 仅用于本地 OAuth(`GITLAB_USE_OAUTH`)。它不会覆盖远程 MCP OAuth 客户端 callback URL,也不应用来修复远程 `Unregistered redirect_uri` 错误。
|
|
161
|
+
|
|
162
|
+
这个变量存在是因为本地 OAuth 流程会在与 MCP 服务器相同的机器上打开浏览器,并通过本地 HTTP 服务器接收 callback,例如 `http://127.0.0.1:8888/callback`。
|
|
163
|
+
|
|
164
|
+
远程 MCP OAuth 不同。在 `GITLAB_MCP_OAUTH=true` 模式下,MCP 客户端会在 `/authorize` 请求中提供自己的 callback URL。`GITLAB_OAUTH_REDIRECT_URI` 不会替换这个客户端提供的 URL。
|
|
165
|
+
|
|
166
|
+
| 模式 | 启用方式 | Callback 变量 | GitLab Redirect URI |
|
|
167
|
+
| --- | --- | --- | --- |
|
|
168
|
+
| 本地 OAuth | `GITLAB_USE_OAUTH=true` | `GITLAB_OAUTH_REDIRECT_URI` | `http://127.0.0.1:8888/callback` 或你的本地 callback |
|
|
169
|
+
| 远程 MCP OAuth | `GITLAB_MCP_OAUTH=true` | `GITLAB_OAUTH_CALLBACK_PROXY=true` | `{MCP_SERVER_URL}/callback` |
|
|
170
|
+
|
|
171
|
+
只有当 MCP 服务器自己接收本地浏览器 callback 时,才使用 `GITLAB_OAUTH_REDIRECT_URI`。当远程 MCP 客户端拥有 callback URL 时,请使用 `GITLAB_OAUTH_CALLBACK_PROXY=true`。
|
|
172
|
+
|
|
158
173
|
**工作方式**:将此 MCP 服务器部署到拥有公开 HTTPS URL 的位置。MCP 客户端连接到 `{MCP_SERVER_URL}/mcp`。服务器处理 OAuth 2.0 流程,并代表客户端与 GitLab 交换凭据。
|
|
159
174
|
|
|
160
175
|
**前置条件:**
|
|
@@ -173,6 +188,16 @@ docker run -i --rm \
|
|
|
173
188
|
| `GITLAB_OAUTH_CALLBACK_PROXY` | 可选 | 设置为 `true` 时使用 MCP 服务器固定的 `/callback` URL |
|
|
174
189
|
| `GITLAB_OAUTH_SCOPES` | 可选 | 逗号分隔的 scope(默认:`api,read_api,read_user`) |
|
|
175
190
|
|
|
191
|
+
> **排查 `Unregistered redirect_uri`**
|
|
192
|
+
>
|
|
193
|
+
> 检查浏览器 URL 中的 `redirect_uri`。如果它指向客户端 callback,例如 `http://127.0.0.1:xxxxx/.../callback`,请启用:
|
|
194
|
+
>
|
|
195
|
+
> ```env
|
|
196
|
+
> GITLAB_OAUTH_CALLBACK_PROXY=true
|
|
197
|
+
> ```
|
|
198
|
+
>
|
|
199
|
+
> 不要通过修改 `GITLAB_OAUTH_REDIRECT_URI` 来修复远程 MCP OAuth。该变量仅用于本地 OAuth(`GITLAB_USE_OAUTH`)。
|
|
200
|
+
|
|
176
201
|
```shell
|
|
177
202
|
docker run -i --rm \
|
|
178
203
|
-e HOST=0.0.0.0 \
|
package/build/index.js
CHANGED
|
@@ -25,15 +25,13 @@ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middlew
|
|
|
25
25
|
import { GitLabClientPool } from "./gitlab-client-pool.js";
|
|
26
26
|
import { allTools, readOnlyTools, destructiveTools, parseEnabledToolsets, parseIndividualTools, buildFeatureFlagOverrides, isToolInEnabledToolset, TOOLSET_DEFINITIONS, ALL_TOOLSET_IDS, } from "./tools/registry.js";
|
|
27
27
|
import { BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, // Added
|
|
28
|
-
CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateRepositorySchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema,
|
|
29
|
-
// pipeline job schemas
|
|
30
|
-
GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
|
|
28
|
+
CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateRepositorySchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
|
|
31
29
|
// Discussion Schemas
|
|
32
30
|
GitLabDiscussionNoteSchema, // Added
|
|
33
31
|
GitLabDiscussionSchema,
|
|
34
32
|
// Draft Notes Schemas
|
|
35
|
-
GitLabDraftNoteSchema, GitLabForkSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
36
|
-
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, } from "./schemas.js";
|
|
33
|
+
GitLabDraftNoteSchema, GitLabForkSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
34
|
+
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, } from "./schemas.js";
|
|
37
35
|
import { randomUUID } from "node:crypto";
|
|
38
36
|
import { pino } from "pino";
|
|
39
37
|
const logger = pino({
|
|
@@ -1097,6 +1095,36 @@ async function listIssues(projectId, options = {}) {
|
|
|
1097
1095
|
const data = await response.json();
|
|
1098
1096
|
return z.array(GitLabIssueSchema).parse(data);
|
|
1099
1097
|
}
|
|
1098
|
+
async function listTodos(options = {}) {
|
|
1099
|
+
const url = new URL(`${getEffectiveApiUrl()}/todos`);
|
|
1100
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
1101
|
+
if (value !== undefined) {
|
|
1102
|
+
url.searchParams.append(key, String(value));
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
const response = await fetch(url.toString(), {
|
|
1106
|
+
...getFetchConfig(),
|
|
1107
|
+
});
|
|
1108
|
+
await handleGitLabError(response);
|
|
1109
|
+
const data = await response.json();
|
|
1110
|
+
return z.array(GitLabTodoSchema).parse(data);
|
|
1111
|
+
}
|
|
1112
|
+
async function markTodoDone(id) {
|
|
1113
|
+
const response = await fetch(`${getEffectiveApiUrl()}/todos/${id}/mark_as_done`, {
|
|
1114
|
+
...getFetchConfig(),
|
|
1115
|
+
method: "POST",
|
|
1116
|
+
});
|
|
1117
|
+
await handleGitLabError(response);
|
|
1118
|
+
const data = await response.json();
|
|
1119
|
+
return GitLabTodoSchema.parse(data);
|
|
1120
|
+
}
|
|
1121
|
+
async function markAllTodosDone() {
|
|
1122
|
+
const response = await fetch(`${getEffectiveApiUrl()}/todos/mark_as_done`, {
|
|
1123
|
+
...getFetchConfig(),
|
|
1124
|
+
method: "POST",
|
|
1125
|
+
});
|
|
1126
|
+
await handleGitLabError(response);
|
|
1127
|
+
}
|
|
1100
1128
|
/**
|
|
1101
1129
|
* List merge requests globally or for a specific GitLab project
|
|
1102
1130
|
*
|
|
@@ -4769,6 +4797,38 @@ async function getPipelineJobOutput(projectId, jobId, limit, offset) {
|
|
|
4769
4797
|
}
|
|
4770
4798
|
return fullTrace;
|
|
4771
4799
|
}
|
|
4800
|
+
async function validateCiLint(projectId, options) {
|
|
4801
|
+
projectId = decodeURIComponent(projectId);
|
|
4802
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/ci/lint`);
|
|
4803
|
+
const response = await fetch(url.toString(), {
|
|
4804
|
+
...getFetchConfig(),
|
|
4805
|
+
method: "POST",
|
|
4806
|
+
body: JSON.stringify(options),
|
|
4807
|
+
});
|
|
4808
|
+
await handleGitLabError(response);
|
|
4809
|
+
const data = await response.json();
|
|
4810
|
+
return GitLabCiLintResultSchema.parse(data);
|
|
4811
|
+
}
|
|
4812
|
+
async function validateProjectCiLint(projectId, options) {
|
|
4813
|
+
projectId = decodeURIComponent(projectId);
|
|
4814
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/ci/lint`);
|
|
4815
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
4816
|
+
if (value !== undefined) {
|
|
4817
|
+
if (typeof value === "boolean") {
|
|
4818
|
+
url.searchParams.append(key, value ? "true" : "false");
|
|
4819
|
+
}
|
|
4820
|
+
else {
|
|
4821
|
+
url.searchParams.append(key, value.toString());
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
});
|
|
4825
|
+
const response = await fetch(url.toString(), {
|
|
4826
|
+
...getFetchConfig(),
|
|
4827
|
+
});
|
|
4828
|
+
await handleGitLabError(response);
|
|
4829
|
+
const data = await response.json();
|
|
4830
|
+
return GitLabCiLintResultSchema.parse(data);
|
|
4831
|
+
}
|
|
4772
4832
|
/**
|
|
4773
4833
|
* List artifact files in a job's artifacts archive
|
|
4774
4834
|
*
|
|
@@ -5791,7 +5851,7 @@ async function getTagSignature(projectId, tagName) {
|
|
|
5791
5851
|
async function handleToolCall(params) {
|
|
5792
5852
|
try {
|
|
5793
5853
|
if (!params.arguments) {
|
|
5794
|
-
|
|
5854
|
+
params.arguments = {};
|
|
5795
5855
|
}
|
|
5796
5856
|
// Ensure session is established for every request if cookie authentication is enabled
|
|
5797
5857
|
if (GITLAB_AUTH_COOKIE_PATH) {
|
|
@@ -6150,6 +6210,35 @@ async function handleToolCall(params) {
|
|
|
6150
6210
|
await deleteRestAwardEmoji(path);
|
|
6151
6211
|
return { content: [{ type: "text", text: "Issue note emoji reaction deleted successfully" }] };
|
|
6152
6212
|
}
|
|
6213
|
+
case "list_todos": {
|
|
6214
|
+
const args = ListTodosSchema.parse(params.arguments);
|
|
6215
|
+
const todos = await listTodos(args);
|
|
6216
|
+
return {
|
|
6217
|
+
content: [{ type: "text", text: JSON.stringify(todos, null, 2) }],
|
|
6218
|
+
};
|
|
6219
|
+
}
|
|
6220
|
+
case "mark_todo_done": {
|
|
6221
|
+
const args = MarkTodoDoneSchema.parse(params.arguments);
|
|
6222
|
+
const todo = await markTodoDone(args.id);
|
|
6223
|
+
return {
|
|
6224
|
+
content: [{ type: "text", text: JSON.stringify(todo, null, 2) }],
|
|
6225
|
+
};
|
|
6226
|
+
}
|
|
6227
|
+
case "mark_all_todos_done": {
|
|
6228
|
+
MarkAllTodosDoneSchema.parse(params.arguments);
|
|
6229
|
+
await markAllTodosDone();
|
|
6230
|
+
return {
|
|
6231
|
+
content: [
|
|
6232
|
+
{
|
|
6233
|
+
type: "text",
|
|
6234
|
+
text: JSON.stringify({
|
|
6235
|
+
status: "success",
|
|
6236
|
+
message: "All pending to-do items marked as done",
|
|
6237
|
+
}, null, 2),
|
|
6238
|
+
},
|
|
6239
|
+
],
|
|
6240
|
+
};
|
|
6241
|
+
}
|
|
6153
6242
|
case "get_merge_request": {
|
|
6154
6243
|
const args = GetMergeRequestSchema.parse(params.arguments);
|
|
6155
6244
|
const mergeRequest = await getMergeRequest(args.project_id, args.merge_request_iid, args.source_branch);
|
|
@@ -6902,6 +6991,22 @@ async function handleToolCall(params) {
|
|
|
6902
6991
|
],
|
|
6903
6992
|
};
|
|
6904
6993
|
}
|
|
6994
|
+
case "validate_ci_lint": {
|
|
6995
|
+
const args = ValidateCiLintSchema.parse(params.arguments);
|
|
6996
|
+
const { project_id, ...options } = args;
|
|
6997
|
+
const result = await validateCiLint(project_id, options);
|
|
6998
|
+
return {
|
|
6999
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
7000
|
+
};
|
|
7001
|
+
}
|
|
7002
|
+
case "validate_project_ci_lint": {
|
|
7003
|
+
const args = ValidateProjectCiLintSchema.parse(params.arguments);
|
|
7004
|
+
const { project_id, ...options } = args;
|
|
7005
|
+
const result = await validateProjectCiLint(project_id, options);
|
|
7006
|
+
return {
|
|
7007
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
7008
|
+
};
|
|
7009
|
+
}
|
|
6905
7010
|
case "create_pipeline": {
|
|
6906
7011
|
const { project_id, ref, variables, inputs } = CreatePipelineSchema.parse(params.arguments);
|
|
6907
7012
|
const pipeline = await createPipeline(project_id, ref, variables, inputs);
|
package/build/schemas.js
CHANGED
|
@@ -275,6 +275,34 @@ export const ListPipelineTriggerJobsSchema = z
|
|
|
275
275
|
.describe("The scope of trigger jobs to show"),
|
|
276
276
|
})
|
|
277
277
|
.merge(PaginationOptionsSchema);
|
|
278
|
+
export const GitLabCiLintResultSchema = z.object({
|
|
279
|
+
valid: z.coerce.boolean(),
|
|
280
|
+
errors: z.array(z.string()),
|
|
281
|
+
warnings: z.array(z.string()).optional(),
|
|
282
|
+
merged_yaml: z.string().optional(),
|
|
283
|
+
includes: z.array(z.unknown()).optional(),
|
|
284
|
+
jobs: z.array(z.unknown()).optional(),
|
|
285
|
+
});
|
|
286
|
+
export const ValidateCiLintSchema = z.object({
|
|
287
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
288
|
+
content: z.string().describe("GitLab CI/CD YAML content to validate"),
|
|
289
|
+
dry_run: z.coerce.boolean().optional().describe("Run pipeline creation simulation"),
|
|
290
|
+
include_jobs: z.coerce.boolean().optional().describe("Include jobs in the lint response"),
|
|
291
|
+
ref: z.string().optional().describe("Branch or tag context for dry_run validation"),
|
|
292
|
+
});
|
|
293
|
+
export const ValidateProjectCiLintSchema = z.object({
|
|
294
|
+
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
295
|
+
content_ref: z
|
|
296
|
+
.string()
|
|
297
|
+
.optional()
|
|
298
|
+
.describe("Commit SHA, branch, or tag to read the existing CI config from"),
|
|
299
|
+
dry_run: z.coerce.boolean().optional().describe("Run pipeline creation simulation"),
|
|
300
|
+
dry_run_ref: z
|
|
301
|
+
.string()
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("Branch or tag context for dry_run validation"),
|
|
304
|
+
include_jobs: z.coerce.boolean().optional().describe("Include jobs in the lint response"),
|
|
305
|
+
});
|
|
278
306
|
// Deployment related schemas
|
|
279
307
|
export const GitLabDeploymentSchema = z.object({
|
|
280
308
|
id: z.coerce.string(),
|
|
@@ -1234,6 +1262,60 @@ export const CreateIssueSchema = ProjectParamsSchema.extend({
|
|
|
1234
1262
|
.describe("The type of issue. One of issue, incident, test_case or task."),
|
|
1235
1263
|
weight: z.coerce.number().optional().describe("Weight of the issue (numeric, typically hours of work)"),
|
|
1236
1264
|
});
|
|
1265
|
+
export const GitLabTodoSchema = z.object({
|
|
1266
|
+
id: z.coerce.number(),
|
|
1267
|
+
project: z.unknown().optional(),
|
|
1268
|
+
author: z.unknown().optional(),
|
|
1269
|
+
action_name: z.string().optional(),
|
|
1270
|
+
target_type: z.string().optional(),
|
|
1271
|
+
target: z.unknown().optional(),
|
|
1272
|
+
target_url: z.string().optional(),
|
|
1273
|
+
body: z.string().optional(),
|
|
1274
|
+
state: z.string(),
|
|
1275
|
+
created_at: z.string().optional(),
|
|
1276
|
+
updated_at: z.string().optional(),
|
|
1277
|
+
});
|
|
1278
|
+
export const ListTodosSchema = z
|
|
1279
|
+
.object({
|
|
1280
|
+
action: z
|
|
1281
|
+
.enum([
|
|
1282
|
+
"assigned",
|
|
1283
|
+
"mentioned",
|
|
1284
|
+
"build_failed",
|
|
1285
|
+
"marked",
|
|
1286
|
+
"approval_required",
|
|
1287
|
+
"unmergeable",
|
|
1288
|
+
"directly_addressed",
|
|
1289
|
+
"merge_train_removed",
|
|
1290
|
+
"member_access_requested",
|
|
1291
|
+
])
|
|
1292
|
+
.optional()
|
|
1293
|
+
.describe("Filter by to-do action"),
|
|
1294
|
+
author_id: z.coerce.number().optional().describe("Filter by author ID"),
|
|
1295
|
+
project_id: z.coerce.number().optional().describe("Filter by project ID"),
|
|
1296
|
+
group_id: z.coerce.number().optional().describe("Filter by group ID"),
|
|
1297
|
+
state: z.enum(["pending", "done"]).optional().describe("Filter by to-do state"),
|
|
1298
|
+
type: z
|
|
1299
|
+
.enum([
|
|
1300
|
+
"Issue",
|
|
1301
|
+
"MergeRequest",
|
|
1302
|
+
"Commit",
|
|
1303
|
+
"Epic",
|
|
1304
|
+
"DesignManagement::Design",
|
|
1305
|
+
"AlertManagement::Alert",
|
|
1306
|
+
"Project",
|
|
1307
|
+
"Namespace",
|
|
1308
|
+
"Vulnerability",
|
|
1309
|
+
"WikiPage::Meta",
|
|
1310
|
+
])
|
|
1311
|
+
.optional()
|
|
1312
|
+
.describe("Filter by to-do target type"),
|
|
1313
|
+
})
|
|
1314
|
+
.merge(PaginationOptionsSchema);
|
|
1315
|
+
export const MarkTodoDoneSchema = z.object({
|
|
1316
|
+
id: z.coerce.number().describe("The ID of the to-do item"),
|
|
1317
|
+
});
|
|
1318
|
+
export const MarkAllTodosDoneSchema = z.object({});
|
|
1237
1319
|
const MergeRequestOptionsSchema = {
|
|
1238
1320
|
title: z.string().describe("Merge request title"),
|
|
1239
1321
|
description: z.string().optional().describe("Merge request description"),
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { describe, test, before, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { MockGitLabServer, findMockServerPort } from "./utils/mock-gitlab-server.js";
|
|
5
|
+
const MOCK_TOKEN = "glpat-ci-lint-test-token";
|
|
6
|
+
const TEST_PROJECT_ID = "123";
|
|
7
|
+
async function callTool(toolName, args, env) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const proc = spawn("node", ["build/index.js"], {
|
|
10
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
11
|
+
env: {
|
|
12
|
+
...process.env,
|
|
13
|
+
...env,
|
|
14
|
+
USE_PIPELINE: "true",
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
let output = "";
|
|
18
|
+
let errorOutput = "";
|
|
19
|
+
proc.stdout?.on("data", (d) => (output += d));
|
|
20
|
+
proc.stderr?.on("data", (d) => (errorOutput += d));
|
|
21
|
+
proc.on("close", code => {
|
|
22
|
+
if (code !== 0) {
|
|
23
|
+
return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
|
|
24
|
+
}
|
|
25
|
+
const line = output.split("\n").find(l => l.startsWith("{"));
|
|
26
|
+
if (!line)
|
|
27
|
+
return reject(new Error("No JSON output found"));
|
|
28
|
+
try {
|
|
29
|
+
const response = JSON.parse(line);
|
|
30
|
+
if (response.error) {
|
|
31
|
+
reject(response.error);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const content = response.result?.content?.[0]?.text;
|
|
35
|
+
resolve(content ? JSON.parse(content) : response.result);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
reject(e);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
proc.stdin?.end(JSON.stringify({
|
|
43
|
+
jsonrpc: "2.0",
|
|
44
|
+
id: 1,
|
|
45
|
+
method: "tools/call",
|
|
46
|
+
params: { name: toolName, arguments: args },
|
|
47
|
+
}) + "\n");
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async function listToolNames(env) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const proc = spawn("node", ["build/index.js"], {
|
|
53
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
...env,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
let output = "";
|
|
60
|
+
let errorOutput = "";
|
|
61
|
+
proc.stdout?.on("data", (d) => (output += d));
|
|
62
|
+
proc.stderr?.on("data", (d) => (errorOutput += d));
|
|
63
|
+
proc.on("close", code => {
|
|
64
|
+
if (code !== 0) {
|
|
65
|
+
return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
|
|
66
|
+
}
|
|
67
|
+
const line = output.split("\n").find(l => l.startsWith("{"));
|
|
68
|
+
if (!line)
|
|
69
|
+
return reject(new Error("No JSON output found"));
|
|
70
|
+
try {
|
|
71
|
+
const response = JSON.parse(line);
|
|
72
|
+
if (response.error) {
|
|
73
|
+
reject(response.error);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
resolve(response.result.tools.map((tool) => tool.name));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
reject(e);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
proc.stdin?.end(JSON.stringify({
|
|
84
|
+
jsonrpc: "2.0",
|
|
85
|
+
id: 1,
|
|
86
|
+
method: "tools/list",
|
|
87
|
+
params: {},
|
|
88
|
+
}) + "\n");
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
describe("GitLab CI lint tools", () => {
|
|
92
|
+
let mockGitLab;
|
|
93
|
+
let mockGitLabUrl;
|
|
94
|
+
before(async () => {
|
|
95
|
+
const mockPort = await findMockServerPort(9260);
|
|
96
|
+
mockGitLab = new MockGitLabServer({
|
|
97
|
+
port: mockPort,
|
|
98
|
+
validTokens: [MOCK_TOKEN],
|
|
99
|
+
});
|
|
100
|
+
mockGitLab.addMockHandler("post", `/projects/${TEST_PROJECT_ID}/ci/lint`, (req, res) => {
|
|
101
|
+
assert.strictEqual(req.body.content, "stages: [test]\ntest:\n script: echo ok");
|
|
102
|
+
assert.strictEqual(req.body.dry_run, true);
|
|
103
|
+
assert.strictEqual(req.body.include_jobs, true);
|
|
104
|
+
assert.strictEqual(req.body.ref, "main");
|
|
105
|
+
res.json({
|
|
106
|
+
valid: true,
|
|
107
|
+
errors: [],
|
|
108
|
+
warnings: [],
|
|
109
|
+
merged_yaml: "test:\n script: echo ok\n",
|
|
110
|
+
includes: [],
|
|
111
|
+
jobs: [{ name: "test", stage: "test" }],
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/ci/lint`, (req, res) => {
|
|
115
|
+
assert.strictEqual(req.query.content_ref, "feature/test");
|
|
116
|
+
assert.strictEqual(req.query.dry_run, "true");
|
|
117
|
+
assert.strictEqual(req.query.dry_run_ref, "main");
|
|
118
|
+
assert.strictEqual(req.query.include_jobs, "true");
|
|
119
|
+
res.json({
|
|
120
|
+
valid: true,
|
|
121
|
+
errors: [],
|
|
122
|
+
warnings: [],
|
|
123
|
+
merged_yaml: "include-job:\n script: echo included\n",
|
|
124
|
+
includes: [{ type: "local", location: "include.yml" }],
|
|
125
|
+
jobs: [{ name: "include-job", stage: "test" }],
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
await mockGitLab.start();
|
|
129
|
+
mockGitLabUrl = mockGitLab.getUrl();
|
|
130
|
+
});
|
|
131
|
+
after(async () => {
|
|
132
|
+
await mockGitLab.stop();
|
|
133
|
+
});
|
|
134
|
+
test("validate_ci_lint posts CI YAML content and returns lint result", async () => {
|
|
135
|
+
const result = await callTool("validate_ci_lint", {
|
|
136
|
+
project_id: TEST_PROJECT_ID,
|
|
137
|
+
content: "stages: [test]\ntest:\n script: echo ok",
|
|
138
|
+
dry_run: true,
|
|
139
|
+
include_jobs: true,
|
|
140
|
+
ref: "main",
|
|
141
|
+
}, {
|
|
142
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
143
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
144
|
+
});
|
|
145
|
+
assert.strictEqual(result.valid, true);
|
|
146
|
+
assert.deepStrictEqual(result.errors, []);
|
|
147
|
+
assert.strictEqual(result.jobs[0].name, "test");
|
|
148
|
+
});
|
|
149
|
+
test("validate_ci_lint surfaces invalid lint responses", async () => {
|
|
150
|
+
mockGitLab.addMockHandler("post", `/projects/999/ci/lint`, (req, res) => {
|
|
151
|
+
res.json({
|
|
152
|
+
valid: false,
|
|
153
|
+
errors: ["jobs config should contain at least one visible job"],
|
|
154
|
+
warnings: [],
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
const result = await callTool("validate_ci_lint", {
|
|
158
|
+
project_id: "999",
|
|
159
|
+
content: ".hidden:\n script: echo hidden",
|
|
160
|
+
}, {
|
|
161
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
162
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
163
|
+
});
|
|
164
|
+
assert.strictEqual(result.valid, false);
|
|
165
|
+
assert.deepStrictEqual(result.errors, ["jobs config should contain at least one visible job"]);
|
|
166
|
+
});
|
|
167
|
+
test("validate_project_ci_lint sends GET query parameters", async () => {
|
|
168
|
+
const result = await callTool("validate_project_ci_lint", {
|
|
169
|
+
project_id: TEST_PROJECT_ID,
|
|
170
|
+
content_ref: "feature/test",
|
|
171
|
+
dry_run: true,
|
|
172
|
+
dry_run_ref: "main",
|
|
173
|
+
include_jobs: true,
|
|
174
|
+
}, {
|
|
175
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
176
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
177
|
+
});
|
|
178
|
+
assert.strictEqual(result.valid, true);
|
|
179
|
+
assert.strictEqual(result.includes[0].location, "include.yml");
|
|
180
|
+
assert.strictEqual(result.jobs[0].name, "include-job");
|
|
181
|
+
});
|
|
182
|
+
test("CI lint tools are visible by default in read-only mode", async () => {
|
|
183
|
+
const tools = await listToolNames({
|
|
184
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
185
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
186
|
+
GITLAB_READ_ONLY_MODE: "true",
|
|
187
|
+
});
|
|
188
|
+
assert.ok(tools.includes("validate_ci_lint"));
|
|
189
|
+
assert.ok(tools.includes("validate_project_ci_lint"));
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { describe, test, before, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { MockGitLabServer, findMockServerPort } from "./utils/mock-gitlab-server.js";
|
|
5
|
+
const MOCK_TOKEN = "glpat-todos-test-token";
|
|
6
|
+
async function callTool(toolName, args, env) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const proc = spawn("node", ["build/index.js"], {
|
|
9
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
10
|
+
env: {
|
|
11
|
+
...process.env,
|
|
12
|
+
...env,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
let output = "";
|
|
16
|
+
let errorOutput = "";
|
|
17
|
+
proc.stdout?.on("data", (d) => (output += d));
|
|
18
|
+
proc.stderr?.on("data", (d) => (errorOutput += d));
|
|
19
|
+
proc.on("close", code => {
|
|
20
|
+
if (code !== 0) {
|
|
21
|
+
return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
|
|
22
|
+
}
|
|
23
|
+
const line = output.split("\n").find(l => l.startsWith("{"));
|
|
24
|
+
if (!line)
|
|
25
|
+
return reject(new Error("No JSON output found"));
|
|
26
|
+
try {
|
|
27
|
+
const response = JSON.parse(line);
|
|
28
|
+
if (response.error) {
|
|
29
|
+
reject(response.error);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const content = response.result?.content?.[0]?.text;
|
|
33
|
+
resolve(content ? JSON.parse(content) : response.result);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
reject(e);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
proc.stdin?.end(JSON.stringify({
|
|
41
|
+
jsonrpc: "2.0",
|
|
42
|
+
id: 1,
|
|
43
|
+
method: "tools/call",
|
|
44
|
+
params: args === undefined ? { name: toolName } : { name: toolName, arguments: args },
|
|
45
|
+
}) + "\n");
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function listToolNames(env) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const proc = spawn("node", ["build/index.js"], {
|
|
51
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
52
|
+
env: {
|
|
53
|
+
...process.env,
|
|
54
|
+
...env,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
let output = "";
|
|
58
|
+
let errorOutput = "";
|
|
59
|
+
proc.stdout?.on("data", (d) => (output += d));
|
|
60
|
+
proc.stderr?.on("data", (d) => (errorOutput += d));
|
|
61
|
+
proc.on("close", code => {
|
|
62
|
+
if (code !== 0) {
|
|
63
|
+
return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
|
|
64
|
+
}
|
|
65
|
+
const line = output.split("\n").find(l => l.startsWith("{"));
|
|
66
|
+
if (!line)
|
|
67
|
+
return reject(new Error("No JSON output found"));
|
|
68
|
+
try {
|
|
69
|
+
const response = JSON.parse(line);
|
|
70
|
+
if (response.error) {
|
|
71
|
+
reject(response.error);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
resolve(response.result.tools.map((tool) => tool.name));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
reject(e);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
proc.stdin?.end(JSON.stringify({
|
|
82
|
+
jsonrpc: "2.0",
|
|
83
|
+
id: 1,
|
|
84
|
+
method: "tools/list",
|
|
85
|
+
params: {},
|
|
86
|
+
}) + "\n");
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function todoFixture(id, state = "pending") {
|
|
90
|
+
return {
|
|
91
|
+
id,
|
|
92
|
+
project: { id: 123, path_with_namespace: "group/project" },
|
|
93
|
+
author: { id: 1, username: "root" },
|
|
94
|
+
action_name: "marked",
|
|
95
|
+
target_type: "MergeRequest",
|
|
96
|
+
target: { id: 34, iid: 7, project_id: 123, title: "Review this MR" },
|
|
97
|
+
target_url: "https://gitlab.example.com/group/project/-/merge_requests/7",
|
|
98
|
+
body: "Review this MR",
|
|
99
|
+
state,
|
|
100
|
+
created_at: "2026-01-01T00:00:00.000Z",
|
|
101
|
+
updated_at: "2026-01-01T00:00:00.000Z",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
describe("GitLab todos tools", () => {
|
|
105
|
+
let mockGitLab;
|
|
106
|
+
let mockGitLabUrl;
|
|
107
|
+
before(async () => {
|
|
108
|
+
const mockPort = await findMockServerPort(9250);
|
|
109
|
+
mockGitLab = new MockGitLabServer({
|
|
110
|
+
port: mockPort,
|
|
111
|
+
validTokens: [MOCK_TOKEN],
|
|
112
|
+
});
|
|
113
|
+
mockGitLab.addMockHandler("get", "/todos", (req, res) => {
|
|
114
|
+
assert.strictEqual(req.query.state, "pending");
|
|
115
|
+
assert.strictEqual(req.query.action, "assigned");
|
|
116
|
+
assert.strictEqual(req.query.project_id, "123");
|
|
117
|
+
assert.strictEqual(req.query.page, "2");
|
|
118
|
+
assert.strictEqual(req.query.per_page, "5");
|
|
119
|
+
res.json([todoFixture(102)]);
|
|
120
|
+
});
|
|
121
|
+
mockGitLab.addMockHandler("post", "/todos/102/mark_as_done", (req, res) => {
|
|
122
|
+
res.json(todoFixture(102, "done"));
|
|
123
|
+
});
|
|
124
|
+
mockGitLab.addMockHandler("post", "/todos/mark_as_done", (req, res) => {
|
|
125
|
+
res.status(204).send();
|
|
126
|
+
});
|
|
127
|
+
await mockGitLab.start();
|
|
128
|
+
mockGitLabUrl = mockGitLab.getUrl();
|
|
129
|
+
});
|
|
130
|
+
after(async () => {
|
|
131
|
+
await mockGitLab.stop();
|
|
132
|
+
});
|
|
133
|
+
test("list_todos sends filters and returns todos", async () => {
|
|
134
|
+
const result = await callTool("list_todos", {
|
|
135
|
+
state: "pending",
|
|
136
|
+
action: "assigned",
|
|
137
|
+
project_id: 123,
|
|
138
|
+
page: 2,
|
|
139
|
+
per_page: 5,
|
|
140
|
+
}, {
|
|
141
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
142
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
143
|
+
});
|
|
144
|
+
assert.ok(Array.isArray(result));
|
|
145
|
+
assert.strictEqual(result[0].id, 102);
|
|
146
|
+
assert.strictEqual(result[0].state, "pending");
|
|
147
|
+
});
|
|
148
|
+
test("mark_todo_done marks one todo done", async () => {
|
|
149
|
+
const result = await callTool("mark_todo_done", { id: 102 }, {
|
|
150
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
151
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
152
|
+
});
|
|
153
|
+
assert.strictEqual(result.id, 102);
|
|
154
|
+
assert.strictEqual(result.state, "done");
|
|
155
|
+
});
|
|
156
|
+
test("mark_all_todos_done reports success", async () => {
|
|
157
|
+
const result = await callTool("mark_all_todos_done", {}, {
|
|
158
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
159
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
160
|
+
});
|
|
161
|
+
assert.deepStrictEqual(result, {
|
|
162
|
+
status: "success",
|
|
163
|
+
message: "All pending to-do items marked as done",
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
test("mark_all_todos_done accepts omitted arguments", async () => {
|
|
167
|
+
const result = await callTool("mark_all_todos_done", undefined, {
|
|
168
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
169
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
170
|
+
});
|
|
171
|
+
assert.deepStrictEqual(result, {
|
|
172
|
+
status: "success",
|
|
173
|
+
message: "All pending to-do items marked as done",
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
test("todo tools are visible in the default issues toolset", async () => {
|
|
177
|
+
const tools = await listToolNames({
|
|
178
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
179
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
180
|
+
});
|
|
181
|
+
assert.ok(tools.includes("list_todos"));
|
|
182
|
+
assert.ok(tools.includes("mark_todo_done"));
|
|
183
|
+
assert.ok(tools.includes("mark_all_todos_done"));
|
|
184
|
+
});
|
|
185
|
+
test("read-only mode keeps list_todos and hides todo mutations", async () => {
|
|
186
|
+
const tools = await listToolNames({
|
|
187
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
188
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
189
|
+
GITLAB_READ_ONLY_MODE: "true",
|
|
190
|
+
});
|
|
191
|
+
assert.ok(tools.includes("list_todos"));
|
|
192
|
+
assert.ok(!tools.includes("mark_todo_done"));
|
|
193
|
+
assert.ok(!tools.includes("mark_all_todos_done"));
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -17,11 +17,12 @@ const MCP_PORT_BASE = 3200;
|
|
|
17
17
|
// Known tool counts per toolset (from TOOLSET_DEFINITIONS)
|
|
18
18
|
const TOOLSET_TOOL_COUNTS = {
|
|
19
19
|
merge_requests: 40,
|
|
20
|
-
issues:
|
|
20
|
+
issues: 23,
|
|
21
21
|
repositories: 7,
|
|
22
22
|
branches: 4,
|
|
23
23
|
projects: 8,
|
|
24
24
|
labels: 5,
|
|
25
|
+
ci: 2,
|
|
25
26
|
pipelines: 19,
|
|
26
27
|
milestones: 9,
|
|
27
28
|
wiki: 10,
|
|
@@ -32,6 +33,7 @@ const TOOLSET_TOOL_COUNTS = {
|
|
|
32
33
|
workitems: 18,
|
|
33
34
|
webhooks: 3,
|
|
34
35
|
};
|
|
36
|
+
const LEGACY_PIPELINE_TOOL_COUNT = TOOLSET_TOOL_COUNTS.pipelines + TOOLSET_TOOL_COUNTS.ci;
|
|
35
37
|
const DEFAULT_TOOLSETS = [
|
|
36
38
|
"merge_requests",
|
|
37
39
|
"issues",
|
|
@@ -39,6 +41,7 @@ const DEFAULT_TOOLSETS = [
|
|
|
39
41
|
"branches",
|
|
40
42
|
"projects",
|
|
41
43
|
"labels",
|
|
44
|
+
"ci",
|
|
42
45
|
"users",
|
|
43
46
|
];
|
|
44
47
|
const NON_DEFAULT_TOOLSETS = [
|
|
@@ -58,11 +61,12 @@ const ALL_TOOLSET_TOOL_COUNT = Object.values(TOOLSET_TOOL_COUNTS).reduce((sum, c
|
|
|
58
61
|
// Representative tools per toolset for spot-checking
|
|
59
62
|
const TOOLSET_SAMPLE_TOOLS = {
|
|
60
63
|
merge_requests: ["merge_merge_request", "create_merge_request_thread", "list_draft_notes"],
|
|
61
|
-
issues: ["create_issue", "list_issues", "create_note"],
|
|
64
|
+
issues: ["create_issue", "list_issues", "create_note", "list_todos"],
|
|
62
65
|
repositories: ["search_repositories", "get_file_contents", "push_files"],
|
|
63
66
|
branches: ["create_branch", "list_commits"],
|
|
64
67
|
projects: ["get_project", "list_namespaces", "list_group_iterations"],
|
|
65
68
|
labels: ["list_labels", "create_label"],
|
|
69
|
+
ci: ["validate_ci_lint", "validate_project_ci_lint"],
|
|
66
70
|
pipelines: ["list_pipelines", "create_pipeline", "cancel_pipeline_job", "list_deployments", "list_job_artifacts"],
|
|
67
71
|
milestones: ["list_milestones", "create_milestone", "get_milestone_burndown_events"],
|
|
68
72
|
wiki: ["list_wiki_pages", "create_wiki_page", "list_group_wiki_pages", "create_group_wiki_page"],
|
|
@@ -260,7 +264,7 @@ describe("Toolset Filtering", { concurrency: 1 }, () => {
|
|
|
260
264
|
});
|
|
261
265
|
after(() => cleanupServers([server]));
|
|
262
266
|
test("returns issue tools + all pipeline tools + discover_tools", () => {
|
|
263
|
-
assert.strictEqual(tools.length, TOOLSET_TOOL_COUNTS.issues +
|
|
267
|
+
assert.strictEqual(tools.length, TOOLSET_TOOL_COUNTS.issues + LEGACY_PIPELINE_TOOL_COUNT + DISCOVER_TOOLS_COUNT);
|
|
264
268
|
});
|
|
265
269
|
test("includes all pipeline tools via legacy flag", () => {
|
|
266
270
|
assertContainsAll(tools, TOOLSET_SAMPLE_TOOLS.pipelines, "pipelines");
|
|
@@ -297,6 +301,7 @@ describe("Toolset Filtering", { concurrency: 1 }, () => {
|
|
|
297
301
|
"list_issue_links",
|
|
298
302
|
"list_issue_discussions",
|
|
299
303
|
"get_issue_link",
|
|
304
|
+
"list_todos",
|
|
300
305
|
"list_issue_emoji_reactions",
|
|
301
306
|
"list_issue_note_emoji_reactions",
|
|
302
307
|
];
|
|
@@ -304,6 +309,8 @@ describe("Toolset Filtering", { concurrency: 1 }, () => {
|
|
|
304
309
|
"create_issue",
|
|
305
310
|
"update_issue",
|
|
306
311
|
"delete_issue",
|
|
312
|
+
"mark_todo_done",
|
|
313
|
+
"mark_all_todos_done",
|
|
307
314
|
"create_issue_note",
|
|
308
315
|
"update_issue_note",
|
|
309
316
|
"create_issue_link",
|
|
@@ -396,7 +403,7 @@ describe("Toolset Filtering", { concurrency: 1 }, () => {
|
|
|
396
403
|
});
|
|
397
404
|
after(() => cleanupServers([server]));
|
|
398
405
|
test("returns exactly pipeline tool count + discover_tools (no duplicates)", () => {
|
|
399
|
-
assert.strictEqual(tools.length,
|
|
406
|
+
assert.strictEqual(tools.length, LEGACY_PIPELINE_TOOL_COUNT + DISCOVER_TOOLS_COUNT);
|
|
400
407
|
});
|
|
401
408
|
});
|
|
402
409
|
// ---- 12. GITLAB_TOOLS with tool already in enabled toolset (no dupes) ----
|
package/build/tools/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
2
|
import { toJSONSchema } from "../utils/schema.js";
|
|
3
3
|
import { USE_GITLAB_WIKI, USE_MILESTONE, USE_PIPELINE, } from "../config.js";
|
|
4
|
-
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListCommitsSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
|
|
4
|
+
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListCommitsSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
|
|
5
5
|
// Define all available tools
|
|
6
6
|
export const allTools = [
|
|
7
7
|
{
|
|
@@ -316,6 +316,21 @@ export const allTools = [
|
|
|
316
316
|
description: "Delete an issue",
|
|
317
317
|
inputSchema: toJSONSchema(DeleteIssueSchema),
|
|
318
318
|
},
|
|
319
|
+
{
|
|
320
|
+
name: "list_todos",
|
|
321
|
+
description: "List GitLab to-do items for the current user",
|
|
322
|
+
inputSchema: toJSONSchema(ListTodosSchema),
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "mark_todo_done",
|
|
326
|
+
description: "Mark a GitLab to-do item as done",
|
|
327
|
+
inputSchema: toJSONSchema(MarkTodoDoneSchema),
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "mark_all_todos_done",
|
|
331
|
+
description: "Mark all pending GitLab to-do items as done for the current user",
|
|
332
|
+
inputSchema: toJSONSchema(MarkAllTodosDoneSchema),
|
|
333
|
+
},
|
|
319
334
|
{
|
|
320
335
|
name: "list_issue_links",
|
|
321
336
|
description: "List all issue links for a specific issue",
|
|
@@ -506,6 +521,16 @@ export const allTools = [
|
|
|
506
521
|
description: "Get the output/trace of a pipeline job with optional pagination",
|
|
507
522
|
inputSchema: toJSONSchema(GetPipelineJobOutputSchema),
|
|
508
523
|
},
|
|
524
|
+
{
|
|
525
|
+
name: "validate_ci_lint",
|
|
526
|
+
description: "Validate provided GitLab CI/CD YAML content for a project",
|
|
527
|
+
inputSchema: toJSONSchema(ValidateCiLintSchema),
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "validate_project_ci_lint",
|
|
531
|
+
description: "Validate an existing .gitlab-ci.yml configuration for a project",
|
|
532
|
+
inputSchema: toJSONSchema(ValidateProjectCiLintSchema),
|
|
533
|
+
},
|
|
509
534
|
{
|
|
510
535
|
name: "create_pipeline",
|
|
511
536
|
description: "Create a new pipeline for a branch or tag",
|
|
@@ -867,6 +892,7 @@ export const readOnlyTools = new Set([
|
|
|
867
892
|
"list_draft_notes",
|
|
868
893
|
"mr_discussions",
|
|
869
894
|
"list_issues",
|
|
895
|
+
"list_todos",
|
|
870
896
|
"my_issues",
|
|
871
897
|
"list_merge_requests",
|
|
872
898
|
"get_issue",
|
|
@@ -889,6 +915,8 @@ export const readOnlyTools = new Set([
|
|
|
889
915
|
"list_pipeline_trigger_jobs",
|
|
890
916
|
"get_pipeline_job",
|
|
891
917
|
"get_pipeline_job_output",
|
|
918
|
+
"validate_ci_lint",
|
|
919
|
+
"validate_project_ci_lint",
|
|
892
920
|
"list_job_artifacts",
|
|
893
921
|
"download_job_artifacts",
|
|
894
922
|
"get_job_artifact_file",
|
|
@@ -998,6 +1026,8 @@ export const pipelineToolNames = new Set([
|
|
|
998
1026
|
"list_pipeline_trigger_jobs",
|
|
999
1027
|
"get_pipeline_job",
|
|
1000
1028
|
"get_pipeline_job_output",
|
|
1029
|
+
"validate_ci_lint",
|
|
1030
|
+
"validate_project_ci_lint",
|
|
1001
1031
|
"create_pipeline",
|
|
1002
1032
|
"retry_pipeline",
|
|
1003
1033
|
"cancel_pipeline",
|
|
@@ -1065,6 +1095,9 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1065
1095
|
"get_issue",
|
|
1066
1096
|
"update_issue",
|
|
1067
1097
|
"delete_issue",
|
|
1098
|
+
"list_todos",
|
|
1099
|
+
"mark_todo_done",
|
|
1100
|
+
"mark_all_todos_done",
|
|
1068
1101
|
"create_issue_note",
|
|
1069
1102
|
"update_issue_note",
|
|
1070
1103
|
"list_issue_links",
|
|
@@ -1129,6 +1162,11 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1129
1162
|
"delete_label",
|
|
1130
1163
|
]),
|
|
1131
1164
|
},
|
|
1165
|
+
{
|
|
1166
|
+
id: "ci",
|
|
1167
|
+
isDefault: true,
|
|
1168
|
+
tools: new Set(["validate_ci_lint", "validate_project_ci_lint"]),
|
|
1169
|
+
},
|
|
1132
1170
|
{
|
|
1133
1171
|
id: "pipelines",
|
|
1134
1172
|
isDefault: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.8",
|
|
4
4
|
"mcpName": "io.github.zereight/gitlab-mcp",
|
|
5
5
|
"description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"changelog": "auto-changelog -p",
|
|
52
52
|
"test": "npm run test:all",
|
|
53
53
|
"test:all": "npm run build && npm run test:mock && npm run test:live",
|
|
54
|
-
"test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
54
|
+
"test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
55
55
|
"test:stateless": "npm run build && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
56
56
|
"test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts",
|
|
57
57
|
"test:live": "node test/validate-api.js",
|