Compare commits

..

70 Commits

Author SHA1 Message Date
f8b1444afd [main] fix: description null error handling
📝 Details:
- GitLab issues/milestones의 null description 처리
- schemas.ts에서 description을 nullable로 변경
2025-05-27 12:25:31 +09:00
06f9437329 Merge pull request #53 from zereight/fix/51-description-nullable
FIX: description null error
2025-05-27 12:20:39 +09:00
dc99f864ca FIX: description null error 2025-05-27 02:00:36 +09:00
8ba33986f3 [main] docs: update changelog for v1.0.45 pipeline tools
🚀 Breaking Changes:
- None

📝 Details:
- Added 5 new pipeline management tools
- Pipeline status monitoring and analysis support
2025-05-24 21:02:58 +09:00
64a936446e [release] feat: update version to 1.0.45
🚀 Breaking Changes:
- Version updated from 1.0.44 to 1.0.45
2025-05-24 20:57:15 +09:00
8ab0ac7145 Merge pull request #52 from vicendominguez/main
feat(release): 1.0.44  adds pipeline jobs tool
2025-05-24 20:55:55 +09:00
ea06c21f29 feat(release): 1.0.44 adds pipeline jobs tool 2025-05-24 13:31:47 +02:00
140620397b chore(release): 1.0.43 - get_repository_tree is added read_only_mode 2025-05-23 00:41:56 +09:00
3d7aa8035d docs: translate issue notes changelog from Korean to English 2025-05-22 21:28:34 +09:00
25be1947b9 chore(release): 1.0.42 - issue note 기능 추가 (#47) 2025-05-22 21:24:29 +09:00
864ee77ae6 Merge pull request #47 from svengt/feat/add-issue-notes-support
feat: add support for creating and updating issue notes
2025-05-22 21:22:04 +09:00
dc6cc59434 feat: add support for creating and updating issue notes
- Added create_issue_note to add a new note to an existing issue thread
- Added update_issue_note to modify an existing issue thread note
- Similar to existing merge request note functions but for issues
2025-05-22 13:25:31 +02:00
5924fd3ed4 Merge pull request #45 from vlucaswang/fix/docs
fix: fix README
2025-05-21 14:28:08 +09:00
f4b265bf2e fix: fix README 2025-05-21 14:35:37 +09:30
b326f4c3c3 docs: update release notes for v1.0.40 (2025-05-21) 2025-05-21 03:40:02 +09:00
1350a024b5 Merge pull request #44 from huerlisi/feat/add-issue-notes-support
feat: add issue discussions support
2025-05-21 03:36:33 +09:00
4c57c37888 feat: add issue discussions support
Added `list_issue_discussions` tool to support GitLab issue discussions
similar to merge request discussions.
2025-05-20 15:45:23 +02:00
e4a28a9a47 버전 1.0.39로 업데이트 2025-05-20 18:34:05 +09:00
9f1e7b5bca Merge pull request #42 from vlucaswang/feat/add-docker
feat: add docker image and push to dockerhub
2025-05-20 18:32:19 +09:00
f37e210ee8 Merge pull request #41 from kamibayashi/fixed_resolve_outdated_diff_discussions_nullable
fixed resolve_outdated_diff_discussions nullable
2025-05-20 18:28:08 +09:00
6f789692be feat: add docker image and push to dockerhub 2025-05-20 16:04:37 +09:30
1bb70dccb9 fixed resolve_outdated_diff_discussions nullable 2025-05-19 17:18:01 +09:00
676bbcd4dd docs: add release-notes.md 2025-05-17 15:41:14 +09:00
0bb59a3217 Bump version 2025-05-17 15:38:18 +09:00
b908f03801 Merge pull request #40 from huerlisi/fix/discussion-enum
fix: add `expanded` to `start` and `end` for GitLabDiscussionNoteSchema
2025-05-17 15:36:11 +09:00
5024a2a5af fix: add expanded to start and end for GitLabDiscussionNoteSchema 2025-05-16 21:36:21 +02:00
d2cced1b38 Bump version 2025-05-15 10:53:14 +09:00
2fec95d469 Merge pull request #38 from ncrum/feat/merge-request-note
Adds threaded comment support for merge requests
2025-05-15 10:51:48 +09:00
3565d1b397 Merge pull request #37 from ncrum/fix/resolve-notes
Support resolving merge request discussion notes
2025-05-15 10:50:39 +09:00
353e62a401 refactor: rename add_merge_request_thread_note to create_merge_request_note for consistency 2025-05-13 16:52:17 -06:00
3f2b35535e feat: Implement add_merge_request_thread_note function for adding notes to existing MR threads 2025-05-13 16:20:21 -06:00
026dd58887 feat: Add create_merge_request_thread tool for diff notes
- Implement new tool for creating MR threads with positioning support
- Create schemas to handle diff notes with file and line number positions
- Support optional created_at timestamp parameter
- Update README with the new tool information
2025-05-13 15:45:43 -06:00
bde83c0a91 feat: support resolving merge request notes 2025-05-13 14:54:05 -06:00
08ab1357a0 feat: Decode project_id for GitLab API calls
- Decode project_id using decodeURIComponent() in relevant helper functions.
- This resolves API call issues related to project ID encoding differences between models.
- Updated CHANGELOG for 1.0.36 and added thanks to Aubermean.
2025-05-13 02:20:59 +09:00
651072dfd7 [main] chore: update version to 1.0.35 🚀
📝 Details:
- Incremented package version from 1.0.34 to 1.0.35
2025-05-13 01:53:10 +09:00
bf250b0d88 [main] refactor: update label_id schema to use string type
🚀 Breaking Changes:
- label_id now requires a string type instead of a union of number and string.

📝 Details:
- Improved consistency in schema definitions for label handling.
2025-05-13 01:53:00 +09:00
3a25e7c5e8 [main] docs: update README with detailed descriptions for merge request functions
📝 Details:
- Added clarifications for `get_merge_request`, `get_merge_request_diffs`, and `update_merge_request` functions.
- Introduced `get_repository_tree` function to the documentation.
2025-05-07 21:46:35 +09:00
23a9bbc728 [main] chore: update version to 1.0.34
🚀 Breaking Changes:
- Updated package version from 1.0.33 to 1.0.34
2025-05-07 21:45:59 +09:00
5398526f94 Merge pull request #35 from BobMerkus/feat/list-repository
feat: Gitlab list repository tree tool
2025-05-07 21:45:35 +09:00
Bob
bccd5f29c3 feat: Gitlab list repository tree tool 2025-05-07 14:11:37 +02:00
8071fef6c4 [main] chore: package.json 버전 1.0.33으로 업데이트
🚀 Breaking Changes:
- 없음
2025-05-01 08:33:56 +09:00
f0eac83788 Merge pull request #34 from nkaewam/main
feat: support search by branch for get_merge_request
2025-05-01 08:33:29 +09:00
7b8cbc0806 fix: rename to source branch 2025-04-30 15:44:21 +07:00
20f62756c1 feat: support search by branch for get_merge_request 2025-04-30 15:41:51 +07:00
95d8118ea5 [main] feat: README.md 도구 섹션 자동 업데이트 기능 추가
🚀 Breaking Changes:
- README.md의 도구 섹션이 자동으로 업데이트됩니다.

📝 Details:
- index.ts에서 도구 정보를 추출하여 README.md의 특정 섹션을 업데이트하는 스크립트 추가
- package.json에 도구 생성 스크립트 명령어 추가
2025-04-25 00:11:34 +09:00
340a5ffdc8 [main] feat: GitLab 위키 기능 활성화 설정 추가
🚀 Breaking Changes:
- GITLAB_READ_ONLY_MODE을 'false'로 변경
- USE_GITLAB_WIKI를 추가하여 위키 관련 도구 활성화

📝 Details:
- README.md에 새로운 환경 변수 설명 추가
2025-04-24 23:57:58 +09:00
152dc1c984 [main] chore: bump version to 1.0.32 🔧
🚀 Breaking Changes:
- 버전이 1.0.31에서 1.0.32로 업데이트되었습니다.
2025-04-24 23:54:12 +09:00
42419bae24 [main] feat: GitLab 위키 API 기능 추가
🚀 Breaking Changes:
- 새로운 위키 페이지 관련 API 추가
- USE_GITLAB_WIKI 환경 변수에 따라 위키 도구 활성화/비활성화 가능

📝 Details:
- 위키 페이지 목록 조회, 특정 페이지 조회, 페이지 생성, 업데이트 및 삭제 기능 구현
- 관련 스키마 추가 및 패키지 종속성 업데이트
2025-04-24 23:53:24 +09:00
06e9f0225d [main] feat: merge request 토론 항목 이름 변경
🚀 Breaking Changes:
- "list_merge_request_discussions"에서 "mr_discussions"로 이름 변경
2025-04-24 22:34:06 +09:00
2ca34a6b34 [main] chore: bump version to 1.0.31
🚀 Breaking Changes:
- 패키지 버전이 1.0.30에서 1.0.31로 변경되었습니다.
2025-04-24 21:07:13 +09:00
f562cd65ef Merge pull request #33 from josephfinlayson/main
feat: Implement proxy configuration for HTTP/HTTPS/SOCKS
2025-04-24 21:06:43 +09:00
7c2578fd4b feat: Implement proxy configuration for HTTP/HTTPS/SOCKS 2025-04-24 09:29:15 +02:00
ac4056370b chore: add MIT License file 2025-04-15 01:35:28 +09:00
dd8311717c chore: bump version to 1.0.30 in package.json 2025-04-07 03:10:49 +09:00
11685d7a90 fix: improve error handling for GitLab API rate limit exceeded 2025-04-07 03:10:33 +09:00
a7a8149008 chore: bump version to 1.0.29 in package.json and package-lock.json 2025-04-05 22:21:25 +09:00
6858e1cf4a chore: bump version to 1.0.27 and update deploy script for public access 2025-04-05 20:59:15 +09:00
c69f8416ac chore: bump version to 1.0.25 2025-04-02 11:22:49 +09:00
440a47404f Merge pull request #29 from gerbal/feat/read-only-mode
feat: Add read-only mode support
2025-04-02 11:22:04 +09:00
1e143291c0 chore: bump version 2025-04-01 16:59:51 -04:00
e3300f099b docs: update README to include Cursor in usage section and clarify GITLAB_READ_ONLY_MODE environment variable details. Enhance security documentation for read-only mode. 2025-04-01 16:58:29 -04:00
5e93e273f8 chore: revert logging change 2025-04-01 16:54:29 -04:00
7be17b7afc feat: add read-only mode support via GITLAB_READ_ONLY_MODE environment variable
Adds a configurable read-only mode that can be enabled by setting GITLAB_READ_ONLY_MODE=true.
When enabled, only read operations are exposed to clients, improving security for sensitive GitLab
instances. The server logs whether it's running in read-only mode and displays the count of
available tools. Also fixes stdio handling to properly support MCP protocol communication.
2025-04-01 16:48:08 -04:00
61ee1244f4 build: test-note.js 파일 삭제 2025-04-02 00:12:24 +09:00
2235953426 Dockerfile 삭제 및 .gitignore에 build 디렉토리 추가 2025-04-02 00:12:14 +09:00
cd686966ef Remove debug logging for GitLab API URL construction 2025-04-01 15:17:42 +09:00
f6076d3702 Bump version to 1.0.24 2025-04-01 15:16:09 +09:00
74bb384a37 Remove debug logging for API URL construction 2025-04-01 15:16:06 +09:00
bb8d553567 Bump version to 1.0.23 2025-03-31 19:12:04 +09:00
3c2f52c9f8 Merge pull request #26 from zereight/feature/fix-note
Add schemas for GitLab discussion notes and merge request discussions
2025-03-31 19:11:35 +09:00
14 changed files with 2480 additions and 3092 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
node_modules node_modules
.DS_Store .DS_Store
build

46
CHANGELOG.md Normal file
View File

@ -0,0 +1,46 @@
## [1.0.46] - 2025-05-27
### Fixed
- Fixed issue where GitLab issues and milestones with null descriptions would cause JSON-RPC errors
- Changed `description` field to be nullable with default empty string in schemas
- This allows proper handling of GitLab issues/milestones without descriptions
- See: [PR #53](https://github.com/zereight/gitlab-mcp/pull/53), [Issue #51](https://github.com/zereight/gitlab-mcp/issues/51)
---
## [1.0.45] - 2025-05-24
### Added
- 🔄 **Pipeline Management Tools**: Added GitLab pipeline status monitoring and management functionality
- `list_pipelines`: List project pipelines with various filtering options
- `get_pipeline`: Get detailed information about a specific pipeline
- `list_pipeline_jobs`: List all jobs in a specific pipeline
- `get_pipeline_job`: Get detailed information about a specific pipeline job
- `get_pipeline_job_output`: Get execution logs/output from pipeline jobs
- 📊 Pipeline status summary and analysis support
- Example: "How many of the last N pipelines are successful?"
- Example: "Can you make a summary of the output in the last pipeline?"
- See: [PR #52](https://github.com/zereight/gitlab-mcp/pull/52)
---
## [1.0.42] - 2025-05-22
### Added
- Added support for creating and updating issue notes (comments) in GitLab.
- You can now add or edit comments on issues.
- See: [PR #47](https://github.com/zereight/gitlab-mcp/pull/47)
---
## [1.0.38] - 2025-05-17
### Fixed
- Added `expanded` property to `start` and `end` in `GitLabDiscussionNoteSchema`
Now you can expand or collapse more information at the start and end of discussion notes.
Example: In code review, you can choose to show or hide specific parts of the discussion.
(See: [PR #40](https://github.com/zereight/gitlab-mcp/pull/40))

View File

@ -1,37 +1,24 @@
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile FROM node:22.15-alpine AS builder
# Use an official Node.js image as the base image
FROM node:16-alpine AS builder COPY . /app
COPY tsconfig.json /tsconfig.json
# Set the working directory
WORKDIR /app WORKDIR /app
# Copy package.json and package-lock.json files RUN --mount=type=cache,target=/root/.npm npm install
COPY package.json package-lock.json ./
# Install dependencies RUN --mount=type=cache,target=/root/.npm-production npm ci --ignore-scripts --omit-dev
RUN npm install --ignore-scripts
# Copy the rest of the application code FROM node:22.12-alpine AS release
COPY . .
# Build the application
RUN npm run build
# Use a smaller image for the runtime
FROM node:16-alpine AS runner
# Set the working directory
WORKDIR /app WORKDIR /app
# Copy the build output and package.json COPY --from=builder /app/build /app/build
COPY --from=builder /app/build ./build COPY --from=builder /app/package.json /app/package.json
COPY --from=builder /app/package.json ./ COPY --from=builder /app/package-lock.json /app/package-lock.json
# Set environment variables ENV NODE_ENV=production
ENV GITLAB_API_URL=https://gitlab.com/api/v4
# Define the command to run the application RUN npm ci --ignore-scripts --omit-dev
ENTRYPOINT ["node", "build/index.js"]
# This image requires the following environment variable at runtime: ENTRYPOINT ["node", "build/index.js"]
# - GITLAB_PERSONAL_ACCESS_TOKEN: Your GitLab personal access token

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Roo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

333
README.md
View File

@ -10,10 +10,12 @@ GitLab MCP(Model Context Protocol) Server. **Includes bug fixes and improvements
## Usage ## Usage
### Using with Claude App, Cline, Roo Code ### Using with Claude App, Cline, Roo Code, Cursor
When using with the Claude App, you need to set up your API key and URLs directly. When using with the Claude App, you need to set up your API key and URLs directly.
#### npx
```json ```json
{ {
"mcpServers": { "mcpServers": {
@ -22,258 +24,105 @@ When using with the Claude App, you need to set up your API key and URLs directl
"args": ["-y", "@zereight/mcp-gitlab"], "args": ["-y", "@zereight/mcp-gitlab"],
"env": { "env": {
"GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token", "GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
"GITLAB_API_URL": "your_gitlab_api_url" "GITLAB_API_URL": "your_gitlab_api_url",
"GITLAB_READ_ONLY_MODE": "false",
"USE_GITLAB_WIKI": "true"
} }
} }
} }
} }
``` ```
### Using with Cursor #### Docker
When using with Cursor, you can set up environment variables and run the server as follows: ```json
{
"mcpServers": {
"GitLab communication server": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITLAB_PERSONAL_ACCESS_TOKEN",
"-e",
"GITLAB_API_URL",
"-e",
"GITLAB_READ_ONLY_MODE",
"-e",
"USE_GITLAB_WIKI",
"nkwd/gitlab-mcp"
],
"env": {
"GITLAB_PERSONAL_ACCESS_TOKEN": "your_gitlab_token",
"GITLAB_API_URL": "https://gitlab.com/api/v4", // Optional, for self-hosted GitLab
"GITLAB_READ_ONLY_MODE": "false",
"USE_GITLAB_WIKI": "true"
}
}
}
}
```
``` ### Environment Variables
env GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token GITLAB_API_URL=your_gitlab_api_url npx @zereight/mcp-gitlab
```
- `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token. - `GITLAB_PERSONAL_ACCESS_TOKEN`: Your GitLab personal access token.
- `GITLAB_API_URL`: Your GitLab API URL. (Default: `https://gitlab.com/api/v4`) - `GITLAB_API_URL`: Your GitLab API URL. (Default: `https://gitlab.com/api/v4`)
- `GITLAB_READ_ONLY_MODE`: When set to 'true', restricts the server to only expose read-only operations. Useful for enhanced security or when write access is not needed. Also useful for using with Cursor and it's 40 tool limit.
- `USE_GITLAB_WIKI`: When set to 'true', enables the wiki-related tools (list_wiki_pages, get_wiki_page, create_wiki_page, update_wiki_page, delete_wiki_page). By default, wiki features are disabled.
## Tools 🛠️ ## Tools 🛠️
1. `create_or_update_file` +<!-- TOOLS-START -->
1. `create_or_update_file` - Create or update a single file in a GitLab project
- Create or update a single file in a GitLab project. 📝 2. `search_repositories` - Search for GitLab projects
- Inputs: 3. `create_repository` - Create a new GitLab project
- `project_id` (string): Project ID or namespace/project_path 4. `get_file_contents` - Get the contents of a file or directory from a GitLab project
- `file_path` (string): Path to create/update the file 5. `push_files` - Push multiple files to a GitLab project in a single commit
- `content` (string): File content 6. `create_issue` - Create a new issue in a GitLab project
- `commit_message` (string): Commit message 7. `create_merge_request` - Create a new merge request in a GitLab project
- `branch` (string): Branch to create/update the file in 8. `fork_repository` - Fork a GitLab project to your account or specified namespace
- `previous_path` (optional string): Previous file path when renaming a file 9. `create_branch` - Create a new branch in a GitLab project
- Returns: File content and commit details 10. `get_merge_request` - Get details of a merge request (Either mergeRequestIid or branchName must be provided)
11. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)
2. `push_files` 12. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided)
13. `create_note` - Create a new note (comment) to an issue or merge request
- Push multiple files in a single commit. 📤 14. `create_merge_request_thread` - Create a new thread on a merge request
- Inputs: 15. `mr_discussions` - List discussion items for a merge request
- `project_id` (string): Project ID or namespace/project_path 16. `update_merge_request_note` - Modify an existing merge request thread note
- `branch` (string): Branch to push to 17. `create_merge_request_note` - Add a new note to an existing merge request thread
- `files` (array): Array of files to push, each with `file_path` and `content` properties 18. `update_issue_note` - Modify an existing issue thread note
- `commit_message` (string): Commit message 19. `create_issue_note` - Add a new note to an existing issue thread
- Returns: Updated branch reference 20. `list_issues` - List issues in a GitLab project with filtering options
21. `get_issue` - Get details of a specific issue in a GitLab project
3. `search_repositories` 22. `update_issue` - Update an issue in a GitLab project
23. `delete_issue` - Delete an issue from a GitLab project
- Search for GitLab projects. 🔍 24. `list_issue_links` - List all issue links for a specific issue
- Inputs: 25. `list_issue_discussions` - List discussions for an issue in a GitLab project
- `search` (string): Search query 26. `get_issue_link` - Get a specific issue link
- `page` (optional number): Page number (default: 1) 27. `create_issue_link` - Create an issue link between two issues
- `per_page` (optional number): Results per page (default: 20, max: 100) 28. `delete_issue_link` - Delete an issue link
- Returns: Project search results 29. `list_namespaces` - List all namespaces available to the current user
30. `get_namespace` - Get details of a namespace by ID or path
4. `create_repository` 31. `verify_namespace` - Verify if a namespace path exists
32. `get_project` - Get details of a specific project
- Create a new GitLab project. 33. `list_projects` - List projects accessible by the current user
- Inputs: 34. `list_labels` - List labels for a project
- `name` (string): Project name 35. `get_label` - Get a single label from a project
- `description` (optional string): Project description 36. `create_label` - Create a new label in a project
- `visibility` (optional string): Project visibility level (public, private, internal) 37. `update_label` - Update an existing label in a project
- `initialize_with_readme` (optional boolean): Initialize with README 38. `delete_label` - Delete a label from a project
- Returns: Details of the created project 39. `list_group_projects` - List projects in a GitLab group with filtering options
40. `list_wiki_pages` - List wiki pages in a GitLab project
5. `get_file_contents` 41. `get_wiki_page` - Get details of a specific wiki page
42. `create_wiki_page` - Create a new wiki page in a GitLab project
- Get the contents of a file or directory. 📂 43. `update_wiki_page` - Update an existing wiki page in a GitLab project
- Inputs: 44. `delete_wiki_page` - Delete a wiki page from a GitLab project
- `project_id` (string): Project ID or namespace/project_path 45. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
- `file_path` (string): Path to the file/directory 46. `list_pipelines` - List pipelines in a GitLab project with filtering options
- `ref` (optional string): Branch, tag, or commit SHA (default: default branch) 47. `get_pipeline` - Get details of a specific pipeline in a GitLab project
- Returns: File/directory content 48. `list_pipeline_jobs` - List all jobs in a specific pipeline
49. `get_pipeline_job` - Get details of a GitLab pipeline job number
6. `create_issue` 50. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job number
<!-- TOOLS-END -->
- Create a new issue. 🐛
- Inputs:
- `project_id` (string): Project ID or namespace/project_path
- `title` (string): Issue title
- `description` (string): Issue description
- `assignee_ids` (optional number[]): Array of assignee IDs
- `milestone_id` (optional number): Milestone ID
- `labels` (optional string[]): Array of labels
- Returns: Details of the created issue
7. `create_merge_request`
- Create a new merge request. 🚀
- Inputs:
- `project_id` (string): Project ID or namespace/project_path
- `title` (string): Merge request title
- `description` (string): Merge request description
- `source_branch` (string): Branch with changes
- `target_branch` (string): Branch to merge into
- `allow_collaboration` (optional boolean): Allow collaborators to push commits to the source branch
- `draft` (optional boolean): Create as a draft merge request
- Returns: Details of the created merge request
8. `fork_repository`
- Fork a project. 🍴
- Inputs:
- `project_id` (string): Project ID or namespace/project_path to fork
- `namespace` (optional string): Namespace to fork into (default: user namespace)
- Returns: Details of the forked project
9. `create_branch`
- Create a new branch. 🌿
- Inputs:
- `project_id` (string): Project ID or namespace/project_path
- `name` (string): New branch name
- `ref` (optional string): Ref to create the branch from (branch, tag, commit SHA, default: default branch)
- Returns: Created branch reference
10. `get_merge_request`
- Get details of a merge request.
- Inputs:
- `project_id` (string): Project ID or namespace/project_path
- `merge_request_iid` (number): Merge request IID
- Returns: Merge request details
11. `get_merge_request_diffs`
- Get changes (diffs) of a merge request. diff
- Inputs:
- `project_id` (string): Project ID or namespace/project_path
- `merge_request_iid` (number): Merge request IID
- `view` (optional string): Diff view type ('inline' or 'parallel')
- Returns: Array of merge request diff information
12. `update_merge_request`
- Update a merge request. 🔄
- Inputs:
- `project_id` (string): Project ID or namespace/project_path
- `merge_request_iid` (number): Merge request IID
- `title` (optional string): New title
- `description` (string): New description
- `target_branch` (optional string): New target branch
- `state_event` (optional string): Merge request state change event ('close', 'reopen')
- `remove_source_branch` (optional boolean): Remove source branch after merge
- `allow_collaboration` (optional boolean): Allow collaborators to push commits to the source branch
- Returns: Updated merge request details
13. `create_note`
- Create a new note (comment) to an issue or merge request. 💬
- Inputs:
- `project_id` (string): Project ID or namespace/project_path
- `noteable_type` (string): Type of noteable ("issue" or "merge_request")
- `noteable_iid` (number): IID of the issue or merge request
- `body` (string): Note content
- Returns: Details of the created note
14. `list_projects`
- List accessible projects with rich filtering options 📊
- Inputs:
- Search/filtering:
- `search`
- `owned`
- `membership`
- `archived`
- `visibility`
- Features filtering:
- `with_issues_enabled`
- `with_merge_requests_enabled`
- Sorting:
- `order_by`
- `sort`
- Access control:
- `min_access_level`
- Pagination:
- `page`
- `per_page`
- `simple`
- Returns: Array of projects
15. `list_labels`
- List all labels for a project with filtering options 🏷️
- Inputs:
- `project_id` (string): Project ID or path
- `with_counts` (optional): Include issue and merge request counts
- `include_ancestor_groups` (optional): Include ancestor groups
- `search` (optional): Filter labels by keyword
- Returns: Array of labels
16. `get_label`
- Get a single label from a project
- Inputs:
- `project_id` (string): Project ID or path
- `label_id` (number/string): Label ID or name
- `include_ancestor_groups` (optional): Include ancestor groups
- Returns: label details
17. `create_label`
- Create a new label in an object 🏷️➕
- Inputs:
- `project_id` (string): Project ID or path
- `name` (string): Label name
- `color` (string): Color in hex format (e.g., "#FF0000")
- `description` (optional): Label description
- `priority` (optional): Label priority
- Returns: Created label details
18. `update_label`
- Update an existing label in a project 🏷️✏️
- Inputs:
- `project_id` (string): Project ID or path
- `label_id` (number/string): Label ID or name
- `new_name` (optional): New label name
- `color` (optional): New color in hex format
- `description` (optional): New description
- `priority` (optional): New priority
- Returns: Updated label details
19. `delete_label`
- Delete a label from a project 🏷️❌
- Inputs:
- `project_id` (string): Project ID or path
- `label_id` (number/string): Label ID or name
- Returns: Success message
14. `list_group_projects`
- List all projects in a GitLab group. 📂
- Inputs:
- `group_id` (string): Project ID or namespace/project_path
- Filtering options:
- `include_subgroups` (optional boolean): Include projects from subgroups
- `search` (optional string): Search term to filter projects
- `archived` (optional boolean): Filter for archived projects
- `visibility` (optional string): Filter by project visibility (public/internal/private)
- `with_programming_language` (optional string): Filter by programming language
- `starred` (optional boolean): Filter by starred projects
- Feature filtering:
- `with_issues_enabled` (optional boolean): Filter projects with issues feature enabled
- `with_merge_requests_enabled` (optional boolean): Filter projects with merge requests feature enabled
- `min_access_level` (optional number): Filter by minimum access level
- Pagination:
- `page` (optional number): Page number
- `per_page` (optional number): Results per page
- Sorting:
- `order_by` (optional string): Field to sort by
- `sort` (optional string): Sort direction (asc/desc)
- Additional data:
- `statistics` (optional boolean): Include project statistics
- `with_custom_attributes` (optional boolean): Include custom attributes
- `with_security_reports` (optional boolean): Include security reports
- Returns: List of projects
## Environment Variable Configuration
Before running the server, you need to set the following environment variables:
```
GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token
GITLAB_API_URL=your_gitlab_api_url # Default: https://gitlab.com/api/v4
```
## License
MIT License

File diff suppressed because it is too large Load Diff

View File

@ -1,684 +0,0 @@
import { z } from "zod";
// Base schemas for common types
export const GitLabAuthorSchema = z.object({
name: z.string(),
email: z.string(),
date: z.string(),
});
// Namespace related schemas
// Base schema for project-related operations
const ProjectParamsSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), // Changed from owner/repo to match GitLab API
});
export const GitLabNamespaceSchema = z.object({
id: z.number(),
name: z.string(),
path: z.string(),
kind: z.enum(["user", "group"]),
full_path: z.string(),
parent_id: z.number().nullable(),
avatar_url: z.string().nullable(),
web_url: z.string(),
members_count_with_descendants: z.number().optional(),
billable_members_count: z.number().optional(),
max_seats_used: z.number().optional(),
seats_in_use: z.number().optional(),
plan: z.string().optional(),
end_date: z.string().nullable().optional(),
trial_ends_on: z.string().nullable().optional(),
trial: z.boolean().optional(),
root_repository_size: z.number().optional(),
projects_count: z.number().optional(),
});
export const GitLabNamespaceExistsResponseSchema = z.object({
exists: z.boolean(),
suggests: z.array(z.string()).optional(),
});
// Repository related schemas
export const GitLabOwnerSchema = z.object({
username: z.string(), // Changed from login to match GitLab API
id: z.number(),
avatar_url: z.string(),
web_url: z.string(), // Changed from html_url to match GitLab API
name: z.string(), // Added as GitLab includes full name
state: z.string(), // Added as GitLab includes user state
});
export const GitLabRepositorySchema = z.object({
id: z.number(),
name: z.string(),
path_with_namespace: z.string(),
visibility: z.string().optional(),
owner: GitLabOwnerSchema.optional(),
web_url: z.string().optional(),
description: z.string().nullable(),
fork: z.boolean().optional(),
ssh_url_to_repo: z.string().optional(),
http_url_to_repo: z.string().optional(),
created_at: z.string().optional(),
last_activity_at: z.string().optional(),
default_branch: z.string().optional(),
namespace: z.object({
id: z.number(),
name: z.string(),
path: z.string(),
kind: z.string(),
full_path: z.string(),
avatar_url: z.string().nullable().optional(),
web_url: z.string().optional(),
}).optional(),
readme_url: z.string().optional().nullable(),
topics: z.array(z.string()).optional(),
tag_list: z.array(z.string()).optional(), // deprecated but still present
open_issues_count: z.number().optional(),
archived: z.boolean().optional(),
forks_count: z.number().optional(),
star_count: z.number().optional(),
permissions: z.object({
project_access: z.object({
access_level: z.number(),
notification_level: z.number().optional(),
}).optional().nullable(),
group_access: z.object({
access_level: z.number(),
notification_level: z.number().optional(),
}).optional().nullable(),
}).optional(),
container_registry_enabled: z.boolean().optional(),
container_registry_access_level: z.string().optional(),
issues_enabled: z.boolean().optional(),
merge_requests_enabled: z.boolean().optional(),
wiki_enabled: z.boolean().optional(),
jobs_enabled: z.boolean().optional(),
snippets_enabled: z.boolean().optional(),
can_create_merge_request_in: z.boolean().optional(),
resolve_outdated_diff_discussions: z.boolean().optional(),
shared_runners_enabled: z.boolean().optional(),
shared_with_groups: z.array(z.object({
group_id: z.number(),
group_name: z.string(),
group_full_path: z.string(),
group_access_level: z.number(),
})).optional(),
});
// Project schema (extended from repository schema)
export const GitLabProjectSchema = GitLabRepositorySchema;
// File content schemas
export const GitLabFileContentSchema = z.object({
file_name: z.string(), // Changed from name to match GitLab API
file_path: z.string(), // Changed from path to match GitLab API
size: z.number(),
encoding: z.string(),
content: z.string(),
content_sha256: z.string(), // Changed from sha to match GitLab API
ref: z.string(), // Added as GitLab requires branch reference
blob_id: z.string(), // Added to match GitLab API
commit_id: z.string(), // ID of the current file version
last_commit_id: z.string(), // Added to match GitLab API
execute_filemode: z.boolean().optional(), // Added to match GitLab API
});
export const GitLabDirectoryContentSchema = z.object({
name: z.string(),
path: z.string(),
type: z.string(),
mode: z.string(),
id: z.string(), // Changed from sha to match GitLab API
web_url: z.string(), // Changed from html_url to match GitLab API
});
export const GitLabContentSchema = z.union([
GitLabFileContentSchema,
z.array(GitLabDirectoryContentSchema),
]);
// Operation schemas
export const FileOperationSchema = z.object({
path: z.string(),
content: z.string(),
});
// Tree and commit schemas
export const GitLabTreeEntrySchema = z.object({
id: z.string(), // Changed from sha to match GitLab API
name: z.string(),
type: z.enum(["blob", "tree"]),
path: z.string(),
mode: z.string(),
});
export const GitLabTreeSchema = z.object({
id: z.string(), // Changed from sha to match GitLab API
tree: z.array(GitLabTreeEntrySchema),
});
export const GitLabCommitSchema = z.object({
id: z.string(), // Changed from sha to match GitLab API
short_id: z.string(), // Added to match GitLab API
title: z.string(), // Changed from message to match GitLab API
author_name: z.string(),
author_email: z.string(),
authored_date: z.string(),
committer_name: z.string(),
committer_email: z.string(),
committed_date: z.string(),
web_url: z.string(), // Changed from html_url to match GitLab API
parent_ids: z.array(z.string()), // Changed from parents to match GitLab API
});
// Reference schema
export const GitLabReferenceSchema = z.object({
name: z.string(), // Changed from ref to match GitLab API
commit: z.object({
id: z.string(), // Changed from sha to match GitLab API
web_url: z.string(), // Changed from url to match GitLab API
}),
});
// Input schemas for operations
export const CreateRepositoryOptionsSchema = z.object({
name: z.string(),
description: z.string().optional(),
visibility: z.enum(["private", "internal", "public"]).optional(), // Changed from private to match GitLab API
initialize_with_readme: z.boolean().optional(), // Changed from auto_init to match GitLab API
});
export const CreateIssueOptionsSchema = z.object({
title: z.string(),
description: z.string().optional(), // Changed from body to match GitLab API
assignee_ids: z.array(z.number()).optional(), // Changed from assignees to match GitLab API
milestone_id: z.number().optional(), // Changed from milestone to match GitLab API
labels: z.array(z.string()).optional(),
});
export const CreateMergeRequestOptionsSchema = z.object({
// Changed from CreatePullRequestOptionsSchema
title: z.string(),
description: z.string().optional(), // Changed from body to match GitLab API
source_branch: z.string(), // Changed from head to match GitLab API
target_branch: z.string(), // Changed from base to match GitLab API
allow_collaboration: z.boolean().optional(), // Changed from maintainer_can_modify to match GitLab API
draft: z.boolean().optional(),
});
export const CreateBranchOptionsSchema = z.object({
name: z.string(), // Changed from ref to match GitLab API
ref: z.string(), // The source branch/commit for the new branch
});
// Response schemas for operations
export const GitLabCreateUpdateFileResponseSchema = z.object({
file_path: z.string(),
branch: z.string(),
commit_id: z.string().optional(), // Optional since it's not always returned by the API
content: GitLabFileContentSchema.optional(),
});
export const GitLabSearchResponseSchema = z.object({
count: z.number().optional(),
total_pages: z.number().optional(),
current_page: z.number().optional(),
items: z.array(GitLabRepositorySchema),
});
// Issue related schemas
export const GitLabLabelSchema = z.object({
id: z.number(),
name: z.string(),
color: z.string(),
text_color: z.string(),
description: z.string().nullable(),
description_html: z.string().nullable(),
open_issues_count: z.number().optional(),
closed_issues_count: z.number().optional(),
open_merge_requests_count: z.number().optional(),
subscribed: z.boolean().optional(),
priority: z.number().nullable().optional(),
is_project_label: z.boolean().optional(),
});
export const GitLabUserSchema = z.object({
username: z.string(), // Changed from login to match GitLab API
id: z.number(),
name: z.string(),
avatar_url: z.string(),
web_url: z.string(), // Changed from html_url to match GitLab API
});
export const GitLabMilestoneSchema = z.object({
id: z.number(),
iid: z.number(), // Added to match GitLab API
title: z.string(),
description: z.string(),
state: z.string(),
web_url: z.string(), // Changed from html_url to match GitLab API
});
export const GitLabIssueSchema = z.object({
id: z.number(),
iid: z.number(), // Added to match GitLab API
project_id: z.number(), // Added to match GitLab API
title: z.string(),
description: z.string(), // Changed from body to match GitLab API
state: z.string(),
author: GitLabUserSchema,
assignees: z.array(GitLabUserSchema),
labels: z.array(GitLabLabelSchema).or(z.array(z.string())), // Support both label objects and strings
milestone: GitLabMilestoneSchema.nullable(),
created_at: z.string(),
updated_at: z.string(),
closed_at: z.string().nullable(),
web_url: z.string(), // Changed from html_url to match GitLab API
references: z.object({
short: z.string(),
relative: z.string(),
full: z.string(),
}).optional(),
time_stats: z.object({
time_estimate: z.number(),
total_time_spent: z.number(),
human_time_estimate: z.string().nullable(),
human_total_time_spent: z.string().nullable(),
}).optional(),
confidential: z.boolean().optional(),
due_date: z.string().nullable().optional(),
discussion_locked: z.boolean().nullable().optional(),
weight: z.number().nullable().optional(),
});
// NEW SCHEMA: For issue with link details (used in listing issue links)
export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
issue_link_id: z.number(),
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
link_created_at: z.string(),
link_updated_at: z.string(),
});
// Fork related schemas
export const GitLabForkParentSchema = z.object({
name: z.string(),
path_with_namespace: z.string(), // Changed from full_name to match GitLab API
owner: z.object({
username: z.string(), // Changed from login to match GitLab API
id: z.number(),
avatar_url: z.string(),
}).optional(), // Made optional to handle cases where GitLab API doesn't include it
web_url: z.string(), // Changed from html_url to match GitLab API
});
export const GitLabForkSchema = GitLabRepositorySchema.extend({
forked_from_project: GitLabForkParentSchema.optional(), // Made optional to handle cases where GitLab API doesn't include it
});
// Merge Request related schemas (equivalent to Pull Request)
export const GitLabMergeRequestDiffRefSchema = z.object({
base_sha: z.string(),
head_sha: z.string(),
start_sha: z.string(),
});
export const GitLabMergeRequestSchema = z.object({
id: z.number(),
iid: z.number(),
project_id: z.number(),
title: z.string(),
description: z.string().nullable(),
state: z.string(),
merged: z.boolean().optional(),
draft: z.boolean().optional(),
author: GitLabUserSchema,
assignees: z.array(GitLabUserSchema).optional(),
source_branch: z.string(),
target_branch: z.string(),
diff_refs: GitLabMergeRequestDiffRefSchema.nullable().optional(),
web_url: z.string(),
created_at: z.string(),
updated_at: z.string(),
merged_at: z.string().nullable(),
closed_at: z.string().nullable(),
merge_commit_sha: z.string().nullable(),
detailed_merge_status: z.string().optional(),
merge_status: z.string().optional(),
merge_error: z.string().nullable().optional(),
work_in_progress: z.boolean().optional(),
blocking_discussions_resolved: z.boolean().optional(),
should_remove_source_branch: z.boolean().nullable().optional(),
force_remove_source_branch: z.boolean().nullable().optional(),
allow_collaboration: z.boolean().optional(),
allow_maintainer_to_push: z.boolean().optional(),
changes_count: z.string().nullable().optional(),
merge_when_pipeline_succeeds: z.boolean().optional(),
squash: z.boolean().optional(),
labels: z.array(z.string()).optional(),
});
// Discussion related schemas
export const GitLabDiscussionNoteSchema = z.object({
id: z.number(),
type: z.enum(["DiscussionNote", "DiffNote", "Note"]).nullable(), // Allow null type for regular notes
body: z.string(),
attachment: z.any().nullable(), // Can be string or object, handle appropriately
author: GitLabUserSchema,
created_at: z.string(),
updated_at: z.string(),
system: z.boolean(),
noteable_id: z.number(),
noteable_type: z.enum(["Issue", "MergeRequest", "Snippet", "Commit", "Epic"]),
project_id: z.number().optional(), // Optional for group-level discussions like Epics
noteable_iid: z.number().nullable(),
resolvable: z.boolean().optional(),
resolved: z.boolean().optional(),
resolved_by: GitLabUserSchema.nullable().optional(),
resolved_at: z.string().nullable().optional(),
position: z.object({
base_sha: z.string(),
start_sha: z.string(),
head_sha: z.string(),
old_path: z.string(),
new_path: z.string(),
position_type: z.enum(["text", "image", "file"]),
old_line: z.number().nullable(),
new_line: z.number().nullable(),
line_range: z.object({
start: z.object({
line_code: z.string(),
type: z.enum(["new", "old"]),
old_line: z.number().nullable(),
new_line: z.number().nullable(),
}),
end: z.object({
line_code: z.string(),
type: z.enum(["new", "old"]),
old_line: z.number().nullable(),
new_line: z.number().nullable(),
}),
}).nullable().optional(), // For multi-line diff notes
width: z.number().optional(), // For image diff notes
height: z.number().optional(), // For image diff notes
x: z.number().optional(), // For image diff notes
y: z.number().optional(), // For image diff notes
}).optional(),
});
export const GitLabDiscussionSchema = z.object({
id: z.string(),
individual_note: z.boolean(),
notes: z.array(GitLabDiscussionNoteSchema),
});
// Input schema for listing merge request discussions
export const ListMergeRequestDiscussionsSchema = ProjectParamsSchema.extend({
merge_request_iid: z.number().describe("The IID of a merge request"),
});
// Input schema for updating a merge request discussion note
export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
merge_request_iid: z.number().describe("The IID of a merge request"),
discussion_id: z.string().describe("The ID of a thread"),
note_id: z.number().describe("The ID of a thread note"),
body: z.string().describe("The content of the note or reply"),
resolved: z.boolean().optional().describe("Resolve or unresolve the note"), // Optional based on API docs
});
// API Operation Parameter Schemas
export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
file_path: z.string().describe("Path where to create/update the file"),
content: z.string().describe("Content of the file"),
commit_message: z.string().describe("Commit message"),
branch: z.string().describe("Branch to create/update the file in"),
previous_path: z
.string()
.optional()
.describe("Path of the file to move/rename"),
last_commit_id: z
.string()
.optional()
.describe("Last known file commit ID"),
commit_id: z
.string()
.optional()
.describe("Current file commit ID (for update operations)"),
});
export const SearchRepositoriesSchema = z.object({
search: z.string().describe("Search query"), // Changed from query to match GitLab API
page: z
.number()
.optional()
.describe("Page number for pagination (default: 1)"),
per_page: z
.number()
.optional()
.describe("Number of results per page (default: 20)"),
});
export const CreateRepositorySchema = z.object({
name: z.string().describe("Repository name"),
description: z.string().optional().describe("Repository description"),
visibility: z
.enum(["private", "internal", "public"])
.optional()
.describe("Repository visibility level"),
initialize_with_readme: z
.boolean()
.optional()
.describe("Initialize with README.md"),
});
export const GetFileContentsSchema = ProjectParamsSchema.extend({
file_path: z.string().describe("Path to the file or directory"),
ref: z.string().optional().describe("Branch/tag/commit to get contents from"),
});
export const PushFilesSchema = ProjectParamsSchema.extend({
branch: z.string().describe("Branch to push to"),
files: z
.array(z.object({
file_path: z.string().describe("Path where to create the file"),
content: z.string().describe("Content of the file"),
}))
.describe("Array of files to push"),
commit_message: z.string().describe("Commit message"),
});
export const CreateIssueSchema = ProjectParamsSchema.extend({
title: z.string().describe("Issue title"),
description: z.string().optional().describe("Issue description"),
assignee_ids: z
.array(z.number())
.optional()
.describe("Array of user IDs to assign"),
labels: z.array(z.string()).optional().describe("Array of label names"),
milestone_id: z.number().optional().describe("Milestone ID to assign"),
});
export const CreateMergeRequestSchema = ProjectParamsSchema.extend({
title: z.string().describe("Merge request title"),
description: z.string().optional().describe("Merge request description"),
source_branch: z.string().describe("Branch containing changes"),
target_branch: z.string().describe("Branch to merge into"),
draft: z.boolean().optional().describe("Create as draft merge request"),
allow_collaboration: z
.boolean()
.optional()
.describe("Allow commits from upstream members"),
});
export const ForkRepositorySchema = ProjectParamsSchema.extend({
namespace: z.string().optional().describe("Namespace to fork to (full path)"),
});
export const CreateBranchSchema = ProjectParamsSchema.extend({
branch: z.string().describe("Name for the new branch"),
ref: z.string().optional().describe("Source branch/commit for new branch"),
});
export const GitLabMergeRequestDiffSchema = z.object({
old_path: z.string(),
new_path: z.string(),
a_mode: z.string(),
b_mode: z.string(),
diff: z.string(),
new_file: z.boolean(),
renamed_file: z.boolean(),
deleted_file: z.boolean(),
});
export const GetMergeRequestSchema = ProjectParamsSchema.extend({
merge_request_iid: z
.number()
.describe("The internal ID of the merge request"),
});
export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
title: z.string().optional().describe("The title of the merge request"),
description: z
.string()
.optional()
.describe("The description of the merge request"),
target_branch: z.string().optional().describe("The target branch"),
assignee_ids: z
.array(z.number())
.optional()
.describe("The ID of the users to assign the MR to"),
labels: z.array(z.string()).optional().describe("Labels for the MR"),
state_event: z
.enum(["close", "reopen"])
.optional()
.describe("New state (close/reopen) for the MR"),
remove_source_branch: z
.boolean()
.optional()
.describe("Flag indicating if the source branch should be removed"),
squash: z
.boolean()
.optional()
.describe("Squash commits into a single commit when merging"),
draft: z.boolean().optional().describe("Work in progress merge request"),
});
export const GetMergeRequestDiffsSchema = GetMergeRequestSchema.extend({
view: z.enum(["inline", "parallel"]).optional().describe("Diff view type"),
});
export const CreateNoteSchema = z.object({
project_id: z.string().describe("Project ID or namespace/project_path"),
noteable_type: z
.enum(["issue", "merge_request"])
.describe("Type of noteable (issue or merge_request)"),
noteable_iid: z.number().describe("IID of the issue or merge request"),
body: z.string().describe("Note content"),
});
// Issues API operation schemas
export const ListIssuesSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
assignee_id: z.number().optional().describe("Return issues assigned to the given user ID"),
assignee_username: z.string().optional().describe("Return issues assigned to the given username"),
author_id: z.number().optional().describe("Return issues created by the given user ID"),
author_username: z.string().optional().describe("Return issues created by the given username"),
confidential: z.boolean().optional().describe("Filter confidential or public issues"),
created_after: z.string().optional().describe("Return issues created after the given time"),
created_before: z.string().optional().describe("Return issues created before the given time"),
due_date: z.string().optional().describe("Return issues that have the due date"),
label_name: z.array(z.string()).optional().describe("Array of label names"),
milestone: z.string().optional().describe("Milestone title"),
scope: z.enum(['created-by-me', 'assigned-to-me', 'all']).optional().describe("Return issues from a specific scope"),
search: z.string().optional().describe("Search for specific terms"),
state: z.enum(['opened', 'closed', 'all']).optional().describe("Return issues with a specific state"),
updated_after: z.string().optional().describe("Return issues updated after the given time"),
updated_before: z.string().optional().describe("Return issues updated before the given time"),
with_labels_details: z.boolean().optional().describe("Return more details for each label"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
});
export const GetIssueSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of the project issue"),
});
export const UpdateIssueSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of the project issue"),
title: z.string().optional().describe("The title of the issue"),
description: z.string().optional().describe("The description of the issue"),
assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign issue to"),
confidential: z.boolean().optional().describe("Set the issue to be confidential"),
discussion_locked: z.boolean().optional().describe("Flag to lock discussions"),
due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"),
labels: z.array(z.string()).optional().describe("Array of label names"),
milestone_id: z.number().optional().describe("Milestone ID to assign"),
state_event: z.enum(['close', 'reopen']).optional().describe("Update issue state (close/reopen)"),
weight: z.number().optional().describe("Weight of the issue (0-9)"),
});
export const DeleteIssueSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of the project issue"),
});
// Issue links related schemas
export const GitLabIssueLinkSchema = z.object({
source_issue: GitLabIssueSchema,
target_issue: GitLabIssueSchema,
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']),
});
export const ListIssueLinksSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"),
});
export const GetIssueLinkSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"),
issue_link_id: z.number().describe("ID of an issue relationship"),
});
export const CreateIssueLinkSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"),
target_project_id: z.string().describe("The ID or URL-encoded path of a target project"),
target_issue_iid: z.number().describe("The internal ID of a target project's issue"),
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']).optional().describe("The type of the relation, defaults to relates_to"),
});
export const DeleteIssueLinkSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"),
issue_link_id: z.number().describe("The ID of an issue relationship"),
});
// Namespace API operation schemas
export const ListNamespacesSchema = z.object({
search: z.string().optional().describe("Search term for namespaces"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
owned: z.boolean().optional().describe("Filter for namespaces owned by current user"),
});
export const GetNamespaceSchema = z.object({
namespace_id: z.string().describe("Namespace ID or full path"),
});
export const VerifyNamespaceSchema = z.object({
path: z.string().describe("Namespace path to verify"),
});
// Project API operation schemas
export const GetProjectSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
});
export const ListProjectsSchema = z.object({
search: z.string().optional().describe("Search term for projects"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
owned: z.boolean().optional().describe("Filter for projects owned by current user"),
membership: z.boolean().optional().describe("Filter for projects where current user is a member"),
simple: z.boolean().optional().describe("Return only limited fields"),
archived: z.boolean().optional().describe("Filter for archived projects"),
visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"),
order_by: z.enum(["id", "name", "path", "created_at", "updated_at", "last_activity_at"]).optional().describe("Return projects ordered by field"),
sort: z.enum(["asc", "desc"]).optional().describe("Return projects sorted in ascending or descending order"),
with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"),
with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"),
min_access_level: z.number().optional().describe("Filter by minimum access level"),
});
// Label operation schemas
export const ListLabelsSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
with_counts: z.boolean().optional().describe("Whether or not to include issue and merge request counts"),
include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"),
search: z.string().optional().describe("Keyword to filter labels by"),
});
export const GetLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"),
});
export const CreateLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
name: z.string().describe("The name of the label"),
color: z.string().describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
description: z.string().optional().describe("The description of the label"),
priority: z.number().nullable().optional().describe("The priority of the label"),
});
export const UpdateLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
new_name: z.string().optional().describe("The new name of the label"),
color: z.string().optional().describe("The color of the label given in 6-digit hex notation with leading '#' sign"),
description: z.string().optional().describe("The new description of the label"),
priority: z.number().nullable().optional().describe("The new priority of the label"),
});
export const DeleteLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"),
});
// Group projects schema
export const ListGroupProjectsSchema = z.object({
group_id: z.string().describe("Group ID or path"),
include_subgroups: z.boolean().optional().describe("Include projects from subgroups"),
search: z.string().optional().describe("Search term to filter projects"),
order_by: z.enum(['name', 'path', 'created_at', 'updated_at', 'last_activity_at']).optional().describe("Field to sort by"),
sort: z.enum(['asc', 'desc']).optional().describe("Sort direction"),
page: z.number().optional().describe("Page number"),
per_page: z.number().optional().describe("Number of results per page"),
archived: z.boolean().optional().describe("Filter for archived projects"),
visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"),
with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"),
with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"),
min_access_level: z.number().optional().describe("Filter by minimum access level"),
with_programming_language: z.string().optional().describe("Filter by programming language"),
starred: z.boolean().optional().describe("Filter by starred projects"),
statistics: z.boolean().optional().describe("Include project statistics"),
with_custom_attributes: z.boolean().optional().describe("Include custom attributes"),
with_security_reports: z.boolean().optional().describe("Include security reports")
});

View File

@ -1,54 +0,0 @@
/**
* This test file verifies that the createNote function works correctly
* with the fixed endpoint URL construction that uses plural resource names
* (issues instead of issue, merge_requests instead of merge_request).
*/
import fetch from "node-fetch";
// GitLab API configuration (replace with actual values when testing)
const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com";
const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_TOKEN || "";
const PROJECT_ID = process.env.PROJECT_ID || "your/project";
const ISSUE_IID = Number(process.env.ISSUE_IID || "1");
async function testCreateIssueNote() {
try {
// Using plural form "issues" in the URL
const url = new URL(`${GITLAB_API_URL}/api/v4/projects/${encodeURIComponent(PROJECT_ID)}/issues/${ISSUE_IID}/notes`);
const response = await fetch(url.toString(), {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`,
},
body: JSON.stringify({ body: "Test note from API - with plural endpoint" }),
});
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`GitLab API error: ${response.status} ${response.statusText}\n${errorBody}`);
}
const data = await response.json();
console.log("Successfully created note:");
console.log(JSON.stringify(data, null, 2));
return true;
}
catch (error) {
console.error("Error creating note:", error);
return false;
}
}
// Only run the test if executed directly
if (require.main === module) {
console.log("Testing note creation with plural 'issues' endpoint...");
testCreateIssueNote().then(success => {
if (success) {
console.log("✅ Test successful!");
process.exit(0);
}
else {
console.log("❌ Test failed!");
process.exit(1);
}
});
}
// Export for use in other tests
export { testCreateIssueNote };

1889
index.ts

File diff suppressed because it is too large Load Diff

106
package-lock.json generated
View File

@ -1,17 +1,21 @@
{ {
"name": "@zereight/mcp-gitlab", "name": "@zereight/mcp-gitlab",
"version": "1.0.21", "version": "1.0.38",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@zereight/mcp-gitlab", "name": "@zereight/mcp-gitlab",
"version": "1.0.21", "version": "1.0.38",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "1.8.0", "@modelcontextprotocol/sdk": "1.8.0",
"@types/node-fetch": "^2.6.12", "@types/node-fetch": "^2.6.12",
"form-data": "^4.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"socks-proxy-agent": "^8.0.5",
"zod-to-json-schema": "^3.23.5" "zod-to-json-schema": "^3.23.5"
}, },
"bin": { "bin": {
@ -79,6 +83,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/agent-base": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -713,6 +726,32 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.6.3", "version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@ -731,6 +770,19 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
"integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
"license": "MIT",
"dependencies": {
"jsbn": "1.1.0",
"sprintf-js": "^1.1.3"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -752,6 +804,12 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/jsbn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
"license": "MIT"
},
"node_modules/math-intrinsics": { "node_modules/math-intrinsics": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@ -1201,6 +1259,50 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
"integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==",
"license": "MIT",
"dependencies": {
"ip-address": "^9.0.5",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
},
"node_modules/statuses": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "@zereight/mcp-gitlab", "name": "@zereight/mcp-gitlab",
"version": "1.0.22", "version": "1.0.46",
"description": "MCP server for using the GitLab API", "description": "MCP server for using the GitLab API",
"license": "MIT", "license": "MIT",
"author": "zereight", "author": "zereight",
@ -19,12 +19,17 @@
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build", "prepare": "npm run build",
"watch": "tsc --watch", "watch": "tsc --watch",
"deploy": "npm run build && npm publish" "deploy": "npm publish --access public",
"generate-tools": "npx ts-node scripts/generate-tools-readme.ts"
}, },
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "1.8.0", "@modelcontextprotocol/sdk": "1.8.0",
"form-data": "^4.0.0",
"@types/node-fetch": "^2.6.12", "@types/node-fetch": "^2.6.12",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"socks-proxy-agent": "^8.0.5",
"zod-to-json-schema": "^3.23.5" "zod-to-json-schema": "^3.23.5"
}, },
"devDependencies": { "devDependencies": {
@ -32,4 +37,4 @@
"typescript": "^5.8.2", "typescript": "^5.8.2",
"zod": "^3.24.2" "zod": "^3.24.2"
} }
} }

5
release-notes.md Normal file
View File

@ -0,0 +1,5 @@
### 1.0.40 (2025-05-21)
- Added support for listing discussions (comments/notes) on GitLab issues.
- Example: You can now easily fetch all conversations (comments) attached to an issue via the API.
- Related PR: [#44](https://github.com/zereight/gitlab-mcp/pull/44)

View File

@ -7,6 +7,119 @@ export const GitLabAuthorSchema = z.object({
date: z.string(), date: z.string(),
}); });
// Pipeline related schemas
export const GitLabPipelineSchema = z.object({
id: z.number(),
project_id: z.number(),
sha: z.string(),
ref: z.string(),
status: z.string(),
source: z.string().optional(),
created_at: z.string(),
updated_at: z.string(),
web_url: z.string(),
duration: z.number().nullable().optional(),
started_at: z.string().nullable().optional(),
finished_at: z.string().nullable().optional(),
coverage: z.number().nullable().optional(),
user: z.object({
id: z.number(),
name: z.string(),
username: z.string(),
avatar_url: z.string().nullable().optional(),
}).optional(),
detailed_status: z.object({
icon: z.string().optional(),
text: z.string().optional(),
label: z.string().optional(),
group: z.string().optional(),
tooltip: z.string().optional(),
has_details: z.boolean().optional(),
details_path: z.string().optional(),
illustration: z.object({
image: z.string().optional(),
size: z.string().optional(),
title: z.string().optional(),
}).optional(),
favicon: z.string().optional(),
}).optional(),
});
// Pipeline job related schemas
export const GitLabPipelineJobSchema = z.object({
id: z.number(),
status: z.string(),
stage: z.string(),
name: z.string(),
ref: z.string(),
tag: z.boolean(),
coverage: z.number().nullable().optional(),
created_at: z.string(),
started_at: z.string().nullable().optional(),
finished_at: z.string().nullable().optional(),
duration: z.number().nullable().optional(),
user: z.object({
id: z.number(),
name: z.string(),
username: z.string(),
avatar_url: z.string().nullable().optional(),
}).optional(),
commit: z.object({
id: z.string(),
short_id: z.string(),
title: z.string(),
author_name: z.string(),
author_email: z.string(),
}).optional(),
pipeline: z.object({
id: z.number(),
project_id: z.number(),
status: z.string(),
ref: z.string(),
sha: z.string(),
}).optional(),
web_url: z.string().optional(),
});
// Schema for listing pipelines
export const ListPipelinesSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
scope: z.enum(['running', 'pending', 'finished', 'branches', 'tags']).optional().describe("The scope of pipelines"),
status: z.enum(['created', 'waiting_for_resource', 'preparing', 'pending', 'running', 'success', 'failed', 'canceled', 'skipped', 'manual', 'scheduled']).optional().describe("The status of pipelines"),
ref: z.string().optional().describe("The ref of pipelines"),
sha: z.string().optional().describe("The SHA of pipelines"),
yaml_errors: z.boolean().optional().describe("Returns pipelines with invalid configurations"),
username: z.string().optional().describe("The username of the user who triggered pipelines"),
updated_after: z.string().optional().describe("Return pipelines updated after the specified date"),
updated_before: z.string().optional().describe("Return pipelines updated before the specified date"),
order_by: z.enum(['id', 'status', 'ref', 'updated_at', 'user_id']).optional().describe("Order pipelines by"),
sort: z.enum(['asc', 'desc']).optional().describe("Sort pipelines"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page (max 100)"),
});
// Schema for getting a specific pipeline
export const GetPipelineSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
pipeline_id: z.number().describe("The ID of the pipeline"),
});
// Schema for listing jobs in a pipeline
export const ListPipelineJobsSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
pipeline_id: z.number().describe("The ID of the pipeline"),
scope: z.enum(['created', 'pending', 'running', 'failed', 'success', 'canceled', 'skipped', 'manual']).optional().describe("The scope of jobs to show"),
include_retried: z.boolean().optional().describe("Whether to include retried jobs"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page (max 100)"),
});
// Schema for the input parameters for pipeline job operations
export const GetPipelineJobOutputSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
job_id: z.number().describe("The ID of the job"),
});
// Namespace related schemas // Namespace related schemas
// Base schema for project-related operations // Base schema for project-related operations
@ -63,15 +176,17 @@ export const GitLabRepositorySchema = z.object({
created_at: z.string().optional(), created_at: z.string().optional(),
last_activity_at: z.string().optional(), last_activity_at: z.string().optional(),
default_branch: z.string().optional(), default_branch: z.string().optional(),
namespace: z.object({ namespace: z
id: z.number(), .object({
name: z.string(), id: z.number(),
path: z.string(), name: z.string(),
kind: z.string(), path: z.string(),
full_path: z.string(), kind: z.string(),
avatar_url: z.string().nullable().optional(), full_path: z.string(),
web_url: z.string().optional(), avatar_url: z.string().nullable().optional(),
}).optional(), web_url: z.string().optional(),
})
.optional(),
readme_url: z.string().optional().nullable(), readme_url: z.string().optional().nullable(),
topics: z.array(z.string()).optional(), topics: z.array(z.string()).optional(),
tag_list: z.array(z.string()).optional(), // deprecated but still present tag_list: z.array(z.string()).optional(), // deprecated but still present
@ -79,16 +194,24 @@ export const GitLabRepositorySchema = z.object({
archived: z.boolean().optional(), archived: z.boolean().optional(),
forks_count: z.number().optional(), forks_count: z.number().optional(),
star_count: z.number().optional(), star_count: z.number().optional(),
permissions: z.object({ permissions: z
project_access: z.object({ .object({
access_level: z.number(), project_access: z
notification_level: z.number().optional(), .object({
}).optional().nullable(), access_level: z.number(),
group_access: z.object({ notification_level: z.number().optional(),
access_level: z.number(), })
notification_level: z.number().optional(), .optional()
}).optional().nullable(), .nullable(),
}).optional(), group_access: z
.object({
access_level: z.number(),
notification_level: z.number().optional(),
})
.optional()
.nullable(),
})
.optional(),
container_registry_enabled: z.boolean().optional(), container_registry_enabled: z.boolean().optional(),
container_registry_access_level: z.string().optional(), container_registry_access_level: z.string().optional(),
issues_enabled: z.boolean().optional(), issues_enabled: z.boolean().optional(),
@ -97,14 +220,18 @@ export const GitLabRepositorySchema = z.object({
jobs_enabled: z.boolean().optional(), jobs_enabled: z.boolean().optional(),
snippets_enabled: z.boolean().optional(), snippets_enabled: z.boolean().optional(),
can_create_merge_request_in: z.boolean().optional(), can_create_merge_request_in: z.boolean().optional(),
resolve_outdated_diff_discussions: z.boolean().optional(), resolve_outdated_diff_discussions: z.boolean().nullable().optional(),
shared_runners_enabled: z.boolean().optional(), shared_runners_enabled: z.boolean().optional(),
shared_with_groups: z.array(z.object({ shared_with_groups: z
group_id: z.number(), .array(
group_name: z.string(), z.object({
group_full_path: z.string(), group_id: z.number(),
group_access_level: z.number(), group_name: z.string(),
})).optional(), group_full_path: z.string(),
group_access_level: z.number(),
})
)
.optional(),
}); });
// Project schema (extended from repository schema) // Project schema (extended from repository schema)
@ -146,17 +273,41 @@ export const FileOperationSchema = z.object({
}); });
// Tree and commit schemas // Tree and commit schemas
export const GitLabTreeEntrySchema = z.object({ export const GitLabTreeItemSchema = z.object({
id: z.string(), // Changed from sha to match GitLab API id: z.string(),
name: z.string(), name: z.string(),
type: z.enum(["blob", "tree"]), type: z.enum(["tree", "blob"]),
path: z.string(), path: z.string(),
mode: z.string(), mode: z.string(),
}); });
export const GetRepositoryTreeSchema = z.object({
project_id: z.string().describe("The ID or URL-encoded path of the project"),
path: z.string().optional().describe("The path inside the repository"),
ref: z
.string()
.optional()
.describe(
"The name of a repository branch or tag. Defaults to the default branch."
),
recursive: z
.boolean()
.optional()
.describe("Boolean value to get a recursive tree"),
per_page: z
.number()
.optional()
.describe("Number of results to show per page"),
page_token: z
.string()
.optional()
.describe("The tree record ID for pagination"),
pagination: z.string().optional().describe("Pagination method (keyset)"),
});
export const GitLabTreeSchema = z.object({ export const GitLabTreeSchema = z.object({
id: z.string(), // Changed from sha to match GitLab API id: z.string(), // Changed from sha to match GitLab API
tree: z.array(GitLabTreeEntrySchema), tree: z.array(GitLabTreeItemSchema),
}); });
export const GitLabCommitSchema = z.object({ export const GitLabCommitSchema = z.object({
@ -256,7 +407,7 @@ export const GitLabMilestoneSchema = z.object({
id: z.number(), id: z.number(),
iid: z.number(), // Added to match GitLab API iid: z.number(), // Added to match GitLab API
title: z.string(), title: z.string(),
description: z.string(), description: z.string().nullable().default(""),
state: z.string(), state: z.string(),
web_url: z.string(), // Changed from html_url to match GitLab API web_url: z.string(), // Changed from html_url to match GitLab API
}); });
@ -266,7 +417,7 @@ export const GitLabIssueSchema = z.object({
iid: z.number(), // Added to match GitLab API iid: z.number(), // Added to match GitLab API
project_id: z.number(), // Added to match GitLab API project_id: z.number(), // Added to match GitLab API
title: z.string(), title: z.string(),
description: z.string(), // Changed from body to match GitLab API description: z.string().nullable().default(""), // Changed from body to match GitLab API
state: z.string(), state: z.string(),
author: GitLabUserSchema, author: GitLabUserSchema,
assignees: z.array(GitLabUserSchema), assignees: z.array(GitLabUserSchema),
@ -276,17 +427,21 @@ export const GitLabIssueSchema = z.object({
updated_at: z.string(), updated_at: z.string(),
closed_at: z.string().nullable(), closed_at: z.string().nullable(),
web_url: z.string(), // Changed from html_url to match GitLab API web_url: z.string(), // Changed from html_url to match GitLab API
references: z.object({ references: z
short: z.string(), .object({
relative: z.string(), short: z.string(),
full: z.string(), relative: z.string(),
}).optional(), full: z.string(),
time_stats: z.object({ })
time_estimate: z.number(), .optional(),
total_time_spent: z.number(), time_stats: z
human_time_estimate: z.string().nullable(), .object({
human_total_time_spent: z.string().nullable(), time_estimate: z.number(),
}).optional(), total_time_spent: z.number(),
human_time_estimate: z.string().nullable(),
human_total_time_spent: z.string().nullable(),
})
.optional(),
confidential: z.boolean().optional(), confidential: z.boolean().optional(),
due_date: z.string().nullable().optional(), due_date: z.string().nullable().optional(),
discussion_locked: z.boolean().nullable().optional(), discussion_locked: z.boolean().nullable().optional(),
@ -296,7 +451,7 @@ export const GitLabIssueSchema = z.object({
// NEW SCHEMA: For issue with link details (used in listing issue links) // NEW SCHEMA: For issue with link details (used in listing issue links)
export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({ export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
issue_link_id: z.number(), issue_link_id: z.number(),
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']), link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
link_created_at: z.string(), link_created_at: z.string(),
link_updated_at: z.string(), link_updated_at: z.string(),
}); });
@ -305,11 +460,13 @@ export const GitLabIssueWithLinkDetailsSchema = GitLabIssueSchema.extend({
export const GitLabForkParentSchema = z.object({ export const GitLabForkParentSchema = z.object({
name: z.string(), name: z.string(),
path_with_namespace: z.string(), // Changed from full_name to match GitLab API path_with_namespace: z.string(), // Changed from full_name to match GitLab API
owner: z.object({ owner: z
username: z.string(), // Changed from login to match GitLab API .object({
id: z.number(), username: z.string(), // Changed from login to match GitLab API
avatar_url: z.string(), id: z.number(),
}).optional(), // Made optional to handle cases where GitLab API doesn't include it avatar_url: z.string(),
})
.optional(), // Made optional to handle cases where GitLab API doesn't include it
web_url: z.string(), // Changed from html_url to match GitLab API web_url: z.string(), // Changed from html_url to match GitLab API
}); });
@ -377,34 +534,40 @@ export const GitLabDiscussionNoteSchema = z.object({
resolved: z.boolean().optional(), resolved: z.boolean().optional(),
resolved_by: GitLabUserSchema.nullable().optional(), resolved_by: GitLabUserSchema.nullable().optional(),
resolved_at: z.string().nullable().optional(), resolved_at: z.string().nullable().optional(),
position: z.object({ // Only present for DiffNote position: z
base_sha: z.string(), .object({
start_sha: z.string(), // Only present for DiffNote
head_sha: z.string(), base_sha: z.string(),
old_path: z.string(), start_sha: z.string(),
new_path: z.string(), head_sha: z.string(),
position_type: z.enum(["text", "image", "file"]), old_path: z.string(),
old_line: z.number().nullable(), new_path: z.string(),
new_line: z.number().nullable(), position_type: z.enum(["text", "image", "file"]),
line_range: z.object({ old_line: z.number().nullable(),
start: z.object({ new_line: z.number().nullable(),
line_code: z.string(), line_range: z
type: z.enum(["new", "old"]), .object({
old_line: z.number().nullable(), start: z.object({
new_line: z.number().nullable(), line_code: z.string(),
}), type: z.enum(["new", "old", "expanded"]),
end: z.object({ old_line: z.number().nullable(),
line_code: z.string(), new_line: z.number().nullable(),
type: z.enum(["new", "old"]), }),
old_line: z.number().nullable(), end: z.object({
new_line: z.number().nullable(), line_code: z.string(),
}), type: z.enum(["new", "old", "expanded"]),
}).nullable().optional(), // For multi-line diff notes old_line: z.number().nullable(),
width: z.number().optional(), // For image diff notes new_line: z.number().nullable(),
height: z.number().optional(), // For image diff notes }),
x: z.number().optional(), // For image diff notes })
y: z.number().optional(), // For image diff notes .nullable()
}).optional(), .optional(), // For multi-line diff notes
width: z.number().optional(), // For image diff notes
height: z.number().optional(), // For image diff notes
x: z.number().optional(), // For image diff notes
y: z.number().optional(), // For image diff notes
})
.optional(),
}); });
export type GitLabDiscussionNote = z.infer<typeof GitLabDiscussionNoteSchema>; export type GitLabDiscussionNote = z.infer<typeof GitLabDiscussionNoteSchema>;
@ -425,8 +588,36 @@ export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({
merge_request_iid: z.number().describe("The IID of a merge request"), merge_request_iid: z.number().describe("The IID of a merge request"),
discussion_id: z.string().describe("The ID of a thread"), discussion_id: z.string().describe("The ID of a thread"),
note_id: z.number().describe("The ID of a thread note"), note_id: z.number().describe("The ID of a thread note"),
body: z.string().optional().describe("The content of the note or reply"),
resolved: z.boolean().optional().describe("Resolve or unresolve the note"),
}).refine(data => data.body !== undefined || data.resolved !== undefined, {
message: "At least one of 'body' or 'resolved' must be provided"
}).refine(data => !(data.body !== undefined && data.resolved !== undefined), {
message: "Only one of 'body' or 'resolved' can be provided, not both"
});
// Input schema for adding a note to an existing merge request discussion
export const CreateMergeRequestNoteSchema = ProjectParamsSchema.extend({
merge_request_iid: z.number().describe("The IID of a merge request"),
discussion_id: z.string().describe("The ID of a thread"),
body: z.string().describe("The content of the note or reply"), body: z.string().describe("The content of the note or reply"),
resolved: z.boolean().optional().describe("Resolve or unresolve the note"), // Optional based on API docs created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"),
});
// Input schema for updating an issue discussion note
export const UpdateIssueNoteSchema = ProjectParamsSchema.extend({
issue_iid: z.number().describe("The IID of an issue"),
discussion_id: z.string().describe("The ID of a thread"),
note_id: z.number().describe("The ID of a thread note"),
body: z.string().describe("The content of the note or reply"),
});
// Input schema for adding a note to an existing issue discussion
export const CreateIssueNoteSchema = ProjectParamsSchema.extend({
issue_iid: z.number().describe("The IID of an issue"),
discussion_id: z.string().describe("The ID of a thread"),
body: z.string().describe("The content of the note or reply"),
created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"),
}); });
// API Operation Parameter Schemas // API Operation Parameter Schemas
@ -440,10 +631,7 @@ export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({
.string() .string()
.optional() .optional()
.describe("Path of the file to move/rename"), .describe("Path of the file to move/rename"),
last_commit_id: z last_commit_id: z.string().optional().describe("Last known file commit ID"),
.string()
.optional()
.describe("Last known file commit ID"),
commit_id: z commit_id: z
.string() .string()
.optional() .optional()
@ -539,7 +727,9 @@ export const GitLabMergeRequestDiffSchema = z.object({
export const GetMergeRequestSchema = ProjectParamsSchema.extend({ export const GetMergeRequestSchema = ProjectParamsSchema.extend({
merge_request_iid: z merge_request_iid: z
.number() .number()
.describe("The internal ID of the merge request"), .optional()
.describe("The IID of a merge request"),
source_branch: z.string().optional().describe("Source branch name"),
}); });
export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({ export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
@ -585,22 +775,61 @@ export const CreateNoteSchema = z.object({
// Issues API operation schemas // Issues API operation schemas
export const ListIssuesSchema = z.object({ export const ListIssuesSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
assignee_id: z.number().optional().describe("Return issues assigned to the given user ID"), assignee_id: z
assignee_username: z.string().optional().describe("Return issues assigned to the given username"), .number()
author_id: z.number().optional().describe("Return issues created by the given user ID"), .optional()
author_username: z.string().optional().describe("Return issues created by the given username"), .describe("Return issues assigned to the given user ID"),
confidential: z.boolean().optional().describe("Filter confidential or public issues"), assignee_username: z
created_after: z.string().optional().describe("Return issues created after the given time"), .string()
created_before: z.string().optional().describe("Return issues created before the given time"), .optional()
due_date: z.string().optional().describe("Return issues that have the due date"), .describe("Return issues assigned to the given username"),
author_id: z
.number()
.optional()
.describe("Return issues created by the given user ID"),
author_username: z
.string()
.optional()
.describe("Return issues created by the given username"),
confidential: z
.boolean()
.optional()
.describe("Filter confidential or public issues"),
created_after: z
.string()
.optional()
.describe("Return issues created after the given time"),
created_before: z
.string()
.optional()
.describe("Return issues created before the given time"),
due_date: z
.string()
.optional()
.describe("Return issues that have the due date"),
label_name: z.array(z.string()).optional().describe("Array of label names"), label_name: z.array(z.string()).optional().describe("Array of label names"),
milestone: z.string().optional().describe("Milestone title"), milestone: z.string().optional().describe("Milestone title"),
scope: z.enum(['created-by-me', 'assigned-to-me', 'all']).optional().describe("Return issues from a specific scope"), scope: z
.enum(["created-by-me", "assigned-to-me", "all"])
.optional()
.describe("Return issues from a specific scope"),
search: z.string().optional().describe("Search for specific terms"), search: z.string().optional().describe("Search for specific terms"),
state: z.enum(['opened', 'closed', 'all']).optional().describe("Return issues with a specific state"), state: z
updated_after: z.string().optional().describe("Return issues updated after the given time"), .enum(["opened", "closed", "all"])
updated_before: z.string().optional().describe("Return issues updated before the given time"), .optional()
with_labels_details: z.boolean().optional().describe("Return more details for each label"), .describe("Return issues with a specific state"),
updated_after: z
.string()
.optional()
.describe("Return issues updated after the given time"),
updated_before: z
.string()
.optional()
.describe("Return issues updated before the given time"),
with_labels_details: z
.boolean()
.optional()
.describe("Return more details for each label"),
page: z.number().optional().describe("Page number for pagination"), page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"), per_page: z.number().optional().describe("Number of items per page"),
}); });
@ -615,13 +844,28 @@ export const UpdateIssueSchema = z.object({
issue_iid: z.number().describe("The internal ID of the project issue"), issue_iid: z.number().describe("The internal ID of the project issue"),
title: z.string().optional().describe("The title of the issue"), title: z.string().optional().describe("The title of the issue"),
description: z.string().optional().describe("The description of the issue"), description: z.string().optional().describe("The description of the issue"),
assignee_ids: z.array(z.number()).optional().describe("Array of user IDs to assign issue to"), assignee_ids: z
confidential: z.boolean().optional().describe("Set the issue to be confidential"), .array(z.number())
discussion_locked: z.boolean().optional().describe("Flag to lock discussions"), .optional()
due_date: z.string().optional().describe("Date the issue is due (YYYY-MM-DD)"), .describe("Array of user IDs to assign issue to"),
confidential: z
.boolean()
.optional()
.describe("Set the issue to be confidential"),
discussion_locked: z
.boolean()
.optional()
.describe("Flag to lock discussions"),
due_date: z
.string()
.optional()
.describe("Date the issue is due (YYYY-MM-DD)"),
labels: z.array(z.string()).optional().describe("Array of label names"), labels: z.array(z.string()).optional().describe("Array of label names"),
milestone_id: z.number().optional().describe("Milestone ID to assign"), milestone_id: z.number().optional().describe("Milestone ID to assign"),
state_event: z.enum(['close', 'reopen']).optional().describe("Update issue state (close/reopen)"), state_event: z
.enum(["close", "reopen"])
.optional()
.describe("Update issue state (close/reopen)"),
weight: z.number().optional().describe("Weight of the issue (0-9)"), weight: z.number().optional().describe("Weight of the issue (0-9)"),
}); });
@ -634,7 +878,7 @@ export const DeleteIssueSchema = z.object({
export const GitLabIssueLinkSchema = z.object({ export const GitLabIssueLinkSchema = z.object({
source_issue: GitLabIssueSchema, source_issue: GitLabIssueSchema,
target_issue: GitLabIssueSchema, target_issue: GitLabIssueSchema,
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']), link_type: z.enum(["relates_to", "blocks", "is_blocked_by"]),
}); });
export const ListIssueLinksSchema = z.object({ export const ListIssueLinksSchema = z.object({
@ -642,6 +886,15 @@ export const ListIssueLinksSchema = z.object({
issue_iid: z.number().describe("The internal ID of a project's issue"), issue_iid: z.number().describe("The internal ID of a project's issue"),
}); });
export const ListIssueDiscussionsSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of the project issue"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
sort: z.enum(["asc", "desc"]).optional().describe("Return issue discussions sorted in ascending or descending order"),
order_by: z.enum(["created_at", "updated_at"]).optional().describe("Return issue discussions ordered by created_at or updated_at fields"),
});
export const GetIssueLinkSchema = z.object({ export const GetIssueLinkSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"), issue_iid: z.number().describe("The internal ID of a project's issue"),
@ -651,9 +904,16 @@ export const GetIssueLinkSchema = z.object({
export const CreateIssueLinkSchema = z.object({ export const CreateIssueLinkSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
issue_iid: z.number().describe("The internal ID of a project's issue"), issue_iid: z.number().describe("The internal ID of a project's issue"),
target_project_id: z.string().describe("The ID or URL-encoded path of a target project"), target_project_id: z
target_issue_iid: z.number().describe("The internal ID of a target project's issue"), .string()
link_type: z.enum(['relates_to', 'blocks', 'is_blocked_by']).optional().describe("The type of the relation, defaults to relates_to"), .describe("The ID or URL-encoded path of a target project"),
target_issue_iid: z
.number()
.describe("The internal ID of a target project's issue"),
link_type: z
.enum(["relates_to", "blocks", "is_blocked_by"])
.optional()
.describe("The type of the relation, defaults to relates_to"),
}); });
export const DeleteIssueLinkSchema = z.object({ export const DeleteIssueLinkSchema = z.object({
@ -667,7 +927,10 @@ export const ListNamespacesSchema = z.object({
search: z.string().optional().describe("Search term for namespaces"), search: z.string().optional().describe("Search term for namespaces"),
page: z.number().optional().describe("Page number for pagination"), page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"), per_page: z.number().optional().describe("Number of items per page"),
owned: z.boolean().optional().describe("Filter for namespaces owned by current user"), owned: z
.boolean()
.optional()
.describe("Filter for namespaces owned by current user"),
}); });
export const GetNamespaceSchema = z.object({ export const GetNamespaceSchema = z.object({
@ -687,99 +950,291 @@ export const ListProjectsSchema = z.object({
search: z.string().optional().describe("Search term for projects"), search: z.string().optional().describe("Search term for projects"),
page: z.number().optional().describe("Page number for pagination"), page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"), per_page: z.number().optional().describe("Number of items per page"),
owned: z.boolean().optional().describe("Filter for projects owned by current user"), search_namespaces: z
membership: z.boolean().optional().describe("Filter for projects where current user is a member"), .boolean()
.optional()
.describe("Needs to be true if search is full path"),
owned: z
.boolean()
.optional()
.describe("Filter for projects owned by current user"),
membership: z
.boolean()
.optional()
.describe("Filter for projects where current user is a member"),
simple: z.boolean().optional().describe("Return only limited fields"), simple: z.boolean().optional().describe("Return only limited fields"),
archived: z.boolean().optional().describe("Filter for archived projects"), archived: z.boolean().optional().describe("Filter for archived projects"),
visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"), visibility: z
order_by: z.enum(["id", "name", "path", "created_at", "updated_at", "last_activity_at"]).optional().describe("Return projects ordered by field"), .enum(["public", "internal", "private"])
sort: z.enum(["asc", "desc"]).optional().describe("Return projects sorted in ascending or descending order"), .optional()
with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"), .describe("Filter by project visibility"),
with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"), order_by: z
min_access_level: z.number().optional().describe("Filter by minimum access level"), .enum([
"id",
"name",
"path",
"created_at",
"updated_at",
"last_activity_at",
])
.optional()
.describe("Return projects ordered by field"),
sort: z
.enum(["asc", "desc"])
.optional()
.describe("Return projects sorted in ascending or descending order"),
with_issues_enabled: z
.boolean()
.optional()
.describe("Filter projects with issues feature enabled"),
with_merge_requests_enabled: z
.boolean()
.optional()
.describe("Filter projects with merge requests feature enabled"),
min_access_level: z
.number()
.optional()
.describe("Filter by minimum access level"),
}); });
// Label operation schemas // Label operation schemas
export const ListLabelsSchema = z.object({ export const ListLabelsSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
with_counts: z.boolean().optional().describe("Whether or not to include issue and merge request counts"), with_counts: z
include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"), .boolean()
.optional()
.describe("Whether or not to include issue and merge request counts"),
include_ancestor_groups: z
.boolean()
.optional()
.describe("Include ancestor groups"),
search: z.string().optional().describe("Keyword to filter labels by"), search: z.string().optional().describe("Keyword to filter labels by"),
}); });
export const GetLabelSchema = z.object({ export const GetLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"), label_id: z.string().describe("The ID or title of a project's label"),
include_ancestor_groups: z.boolean().optional().describe("Include ancestor groups"), include_ancestor_groups: z
.boolean()
.optional()
.describe("Include ancestor groups"),
}); });
export const CreateLabelSchema = z.object({ export const CreateLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
name: z.string().describe("The name of the label"), name: z.string().describe("The name of the label"),
color: z.string().describe("The color of the label given in 6-digit hex notation with leading '#' sign"), color: z
.string()
.describe(
"The color of the label given in 6-digit hex notation with leading '#' sign"
),
description: z.string().optional().describe("The description of the label"), description: z.string().optional().describe("The description of the label"),
priority: z.number().nullable().optional().describe("The priority of the label"), priority: z
.number()
.nullable()
.optional()
.describe("The priority of the label"),
}); });
export const UpdateLabelSchema = z.object({ export const UpdateLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"), label_id: z.string().describe("The ID or title of a project's label"),
new_name: z.string().optional().describe("The new name of the label"), new_name: z.string().optional().describe("The new name of the label"),
color: z.string().optional().describe("The color of the label given in 6-digit hex notation with leading '#' sign"), color: z
description: z.string().optional().describe("The new description of the label"), .string()
priority: z.number().nullable().optional().describe("The new priority of the label"), .optional()
.describe(
"The color of the label given in 6-digit hex notation with leading '#' sign"
),
description: z
.string()
.optional()
.describe("The new description of the label"),
priority: z
.number()
.nullable()
.optional()
.describe("The new priority of the label"),
}); });
export const DeleteLabelSchema = z.object({ export const DeleteLabelSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"), project_id: z.string().describe("Project ID or URL-encoded path"),
label_id: z.union([z.number(), z.string()]).describe("The ID or title of a project's label"), label_id: z.string().describe("The ID or title of a project's label"),
}); });
// Group projects schema // Group projects schema
export const ListGroupProjectsSchema = z.object({ export const ListGroupProjectsSchema = z.object({
group_id: z.string().describe("Group ID or path"), group_id: z.string().describe("Group ID or path"),
include_subgroups: z.boolean().optional().describe("Include projects from subgroups"), include_subgroups: z
.boolean()
.optional()
.describe("Include projects from subgroups"),
search: z.string().optional().describe("Search term to filter projects"), search: z.string().optional().describe("Search term to filter projects"),
order_by: z.enum(['name', 'path', 'created_at', 'updated_at', 'last_activity_at']).optional().describe("Field to sort by"), order_by: z
sort: z.enum(['asc', 'desc']).optional().describe("Sort direction"), .enum(["name", "path", "created_at", "updated_at", "last_activity_at"])
.optional()
.describe("Field to sort by"),
sort: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
page: z.number().optional().describe("Page number"), page: z.number().optional().describe("Page number"),
per_page: z.number().optional().describe("Number of results per page"), per_page: z.number().optional().describe("Number of results per page"),
archived: z.boolean().optional().describe("Filter for archived projects"), archived: z.boolean().optional().describe("Filter for archived projects"),
visibility: z.enum(["public", "internal", "private"]).optional().describe("Filter by project visibility"), visibility: z
with_issues_enabled: z.boolean().optional().describe("Filter projects with issues feature enabled"), .enum(["public", "internal", "private"])
with_merge_requests_enabled: z.boolean().optional().describe("Filter projects with merge requests feature enabled"), .optional()
min_access_level: z.number().optional().describe("Filter by minimum access level"), .describe("Filter by project visibility"),
with_programming_language: z.string().optional().describe("Filter by programming language"), with_issues_enabled: z
.boolean()
.optional()
.describe("Filter projects with issues feature enabled"),
with_merge_requests_enabled: z
.boolean()
.optional()
.describe("Filter projects with merge requests feature enabled"),
min_access_level: z
.number()
.optional()
.describe("Filter by minimum access level"),
with_programming_language: z
.string()
.optional()
.describe("Filter by programming language"),
starred: z.boolean().optional().describe("Filter by starred projects"), starred: z.boolean().optional().describe("Filter by starred projects"),
statistics: z.boolean().optional().describe("Include project statistics"), statistics: z.boolean().optional().describe("Include project statistics"),
with_custom_attributes: z.boolean().optional().describe("Include custom attributes"), with_custom_attributes: z
with_security_reports: z.boolean().optional().describe("Include security reports") .boolean()
.optional()
.describe("Include custom attributes"),
with_security_reports: z
.boolean()
.optional()
.describe("Include security reports"),
});
// Add wiki operation schemas
export const ListWikiPagesSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
page: z.number().optional().describe("Page number for pagination"),
per_page: z.number().optional().describe("Number of items per page"),
});
export const GetWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
slug: z.string().describe("URL-encoded slug of the wiki page"),
});
export const CreateWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
title: z.string().describe("Title of the wiki page"),
content: z.string().describe("Content of the wiki page"),
format: z
.string()
.optional()
.describe("Content format, e.g., markdown, rdoc"),
});
export const UpdateWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
slug: z.string().describe("URL-encoded slug of the wiki page"),
title: z.string().optional().describe("New title of the wiki page"),
content: z.string().optional().describe("New content of the wiki page"),
format: z
.string()
.optional()
.describe("Content format, e.g., markdown, rdoc"),
});
export const DeleteWikiPageSchema = z.object({
project_id: z.string().describe("Project ID or URL-encoded path"),
slug: z.string().describe("URL-encoded slug of the wiki page"),
});
// Define wiki response schemas
export const GitLabWikiPageSchema = z.object({
title: z.string(),
slug: z.string(),
format: z.string(),
content: z.string(),
created_at: z.string().optional(),
updated_at: z.string().optional(),
});
// Merge Request Thread position schema - used for diff notes
export const MergeRequestThreadPositionSchema = z.object({
base_sha: z.string().describe("Base commit SHA in the source branch"),
head_sha: z.string().describe("SHA referencing HEAD of the source branch"),
start_sha: z.string().describe("SHA referencing the start commit of the source branch"),
position_type: z.enum(["text", "image", "file"]).describe("Type of position reference"),
new_path: z.string().optional().describe("File path after change"),
old_path: z.string().optional().describe("File path before change"),
new_line: z.number().nullable().optional().describe("Line number after change"),
old_line: z.number().nullable().optional().describe("Line number before change"),
width: z.number().optional().describe("Width of the image (for image diffs)"),
height: z.number().optional().describe("Height of the image (for image diffs)"),
x: z.number().optional().describe("X coordinate on the image (for image diffs)"),
y: z.number().optional().describe("Y coordinate on the image (for image diffs)"),
});
// Schema for creating a new merge request thread
export const CreateMergeRequestThreadSchema = ProjectParamsSchema.extend({
merge_request_iid: z.number().describe("The IID of a merge request"),
body: z.string().describe("The content of the thread"),
position: MergeRequestThreadPositionSchema.optional().describe("Position when creating a diff note"),
created_at: z.string().optional().describe("Date the thread was created at (ISO 8601 format)"),
}); });
// Export types // Export types
export type GitLabAuthor = z.infer<typeof GitLabAuthorSchema>; export type GitLabAuthor = z.infer<typeof GitLabAuthorSchema>;
export type GitLabFork = z.infer<typeof GitLabForkSchema>; export type GitLabFork = z.infer<typeof GitLabForkSchema>;
export type GitLabIssue = z.infer<typeof GitLabIssueSchema>; export type GitLabIssue = z.infer<typeof GitLabIssueSchema>;
export type GitLabIssueWithLinkDetails = z.infer<typeof GitLabIssueWithLinkDetailsSchema>; export type GitLabIssueWithLinkDetails = z.infer<
typeof GitLabIssueWithLinkDetailsSchema
>;
export type GitLabMergeRequest = z.infer<typeof GitLabMergeRequestSchema>; export type GitLabMergeRequest = z.infer<typeof GitLabMergeRequestSchema>;
export type GitLabRepository = z.infer<typeof GitLabRepositorySchema>; export type GitLabRepository = z.infer<typeof GitLabRepositorySchema>;
export type GitLabFileContent = z.infer<typeof GitLabFileContentSchema>; export type GitLabFileContent = z.infer<typeof GitLabFileContentSchema>;
export type GitLabDirectoryContent = z.infer<typeof GitLabDirectoryContentSchema>; export type GitLabDirectoryContent = z.infer<
typeof GitLabDirectoryContentSchema
>;
export type GitLabContent = z.infer<typeof GitLabContentSchema>; export type GitLabContent = z.infer<typeof GitLabContentSchema>;
export type FileOperation = z.infer<typeof FileOperationSchema>; export type FileOperation = z.infer<typeof FileOperationSchema>;
export type GitLabTree = z.infer<typeof GitLabTreeSchema>; export type GitLabTree = z.infer<typeof GitLabTreeSchema>;
export type GitLabCommit = z.infer<typeof GitLabCommitSchema>; export type GitLabCommit = z.infer<typeof GitLabCommitSchema>;
export type GitLabReference = z.infer<typeof GitLabReferenceSchema>; export type GitLabReference = z.infer<typeof GitLabReferenceSchema>;
export type CreateRepositoryOptions = z.infer<typeof CreateRepositoryOptionsSchema>; export type CreateRepositoryOptions = z.infer<
typeof CreateRepositoryOptionsSchema
>;
export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>; export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>;
export type CreateMergeRequestOptions = z.infer<typeof CreateMergeRequestOptionsSchema>; export type CreateMergeRequestOptions = z.infer<
typeof CreateMergeRequestOptionsSchema
>;
export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>; export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>;
export type GitLabCreateUpdateFileResponse = z.infer<typeof GitLabCreateUpdateFileResponseSchema>; export type GitLabCreateUpdateFileResponse = z.infer<
typeof GitLabCreateUpdateFileResponseSchema
>;
export type GitLabSearchResponse = z.infer<typeof GitLabSearchResponseSchema>; export type GitLabSearchResponse = z.infer<typeof GitLabSearchResponseSchema>;
export type GitLabMergeRequestDiff = z.infer<typeof GitLabMergeRequestDiffSchema>; export type GitLabMergeRequestDiff = z.infer<
typeof GitLabMergeRequestDiffSchema
>;
export type CreateNoteOptions = z.infer<typeof CreateNoteSchema>; export type CreateNoteOptions = z.infer<typeof CreateNoteSchema>;
export type GitLabIssueLink = z.infer<typeof GitLabIssueLinkSchema>; export type GitLabIssueLink = z.infer<typeof GitLabIssueLinkSchema>;
export type ListIssueDiscussionsOptions = z.infer<typeof ListIssueDiscussionsSchema>;
export type UpdateIssueNoteOptions = z.infer<typeof UpdateIssueNoteSchema>;
export type CreateIssueNoteOptions = z.infer<typeof CreateIssueNoteSchema>;
export type GitLabNamespace = z.infer<typeof GitLabNamespaceSchema>; export type GitLabNamespace = z.infer<typeof GitLabNamespaceSchema>;
export type GitLabNamespaceExistsResponse = z.infer<typeof GitLabNamespaceExistsResponseSchema>; export type GitLabNamespaceExistsResponse = z.infer<
typeof GitLabNamespaceExistsResponseSchema
>;
export type GitLabProject = z.infer<typeof GitLabProjectSchema>; export type GitLabProject = z.infer<typeof GitLabProjectSchema>;
export type GitLabLabel = z.infer<typeof GitLabLabelSchema>; export type GitLabLabel = z.infer<typeof GitLabLabelSchema>;
export type ListWikiPagesOptions = z.infer<typeof ListWikiPagesSchema>;
export type GetWikiPageOptions = z.infer<typeof GetWikiPageSchema>;
export type CreateWikiPageOptions = z.infer<typeof CreateWikiPageSchema>;
export type UpdateWikiPageOptions = z.infer<typeof UpdateWikiPageSchema>;
export type DeleteWikiPageOptions = z.infer<typeof DeleteWikiPageSchema>;
export type GitLabWikiPage = z.infer<typeof GitLabWikiPageSchema>;
export type GitLabTreeItem = z.infer<typeof GitLabTreeItemSchema>;
export type GetRepositoryTreeOptions = z.infer<typeof GetRepositoryTreeSchema>;
export type MergeRequestThreadPosition = z.infer<typeof MergeRequestThreadPositionSchema>;
export type CreateMergeRequestThreadOptions = z.infer<typeof CreateMergeRequestThreadSchema>;
export type CreateMergeRequestNoteOptions = z.infer<typeof CreateMergeRequestNoteSchema>;
export type GitLabPipelineJob = z.infer<typeof GitLabPipelineJobSchema>;
export type GitLabPipeline = z.infer<typeof GitLabPipelineSchema>;
export type ListPipelinesOptions = z.infer<typeof ListPipelinesSchema>;
export type GetPipelineOptions = z.infer<typeof GetPipelineSchema>;
export type ListPipelineJobsOptions = z.infer<typeof ListPipelineJobsSchema>;

View File

@ -0,0 +1,53 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function main() {
const repoRoot = path.resolve(__dirname, '..');
const indexPath = path.join(repoRoot, 'index.ts');
const readmePath = path.join(repoRoot, 'README.md');
// 1. Read index.ts
const code = fs.readFileSync(indexPath, 'utf-8');
// 2. Extract allTools array block
const match = code.match(/const allTools = \[([\s\S]*?)\];/);
if (!match) {
console.error('Unable to locate allTools array in index.ts');
process.exit(1);
}
const toolsBlock = match[1];
// 3. Parse tool entries
const toolRegex = /name:\s*"([^"]+)",[\s\S]*?description:\s*"([^"]+)"/g;
const tools: { name: string; description: string }[] = [];
let m: RegExpExecArray | null;
while ((m = toolRegex.exec(toolsBlock)) !== null) {
tools.push({ name: m[1], description: m[2] });
}
// 4. Generate markdown
const lines = tools.map((tool, index) => {
return `${index + 1}. \`${tool.name}\` - ${tool.description}`;
});
const markdown = lines.join('\n');
// 5. Read README.md and replace between markers
const readme = fs.readFileSync(readmePath, 'utf-8');
const updated = readme.replace(
/<!-- TOOLS-START -->([\s\S]*?)<!-- TOOLS-END -->/,
`<!-- TOOLS-START -->\n${markdown}\n<!-- TOOLS-END -->`
);
// 6. Write back
fs.writeFileSync(readmePath, updated, 'utf-8');
console.log('README.md tools section updated.');
}
main().catch(err => {
console.error(err);
process.exit(1);
});