@zereight/mcp-gitlab 2.1.8 → 2.1.9

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
@@ -129,7 +129,7 @@ docker run -i --rm \
129
129
  ```shell
130
130
  docker run -i --rm \
131
131
  -e HOST=0.0.0.0 \
132
- -e GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token \
132
+ -e REMOTE_AUTHORIZATION=true \
133
133
  -e GITLAB_API_URL="https://gitlab.com/api/v4" \
134
134
  -e GITLAB_READ_ONLY_MODE=true \
135
135
  -e USE_GITLAB_WIKI=true \
@@ -145,7 +145,10 @@ docker run -i --rm \
145
145
  "mcpServers": {
146
146
  "gitlab": {
147
147
  "type": "streamable-http",
148
- "url": "http://localhost:3333/mcp"
148
+ "url": "http://localhost:3333/mcp",
149
+ "headers": {
150
+ "Authorization": "Bearer glpat-..."
151
+ }
149
152
  }
150
153
  }
151
154
  }
@@ -206,6 +209,8 @@ exchanging credentials with GitLab on behalf of the client.
206
209
  | `GITLAB_OAUTH_CALLBACK_PROXY` | optional | Set to `true` to use the MCP server's fixed `/callback` URL |
207
210
  | `GITLAB_OAUTH_SCOPES` | optional | Comma-separated scopes (default: `api,read_api,read_user`) |
208
211
 
212
+ When `STREAMABLE_HTTP=true`, server-side `GITLAB_PERSONAL_ACCESS_TOKEN` or `GITLAB_JOB_TOKEN` require `REMOTE_AUTHORIZATION=true` or `GITLAB_MCP_OAUTH=true`.
213
+
209
214
  > **Troubleshooting `Unregistered redirect_uri`**
210
215
  >
211
216
  > Check the `redirect_uri` in the browser URL. If it points to a client callback
@@ -601,42 +606,44 @@ Register the skill directory in your AI client to get optimal tool usage guidanc
601
606
  113. `list_commits` - List repository commits with filtering options
602
607
  114. `get_commit` - Get details of a specific commit
603
608
  115. `get_commit_diff` - Get changes/diffs of a specific commit
604
- 116. `list_releases` - List all releases for a project
605
- 117. `get_release` - Get a release by tag name
606
- 118. `create_release` - Create a new release in a GitLab project
607
- 119. `update_release` - Update an existing release in a GitLab project
608
- 120. `delete_release` - Delete a release from a GitLab project (does not delete the associated tag)
609
- 121. `create_release_evidence` - Create release evidence for an existing release (GitLab Premium/Ultimate only)
610
- 122. `download_release_asset` - Download a release asset file by direct asset path
611
- 123. `list_tags` - List repository tags with filtering and pagination support
612
- 124. `get_tag` - Get details of a specific repository tag
613
- 125. `create_tag` - Create a new tag in the repository
614
- 126. `delete_tag` - Delete a tag from the repository
615
- 127. `get_tag_signature` - Get the signature of a signed tag
616
- 128. `get_users` - Get GitLab user details by usernames
617
- 129. `list_events` - List all events for the currently authenticated user
618
- 130. `get_project_events` - List all visible events for a specified project
619
- 131. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
620
- 132. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
621
- 133. `get_work_item` - Get a single work item with full details including status, hierarchy (parent/children), type, labels, assignees, and all widgets
622
- 134. `list_work_items` - List work items in a project with filters (type, state, search, assignees, labels). Returns items with status and hierarchy info
623
- 135. `create_work_item` - Create a new work item (issue, task, incident, test_case, epic, key_result, objective, requirement, ticket). Supports setting title, description, labels, assignees, weight, parent, health status, start/due dates, milestone, and confidentiality
624
- 136. `update_work_item` - Update a work item. Can modify title, description, labels, assignees, weight, state, status, parent hierarchy, children, health status, start/due dates, milestone, confidentiality, linked items, and custom fields
625
- 137. `convert_work_item_type` - Convert a work item to a different type (e.g. issue to task, task to incident)
626
- 138. `list_work_item_statuses` - List available statuses for a work item type in a project. Requires GitLab Premium/Ultimate with configurable statuses
627
- 139. `list_custom_field_definitions` - List available custom field definitions for a work item type in a project. Returns field names, types, and IDs needed for setting custom fields via update_work_item
628
- 140. `move_work_item` - Move a work item (issue, task, etc.) to a different project. Uses GitLab GraphQL issueMove mutation
629
- 141. `list_work_item_notes` - List notes and discussions on a work item. Returns threaded discussions with author, body, timestamps, and system/internal flags
630
- 142. `create_work_item_note` - Add a note/comment to a work item. Supports Markdown, internal notes, and threaded replies
631
- 143. `get_timeline_events` - List timeline events for an incident. Returns chronological events with notes, timestamps, and tags
632
- 144. `create_timeline_event` - Create a timeline event on an incident. Supports tags: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'
633
- 145. `list_webhooks` - List all configured webhooks for a GitLab project or group. Provide either project_id or group_id
634
- 146. `list_webhook_events` - List recent webhook events (past 7 days) for a project or group webhook. Use summary mode for overview, then get_webhook_event for full details
635
- 147. `get_webhook_event` - Get full details of a specific webhook event by ID, including request/response payloads
636
- 148. `search_code` - Search for code across all projects on the GitLab instance (requires advanced search or exact code search to be enabled)
637
- 149. `search_project_code` - Search for code within a specific GitLab project (requires advanced search or exact code search to be enabled)
638
- 150. `search_group_code` - Search for code within a specific GitLab group (requires advanced search or exact code search to be enabled)
639
- 151. `execute_graphql` - Execute a GitLab GraphQL query
609
+ 116. `list_commit_statuses` - List statuses for a specific commit
610
+ 117. `create_commit_status` - Create or update the status of a specific commit
611
+ 118. `list_releases` - List all releases for a project
612
+ 119. `get_release` - Get a release by tag name
613
+ 120. `create_release` - Create a new release in a GitLab project
614
+ 121. `update_release` - Update an existing release in a GitLab project
615
+ 122. `delete_release` - Delete a release from a GitLab project (does not delete the associated tag)
616
+ 123. `create_release_evidence` - Create release evidence for an existing release (GitLab Premium/Ultimate only)
617
+ 124. `download_release_asset` - Download a release asset file by direct asset path
618
+ 125. `list_tags` - List repository tags with filtering and pagination support
619
+ 126. `get_tag` - Get details of a specific repository tag
620
+ 127. `create_tag` - Create a new tag in the repository
621
+ 128. `delete_tag` - Delete a tag from the repository
622
+ 129. `get_tag_signature` - Get the signature of a signed tag
623
+ 130. `get_users` - Get GitLab user details by usernames
624
+ 131. `list_events` - List all events for the currently authenticated user
625
+ 132. `get_project_events` - List all visible events for a specified project
626
+ 133. `upload_markdown` - Upload a file to a GitLab project for use in markdown content
627
+ 134. `download_attachment` - Download an uploaded file from a GitLab project by secret and filename
628
+ 135. `get_work_item` - Get a single work item with full details including status, hierarchy (parent/children), type, labels, assignees, and all widgets
629
+ 136. `list_work_items` - List work items in a project with filters (type, state, search, assignees, labels). Returns items with status and hierarchy info
630
+ 137. `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
631
+ 138. `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
632
+ 139. `convert_work_item_type` - Convert a work item to a different type (e.g. issue to task, task to incident)
633
+ 140. `list_work_item_statuses` - List available statuses for a work item type in a project. Requires GitLab Premium/Ultimate with configurable statuses
634
+ 141. `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
635
+ 142. `move_work_item` - Move a work item (issue, task, etc.) to a different project. Uses GitLab GraphQL issueMove mutation
636
+ 143. `list_work_item_notes` - List notes and discussions on a work item. Returns threaded discussions with author, body, timestamps, and system/internal flags
637
+ 144. `create_work_item_note` - Add a note/comment to a work item. Supports Markdown, internal notes, and threaded replies
638
+ 145. `get_timeline_events` - List timeline events for an incident. Returns chronological events with notes, timestamps, and tags
639
+ 146. `create_timeline_event` - Create a timeline event on an incident. Supports tags: 'Start time', 'End time', 'Impact detected', 'Response initiated', 'Impact mitigated', 'Cause identified'
640
+ 147. `list_webhooks` - List all configured webhooks for a GitLab project or group. Provide either project_id or group_id
641
+ 148. `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
642
+ 149. `get_webhook_event` - Get full details of a specific webhook event by ID, including request/response payloads
643
+ 150. `search_code` - Search for code across all projects on the GitLab instance (requires advanced search or exact code search to be enabled)
644
+ 151. `search_project_code` - Search for code within a specific GitLab project (requires advanced search or exact code search to be enabled)
645
+ 152. `search_group_code` - Search for code within a specific GitLab group (requires advanced search or exact code search to be enabled)
646
+ 153. `execute_graphql` - Execute a GitLab GraphQL query
640
647
 
641
648
  <!-- TOOLS-END -->
642
649
 
package/build/index.js CHANGED
@@ -25,12 +25,12 @@ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middlew
25
25
  import { GitLabClientPool } from "./gitlab-client-pool.js";
26
26
  import { allTools, readOnlyTools, destructiveTools, parseEnabledToolsets, parseIndividualTools, buildFeatureFlagOverrides, isToolInEnabledToolset, TOOLSET_DEFINITIONS, ALL_TOOLSET_IDS, } from "./tools/registry.js";
27
27
  import { BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, // Added
28
- CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateRepositorySchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
28
+ CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateRepositorySchema, CreateWikiPageSchema, CreateGroupWikiPageSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteProjectMilestoneSchema, DeleteWikiPageSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, EditProjectMilestoneSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDraftNoteSchema, GetFileContentsSchema, GetIssueLinkSchema, GetIssueSchema, GetLabelSchema, GetMergeRequestDiffsSchema, GetMergeRequestSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetDeploymentSchema, GetEnvironmentSchema, GetNamespaceSchema, GitLabCiLintResultSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectMilestoneSchema, GetProjectSchema, GetRepositoryTreeSchema, GetUsersSchema, GetWikiPageSchema, GitLabCommitSchema, GitLabCommitStatusSchema, GitLabCompareResultSchema, GitLabContentSchema, GitLabCreateUpdateFileResponseSchema, GitLabDiffSchema,
29
29
  // Discussion Schemas
30
30
  GitLabDiscussionNoteSchema, // Added
31
31
  GitLabDiscussionSchema,
32
32
  // Draft Notes Schemas
33
- GitLabDraftNoteSchema, GitLabForkSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
33
+ GitLabDraftNoteSchema, GitLabForkSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
34
34
  GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, } from "./schemas.js";
35
35
  import { randomUUID } from "node:crypto";
36
36
  import { pino } from "pino";
@@ -394,9 +394,13 @@ function validateConfiguration() {
394
394
  const hasCookie = !!getConfig("cookie-path", "GITLAB_AUTH_COOKIE_PATH");
395
395
  const mcpOAuth = getConfig("mcp-oauth", "GITLAB_MCP_OAUTH") === "true";
396
396
  const mcpServerUrl = getConfig("mcp-server-url", "MCP_SERVER_URL");
397
+ const streamableHttp = getConfig("streamable-http", "STREAMABLE_HTTP") === "true";
397
398
  if (!remoteAuth && !useOAuth && !hasToken && !hasJobToken && !hasCookie && !mcpOAuth) {
398
399
  errors.push("Either --token, --job-token, --cookie-path, --use-oauth=true, --remote-auth=true, or --mcp-oauth=true must be set (or use environment variables)");
399
400
  }
401
+ if (streamableHttp && (hasToken || hasJobToken) && !remoteAuth && !mcpOAuth) {
402
+ errors.push("STREAMABLE_HTTP=true/--streamable-http with GITLAB_PERSONAL_ACCESS_TOKEN/--token or GITLAB_JOB_TOKEN/--job-token requires REMOTE_AUTHORIZATION=true/--remote-auth=true or GITLAB_MCP_OAUTH=true/--mcp-oauth=true");
403
+ }
400
404
  if (mcpOAuth) {
401
405
  if (!mcpServerUrl) {
402
406
  errors.push("MCP_SERVER_URL is required when GITLAB_MCP_OAUTH=true (e.g. https://mcp.example.com)");
@@ -5387,6 +5391,56 @@ async function getCommitDiff(projectId, sha, full_diff) {
5387
5391
  }
5388
5392
  return allDiffs;
5389
5393
  }
5394
+ /**
5395
+ * List statuses for a commit.
5396
+ *
5397
+ * @param {string} projectId - Project ID or URL-encoded path
5398
+ * @param {string} sha - The commit hash or name of a repository branch or tag
5399
+ * @param {ListCommitStatusesOptions} options - List commit statuses options
5400
+ * @returns {Promise<GitLabCommitStatus[]>} List of commit statuses
5401
+ */
5402
+ async function listCommitStatuses(projectId, sha, options = {}) {
5403
+ projectId = decodeURIComponent(projectId);
5404
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/repository/commits/${encodeURIComponent(sha)}/statuses`);
5405
+ Object.entries(options).forEach(([key, value]) => {
5406
+ if (value !== undefined) {
5407
+ url.searchParams.append(key, value.toString());
5408
+ }
5409
+ });
5410
+ const response = await fetch(url.toString(), {
5411
+ ...getFetchConfig(),
5412
+ });
5413
+ await handleGitLabError(response);
5414
+ const data = await response.json();
5415
+ return z.array(GitLabCommitStatusSchema).parse(data);
5416
+ }
5417
+ /**
5418
+ * Create or update a commit status.
5419
+ *
5420
+ * @param {string} projectId - Project ID or URL-encoded path
5421
+ * @param {string} sha - The commit hash
5422
+ * @param {CreateCommitStatusOptions} options - Commit status fields
5423
+ * @returns {Promise<GitLabCommitStatus>} The created commit status
5424
+ */
5425
+ async function createCommitStatus(projectId, sha, options) {
5426
+ if (options.name && options.context) {
5427
+ throw new Error("Use either name or context when creating a commit status, not both.");
5428
+ }
5429
+ projectId = decodeURIComponent(projectId);
5430
+ const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/statuses/${encodeURIComponent(sha)}`);
5431
+ Object.entries(options).forEach(([key, value]) => {
5432
+ if (value !== undefined) {
5433
+ url.searchParams.append(key, value.toString());
5434
+ }
5435
+ });
5436
+ const response = await fetch(url.toString(), {
5437
+ ...getFetchConfig(),
5438
+ method: "POST",
5439
+ });
5440
+ await handleGitLabError(response);
5441
+ const data = await response.json();
5442
+ return GitLabCommitStatusSchema.parse(data);
5443
+ }
5390
5444
  /**
5391
5445
  * Get the current authenticated user
5392
5446
  * 현재 인증된 사용자 가져오기
@@ -7266,6 +7320,22 @@ async function handleToolCall(params) {
7266
7320
  content: [{ type: "text", text: JSON.stringify(diff, null, 2) }],
7267
7321
  };
7268
7322
  }
7323
+ case "list_commit_statuses": {
7324
+ const args = ListCommitStatusesSchema.parse(params.arguments);
7325
+ const { project_id, sha, ...options } = args;
7326
+ const statuses = await listCommitStatuses(project_id, sha, options);
7327
+ return {
7328
+ content: [{ type: "text", text: JSON.stringify(statuses, null, 2) }],
7329
+ };
7330
+ }
7331
+ case "create_commit_status": {
7332
+ const args = CreateCommitStatusSchema.parse(params.arguments);
7333
+ const { project_id, sha, ...options } = args;
7334
+ const status = await createCommitStatus(project_id, sha, options);
7335
+ return {
7336
+ content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
7337
+ };
7338
+ }
7269
7339
  case "list_group_iterations": {
7270
7340
  const args = ListGroupIterationsSchema.parse(params.arguments);
7271
7341
  const iterations = await listGroupIterations(args.group_id, args);
@@ -7977,11 +8047,10 @@ async function startStreamableHTTPServer() {
7977
8047
  app._mcpOAuthProvider = oauthProvider;
7978
8048
  // Mount /callback route for callback proxy mode
7979
8049
  if (GITLAB_OAUTH_CALLBACK_PROXY) {
7980
- const callbackPath = `${issuerUrl.pathname.replace(/\/$/, "")}/callback`;
7981
- app.get(callbackPath, (req, res, next) => {
8050
+ app.get("/callback", (req, res, next) => {
7982
8051
  oauthProvider.handleCallback(req, res).catch(next);
7983
8052
  });
7984
- logger.info(`Callback proxy mode enabled — ${callbackPath} route mounted`);
8053
+ logger.info(`Callback proxy mode enabled — /callback route mounted`);
7985
8054
  }
7986
8055
  }
7987
8056
  // Build bearer-auth middleware — no-op unless GITLAB_MCP_OAUTH is enabled.
package/build/schemas.js CHANGED
@@ -12,6 +12,16 @@ const coerceStringArray = z.preprocess((val) => {
12
12
  }
13
13
  return val;
14
14
  }, z.array(z.string()));
15
+ const coerceBooleanString = z.preprocess((val) => {
16
+ if (typeof val === "string") {
17
+ const normalized = val.trim().toLowerCase();
18
+ if (normalized === "true")
19
+ return true;
20
+ if (normalized === "false")
21
+ return false;
22
+ }
23
+ return val;
24
+ }, z.boolean());
15
25
  // Base schemas for common types
16
26
  export const GitLabAuthorSchema = z.object({
17
27
  name: z.string(),
@@ -699,6 +709,35 @@ export const GitLabCommitSchema = z.object({
699
709
  trailers: z.record(z.string()).optional().default({}), // Git trailers, may be empty object
700
710
  extended_trailers: z.record(z.array(z.string())).optional().default({}), // Extended trailers, may be empty object
701
711
  });
712
+ export const GitLabCommitStatusSchema = z
713
+ .object({
714
+ id: z.coerce.number().optional(),
715
+ sha: z.string(),
716
+ ref: z.string().nullable().optional(),
717
+ status: z.string(),
718
+ name: z.string().optional(),
719
+ context: z.string().optional(),
720
+ target_url: z.string().nullable().optional(),
721
+ description: z.string().nullable().optional(),
722
+ coverage: z.coerce.number().nullable().optional(),
723
+ allow_failure: z.coerce.boolean().optional(),
724
+ created_at: z.string().optional(),
725
+ started_at: z.string().nullable().optional(),
726
+ finished_at: z.string().nullable().optional(),
727
+ author: z
728
+ .object({
729
+ id: z.coerce.number().optional(),
730
+ name: z.string().optional(),
731
+ username: z.string().optional(),
732
+ state: z.string().optional(),
733
+ avatar_url: z.string().nullable().optional(),
734
+ web_url: z.string().optional(),
735
+ })
736
+ .passthrough()
737
+ .nullable()
738
+ .optional(),
739
+ })
740
+ .passthrough();
702
741
  // Reference schema
703
742
  export const GitLabReferenceSchema = z.object({
704
743
  name: z.string(), // Changed from ref to match GitLab API
@@ -926,6 +965,7 @@ export const GitLabMergeRequestSchema = z.object({
926
965
  author: GitLabUserSchema,
927
966
  assignees: z.array(GitLabUserSchema).optional(),
928
967
  reviewers: z.array(GitLabUserSchema).optional(),
968
+ milestone: GitLabMilestoneSchema.nullable().optional(),
929
969
  source_branch: z.string(),
930
970
  target_branch: z.string(),
931
971
  diff_refs: GitLabMergeRequestDiffRefSchema.nullable().optional(),
@@ -2214,6 +2254,44 @@ export const GetCommitDiffSchema = z.object({
2214
2254
  .optional()
2215
2255
  .describe("Whether to return the full diff or only first page (default: false)"),
2216
2256
  });
2257
+ export const ListCommitStatusesSchema = z
2258
+ .object({
2259
+ project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
2260
+ sha: z.string().describe("The commit hash or name of a repository branch or tag"),
2261
+ ref: z.string().optional().describe("Filter statuses by Git ref"),
2262
+ stage: z.string().optional().describe("Filter statuses by build stage"),
2263
+ name: z.string().optional().describe("Filter statuses by status name or context"),
2264
+ pipeline_id: z.coerce.number().optional().describe("Filter statuses by pipeline ID"),
2265
+ order_by: z
2266
+ .enum(["id", "pipeline_id"])
2267
+ .optional()
2268
+ .describe("Field to order statuses by"),
2269
+ sort: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
2270
+ all: coerceBooleanString.optional().describe("Return all statuses, not only latest ones"),
2271
+ })
2272
+ .merge(PaginationOptionsSchema);
2273
+ export const CreateCommitStatusSchema = z.object({
2274
+ project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
2275
+ sha: z.string().describe("The commit hash to set the status on"),
2276
+ state: z
2277
+ .enum(["pending", "running", "success", "failed", "canceled", "skipped"])
2278
+ .describe("Commit status state"),
2279
+ ref: z.string().max(255).optional().describe("The branch or tag ref"),
2280
+ name: z
2281
+ .string()
2282
+ .max(255)
2283
+ .optional()
2284
+ .describe("Status name. GitLab defaults to 'default' when omitted."),
2285
+ context: z
2286
+ .string()
2287
+ .max(255)
2288
+ .optional()
2289
+ .describe("Alias for name. Provide either name or context, not both."),
2290
+ target_url: z.string().max(255).optional().describe("Target URL associated with this status"),
2291
+ description: z.string().max(255).optional().describe("Short status description"),
2292
+ coverage: z.coerce.number().optional().describe("Total code coverage for this status"),
2293
+ pipeline_id: z.coerce.number().optional().describe("Pipeline ID to attach the status to"),
2294
+ });
2217
2295
  // Schema for listing issues assigned to the current user
2218
2296
  export const MyIssuesSchema = z.object({
2219
2297
  project_id: z
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ts-node
2
- import { GetFileContentsSchema, GitLabFileContentSchema, GitLabRepositorySchema, CreatePipelineSchema, CreateIssueNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateIssueEmojiReactionSchema, DeleteMergeRequestEmojiReactionSchema, DeleteIssueEmojiReactionSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, CreateIssueSchema, ListIssuesSchema, ListMergeRequestsSchema, GitLabTreeItemSchema, GetMergeRequestSchema, GetRepositoryTreeSchema } from '../schemas.js';
2
+ import { GetFileContentsSchema, GitLabFileContentSchema, GitLabRepositorySchema, CreatePipelineSchema, CreateCommitStatusSchema, ListCommitStatusesSchema, CreateIssueNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateIssueEmojiReactionSchema, DeleteMergeRequestEmojiReactionSchema, DeleteIssueEmojiReactionSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, CreateIssueSchema, ListIssuesSchema, ListMergeRequestsSchema, GitLabMergeRequestSchema, GitLabTreeItemSchema, GetMergeRequestSchema, GetRepositoryTreeSchema } from '../schemas.js';
3
3
  function runGetFileContentsSchemaTests() {
4
4
  console.log('🧪 Testing GetFileContentsSchema...');
5
5
  const cases = [
@@ -298,6 +298,142 @@ function runCreatePipelineSchemaTests() {
298
298
  console.log(`\nResults: ${passed} passed, ${failed} failed`);
299
299
  return { passed, failed };
300
300
  }
301
+ function runCommitStatusSchemaTests() {
302
+ console.log('\n🧪 Testing Commit Status Schemas...');
303
+ const cases = [
304
+ {
305
+ name: 'schema:create_commit_status:minimal-required-fields',
306
+ schema: CreateCommitStatusSchema,
307
+ input: { project_id: 'my/project', sha: 'abc123', state: 'success' },
308
+ expected: { project_id: 'my/project', sha: 'abc123', state: 'success' }
309
+ },
310
+ {
311
+ name: 'schema:create_commit_status:with-optional-fields',
312
+ schema: CreateCommitStatusSchema,
313
+ input: {
314
+ project_id: 'my/project',
315
+ sha: 'abc123',
316
+ state: 'failed',
317
+ name: 'external/check',
318
+ target_url: 'https://ci.example.com/build/1',
319
+ description: 'External check failed',
320
+ coverage: '87.5',
321
+ pipeline_id: '42'
322
+ },
323
+ expected: {
324
+ project_id: 'my/project',
325
+ sha: 'abc123',
326
+ state: 'failed',
327
+ name: 'external/check',
328
+ target_url: 'https://ci.example.com/build/1',
329
+ description: 'External check failed',
330
+ coverage: 87.5,
331
+ pipeline_id: 42
332
+ }
333
+ },
334
+ {
335
+ name: 'schema:create_commit_status:context-alias',
336
+ schema: CreateCommitStatusSchema,
337
+ input: { project_id: 123, sha: 'abc123', state: 'pending', context: 'external/check' },
338
+ expected: { project_id: '123', sha: 'abc123', state: 'pending', context: 'external/check' }
339
+ },
340
+ {
341
+ name: 'schema:create_commit_status:reject-invalid-state',
342
+ schema: CreateCommitStatusSchema,
343
+ input: { project_id: 'my/project', sha: 'abc123', state: 'passing' },
344
+ shouldFail: true
345
+ },
346
+ {
347
+ name: 'schema:create_commit_status:reject-missing-state',
348
+ schema: CreateCommitStatusSchema,
349
+ input: { project_id: 'my/project', sha: 'abc123' },
350
+ shouldFail: true
351
+ },
352
+ {
353
+ name: 'schema:list_commit_statuses:filters',
354
+ schema: ListCommitStatusesSchema,
355
+ input: {
356
+ project_id: 'my/project',
357
+ sha: 'abc123',
358
+ ref: 'main',
359
+ name: 'external/check',
360
+ pipeline_id: '42',
361
+ order_by: 'pipeline_id',
362
+ sort: 'desc',
363
+ all: 'true',
364
+ page: '2',
365
+ per_page: '50'
366
+ },
367
+ expected: {
368
+ project_id: 'my/project',
369
+ sha: 'abc123',
370
+ ref: 'main',
371
+ name: 'external/check',
372
+ pipeline_id: 42,
373
+ order_by: 'pipeline_id',
374
+ sort: 'desc',
375
+ all: true,
376
+ page: 2,
377
+ per_page: 50
378
+ }
379
+ },
380
+ {
381
+ name: 'schema:list_commit_statuses:all-false-string',
382
+ schema: ListCommitStatusesSchema,
383
+ input: { project_id: 'my/project', sha: 'abc123', all: 'false' },
384
+ expected: { project_id: 'my/project', sha: 'abc123', all: false }
385
+ },
386
+ {
387
+ name: 'schema:list_commit_statuses:reject-invalid-all-string',
388
+ schema: ListCommitStatusesSchema,
389
+ input: { project_id: 'my/project', sha: 'abc123', all: 'yes' },
390
+ shouldFail: true
391
+ }
392
+ ];
393
+ let passed = 0;
394
+ let failed = 0;
395
+ cases.forEach(testCase => {
396
+ const result = {
397
+ name: testCase.name,
398
+ status: 'failed'
399
+ };
400
+ const parsed = testCase.schema.safeParse(testCase.input);
401
+ if (testCase.shouldFail) {
402
+ if (parsed.success) {
403
+ result.error = 'Expected schema validation to fail';
404
+ }
405
+ else {
406
+ result.status = 'passed';
407
+ }
408
+ }
409
+ else if (parsed.success) {
410
+ const expected = testCase.expected || {};
411
+ const matches = Object.entries(expected).every(([key, value]) => {
412
+ const actual = parsed.data[key];
413
+ return JSON.stringify(actual) === JSON.stringify(value);
414
+ });
415
+ if (matches) {
416
+ result.status = 'passed';
417
+ }
418
+ else {
419
+ result.error = `Unexpected parsed result: ${JSON.stringify(parsed.data)}`;
420
+ }
421
+ }
422
+ else {
423
+ result.error = parsed.error?.message || 'Schema validation failed';
424
+ }
425
+ if (result.status === 'passed') {
426
+ passed++;
427
+ console.log(`✅ ${result.name}`);
428
+ }
429
+ else {
430
+ failed++;
431
+ console.log(`❌ ${result.name}: ${result.error}`);
432
+ }
433
+ });
434
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
435
+ return { passed, failed };
436
+ }
301
437
  function runCreateIssueNoteSchemaTests() {
302
438
  console.log('\n🧪 Testing CreateIssueNoteSchema...');
303
439
  const cases = [
@@ -448,6 +584,93 @@ function runGetMergeRequestSchemaTests() {
448
584
  console.log(`\nResults: ${passed} passed, ${failed} failed`);
449
585
  return { passed, failed };
450
586
  }
587
+ function runGitLabMergeRequestSchemaTests() {
588
+ console.log('\n🧪 Testing GitLabMergeRequestSchema...');
589
+ const baseMergeRequest = {
590
+ id: '1001',
591
+ iid: '42',
592
+ project_id: '123',
593
+ title: 'Add milestone exposure',
594
+ description: 'Expose MR milestone',
595
+ state: 'opened',
596
+ author: {
597
+ id: '1',
598
+ username: 'octocat',
599
+ name: 'Octo Cat',
600
+ avatar_url: null,
601
+ web_url: 'https://gitlab.example.com/octocat',
602
+ },
603
+ assignees: [],
604
+ reviewers: [],
605
+ source_branch: 'feature/milestone',
606
+ target_branch: 'main',
607
+ web_url: 'https://gitlab.example.com/group/project/-/merge_requests/42',
608
+ created_at: '2026-05-07T00:00:00.000Z',
609
+ updated_at: '2026-05-07T00:00:00.000Z',
610
+ merged_at: null,
611
+ closed_at: null,
612
+ merge_commit_sha: null,
613
+ };
614
+ const cases = [
615
+ {
616
+ name: 'schema:gitlab_merge_request:preserves-milestone',
617
+ input: {
618
+ ...baseMergeRequest,
619
+ milestone: {
620
+ id: '5',
621
+ iid: '2',
622
+ title: 'v1.0',
623
+ description: 'Version 1.0',
624
+ state: 'active',
625
+ web_url: 'https://gitlab.example.com/group/project/-/milestones/2',
626
+ },
627
+ },
628
+ validate: (data) => data.milestone?.title === 'v1.0' &&
629
+ data.milestone?.id === '5' &&
630
+ data.milestone?.iid === '2',
631
+ },
632
+ {
633
+ name: 'schema:gitlab_merge_request:allows-null-milestone',
634
+ input: {
635
+ ...baseMergeRequest,
636
+ milestone: null,
637
+ },
638
+ validate: (data) => data.milestone === null,
639
+ },
640
+ {
641
+ name: 'schema:gitlab_merge_request:allows-omitted-milestone',
642
+ input: {
643
+ ...baseMergeRequest,
644
+ },
645
+ validate: (data) => data.milestone === undefined,
646
+ },
647
+ ];
648
+ let passed = 0;
649
+ let failed = 0;
650
+ cases.forEach(testCase => {
651
+ const result = { name: testCase.name, status: 'failed' };
652
+ const parsed = GitLabMergeRequestSchema.safeParse(testCase.input);
653
+ if (parsed.success && testCase.validate(parsed.data)) {
654
+ result.status = 'passed';
655
+ }
656
+ else if (parsed.success) {
657
+ result.error = `Unexpected parsed result: ${JSON.stringify(parsed.data)}`;
658
+ }
659
+ else {
660
+ result.error = parsed.error?.message || 'Schema validation failed';
661
+ }
662
+ if (result.status === 'passed') {
663
+ passed++;
664
+ console.log(`✅ ${result.name}`);
665
+ }
666
+ else {
667
+ failed++;
668
+ console.log(`❌ ${result.name}: ${result.error}`);
669
+ }
670
+ });
671
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
672
+ return { passed, failed };
673
+ }
451
674
  function runEmojiReactionSchemaTests() {
452
675
  console.log('\n🧪 Testing Emoji Reaction Schemas...');
453
676
  const cases = [
@@ -778,15 +1001,17 @@ if (import.meta.url === `file://${process.argv[1]}`) {
778
1001
  const getFileContentsResult = runGetFileContentsSchemaTests();
779
1002
  const fileContentResult = runGitLabFileContentSchemaTests();
780
1003
  const createPipelineResult = runCreatePipelineSchemaTests();
1004
+ const commitStatusResult = runCommitStatusSchemaTests();
781
1005
  const createIssueNoteResult = runCreateIssueNoteSchemaTests();
782
1006
  const getMergeRequestResult = runGetMergeRequestSchemaTests();
1007
+ const gitLabMergeRequestResult = runGitLabMergeRequestSchemaTests();
783
1008
  const emojiReactionResult = runEmojiReactionSchemaTests();
784
1009
  const repositorySchemaResult = runGitLabRepositorySchemaTests();
785
1010
  const labelsCoercionResult = runLabelsCoercionSchemaTests();
786
1011
  const treeItemResult = runGitLabTreeItemSchemaTests();
787
1012
  const repositoryTreeResult = runGetRepositoryTreeSchemaTests();
788
- const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + treeItemResult.passed + repositoryTreeResult.passed;
789
- const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + treeItemResult.failed + repositoryTreeResult.failed;
1013
+ const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + commitStatusResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + gitLabMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + treeItemResult.passed + repositoryTreeResult.passed;
1014
+ const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + commitStatusResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + gitLabMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + treeItemResult.failed + repositoryTreeResult.failed;
790
1015
  console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
791
1016
  if (totalFailed > 0) {
792
1017
  process.exit(1);
@@ -0,0 +1,154 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawn } from "node:child_process";
3
+ import { once } from "node:events";
4
+ import { afterEach, describe, test } from "node:test";
5
+ import * as path from "node:path";
6
+ import { findAvailablePort } from "./utils/server-launcher.js";
7
+ const ERROR_MESSAGE = "STREAMABLE_HTTP=true/--streamable-http with GITLAB_PERSONAL_ACCESS_TOKEN/--token or GITLAB_JOB_TOKEN/--job-token requires REMOTE_AUTHORIZATION=true/--remote-auth=true or GITLAB_MCP_OAUTH=true/--mcp-oauth=true";
8
+ const HOST = process.env.HOST || "127.0.0.1";
9
+ const SERVER_PATH = path.resolve(process.cwd(), "build/index.js");
10
+ const running = new Set();
11
+ function startServer(env, port) {
12
+ const child = spawn("node", [SERVER_PATH], {
13
+ env: {
14
+ ...process.env,
15
+ GITLAB_API_URL: "https://gitlab.example.com",
16
+ HOST,
17
+ PORT: String(port),
18
+ STREAMABLE_HTTP: "true",
19
+ REMOTE_AUTHORIZATION: "false",
20
+ GITLAB_MCP_OAUTH: "false",
21
+ GITLAB_USE_OAUTH: "false",
22
+ GITLAB_PERSONAL_ACCESS_TOKEN: "",
23
+ GITLAB_JOB_TOKEN: "",
24
+ GITLAB_AUTH_COOKIE_PATH: "",
25
+ MCP_SERVER_URL: "",
26
+ GITLAB_OAUTH_APP_ID: "",
27
+ ...env,
28
+ },
29
+ stdio: ["ignore", "pipe", "pipe"],
30
+ });
31
+ running.add(child);
32
+ child.once("exit", () => running.delete(child));
33
+ return child;
34
+ }
35
+ async function waitForExit(child, timeoutMs = 5000) {
36
+ let output = "";
37
+ child.stdout?.on("data", chunk => {
38
+ output += chunk.toString();
39
+ });
40
+ child.stderr?.on("data", chunk => {
41
+ output += chunk.toString();
42
+ });
43
+ let timeoutHandle;
44
+ const timeout = new Promise((_, reject) => {
45
+ timeoutHandle = setTimeout(() => reject(new Error(`server did not exit within ${timeoutMs}ms`)), timeoutMs);
46
+ });
47
+ try {
48
+ const [code] = (await Promise.race([once(child, "exit"), timeout]));
49
+ return { code, output };
50
+ }
51
+ finally {
52
+ clearTimeout(timeoutHandle);
53
+ }
54
+ }
55
+ async function waitForHealth(port, timeoutMs = 5000) {
56
+ const deadline = Date.now() + timeoutMs;
57
+ let lastError;
58
+ while (Date.now() < deadline) {
59
+ try {
60
+ const response = await fetch(`http://${HOST}:${port}/health`);
61
+ if (response.ok)
62
+ return;
63
+ }
64
+ catch (error) {
65
+ lastError = error;
66
+ }
67
+ await new Promise(resolve => setTimeout(resolve, 100));
68
+ }
69
+ throw new Error(`server did not become healthy: ${String(lastError)}`);
70
+ }
71
+ async function postMcpWithoutAuth(port) {
72
+ return fetch(`http://${HOST}:${port}/mcp`, {
73
+ method: "POST",
74
+ headers: {
75
+ "Content-Type": "application/json",
76
+ Accept: "application/json, text/event-stream",
77
+ },
78
+ body: JSON.stringify({
79
+ jsonrpc: "2.0",
80
+ id: 1,
81
+ method: "initialize",
82
+ params: {
83
+ protocolVersion: "2025-03-26",
84
+ capabilities: {},
85
+ clientInfo: { name: "static-token-auth-test", version: "1.0.0" },
86
+ },
87
+ }),
88
+ });
89
+ }
90
+ async function postMcpWithSessionWithoutAuth(port, sessionId) {
91
+ return fetch(`http://${HOST}:${port}/mcp`, {
92
+ method: "POST",
93
+ headers: {
94
+ "Content-Type": "application/json",
95
+ Accept: "application/json, text/event-stream",
96
+ "Mcp-Session-Id": sessionId,
97
+ },
98
+ body: JSON.stringify({
99
+ jsonrpc: "2.0",
100
+ id: 2,
101
+ method: "tools/list",
102
+ params: {},
103
+ }),
104
+ });
105
+ }
106
+ afterEach(() => {
107
+ for (const child of running) {
108
+ if (!child.killed)
109
+ child.kill("SIGTERM");
110
+ }
111
+ running.clear();
112
+ });
113
+ describe("Streamable HTTP static server token auth", () => {
114
+ test("refuses startup with PAT and no MCP-layer auth", async () => {
115
+ const port = await findAvailablePort(4300);
116
+ const child = startServer({ GITLAB_PERSONAL_ACCESS_TOKEN: "glpat_test" }, port);
117
+ const { code, output } = await waitForExit(child);
118
+ assert.notEqual(code, 0);
119
+ assert.match(output, new RegExp(ERROR_MESSAGE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
120
+ });
121
+ test("refuses startup with JOB-TOKEN and no MCP-layer auth", async () => {
122
+ const port = await findAvailablePort(4310);
123
+ const child = startServer({ GITLAB_JOB_TOKEN: "job_test" }, port);
124
+ const { code, output } = await waitForExit(child);
125
+ assert.notEqual(code, 0);
126
+ assert.match(output, new RegExp(ERROR_MESSAGE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
127
+ });
128
+ test("allows startup with PAT when REMOTE_AUTHORIZATION is enabled", async () => {
129
+ const port = await findAvailablePort(4320);
130
+ startServer({
131
+ GITLAB_PERSONAL_ACCESS_TOKEN: "glpat_test",
132
+ REMOTE_AUTHORIZATION: "true",
133
+ }, port);
134
+ await waitForHealth(port);
135
+ const initResponse = await postMcpWithoutAuth(port);
136
+ assert.equal(initResponse.status, 200);
137
+ const sessionId = initResponse.headers.get("mcp-session-id");
138
+ assert.ok(sessionId);
139
+ const followUpResponse = await postMcpWithSessionWithoutAuth(port, sessionId);
140
+ assert.equal(followUpResponse.status, 401);
141
+ });
142
+ test("allows startup with PAT when GITLAB_MCP_OAUTH is enabled", async () => {
143
+ const port = await findAvailablePort(4330);
144
+ startServer({
145
+ GITLAB_PERSONAL_ACCESS_TOKEN: "glpat_test",
146
+ GITLAB_MCP_OAUTH: "true",
147
+ MCP_SERVER_URL: `http://${HOST}:${port}`,
148
+ GITLAB_OAUTH_APP_ID: "oauth-app-id",
149
+ }, port);
150
+ await waitForHealth(port);
151
+ const response = await postMcpWithoutAuth(port);
152
+ assert.equal(response.status, 401);
153
+ });
154
+ });
@@ -19,7 +19,7 @@ const TOOLSET_TOOL_COUNTS = {
19
19
  merge_requests: 40,
20
20
  issues: 23,
21
21
  repositories: 7,
22
- branches: 4,
22
+ branches: 6,
23
23
  projects: 8,
24
24
  labels: 5,
25
25
  ci: 2,
@@ -63,7 +63,7 @@ const TOOLSET_SAMPLE_TOOLS = {
63
63
  merge_requests: ["merge_merge_request", "create_merge_request_thread", "list_draft_notes"],
64
64
  issues: ["create_issue", "list_issues", "create_note", "list_todos"],
65
65
  repositories: ["search_repositories", "get_file_contents", "push_files"],
66
- branches: ["create_branch", "list_commits"],
66
+ branches: ["create_branch", "list_commits", "list_commit_statuses", "create_commit_status"],
67
67
  projects: ["get_project", "list_namespaces", "list_group_iterations"],
68
68
  labels: ["list_labels", "create_label"],
69
69
  ci: ["validate_ci_lint", "validate_project_ci_lint"],
@@ -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, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListCommitsSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
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, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
5
5
  // Define all available tools
6
6
  export const allTools = [
7
7
  {
@@ -646,6 +646,16 @@ export const allTools = [
646
646
  description: "Get changes/diffs of a specific commit",
647
647
  inputSchema: toJSONSchema(GetCommitDiffSchema),
648
648
  },
649
+ {
650
+ name: "list_commit_statuses",
651
+ description: "List statuses for a commit",
652
+ inputSchema: toJSONSchema(ListCommitStatusesSchema),
653
+ },
654
+ {
655
+ name: "create_commit_status",
656
+ description: "Create or update the status of a commit",
657
+ inputSchema: toJSONSchema(CreateCommitStatusSchema),
658
+ },
649
659
  {
650
660
  name: "list_group_iterations",
651
661
  description: "List group iterations with filtering options",
@@ -937,6 +947,7 @@ export const readOnlyTools = new Set([
937
947
  "list_commits",
938
948
  "get_commit",
939
949
  "get_commit_diff",
950
+ "list_commit_statuses",
940
951
  "list_group_iterations",
941
952
  "get_group_iteration",
942
953
  "download_attachment",
@@ -1135,6 +1146,8 @@ export const TOOLSET_DEFINITIONS = [
1135
1146
  "list_commits",
1136
1147
  "get_commit",
1137
1148
  "get_commit_diff",
1149
+ "list_commit_statuses",
1150
+ "create_commit_status",
1138
1151
  ]),
1139
1152
  },
1140
1153
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.1.8",
3
+ "version": "2.1.9",
4
4
  "mcpName": "io.github.zereight/gitlab-mcp",
5
5
  "description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
6
6
  "keywords": [
@@ -51,7 +51,7 @@
51
51
  "changelog": "auto-changelog -p",
52
52
  "test": "npm run test:all",
53
53
  "test:all": "npm run build && npm run test:mock && npm run test:live",
54
- "test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
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 && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
55
55
  "test:stateless": "npm run build && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
56
56
  "test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts",
57
57
  "test:live": "node test/validate-api.js",