@zereight/mcp-gitlab 2.1.12 → 2.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -495,157 +495,158 @@ Register the skill directory in your AI client to get optimal tool usage guidanc
495
495
  2. `create_or_update_file` - Create or update a single file in a GitLab project
496
496
  3. `search_repositories` - Search for GitLab projects
497
497
  4. `create_repository` - Create a new GitLab project
498
- 5. `get_file_contents` - Get the contents of a file or directory from a GitLab project
499
- 6. `push_files` - Push multiple files to a GitLab project in a single commit
500
- 7. `create_issue` - Create a new issue in a GitLab project
501
- 8. `create_merge_request` - Create a new merge request in a GitLab project
502
- 9. `fork_repository` - Fork a GitLab project to your account or specified namespace
503
- 10. `create_branch` - Create a new branch in a GitLab project
504
- 11. `get_merge_request` - Get details of a merge request with compact deployment summary, behind-count, commit addition summary, and approval summary (Either mergeRequestIid or branchName must be provided)
505
- 12. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)
506
- 13. `list_merge_request_diffs` - List merge request diffs with pagination support (Either mergeRequestIid or branchName must be provided)
507
- 14. `get_merge_request_conflicts` - Get the conflicts of a merge request in a GitLab project
508
- 15. `list_merge_request_changed_files` - STEP 1 of code review workflow. Returns ONLY the list of changed file paths in a merge request — WITHOUT diff content. Call this first to get file paths, then call get_merge_request_file_diff with multiple files in a single batched call (recommended 3-5 files per call). Supports excluded_file_patterns filtering using regex. (Either mergeRequestIid or branchName must be provided)
509
- 16. `get_merge_request_file_diff` - STEP 2 of code review workflow. Get diffs for one or more files from a merge request. Call list_merge_request_changed_files first, then pass them as an array to fetch diffs efficiently. Batching multiple files (recommended 3-5) is supported. (Either mergeRequestIid or branchName must be provided)
510
- 17. `list_merge_request_versions` - List all versions of a merge request
511
- 18. `get_merge_request_version` - Get a specific version of a merge request
512
- 19. `get_branch_diffs` - Get the changes/diffs between two branches or commits in a GitLab project
513
- 20. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided)
514
- 21. `create_note` - Create a new note (comment) to an issue or merge request
515
- 22. `create_merge_request_thread` - Create a new thread on a merge request
516
- 23. `mr_discussions` - List discussion items for a merge request
517
- 24. `resolve_merge_request_thread` - Resolve a thread on a merge request
518
- 25. `update_merge_request_note` - Modify an existing merge request thread note
519
- 26. `create_merge_request_note` - Add a new note to an existing merge request thread
520
- 27. `delete_merge_request_discussion_note` - Delete a discussion note on a merge request
521
- 28. `update_merge_request_discussion_note` - Update a discussion note on a merge request
522
- 29. `create_merge_request_discussion_note` - Add a new discussion note to an existing merge request thread
523
- 30. `delete_merge_request_note` - Delete an existing merge request note
524
- 31. `get_merge_request_note` - Get a specific note for a merge request
525
- 32. `get_merge_request_notes` - List notes for a merge request
526
- 33. `get_draft_note` - Get a single draft note from a merge request
527
- 34. `list_draft_notes` - List draft notes for a merge request
528
- 35. `create_draft_note` - Create a draft note for a merge request
529
- 36. `update_draft_note` - Update an existing draft note
530
- 37. `delete_draft_note` - Delete a draft note
531
- 38. `publish_draft_note` - Publish a single draft note
532
- 39. `bulk_publish_draft_notes` - Publish all draft notes for a merge request
533
- 40. `list_merge_requests` - List merge requests globally or in a specific GitLab project with filtering options (project_id is now optional)
534
- 41. `approve_merge_request` - Approve a merge request (requires appropriate permissions)
535
- 42. `unapprove_merge_request` - Unapprove a previously approved merge request
536
- 43. `get_merge_request_approval_state` - Get merge request approval details including approvers (uses `approval_state` when available, otherwise falls back to `approvals`)
537
- 44. `update_issue_note` - Modify an existing issue thread note
538
- 45. `create_issue_note` - Add a new note to an existing issue thread
539
- 46. `list_issues` - List issues (default: created by current user only; use scope='all' for all accessible issues)
540
- 47. `my_issues` - List issues assigned to the authenticated user (defaults to open issues)
541
- 48. `get_issue` - Get details of a specific issue in a GitLab project
542
- 49. `update_issue` - Update an issue in a GitLab project
543
- 50. `update_issue_description_patch` - Apply a patch (search/replace or unified diff) to an issue description. Reduces token usage by sending only the change instead of the full description. Supports `dry_run` to preview and `create_note` to summarize.
544
- 51. `delete_issue` - Delete an issue from a GitLab project
545
- 52. `list_todos` - List GitLab to-do items for the current user
546
- 53. `mark_todo_done` - Mark a GitLab to-do item as done
547
- 54. `mark_all_todos_done` - Mark all pending GitLab to-do items as done for the current user
548
- 55. `list_issue_links` - List all issue links for a specific issue
549
- 56. `list_issue_discussions` - List discussions for an issue in a GitLab project
550
- 57. `get_issue_link` - Get a specific issue link
551
- 58. `create_issue_link` - Create an issue link between two issues
552
- 59. `delete_issue_link` - Delete an issue link
553
- 60. `list_namespaces` - List all namespaces available to the current user
554
- 61. `get_namespace` - Get details of a namespace by ID or path
555
- 62. `verify_namespace` - Verify if a namespace path exists
556
- 63. `get_project` - Get details of a specific project
557
- 64. `list_projects` - List projects accessible by the current user
558
- 65. `list_project_members` - List members of a GitLab project
559
- 66. `list_group_projects` - List projects in a GitLab group with filtering options
560
- 67. `list_group_iterations` - List group iterations with filtering options
561
- 68. `list_labels` - List labels for a project
562
- 69. `get_label` - Get a single label from a project
563
- 70. `create_label` - Create a new label in a project
564
- 71. `update_label` - Update an existing label in a project
565
- 72. `delete_label` - Delete a label from a project
566
- 73. `list_pipelines` - List pipelines in a GitLab project with filtering options
567
- 74. `get_pipeline` - Get details of a specific pipeline in a GitLab project
568
- 75. `list_pipeline_jobs` - List all jobs in a specific pipeline
569
- 76. `list_pipeline_trigger_jobs` - List all trigger jobs (bridges) in a specific pipeline that trigger downstream pipelines
570
- 77. `get_pipeline_job` - Get details of a GitLab pipeline job number
571
- 78. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job with optional pagination to limit context window usage
572
- 79. `validate_ci_lint` - Validate provided GitLab CI/CD YAML content for a project
573
- 80. `validate_project_ci_lint` - Validate an existing `.gitlab-ci.yml` configuration for a project
574
- 81. `create_pipeline` - Create a new pipeline for a branch or tag
575
- 82. `retry_pipeline` - Retry a failed or canceled pipeline
576
- 83. `cancel_pipeline` - Cancel a running pipeline
577
- 84. `play_pipeline_job` - Run a manual pipeline job
578
- 85. `retry_pipeline_job` - Retry a failed or canceled pipeline job
579
- 86. `cancel_pipeline_job` - Cancel a running pipeline job
580
- 87. `list_deployments` - List deployments in a GitLab project with filtering options
581
- 88. `get_deployment` - Get details of a specific deployment in a GitLab project
582
- 89. `list_environments` - List environments in a GitLab project
583
- 90. `get_environment` - Get details of a specific environment in a GitLab project
584
- 91. `list_job_artifacts` - List artifact files in a job's artifacts archive. Returns file names, paths, types, and sizes
585
- 92. `download_job_artifacts` - Download the entire artifact archive (zip) for a job to a local path. Returns the saved file path
586
- 93. `get_job_artifact_file` - Get the content of a single file from a job's artifacts by its path within the archive
587
- 94. `list_milestones` - List milestones in a GitLab project with filtering options
588
- 95. `get_milestone` - Get details of a specific milestone
589
- 96. `create_milestone` - Create a new milestone in a GitLab project
590
- 97. `edit_milestone` - Edit an existing milestone in a GitLab project
591
- 98. `delete_milestone` - Delete a milestone from a GitLab project
592
- 99. `get_milestone_issue` - Get issues associated with a specific milestone
593
- 100. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone
594
- 101. `promote_milestone` - Promote a milestone to the next stage
595
- 102. `get_milestone_burndown_events` - Get burndown events for a specific milestone
596
- 103. `list_wiki_pages` - List wiki pages in a GitLab project
597
- 104. `get_wiki_page` - Get details of a specific wiki page
598
- 105. `create_wiki_page` - Create a new wiki page in a GitLab project
599
- 106. `update_wiki_page` - Update an existing wiki page in a GitLab project
600
- 107. `delete_wiki_page` - Delete a wiki page from a GitLab project
601
- 108. `list_group_wiki_pages` - List wiki pages in a GitLab group
602
- 109. `get_group_wiki_page` - Get details of a specific group wiki page
603
- 110. `create_group_wiki_page` - Create a new wiki page in a GitLab group
604
- 111. `update_group_wiki_page` - Update an existing wiki page in a GitLab group
605
- 112. `delete_group_wiki_page` - Delete a wiki page from a GitLab group
606
- 113. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
607
- 114. `list_commits` - List repository commits with filtering options
608
- 115. `get_commit` - Get details of a specific commit
609
- 116. `get_commit_diff` - Get changes/diffs of a specific commit
610
- 117. `list_commit_statuses` - List statuses for a specific commit
611
- 118. `create_commit_status` - Create or update the status of a specific commit
612
- 119. `list_releases` - List all releases for a project
613
- 120. `get_release` - Get a release by tag name
614
- 121. `create_release` - Create a new release in a GitLab project
615
- 122. `update_release` - Update an existing release in a GitLab project
616
- 123. `delete_release` - Delete a release from a GitLab project (does not delete the associated tag)
617
- 124. `create_release_evidence` - Create release evidence for an existing release (GitLab Premium/Ultimate only)
618
- 125. `download_release_asset` - Download a release asset file by direct asset path
619
- 126. `list_tags` - List repository tags with filtering and pagination support
620
- 127. `get_tag` - Get details of a specific repository tag
621
- 128. `create_tag` - Create a new tag in the repository
622
- 129. `delete_tag` - Delete a tag from the repository
623
- 130. `get_tag_signature` - Get the signature of a signed tag
624
- 131. `get_users` - Get GitLab user details by usernames
625
- 132. `list_events` - List all events for the currently authenticated user
626
- 133. `get_project_events` - List all visible events for a specified project
627
- 134. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
628
- 135. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
629
- 136. `get_work_item` - Get a single work item with full details including status, hierarchy (parent/children), type, labels, assignees, and all widgets
630
- 137. `list_work_items` - List work items in a project with filters (type, state, search, assignees, labels). Returns items with status and hierarchy info
631
- 138. `create_work_item` - Create a new work item (issue, task, incident, test_case, epic, key_result, objective, requirement, ticket). Supports setting title, description, labels, assignees, weight, parent, health status, start/due dates, milestone, and confidentiality
632
- 139. `update_work_item` - Update a work item. Can modify title, description, labels, assignees, weight, state, status, parent hierarchy, children, health status, start/due dates, milestone, confidentiality, linked items, and custom fields
633
- 140. `convert_work_item_type` - Convert a work item to a different type (e.g. issue to task, task to incident)
634
- 141. `list_work_item_statuses` - List available statuses for a work item type in a project. Requires GitLab Premium/Ultimate with configurable statuses
635
- 142. `list_custom_field_definitions` - List available custom field definitions for a work item type in a project. Returns field names, types, and IDs needed for setting custom fields via update_work_item
636
- 143. `move_work_item` - Move a work item (issue, task, etc.) to a different project. Uses GitLab GraphQL issueMove mutation
637
- 144. `list_work_item_notes` - List notes and discussions on a work item. Returns threaded discussions with author, body, timestamps, and system/internal flags
638
- 145. `create_work_item_note` - Add a note/comment to a work item. Supports Markdown, internal notes, and threaded replies
639
- 146. `get_timeline_events` - List timeline events for an incident. Returns chronological events with notes, timestamps, and tags
640
- 147. `create_timeline_event` - Create a timeline event on an incident. Supports tags: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'
641
- 148. `list_webhooks` - List all configured webhooks for a GitLab project or group. Provide either project_id or group_id
642
- 149. `list_webhook_events` - List recent webhook events (past 7 days) for a project or group webhook. Use summary mode for overview, then get_webhook_event for full details
643
- 150. `get_webhook_event` - Get full details of a specific webhook event by ID, including request/response payloads
644
- 151. `search_code` - Search for code across all projects on the GitLab instance (requires advanced search or exact code search to be enabled)
645
- 152. `search_project_code` - Search for code within a specific GitLab project (requires advanced search or exact code search to be enabled)
646
- 153. `search_group_code` - Search for code within a specific GitLab group (requires advanced search or exact code search to be enabled)
647
- 154. `execute_graphql` - Execute a GitLab GraphQL query
648
- 155. `list_merge_request_pipelines` - List pipelines for a merge request with pagination support
498
+ 5. `create_group` - Create a new GitLab group or subgroup (name, path, description, visibility, and optional parent_id)
499
+ 6. `get_file_contents` - Get the contents of a file or directory from a GitLab project
500
+ 7. `push_files` - Push multiple files to a GitLab project in a single commit
501
+ 8. `create_issue` - Create a new issue in a GitLab project
502
+ 9. `create_merge_request` - Create a new merge request in a GitLab project
503
+ 10. `fork_repository` - Fork a GitLab project to your account or specified namespace
504
+ 11. `create_branch` - Create a new branch in a GitLab project
505
+ 12. `get_merge_request` - Get details of a merge request with compact deployment summary, behind-count, commit addition summary, and approval summary (Either mergeRequestIid or branchName must be provided)
506
+ 13. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided)
507
+ 14. `list_merge_request_diffs` - List merge request diffs with pagination support (Either mergeRequestIid or branchName must be provided)
508
+ 15. `get_merge_request_conflicts` - Get the conflicts of a merge request in a GitLab project
509
+ 16. `list_merge_request_changed_files` - STEP 1 of code review workflow. Returns ONLY the list of changed file paths in a merge request — WITHOUT diff content. Call this first to get file paths, then call get_merge_request_file_diff with multiple files in a single batched call (recommended 3-5 files per call). Supports excluded_file_patterns filtering using regex. (Either mergeRequestIid or branchName must be provided)
510
+ 17. `get_merge_request_file_diff` - STEP 2 of code review workflow. Get diffs for one or more files from a merge request. Call list_merge_request_changed_files first, then pass them as an array to fetch diffs efficiently. Batching multiple files (recommended 3-5) is supported. (Either mergeRequestIid or branchName must be provided)
511
+ 18. `list_merge_request_versions` - List all versions of a merge request
512
+ 19. `get_merge_request_version` - Get a specific version of a merge request
513
+ 20. `get_branch_diffs` - Get the changes/diffs between two branches or commits in a GitLab project
514
+ 21. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided)
515
+ 22. `create_note` - Create a new note (comment) to an issue or merge request
516
+ 23. `create_merge_request_thread` - Create a new thread on a merge request
517
+ 24. `mr_discussions` - List discussion items for a merge request
518
+ 25. `resolve_merge_request_thread` - Resolve a thread on a merge request
519
+ 26. `update_merge_request_note` - Modify an existing merge request thread note
520
+ 27. `create_merge_request_note` - Add a new note to an existing merge request thread
521
+ 28. `delete_merge_request_discussion_note` - Delete a discussion note on a merge request
522
+ 29. `update_merge_request_discussion_note` - Update a discussion note on a merge request
523
+ 30. `create_merge_request_discussion_note` - Add a new discussion note to an existing merge request thread
524
+ 31. `delete_merge_request_note` - Delete an existing merge request note
525
+ 32. `get_merge_request_note` - Get a specific note for a merge request
526
+ 33. `get_merge_request_notes` - List notes for a merge request
527
+ 34. `get_draft_note` - Get a single draft note from a merge request
528
+ 35. `list_draft_notes` - List draft notes for a merge request
529
+ 36. `create_draft_note` - Create a draft note for a merge request
530
+ 37. `update_draft_note` - Update an existing draft note
531
+ 38. `delete_draft_note` - Delete a draft note
532
+ 39. `publish_draft_note` - Publish a single draft note
533
+ 40. `bulk_publish_draft_notes` - Publish all draft notes for a merge request
534
+ 41. `list_merge_requests` - List merge requests globally or in a specific GitLab project with filtering options (project_id is now optional)
535
+ 42. `approve_merge_request` - Approve a merge request (requires appropriate permissions)
536
+ 43. `unapprove_merge_request` - Unapprove a previously approved merge request
537
+ 44. `get_merge_request_approval_state` - Get merge request approval details including approvers (uses `approval_state` when available, otherwise falls back to `approvals`)
538
+ 45. `update_issue_note` - Modify an existing issue thread note
539
+ 46. `create_issue_note` - Add a new note to an existing issue thread
540
+ 47. `list_issues` - List issues (default: created by current user only; use scope='all' for all accessible issues)
541
+ 48. `my_issues` - List issues assigned to the authenticated user (defaults to open issues)
542
+ 49. `get_issue` - Get details of a specific issue in a GitLab project
543
+ 50. `update_issue` - Update an issue in a GitLab project
544
+ 51. `update_issue_description_patch` - Apply a patch (search/replace or unified diff) to an issue description. Reduces token usage by sending only the change instead of the full description. Supports `dry_run` to preview and `create_note` to summarize.
545
+ 52. `delete_issue` - Delete an issue from a GitLab project
546
+ 53. `list_todos` - List GitLab to-do items for the current user
547
+ 54. `mark_todo_done` - Mark a GitLab to-do item as done
548
+ 55. `mark_all_todos_done` - Mark all pending GitLab to-do items as done for the current user
549
+ 56. `list_issue_links` - List all issue links for a specific issue
550
+ 57. `list_issue_discussions` - List discussions for an issue in a GitLab project
551
+ 58. `get_issue_link` - Get a specific issue link
552
+ 59. `create_issue_link` - Create an issue link between two issues
553
+ 60. `delete_issue_link` - Delete an issue link
554
+ 61. `list_namespaces` - List all namespaces available to the current user
555
+ 62. `get_namespace` - Get details of a namespace by ID or path
556
+ 63. `verify_namespace` - Verify if a namespace path exists
557
+ 64. `get_project` - Get details of a specific project
558
+ 65. `list_projects` - List projects accessible by the current user
559
+ 66. `list_project_members` - List members of a GitLab project
560
+ 67. `list_group_projects` - List projects in a GitLab group with filtering options
561
+ 68. `list_group_iterations` - List group iterations with filtering options
562
+ 69. `list_labels` - List labels for a project
563
+ 70. `get_label` - Get a single label from a project
564
+ 71. `create_label` - Create a new label in a project
565
+ 72. `update_label` - Update an existing label in a project
566
+ 73. `delete_label` - Delete a label from a project
567
+ 74. `list_pipelines` - List pipelines in a GitLab project with filtering options
568
+ 75. `get_pipeline` - Get details of a specific pipeline in a GitLab project
569
+ 76. `list_pipeline_jobs` - List all jobs in a specific pipeline
570
+ 77. `list_pipeline_trigger_jobs` - List all trigger jobs (bridges) in a specific pipeline that trigger downstream pipelines
571
+ 78. `get_pipeline_job` - Get details of a GitLab pipeline job number
572
+ 79. `get_pipeline_job_output` - Get the output/trace of a GitLab pipeline job with optional pagination to limit context window usage
573
+ 80. `validate_ci_lint` - Validate provided GitLab CI/CD YAML content for a project
574
+ 81. `validate_project_ci_lint` - Validate an existing `.gitlab-ci.yml` configuration for a project
575
+ 82. `create_pipeline` - Create a new pipeline for a branch or tag
576
+ 83. `retry_pipeline` - Retry a failed or canceled pipeline
577
+ 84. `cancel_pipeline` - Cancel a running pipeline
578
+ 85. `play_pipeline_job` - Run a manual pipeline job
579
+ 86. `retry_pipeline_job` - Retry a failed or canceled pipeline job
580
+ 87. `cancel_pipeline_job` - Cancel a running pipeline job
581
+ 88. `list_deployments` - List deployments in a GitLab project with filtering options
582
+ 89. `get_deployment` - Get details of a specific deployment in a GitLab project
583
+ 90. `list_environments` - List environments in a GitLab project
584
+ 91. `get_environment` - Get details of a specific environment in a GitLab project
585
+ 92. `list_job_artifacts` - List artifact files in a job's artifacts archive. Returns file names, paths, types, and sizes
586
+ 93. `download_job_artifacts` - Download the entire artifact archive (zip) for a job to a local path. Returns the saved file path
587
+ 94. `get_job_artifact_file` - Get the content of a single file from a job's artifacts by its path within the archive
588
+ 95. `list_milestones` - List milestones in a GitLab project with filtering options
589
+ 96. `get_milestone` - Get details of a specific milestone
590
+ 97. `create_milestone` - Create a new milestone in a GitLab project
591
+ 98. `edit_milestone` - Edit an existing milestone in a GitLab project
592
+ 99. `delete_milestone` - Delete a milestone from a GitLab project
593
+ 100. `get_milestone_issue` - Get issues associated with a specific milestone
594
+ 101. `get_milestone_merge_requests` - Get merge requests associated with a specific milestone
595
+ 102. `promote_milestone` - Promote a milestone to the next stage
596
+ 103. `get_milestone_burndown_events` - Get burndown events for a specific milestone
597
+ 104. `list_wiki_pages` - List wiki pages in a GitLab project
598
+ 105. `get_wiki_page` - Get details of a specific wiki page
599
+ 106. `create_wiki_page` - Create a new wiki page in a GitLab project
600
+ 107. `update_wiki_page` - Update an existing wiki page in a GitLab project
601
+ 108. `delete_wiki_page` - Delete a wiki page from a GitLab project
602
+ 109. `list_group_wiki_pages` - List wiki pages in a GitLab group
603
+ 110. `get_group_wiki_page` - Get details of a specific group wiki page
604
+ 111. `create_group_wiki_page` - Create a new wiki page in a GitLab group
605
+ 112. `update_group_wiki_page` - Update an existing wiki page in a GitLab group
606
+ 113. `delete_group_wiki_page` - Delete a wiki page from a GitLab group
607
+ 114. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories)
608
+ 115. `list_commits` - List repository commits with filtering options
609
+ 116. `get_commit` - Get details of a specific commit
610
+ 117. `get_commit_diff` - Get changes/diffs of a specific commit
611
+ 118. `list_commit_statuses` - List statuses for a specific commit
612
+ 119. `create_commit_status` - Create or update the status of a specific commit
613
+ 120. `list_releases` - List all releases for a project
614
+ 121. `get_release` - Get a release by tag name
615
+ 122. `create_release` - Create a new release in a GitLab project
616
+ 123. `update_release` - Update an existing release in a GitLab project
617
+ 124. `delete_release` - Delete a release from a GitLab project (does not delete the associated tag)
618
+ 125. `create_release_evidence` - Create release evidence for an existing release (GitLab Premium/Ultimate only)
619
+ 126. `download_release_asset` - Download a release asset file by direct asset path
620
+ 127. `list_tags` - List repository tags with filtering and pagination support
621
+ 128. `get_tag` - Get details of a specific repository tag
622
+ 129. `create_tag` - Create a new tag in the repository
623
+ 130. `delete_tag` - Delete a tag from the repository
624
+ 131. `get_tag_signature` - Get the signature of a signed tag
625
+ 132. `get_users` - Get GitLab user details by usernames
626
+ 133. `list_events` - List all events for the currently authenticated user
627
+ 134. `get_project_events` - List all visible events for a specified project
628
+ 135. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
629
+ 136. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
630
+ 137. `get_work_item` - Get a single work item with full details including status, hierarchy (parent/children), type, labels, assignees, and all widgets
631
+ 138. `list_work_items` - List work items in a project with filters (type, state, search, assignees, labels). Returns items with status and hierarchy info
632
+ 139. `create_work_item` - Create a new work item (issue, task, incident, test_case, epic, key_result, objective, requirement, ticket). Supports setting title, description, labels, assignees, weight, parent, health status, start/due dates, milestone, and confidentiality
633
+ 140. `update_work_item` - Update a work item. Can modify title, description, labels, assignees, weight, state, status, parent hierarchy, children, health status, start/due dates, milestone, confidentiality, linked items, and custom fields
634
+ 141. `convert_work_item_type` - Convert a work item to a different type (e.g. issue to task, task to incident)
635
+ 142. `list_work_item_statuses` - List available statuses for a work item type in a project. Requires GitLab Premium/Ultimate with configurable statuses
636
+ 143. `list_custom_field_definitions` - List available custom field definitions for a work item type in a project. Returns field names, types, and IDs needed for setting custom fields via update_work_item
637
+ 144. `move_work_item` - Move a work item (issue, task, etc.) to a different project. Uses GitLab GraphQL issueMove mutation
638
+ 145. `list_work_item_notes` - List notes and discussions on a work item. Returns threaded discussions with author, body, timestamps, and system/internal flags
639
+ 146. `create_work_item_note` - Add a note/comment to a work item. Supports Markdown, internal notes, and threaded replies
640
+ 147. `get_timeline_events` - List timeline events for an incident. Returns chronological events with notes, timestamps, and tags
641
+ 148. `create_timeline_event` - Create a timeline event on an incident. Supports tags: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'
642
+ 149. `list_webhooks` - List all configured webhooks for a GitLab project or group. Provide either project_id or group_id
643
+ 150. `list_webhook_events` - List recent webhook events (past 7 days) for a project or group webhook. Use summary mode for overview, then get_webhook_event for full details
644
+ 151. `get_webhook_event` - Get full details of a specific webhook event by ID, including request/response payloads
645
+ 152. `search_code` - Search for code across all projects on the GitLab instance (requires advanced search or exact code search to be enabled)
646
+ 153. `search_project_code` - Search for code within a specific GitLab project (requires advanced search or exact code search to be enabled)
647
+ 154. `search_group_code` - Search for code within a specific GitLab group (requires advanced search or exact code search to be enabled)
648
+ 155. `execute_graphql` - Execute a GitLab GraphQL query
649
+ 156. `list_merge_request_pipelines` - List pipelines for a merge request with pagination support
649
650
 
650
651
  <!-- TOOLS-END -->
651
652
 
package/build/index.js CHANGED
@@ -26,12 +26,12 @@ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middlew
26
26
  import { GitLabClientPool } from "./gitlab-client-pool.js";
27
27
  import { allTools, readOnlyTools, destructiveTools, parseEnabledToolsets, parseIndividualTools, buildFeatureFlagOverrides, isToolInEnabledToolset, TOOLSET_DEFINITIONS, ALL_TOOLSET_IDS, } from "./tools/registry.js";
28
28
  import { BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, // Added
29
- CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateRepositorySchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetBranchSchema, GetCommitDiffSchema, GetCommitSchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetUserSchema, GitLabUserFullSchema, WhoAmISchema, GitLabCurrentUserSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCommitStatusSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
29
+ CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateRepositorySchema, CreateGroupSchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetBranchSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GitLabBlameEntrySchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetUserSchema, GitLabUserFullSchema, WhoAmISchema, GitLabCurrentUserSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCommitStatusSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
30
30
  // Discussion Schemas
31
31
  GitLabDiscussionNoteSchema, // Added
32
32
  GitLabDiscussionSchema,
33
33
  // Draft Notes Schemas
34
- GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListBranchesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
34
+ GitLabDraftNoteSchema, GitLabForkSchema, GitLabBranchSchema, GitLabGroupSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListBranchesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
35
35
  GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, HealthCheckSchema, } from "./schemas.js";
36
36
  import { randomUUID } from "node:crypto";
37
37
  import { pino } from "pino";
@@ -937,6 +937,14 @@ function getEffectiveProjectId(projectId) {
937
937
  }
938
938
  throw new Error("No project ID provided and GITLAB_PROJECT_ID is not set");
939
939
  }
940
+ function rejectIfProjectScopedDeployment(toolName) {
941
+ if (GITLAB_PROJECT_ID) {
942
+ throw new Error(`${toolName} is not allowed when GITLAB_PROJECT_ID is set (server is locked to a single project)`);
943
+ }
944
+ if (GITLAB_ALLOWED_PROJECT_IDS.length > 0) {
945
+ throw new Error(`${toolName} is not allowed when GITLAB_ALLOWED_PROJECT_IDS is set (server access is restricted to configured projects)`);
946
+ }
947
+ }
940
948
  /**
941
949
  * Create a fork of a GitLab project
942
950
  * 프로젝트 포크 생성 (Create a project fork)
@@ -5416,6 +5424,33 @@ async function getCommitDiff(projectId, sha, full_diff) {
5416
5424
  }
5417
5425
  return allDiffs;
5418
5426
  }
5427
+ /**
5428
+ * Get blame for a file at a specific ref.
5429
+ *
5430
+ * Wraps GitLab REST endpoint
5431
+ * GET /projects/:id/repository/files/:file_path/blame?ref=
5432
+ * Returns an array of entries; each entry has `lines` (the source lines covered)
5433
+ * and `commit` (the commit that last changed those lines: id, author, message, ...).
5434
+ *
5435
+ * @param {string} projectId - Project ID or URL-encoded path
5436
+ * @param {Omit<GetFileBlameOptions,"project_id">} options - file_path, ref, optional range_start/range_end
5437
+ * @returns {Promise<GitLabBlameEntry[]>} Blame entries in source order.
5438
+ */
5439
+ async function getFileBlame(projectId, options) {
5440
+ projectId = decodeURIComponent(projectId);
5441
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/files/${encodeURIComponent(options.file_path)}/blame`);
5442
+ url.searchParams.append("ref", options.ref);
5443
+ if (options.range_start !== undefined && options.range_end !== undefined) {
5444
+ url.searchParams.append("range[start]", options.range_start.toString());
5445
+ url.searchParams.append("range[end]", options.range_end.toString());
5446
+ }
5447
+ const response = await fetch(url.toString(), {
5448
+ ...getFetchConfig(),
5449
+ });
5450
+ await handleGitLabError(response);
5451
+ const data = await response.json();
5452
+ return z.array(GitLabBlameEntrySchema).parse(data);
5453
+ }
5419
5454
  /**
5420
5455
  * List statuses for a commit.
5421
5456
  *
@@ -5959,6 +5994,10 @@ async function handleToolCall(params) {
5959
5994
  delete args.work_item_iid;
5960
5995
  }
5961
5996
  }
5997
+ // Centralized read-only guard: reject write tools even if client bypasses list_tools filtering
5998
+ if (GITLAB_READ_ONLY_MODE && !readOnlyTools.has(params.name)) {
5999
+ throw new Error(`${params.name} is not allowed in read-only mode`);
6000
+ }
5962
6001
  logger.info({ tool: params.name, event: "tool_call_start" }, `tool_call_start: ${params.name}`);
5963
6002
  switch (params.name) {
5964
6003
  case "execute_graphql": {
@@ -6009,9 +6048,7 @@ async function handleToolCall(params) {
6009
6048
  }
6010
6049
  }
6011
6050
  case "fork_repository": {
6012
- if (GITLAB_PROJECT_ID) {
6013
- throw new Error("Direct project ID is set. So fork_repository is not allowed");
6014
- }
6051
+ rejectIfProjectScopedDeployment("fork_repository");
6015
6052
  const forkArgs = ForkRepositorySchema.parse(params.arguments);
6016
6053
  try {
6017
6054
  const forkedProject = await forkProject(forkArgs.project_id, forkArgs.namespace);
@@ -6110,15 +6147,40 @@ async function handleToolCall(params) {
6110
6147
  };
6111
6148
  }
6112
6149
  case "create_repository": {
6113
- if (GITLAB_PROJECT_ID) {
6114
- throw new Error("Direct project ID is set. So fork_repository is not allowed");
6115
- }
6150
+ rejectIfProjectScopedDeployment("create_repository");
6116
6151
  const args = CreateRepositorySchema.parse(params.arguments);
6117
6152
  const repository = await createRepository(args);
6118
6153
  return {
6119
6154
  content: [{ type: "text", text: JSON.stringify(repository, null, 2) }],
6120
6155
  };
6121
6156
  }
6157
+ case "create_group": {
6158
+ rejectIfProjectScopedDeployment("create_group");
6159
+ const args = CreateGroupSchema.parse(params.arguments);
6160
+ const url = new URL(`${getEffectiveApiUrl()}/groups`);
6161
+ const body = {
6162
+ name: args.name,
6163
+ path: args.path,
6164
+ };
6165
+ if (args.description)
6166
+ body.description = args.description;
6167
+ if (args.visibility)
6168
+ body.visibility = args.visibility;
6169
+ if (args.parent_id)
6170
+ body.parent_id = args.parent_id;
6171
+ const response = await fetch(url.toString(), {
6172
+ ...getFetchConfig(),
6173
+ method: "POST",
6174
+ headers: { ...getFetchConfig().headers, "Content-Type": "application/json" },
6175
+ body: JSON.stringify(body),
6176
+ });
6177
+ await handleGitLabError(response);
6178
+ const data = await response.json();
6179
+ const group = GitLabGroupSchema.parse(data);
6180
+ return {
6181
+ content: [{ type: "text", text: JSON.stringify(group, null, 2) }],
6182
+ };
6183
+ }
6122
6184
  case "get_file_contents": {
6123
6185
  const args = GetFileContentsSchema.parse(params.arguments);
6124
6186
  const contents = await getFileContents(args.project_id, args.file_path, args.ref);
@@ -7466,6 +7528,14 @@ async function handleToolCall(params) {
7466
7528
  content: [{ type: "text", text: JSON.stringify(diff, null, 2) }],
7467
7529
  };
7468
7530
  }
7531
+ case "get_file_blame": {
7532
+ const args = GetFileBlameSchema.parse(params.arguments);
7533
+ const { project_id, ...options } = args;
7534
+ const blame = await getFileBlame(project_id, options);
7535
+ return {
7536
+ content: [{ type: "text", text: JSON.stringify(blame, null, 2) }],
7537
+ };
7538
+ }
7469
7539
  case "list_commit_statuses": {
7470
7540
  const args = ListCommitStatusesSchema.parse(params.arguments);
7471
7541
  const { project_id, sha, ...options } = args;
package/build/schemas.js CHANGED
@@ -595,6 +595,39 @@ export const GitLabCurrentUserSchema = z.object({
595
595
  extern_uid: z.string(),
596
596
  })).optional(),
597
597
  }).passthrough();
598
+ // Group related schemas
599
+ export const CreateGroupSchema = z.object({
600
+ name: z.string().describe("The name of the group"),
601
+ path: z.string().describe("The path of the group"),
602
+ description: z.string().optional().describe("The group's description"),
603
+ visibility: z.enum(["private", "internal", "public"]).optional().describe("The group's visibility level"),
604
+ parent_id: z.coerce.number().optional().describe("The parent group ID for creating a subgroup"),
605
+ });
606
+ export const GitLabGroupSchema = z.object({
607
+ id: z.coerce.string(),
608
+ name: z.string(),
609
+ path: z.string(),
610
+ description: z.string().nullable(),
611
+ visibility: z.string().optional(),
612
+ share_with_group_lock: z.boolean().optional(),
613
+ require_two_factor_authentication: z.boolean().optional(),
614
+ two_factor_grace_period: z.number().optional(),
615
+ project_creation_level: z.string().optional(),
616
+ auto_devops_enabled: z.boolean().nullable().optional(),
617
+ subgroup_creation_level: z.string().optional(),
618
+ emails_disabled: z.boolean().nullable().optional(),
619
+ mentions_disabled: z.boolean().nullable().optional(),
620
+ lfs_enabled: z.boolean().nullable().optional(),
621
+ avatar_url: z.string().nullable().optional(),
622
+ web_url: z.string(),
623
+ request_access_enabled: z.boolean().nullable().optional(),
624
+ full_name: z.string(),
625
+ full_path: z.string(),
626
+ file_template_project_id: z.number().nullable().optional(),
627
+ parent_id: z.coerce.string().nullable().optional(),
628
+ created_at: z.string().optional(),
629
+ statistics: z.any().optional(),
630
+ });
598
631
  // Namespace related schemas
599
632
  // Base schema for project-related operations
600
633
  const ProjectParamsSchema = z.object({
@@ -2404,6 +2437,50 @@ export const GetCommitDiffSchema = z.object({
2404
2437
  .optional()
2405
2438
  .describe("Whether to return the full diff or only first page (default: false)"),
2406
2439
  });
2440
+ export const GetFileBlameSchema = z
2441
+ .object({
2442
+ project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
2443
+ file_path: z.string().describe("The full path of the file to blame, relative to repo root"),
2444
+ ref: z
2445
+ .string()
2446
+ .describe("The name of branch, tag or commit (required by GitLab blame API)"),
2447
+ range_start: z
2448
+ .coerce.number()
2449
+ .int()
2450
+ .optional()
2451
+ .describe("First line of the blame range (inclusive, 1-based). Both range[start] and range[end] must be set together."),
2452
+ range_end: z
2453
+ .coerce.number()
2454
+ .int()
2455
+ .optional()
2456
+ .describe("Last line of the blame range (inclusive, 1-based). Both range[start] and range[end] must be set together."),
2457
+ })
2458
+ .refine((v) => (v.range_start === undefined) === (v.range_end === undefined), {
2459
+ message: "range_start and range_end must be provided together (both or neither). Passing only one silently returned full-file blame on GitLab side.",
2460
+ path: ["range_end"],
2461
+ })
2462
+ .refine((v) => v.range_start === undefined ||
2463
+ v.range_end === undefined ||
2464
+ v.range_start <= v.range_end, {
2465
+ message: "range_start must be less than or equal to range_end.",
2466
+ path: ["range_start"],
2467
+ });
2468
+ export const GitLabBlameEntrySchema = z.object({
2469
+ lines: z.array(z.string()).describe("Source lines covered by this blame range"),
2470
+ commit: z
2471
+ .object({
2472
+ id: z.string(),
2473
+ parent_ids: z.array(z.string()).optional(),
2474
+ message: z.string().optional(),
2475
+ authored_date: z.string().optional(),
2476
+ author_name: z.string().optional(),
2477
+ author_email: z.string().optional(),
2478
+ committed_date: z.string().optional(),
2479
+ committer_name: z.string().optional(),
2480
+ committer_email: z.string().optional(),
2481
+ })
2482
+ .passthrough(),
2483
+ });
2407
2484
  export const ListCommitStatusesSchema = z
2408
2485
  .object({
2409
2486
  project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
@@ -0,0 +1,145 @@
1
+ import { describe, test, before, after } from "node:test";
2
+ import assert from "node:assert";
3
+ import { spawn } from "child_process";
4
+ import { MockGitLabServer, findMockServerPort } from "./utils/mock-gitlab-server.js";
5
+ const MOCK_TOKEN = "glpat-mock-token-12345";
6
+ const TEST_PROJECT_ID = "123";
7
+ const MOCK_BLAME = [
8
+ {
9
+ lines: ["line one", ""],
10
+ commit: {
11
+ id: "1111111111111111111111111111111111111111",
12
+ message: "feat: initial commit",
13
+ authored_date: "2024-01-01T00:00:00.000Z",
14
+ author_name: "Alice",
15
+ author_email: "alice@example.com",
16
+ },
17
+ },
18
+ {
19
+ lines: ["line three"],
20
+ commit: {
21
+ id: "2222222222222222222222222222222222222222",
22
+ message: "feat: add second change",
23
+ authored_date: "2024-02-02T00:00:00.000Z",
24
+ author_name: "Bob",
25
+ author_email: "bob@example.com",
26
+ },
27
+ },
28
+ ];
29
+ async function callGetFileBlame(args, env) {
30
+ return new Promise((resolve, reject) => {
31
+ const proc = spawn("node", ["build/index.js"], {
32
+ stdio: ["pipe", "pipe", "pipe"],
33
+ env: {
34
+ ...process.env,
35
+ ...env,
36
+ GITLAB_READ_ONLY_MODE: "true",
37
+ },
38
+ });
39
+ let output = "";
40
+ let errorOutput = "";
41
+ proc.stdout?.on("data", (d) => (output += d));
42
+ proc.stderr?.on("data", (d) => (errorOutput += d));
43
+ proc.on("close", (code) => {
44
+ if (code !== 0)
45
+ return reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
46
+ const line = output.split("\n").find((l) => l.startsWith("{"));
47
+ if (!line)
48
+ return reject(new Error("No JSON output found"));
49
+ try {
50
+ const response = JSON.parse(line);
51
+ if (response.error)
52
+ return reject(response.error);
53
+ const content = response.result?.content?.[0]?.text;
54
+ if (content)
55
+ return resolve(JSON.parse(content));
56
+ resolve(response.result);
57
+ }
58
+ catch (e) {
59
+ reject(e);
60
+ }
61
+ });
62
+ proc.stdin?.end(JSON.stringify({
63
+ jsonrpc: "2.0",
64
+ id: 1,
65
+ method: "tools/call",
66
+ params: { name: "get_file_blame", arguments: args },
67
+ }) + "\n");
68
+ });
69
+ }
70
+ describe("get_file_blame", () => {
71
+ let mockGitLab;
72
+ let mockGitLabUrl;
73
+ let lastQuery = {};
74
+ before(async () => {
75
+ const mockPort = await findMockServerPort(9000);
76
+ mockGitLab = new MockGitLabServer({
77
+ port: mockPort,
78
+ validTokens: [MOCK_TOKEN],
79
+ });
80
+ mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/repository/files/src%2Fexample.txt/blame`, (req, res) => {
81
+ lastQuery = req.query;
82
+ res.json(MOCK_BLAME);
83
+ });
84
+ await mockGitLab.start();
85
+ mockGitLabUrl = mockGitLab.getUrl();
86
+ });
87
+ after(async () => {
88
+ await mockGitLab.stop();
89
+ });
90
+ test("returns blame entries for a file at ref", async () => {
91
+ const blame = await callGetFileBlame({
92
+ project_id: TEST_PROJECT_ID,
93
+ file_path: "src/example.txt",
94
+ ref: "main",
95
+ }, {
96
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
97
+ GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
98
+ });
99
+ assert.ok(Array.isArray(blame), "Response should be an array");
100
+ assert.strictEqual(blame.length, 2, "Two blame entries expected");
101
+ assert.strictEqual(blame[1].commit.id, "2222222222222222222222222222222222222222", "second entry commit id matches");
102
+ assert.deepStrictEqual(blame[1].lines, ["line three"]);
103
+ assert.strictEqual(lastQuery.ref, "main", "ref propagated to GitLab API");
104
+ assert.ok(!("range[start]" in lastQuery) && !("range[end]" in lastQuery), "no range params when omitted");
105
+ });
106
+ test("passes range[start]/range[end] when both set", async () => {
107
+ await callGetFileBlame({
108
+ project_id: TEST_PROJECT_ID,
109
+ file_path: "src/example.txt",
110
+ ref: "main",
111
+ range_start: 10,
112
+ range_end: 20,
113
+ }, {
114
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
115
+ GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
116
+ });
117
+ assert.strictEqual(lastQuery["range[start]"], "10");
118
+ assert.strictEqual(lastQuery["range[end]"], "20");
119
+ });
120
+ test("rejects partial range (range_start only) at schema layer", async () => {
121
+ await assert.rejects(() => callGetFileBlame({
122
+ project_id: TEST_PROJECT_ID,
123
+ file_path: "src/example.txt",
124
+ ref: "main",
125
+ range_start: 10,
126
+ }, {
127
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
128
+ GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
129
+ }), (e) => typeof e?.message === "string" &&
130
+ e.message.includes("range_start and range_end must be provided together"));
131
+ });
132
+ test("rejects inverted range (start > end) at schema layer", async () => {
133
+ await assert.rejects(() => callGetFileBlame({
134
+ project_id: TEST_PROJECT_ID,
135
+ file_path: "src/example.txt",
136
+ ref: "main",
137
+ range_start: 20,
138
+ range_end: 10,
139
+ }, {
140
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
141
+ GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
142
+ }), (e) => typeof e?.message === "string" &&
143
+ e.message.includes("range_start must be less than or equal to range_end"));
144
+ });
145
+ });
@@ -6,7 +6,7 @@ import { describe, test, before, after } from 'node:test';
6
6
  import assert from 'node:assert';
7
7
  import { launchServer, findAvailablePort, cleanupServers, TransportMode, HOST } from './utils/server-launcher.js';
8
8
  import { MockGitLabServer, findMockServerPort } from './utils/mock-gitlab-server.js';
9
- import { StreamableHTTPTestClient } from './clients/streamable-http-client.js';
9
+ import { CustomHeaderClient } from './clients/custom-header-client.js';
10
10
  // Use the same token that will be passed via GITLAB_TOKEN_TEST environment variable
11
11
  const MOCK_TOKEN = process.env.GITLAB_TOKEN_TEST || 'glpat-mock-token-12345';
12
12
  const DEFAULT_PROJECT_ID = '123';
@@ -43,6 +43,7 @@ describe('getEffectiveProjectId', { concurrency: 1 }, () => {
43
43
  timeout: 5000,
44
44
  env: {
45
45
  STREAMABLE_HTTP: 'true',
46
+ REMOTE_AUTHORIZATION: 'true',
46
47
  GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
47
48
  GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
48
49
  GITLAB_READ_ONLY_MODE: 'true',
@@ -50,7 +51,9 @@ describe('getEffectiveProjectId', { concurrency: 1 }, () => {
50
51
  });
51
52
  servers.push(server);
52
53
  mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
53
- client = new StreamableHTTPTestClient();
54
+ client = new CustomHeaderClient({
55
+ authorization: `Bearer ${MOCK_TOKEN}`,
56
+ });
54
57
  await client.connect(mcpUrl);
55
58
  console.log(`Mock GitLab: ${mockGitLabUrl}`);
56
59
  console.log(`MCP Server: ${mcpUrl}`);
@@ -113,7 +116,7 @@ describe('getEffectiveProjectId', { concurrency: 1 }, () => {
113
116
  port: mcpPort,
114
117
  timeout: 5000,
115
118
  env: {
116
- STREAMABLE_HTTP: 'true',
119
+ REMOTE_AUTHORIZATION: 'true',
117
120
  GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
118
121
  GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
119
122
  GITLAB_ALLOWED_PROJECT_IDS: DEFAULT_PROJECT_ID,
@@ -122,7 +125,9 @@ describe('getEffectiveProjectId', { concurrency: 1 }, () => {
122
125
  });
123
126
  servers.push(server);
124
127
  mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
125
- client = new StreamableHTTPTestClient();
128
+ client = new CustomHeaderClient({
129
+ authorization: `Bearer ${MOCK_TOKEN}`,
130
+ });
126
131
  await client.connect(mcpUrl);
127
132
  console.log(`Mock GitLab: ${mockGitLabUrl}`);
128
133
  console.log(`MCP Server: ${mcpUrl}`);
@@ -183,7 +188,7 @@ describe('getEffectiveProjectId', { concurrency: 1 }, () => {
183
188
  port: mcpPort,
184
189
  timeout: 5000,
185
190
  env: {
186
- STREAMABLE_HTTP: 'true',
191
+ REMOTE_AUTHORIZATION: 'true',
187
192
  GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
188
193
  GITLAB_ALLOWED_PROJECT_IDS: `${DEFAULT_PROJECT_ID},${OTHER_PROJECT_ID}`,
189
194
  GITLAB_READ_ONLY_MODE: 'true',
@@ -191,7 +196,9 @@ describe('getEffectiveProjectId', { concurrency: 1 }, () => {
191
196
  });
192
197
  servers.push(server);
193
198
  mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
194
- client = new StreamableHTTPTestClient();
199
+ client = new CustomHeaderClient({
200
+ authorization: `Bearer ${MOCK_TOKEN}`,
201
+ });
195
202
  await client.connect(mcpUrl);
196
203
  console.log(`Mock GitLab: ${mockGitLabUrl}`);
197
204
  console.log(`MCP Server: ${mcpUrl}`);
@@ -242,4 +249,221 @@ describe('getEffectiveProjectId', { concurrency: 1 }, () => {
242
249
  console.log(` ✓ Allowed access to second project ${OTHER_PROJECT_ID}`);
243
250
  });
244
251
  });
252
+ describe('GITLAB_PROJECT_ID guards repository and group mutators', () => {
253
+ let mcpUrl;
254
+ let mockGitLab;
255
+ let servers = [];
256
+ let client;
257
+ before(async () => {
258
+ const mockPort = await findMockServerPort(9400);
259
+ mockGitLab = new MockGitLabServer({
260
+ port: mockPort,
261
+ validTokens: [MOCK_TOKEN]
262
+ });
263
+ await mockGitLab.start();
264
+ const mockGitLabUrl = mockGitLab.getUrl();
265
+ const mcpPort = await findAvailablePort(3400);
266
+ const server = await launchServer({
267
+ mode: TransportMode.STREAMABLE_HTTP,
268
+ port: mcpPort,
269
+ timeout: 5000,
270
+ env: {
271
+ REMOTE_AUTHORIZATION: 'true',
272
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
273
+ GITLAB_PROJECT_ID: DEFAULT_PROJECT_ID,
274
+ }
275
+ });
276
+ servers.push(server);
277
+ mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
278
+ client = new CustomHeaderClient({
279
+ authorization: `Bearer ${MOCK_TOKEN}`,
280
+ });
281
+ await client.connect(mcpUrl);
282
+ });
283
+ after(async () => {
284
+ if (client)
285
+ await client.disconnect();
286
+ cleanupServers(servers);
287
+ if (mockGitLab)
288
+ await mockGitLab.stop();
289
+ });
290
+ test('should reject create_repository when GITLAB_PROJECT_ID is set', async () => {
291
+ try {
292
+ await client.callTool('create_repository', { name: 'test-repo' });
293
+ assert.fail('Should have rejected create_repository');
294
+ }
295
+ catch (error) {
296
+ assert.ok(error instanceof Error);
297
+ assert.ok(error.message.includes('create_repository is not allowed'), 'Should mention create_repository');
298
+ }
299
+ });
300
+ test('should reject fork_repository when GITLAB_PROJECT_ID is set', async () => {
301
+ try {
302
+ await client.callTool('fork_repository', { project_id: '999' });
303
+ assert.fail('Should have rejected fork_repository');
304
+ }
305
+ catch (error) {
306
+ assert.ok(error instanceof Error);
307
+ assert.ok(error.message.includes('fork_repository is not allowed'), 'Should mention fork_repository');
308
+ }
309
+ });
310
+ test('should reject create_group when GITLAB_PROJECT_ID is set', async () => {
311
+ try {
312
+ await client.callTool('create_group', { name: 'test-group', path: 'test-group' });
313
+ assert.fail('Should have rejected create_group');
314
+ }
315
+ catch (error) {
316
+ assert.ok(error instanceof Error);
317
+ assert.ok(error.message.includes('create_group is not allowed'), 'Should mention create_group');
318
+ }
319
+ });
320
+ test('should allow get_project (non-mutator) when GITLAB_PROJECT_ID is set', async () => {
321
+ const result = await client.callTool('get_project', { project_id: '' });
322
+ assert.ok(result.content, 'Should have content');
323
+ const content = result.content[0];
324
+ assert.ok('text' in content, 'Content should have text');
325
+ const project = JSON.parse(content.text);
326
+ assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use default project');
327
+ });
328
+ });
329
+ describe('GITLAB_ALLOWED_PROJECT_IDS guards repository and group mutators (allowlist-only, no GITLAB_PROJECT_ID)', () => {
330
+ let mcpUrl;
331
+ let mockGitLab;
332
+ let servers = [];
333
+ let client;
334
+ before(async () => {
335
+ const mockPort = await findMockServerPort(9600);
336
+ mockGitLab = new MockGitLabServer({
337
+ port: mockPort,
338
+ validTokens: [MOCK_TOKEN]
339
+ });
340
+ await mockGitLab.start();
341
+ const mockGitLabUrl = mockGitLab.getUrl();
342
+ const mcpPort = await findAvailablePort(3600);
343
+ const server = await launchServer({
344
+ mode: TransportMode.STREAMABLE_HTTP,
345
+ port: mcpPort,
346
+ timeout: 5000,
347
+ env: {
348
+ REMOTE_AUTHORIZATION: 'true',
349
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
350
+ GITLAB_ALLOWED_PROJECT_IDS: DEFAULT_PROJECT_ID,
351
+ }
352
+ });
353
+ servers.push(server);
354
+ mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
355
+ client = new CustomHeaderClient({
356
+ authorization: `Bearer ${MOCK_TOKEN}`,
357
+ });
358
+ await client.connect(mcpUrl);
359
+ });
360
+ after(async () => {
361
+ if (client)
362
+ await client.disconnect();
363
+ cleanupServers(servers);
364
+ if (mockGitLab)
365
+ await mockGitLab.stop();
366
+ });
367
+ test('should reject create_repository with GITLAB_ALLOWED_PROJECT_IDS', async () => {
368
+ try {
369
+ await client.callTool('create_repository', { name: 'test-repo' });
370
+ assert.fail('Should have rejected create_repository');
371
+ }
372
+ catch (error) {
373
+ assert.ok(error instanceof Error);
374
+ assert.ok(error.message.includes('create_repository is not allowed'), 'Should mention create_repository');
375
+ }
376
+ });
377
+ test('should reject fork_repository with GITLAB_ALLOWED_PROJECT_IDS', async () => {
378
+ try {
379
+ await client.callTool('fork_repository', { project_id: '999' });
380
+ assert.fail('Should have rejected fork_repository');
381
+ }
382
+ catch (error) {
383
+ assert.ok(error instanceof Error);
384
+ assert.ok(error.message.includes('fork_repository is not allowed'), 'Should mention fork_repository');
385
+ }
386
+ });
387
+ test('should reject create_group with GITLAB_ALLOWED_PROJECT_IDS', async () => {
388
+ try {
389
+ await client.callTool('create_group', { name: 'test-group', path: 'test-group' });
390
+ assert.fail('Should have rejected create_group');
391
+ }
392
+ catch (error) {
393
+ assert.ok(error instanceof Error);
394
+ assert.ok(error.message.includes('create_group is not allowed'), 'Should mention create_group');
395
+ }
396
+ });
397
+ test('should allow get_project (non-mutator) with GITLAB_ALLOWED_PROJECT_IDS', async () => {
398
+ const result = await client.callTool('get_project', { project_id: '' });
399
+ assert.ok(result.content, 'Should have content');
400
+ const content = result.content[0];
401
+ assert.ok('text' in content, 'Content should have text');
402
+ const project = JSON.parse(content.text);
403
+ assert.strictEqual(project.id.toString(), DEFAULT_PROJECT_ID, 'Should use default project');
404
+ });
405
+ });
406
+ describe('GITLAB_READ_ONLY_MODE enforces read-only for all write tools', () => {
407
+ let mcpUrl;
408
+ let mockGitLab;
409
+ let servers = [];
410
+ let client;
411
+ before(async () => {
412
+ const mockPort = await findMockServerPort(9500);
413
+ mockGitLab = new MockGitLabServer({
414
+ port: mockPort,
415
+ validTokens: [MOCK_TOKEN]
416
+ });
417
+ await mockGitLab.start();
418
+ const mockGitLabUrl = mockGitLab.getUrl();
419
+ const mcpPort = await findAvailablePort(3500);
420
+ const server = await launchServer({
421
+ mode: TransportMode.STREAMABLE_HTTP,
422
+ port: mcpPort,
423
+ timeout: 5000,
424
+ env: {
425
+ REMOTE_AUTHORIZATION: 'true',
426
+ GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
427
+ GITLAB_READ_ONLY_MODE: 'true',
428
+ }
429
+ });
430
+ servers.push(server);
431
+ mcpUrl = `http://${HOST}:${mcpPort}/mcp`;
432
+ client = new CustomHeaderClient({
433
+ authorization: `Bearer ${MOCK_TOKEN}`,
434
+ });
435
+ await client.connect(mcpUrl);
436
+ });
437
+ after(async () => {
438
+ if (client)
439
+ await client.disconnect();
440
+ cleanupServers(servers);
441
+ if (mockGitLab)
442
+ await mockGitLab.stop();
443
+ });
444
+ test('should reject create_group in read-only mode (no project ID)', async () => {
445
+ try {
446
+ await client.callTool('create_group', { name: 'test-group', path: 'test-group' });
447
+ assert.fail('Should have rejected create_group in read-only mode');
448
+ }
449
+ catch (error) {
450
+ assert.ok(error instanceof Error);
451
+ assert.ok(error.message.includes('create_group is not allowed'), 'Should mention create_group');
452
+ }
453
+ });
454
+ test('should reject create_repository in read-only mode', async () => {
455
+ try {
456
+ await client.callTool('create_repository', { name: 'test-repo' });
457
+ assert.fail('Should have rejected create_repository in read-only mode');
458
+ }
459
+ catch (error) {
460
+ assert.ok(error instanceof Error);
461
+ assert.ok(error.message.includes('create_repository is not allowed'), 'Should mention create_repository');
462
+ }
463
+ });
464
+ test('should allow get_project (read-only) in read-only mode', async () => {
465
+ const result = await client.callTool('get_project', { project_id: DEFAULT_PROJECT_ID });
466
+ assert.ok(result.content, 'Should have content');
467
+ });
468
+ });
245
469
  }); // end wrapper describe
@@ -19,7 +19,7 @@ const TOOLSET_TOOL_COUNTS = {
19
19
  merge_requests: 41,
20
20
  issues: 24,
21
21
  repositories: 7,
22
- branches: 9,
22
+ branches: 10,
23
23
  projects: 9,
24
24
  labels: 5,
25
25
  ci: 2,
@@ -32,6 +32,7 @@ const TOOLSET_TOOL_COUNTS = {
32
32
  search: 3,
33
33
  workitems: 18,
34
34
  webhooks: 3,
35
+ groups: 1,
35
36
  };
36
37
  const LEGACY_PIPELINE_TOOL_COUNT = TOOLSET_TOOL_COUNTS.pipelines + TOOLSET_TOOL_COUNTS.ci;
37
38
  const DEFAULT_TOOLSETS = [
@@ -43,6 +44,7 @@ const DEFAULT_TOOLSETS = [
43
44
  "labels",
44
45
  "ci",
45
46
  "users",
47
+ "groups",
46
48
  ];
47
49
  const NON_DEFAULT_TOOLSETS = [
48
50
  "pipelines",
@@ -75,6 +77,7 @@ const TOOLSET_SAMPLE_TOOLS = {
75
77
  users: ["get_users", "upload_markdown", "download_attachment"],
76
78
  search: ["search_code", "search_project_code", "search_group_code"],
77
79
  webhooks: ["list_webhooks", "list_webhook_events", "get_webhook_event"],
80
+ groups: ["create_group"],
78
81
  };
79
82
  // --- Helpers ---
80
83
  async function launchMcpServer(mockGitLabUrl, mcpPort, extraEnv = {}) {
@@ -1,7 +1,7 @@
1
1
  import { zodToJsonSchema } from "zod-to-json-schema";
2
2
  import { toJSONSchema } from "../utils/schema.js";
3
3
  import { USE_GITLAB_WIKI, USE_MILESTONE, USE_PIPELINE, } from "../config.js";
4
- import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
4
+ import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
5
5
  // Define all available tools
6
6
  export const allTools = [
7
7
  {
@@ -54,6 +54,11 @@ export const allTools = [
54
54
  description: "Create a new GitLab project",
55
55
  inputSchema: toJSONSchema(CreateRepositorySchema),
56
56
  },
57
+ {
58
+ name: "create_group",
59
+ description: "Create new group or subgroup",
60
+ inputSchema: toJSONSchema(CreateGroupSchema),
61
+ },
57
62
  {
58
63
  name: "get_file_contents",
59
64
  description: "Get contents of a file or directory from a GitLab project",
@@ -385,12 +390,12 @@ export const allTools = [
385
390
  },
386
391
  {
387
392
  name: "list_namespaces",
388
- description: "List all namespaces available to the current user",
393
+ description: "List all namespaces (users and groups) available to the current user. Filter by kind='group' for groups only.",
389
394
  inputSchema: toJSONSchema(ListNamespacesSchema),
390
395
  },
391
396
  {
392
397
  name: "get_namespace",
393
- description: "Get details of a namespace by ID or path",
398
+ description: "Get details of a namespace (user or group) by ID or path. Groups are namespaces with kind='group'.",
394
399
  inputSchema: toJSONSchema(GetNamespaceSchema),
395
400
  },
396
401
  {
@@ -683,6 +688,11 @@ export const allTools = [
683
688
  description: "Get changes/diffs of a specific commit",
684
689
  inputSchema: toJSONSchema(GetCommitDiffSchema),
685
690
  },
691
+ {
692
+ name: "get_file_blame",
693
+ description: "Get git blame for a file at a given ref. Each entry maps a contiguous range of source lines to the commit that last changed them (id, author, authored_date, message). Use range_start/range_end to limit blame to specific lines.",
694
+ inputSchema: toJSONSchema(GetFileBlameSchema),
695
+ },
686
696
  {
687
697
  name: "list_commit_statuses",
688
698
  description: "List statuses for a commit",
@@ -995,6 +1005,7 @@ export const readOnlyTools = new Set([
995
1005
  "list_commits",
996
1006
  "get_commit",
997
1007
  "get_commit_diff",
1008
+ "get_file_blame",
998
1009
  "list_commit_statuses",
999
1010
  "list_group_iterations",
1000
1011
  "get_group_iteration",
@@ -1203,6 +1214,7 @@ export const TOOLSET_DEFINITIONS = [
1203
1214
  "list_commits",
1204
1215
  "get_commit",
1205
1216
  "get_commit_diff",
1217
+ "get_file_blame",
1206
1218
  "list_commit_statuses",
1207
1219
  "create_commit_status",
1208
1220
  ]),
@@ -1238,6 +1250,11 @@ export const TOOLSET_DEFINITIONS = [
1238
1250
  isDefault: true,
1239
1251
  tools: new Set(["validate_ci_lint", "validate_project_ci_lint"]),
1240
1252
  },
1253
+ {
1254
+ id: "groups",
1255
+ isDefault: true,
1256
+ tools: new Set(["create_group"]),
1257
+ },
1241
1258
  {
1242
1259
  id: "pipelines",
1243
1260
  isDefault: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.1.12",
3
+ "version": "2.1.13",
4
4
  "mcpName": "io.github.zereight/gitlab-mcp",
5
5
  "description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
6
6
  "keywords": [
@@ -51,7 +51,7 @@
51
51
  "changelog": "auto-changelog -p",
52
52
  "test": "npm run test:all",
53
53
  "test:all": "npm run build && npm run test:mock && npm run test:live",
54
- "test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/test-issue-description-patch.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
54
+ "test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/test-issue-description-patch.ts && node --import tsx/esm --test test/test-geteffectiveprojectid.ts && node --import tsx/esm --test test/test-get-file-blame.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
55
55
  "test:stateless": "npm run build && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
56
56
  "test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts",
57
57
  "test:live": "node test/validate-api.js",