@zereight/mcp-gitlab 2.1.8 → 2.1.10
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 +45 -38
- package/build/index.js +74 -5
- package/build/schemas.js +91 -2
- package/build/test/dynamic-routing-tests.js +5 -1
- package/build/test/schema-tests.js +300 -3
- package/build/test/streamable-http-static-token-auth.test.js +154 -0
- package/build/test/test-json-schema.js +18 -0
- package/build/test/test-toolset-filtering.js +2 -2
- package/build/tools/registry.js +14 -1
- package/build/utils/schema.js +18 -11
- package/package.json +2 -2
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
|
|
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. `
|
|
605
|
-
117. `
|
|
606
|
-
118. `
|
|
607
|
-
119. `
|
|
608
|
-
120. `
|
|
609
|
-
121. `
|
|
610
|
-
122. `
|
|
611
|
-
123. `
|
|
612
|
-
124. `
|
|
613
|
-
125. `
|
|
614
|
-
126. `
|
|
615
|
-
127. `
|
|
616
|
-
128. `
|
|
617
|
-
129. `
|
|
618
|
-
130. `
|
|
619
|
-
131. `
|
|
620
|
-
132. `
|
|
621
|
-
133. `
|
|
622
|
-
134. `
|
|
623
|
-
135. `
|
|
624
|
-
136. `
|
|
625
|
-
137. `
|
|
626
|
-
138. `
|
|
627
|
-
139. `
|
|
628
|
-
140. `
|
|
629
|
-
141. `
|
|
630
|
-
142. `
|
|
631
|
-
143. `
|
|
632
|
-
144. `
|
|
633
|
-
145. `
|
|
634
|
-
146. `
|
|
635
|
-
147. `
|
|
636
|
-
148. `
|
|
637
|
-
149. `
|
|
638
|
-
150. `
|
|
639
|
-
151. `
|
|
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
|
-
|
|
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 —
|
|
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(),
|
|
@@ -1364,6 +1404,15 @@ export const GetBranchDiffsSchema = ProjectParamsSchema.extend({
|
|
|
1364
1404
|
.describe('Array of regex patterns to exclude files from the diff results. Each pattern is a JavaScript-compatible regular expression that matches file paths to ignore. Examples: ["^vendor/", "^test/mocks/", "\\.spec\\.ts$", "package-lock\\.json"]'),
|
|
1365
1405
|
});
|
|
1366
1406
|
export const GetMergeRequestSchema = ProjectParamsSchema.extend({
|
|
1407
|
+
project_id: z
|
|
1408
|
+
.preprocess(value => (value === undefined || value === null ? value : String(value)), z
|
|
1409
|
+
.string({
|
|
1410
|
+
required_error: "project_id is required",
|
|
1411
|
+
invalid_type_error: "project_id is required",
|
|
1412
|
+
})
|
|
1413
|
+
.refine(value => value === "" || value.trim().length > 0, "project_id is required")
|
|
1414
|
+
.transform(value => (value === "" ? value : value.trim())))
|
|
1415
|
+
.describe("Project ID or complete URL-encoded path to project"),
|
|
1367
1416
|
merge_request_iid: z.coerce.string().optional().describe("The IID of a merge request"),
|
|
1368
1417
|
source_branch: z.string().optional().describe("Source branch name"),
|
|
1369
1418
|
});
|
|
@@ -1781,7 +1830,8 @@ export const ListProjectsSchema = z
|
|
|
1781
1830
|
})
|
|
1782
1831
|
.merge(PaginationOptionsSchema);
|
|
1783
1832
|
// Label operation schemas
|
|
1784
|
-
export const ListLabelsSchema = z
|
|
1833
|
+
export const ListLabelsSchema = z
|
|
1834
|
+
.object({
|
|
1785
1835
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1786
1836
|
with_counts: z
|
|
1787
1837
|
.coerce.boolean()
|
|
@@ -1789,7 +1839,8 @@ export const ListLabelsSchema = z.object({
|
|
|
1789
1839
|
.describe("Whether or not to include issue and merge request counts"),
|
|
1790
1840
|
include_ancestor_groups: z.coerce.boolean().optional().describe("Include ancestor groups"),
|
|
1791
1841
|
search: z.string().optional().describe("Keyword to filter labels by"),
|
|
1792
|
-
})
|
|
1842
|
+
})
|
|
1843
|
+
.merge(PaginationOptionsSchema);
|
|
1793
1844
|
export const GetLabelSchema = z.object({
|
|
1794
1845
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1795
1846
|
label_id: z.coerce.string().describe("The ID or title of a project's label"),
|
|
@@ -2214,6 +2265,44 @@ export const GetCommitDiffSchema = z.object({
|
|
|
2214
2265
|
.optional()
|
|
2215
2266
|
.describe("Whether to return the full diff or only first page (default: false)"),
|
|
2216
2267
|
});
|
|
2268
|
+
export const ListCommitStatusesSchema = z
|
|
2269
|
+
.object({
|
|
2270
|
+
project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
|
|
2271
|
+
sha: z.string().describe("The commit hash or name of a repository branch or tag"),
|
|
2272
|
+
ref: z.string().optional().describe("Filter statuses by Git ref"),
|
|
2273
|
+
stage: z.string().optional().describe("Filter statuses by build stage"),
|
|
2274
|
+
name: z.string().optional().describe("Filter statuses by status name or context"),
|
|
2275
|
+
pipeline_id: z.coerce.number().optional().describe("Filter statuses by pipeline ID"),
|
|
2276
|
+
order_by: z
|
|
2277
|
+
.enum(["id", "pipeline_id"])
|
|
2278
|
+
.optional()
|
|
2279
|
+
.describe("Field to order statuses by"),
|
|
2280
|
+
sort: z.enum(["asc", "desc"]).optional().describe("Sort direction"),
|
|
2281
|
+
all: coerceBooleanString.optional().describe("Return all statuses, not only latest ones"),
|
|
2282
|
+
})
|
|
2283
|
+
.merge(PaginationOptionsSchema);
|
|
2284
|
+
export const CreateCommitStatusSchema = z.object({
|
|
2285
|
+
project_id: z.coerce.string().describe("Project ID or complete URL-encoded path to project"),
|
|
2286
|
+
sha: z.string().describe("The commit hash to set the status on"),
|
|
2287
|
+
state: z
|
|
2288
|
+
.enum(["pending", "running", "success", "failed", "canceled", "skipped"])
|
|
2289
|
+
.describe("Commit status state"),
|
|
2290
|
+
ref: z.string().max(255).optional().describe("The branch or tag ref"),
|
|
2291
|
+
name: z
|
|
2292
|
+
.string()
|
|
2293
|
+
.max(255)
|
|
2294
|
+
.optional()
|
|
2295
|
+
.describe("Status name. GitLab defaults to 'default' when omitted."),
|
|
2296
|
+
context: z
|
|
2297
|
+
.string()
|
|
2298
|
+
.max(255)
|
|
2299
|
+
.optional()
|
|
2300
|
+
.describe("Alias for name. Provide either name or context, not both."),
|
|
2301
|
+
target_url: z.string().max(255).optional().describe("Target URL associated with this status"),
|
|
2302
|
+
description: z.string().max(255).optional().describe("Short status description"),
|
|
2303
|
+
coverage: z.coerce.number().optional().describe("Total code coverage for this status"),
|
|
2304
|
+
pipeline_id: z.coerce.number().optional().describe("Pipeline ID to attach the status to"),
|
|
2305
|
+
});
|
|
2217
2306
|
// Schema for listing issues assigned to the current user
|
|
2218
2307
|
export const MyIssuesSchema = z.object({
|
|
2219
2308
|
project_id: z
|
|
@@ -417,7 +417,7 @@ async function validateToolCalls(client, mockServer, expectedToken) {
|
|
|
417
417
|
{ name: 'get_merge_request', params: { project_id: '1', merge_request_iid: '1' } },
|
|
418
418
|
{ name: 'list_merge_requests', params: { project_id: '1' } },
|
|
419
419
|
{ name: 'get_repository_tree', params: { project_id: '1' } },
|
|
420
|
-
{ name: 'list_labels', params: { project_id: '1' } },
|
|
420
|
+
{ name: 'list_labels', params: { project_id: '1', page: 2, per_page: 50 } },
|
|
421
421
|
{ name: 'list_pipelines', params: { project_id: '1' } },
|
|
422
422
|
{ name: 'list_commits', params: { project_id: '1' } },
|
|
423
423
|
];
|
|
@@ -468,6 +468,10 @@ async function validateToolCalls(client, mockServer, expectedToken) {
|
|
|
468
468
|
else {
|
|
469
469
|
assert.strictEqual(req.headers['private-token'], expectedToken);
|
|
470
470
|
}
|
|
471
|
+
if (tool.name === 'list_labels') {
|
|
472
|
+
assert.strictEqual(req.query.page, '2');
|
|
473
|
+
assert.strictEqual(req.query.per_page, '50');
|
|
474
|
+
}
|
|
471
475
|
res.json(mockResponse);
|
|
472
476
|
});
|
|
473
477
|
const result = await client.callTool(tool.name, tool.params);
|
|
@@ -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, ListLabelsSchema, 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 = [
|
|
@@ -398,11 +534,36 @@ function runGetMergeRequestSchemaTests() {
|
|
|
398
534
|
input: { project_id: 'my/project', merge_request_iid: 24 },
|
|
399
535
|
expected: { project_id: 'my/project', merge_request_iid: '24' },
|
|
400
536
|
},
|
|
537
|
+
{
|
|
538
|
+
name: 'schema:get_merge_request:coerced-project-id',
|
|
539
|
+
input: { project_id: 123, merge_request_iid: '42' },
|
|
540
|
+
expected: { project_id: '123', merge_request_iid: '42' },
|
|
541
|
+
},
|
|
401
542
|
{
|
|
402
543
|
name: 'schema:get_merge_request:coerced-source-branch',
|
|
403
544
|
input: { project_id: 'my/project', source_branch: 'feature' },
|
|
404
545
|
expected: { project_id: 'my/project', source_branch: 'feature' },
|
|
405
546
|
},
|
|
547
|
+
{
|
|
548
|
+
name: 'schema:get_merge_request:reject-missing-project-id-with-merge-request-iid',
|
|
549
|
+
input: { merge_request_iid: '42' },
|
|
550
|
+
shouldFail: true,
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: 'schema:get_merge_request:reject-missing-project-id-with-source-branch',
|
|
554
|
+
input: { source_branch: 'feature' },
|
|
555
|
+
shouldFail: true,
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
name: 'schema:get_merge_request:allow-empty-project-id-for-default-project',
|
|
559
|
+
input: { project_id: '', merge_request_iid: '42' },
|
|
560
|
+
expected: { project_id: '', merge_request_iid: '42' },
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: 'schema:get_merge_request:reject-whitespace-project-id',
|
|
564
|
+
input: { project_id: ' ', merge_request_iid: '42' },
|
|
565
|
+
shouldFail: true,
|
|
566
|
+
},
|
|
406
567
|
];
|
|
407
568
|
let passed = 0;
|
|
408
569
|
let failed = 0;
|
|
@@ -448,6 +609,93 @@ function runGetMergeRequestSchemaTests() {
|
|
|
448
609
|
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
449
610
|
return { passed, failed };
|
|
450
611
|
}
|
|
612
|
+
function runGitLabMergeRequestSchemaTests() {
|
|
613
|
+
console.log('\n🧪 Testing GitLabMergeRequestSchema...');
|
|
614
|
+
const baseMergeRequest = {
|
|
615
|
+
id: '1001',
|
|
616
|
+
iid: '42',
|
|
617
|
+
project_id: '123',
|
|
618
|
+
title: 'Add milestone exposure',
|
|
619
|
+
description: 'Expose MR milestone',
|
|
620
|
+
state: 'opened',
|
|
621
|
+
author: {
|
|
622
|
+
id: '1',
|
|
623
|
+
username: 'octocat',
|
|
624
|
+
name: 'Octo Cat',
|
|
625
|
+
avatar_url: null,
|
|
626
|
+
web_url: 'https://gitlab.example.com/octocat',
|
|
627
|
+
},
|
|
628
|
+
assignees: [],
|
|
629
|
+
reviewers: [],
|
|
630
|
+
source_branch: 'feature/milestone',
|
|
631
|
+
target_branch: 'main',
|
|
632
|
+
web_url: 'https://gitlab.example.com/group/project/-/merge_requests/42',
|
|
633
|
+
created_at: '2026-05-07T00:00:00.000Z',
|
|
634
|
+
updated_at: '2026-05-07T00:00:00.000Z',
|
|
635
|
+
merged_at: null,
|
|
636
|
+
closed_at: null,
|
|
637
|
+
merge_commit_sha: null,
|
|
638
|
+
};
|
|
639
|
+
const cases = [
|
|
640
|
+
{
|
|
641
|
+
name: 'schema:gitlab_merge_request:preserves-milestone',
|
|
642
|
+
input: {
|
|
643
|
+
...baseMergeRequest,
|
|
644
|
+
milestone: {
|
|
645
|
+
id: '5',
|
|
646
|
+
iid: '2',
|
|
647
|
+
title: 'v1.0',
|
|
648
|
+
description: 'Version 1.0',
|
|
649
|
+
state: 'active',
|
|
650
|
+
web_url: 'https://gitlab.example.com/group/project/-/milestones/2',
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
validate: (data) => data.milestone?.title === 'v1.0' &&
|
|
654
|
+
data.milestone?.id === '5' &&
|
|
655
|
+
data.milestone?.iid === '2',
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
name: 'schema:gitlab_merge_request:allows-null-milestone',
|
|
659
|
+
input: {
|
|
660
|
+
...baseMergeRequest,
|
|
661
|
+
milestone: null,
|
|
662
|
+
},
|
|
663
|
+
validate: (data) => data.milestone === null,
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: 'schema:gitlab_merge_request:allows-omitted-milestone',
|
|
667
|
+
input: {
|
|
668
|
+
...baseMergeRequest,
|
|
669
|
+
},
|
|
670
|
+
validate: (data) => data.milestone === undefined,
|
|
671
|
+
},
|
|
672
|
+
];
|
|
673
|
+
let passed = 0;
|
|
674
|
+
let failed = 0;
|
|
675
|
+
cases.forEach(testCase => {
|
|
676
|
+
const result = { name: testCase.name, status: 'failed' };
|
|
677
|
+
const parsed = GitLabMergeRequestSchema.safeParse(testCase.input);
|
|
678
|
+
if (parsed.success && testCase.validate(parsed.data)) {
|
|
679
|
+
result.status = 'passed';
|
|
680
|
+
}
|
|
681
|
+
else if (parsed.success) {
|
|
682
|
+
result.error = `Unexpected parsed result: ${JSON.stringify(parsed.data)}`;
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
result.error = parsed.error?.message || 'Schema validation failed';
|
|
686
|
+
}
|
|
687
|
+
if (result.status === 'passed') {
|
|
688
|
+
passed++;
|
|
689
|
+
console.log(`✅ ${result.name}`);
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
failed++;
|
|
693
|
+
console.log(`❌ ${result.name}: ${result.error}`);
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
697
|
+
return { passed, failed };
|
|
698
|
+
}
|
|
451
699
|
function runEmojiReactionSchemaTests() {
|
|
452
700
|
console.log('\n🧪 Testing Emoji Reaction Schemas...');
|
|
453
701
|
const cases = [
|
|
@@ -653,6 +901,52 @@ function runLabelsCoercionSchemaTests() {
|
|
|
653
901
|
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
654
902
|
return { passed, failed };
|
|
655
903
|
}
|
|
904
|
+
function runListLabelsSchemaTests() {
|
|
905
|
+
console.log('\n=== List Labels Schema Tests ===');
|
|
906
|
+
const cases = [
|
|
907
|
+
{
|
|
908
|
+
name: 'schema:list_labels:pagination-coercion',
|
|
909
|
+
input: { project_id: 'my/project', page: '2', per_page: '100' },
|
|
910
|
+
expected: { project_id: 'my/project', page: 2, per_page: 100 },
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: 'schema:list_labels:filters-with-pagination',
|
|
914
|
+
input: { project_id: 'my/project', search: 'backend', with_counts: 'true', page: 3, per_page: 50 },
|
|
915
|
+
expected: { project_id: 'my/project', search: 'backend', with_counts: true, page: 3, per_page: 50 },
|
|
916
|
+
},
|
|
917
|
+
];
|
|
918
|
+
let passed = 0;
|
|
919
|
+
let failed = 0;
|
|
920
|
+
cases.forEach(testCase => {
|
|
921
|
+
const result = { name: testCase.name, status: 'failed' };
|
|
922
|
+
const parsed = ListLabelsSchema.safeParse(testCase.input);
|
|
923
|
+
if (!parsed.success) {
|
|
924
|
+
result.error = parsed.error?.message || 'Schema validation failed';
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
const matches = Object.entries(testCase.expected).every(([key, value]) => {
|
|
928
|
+
const actual = parsed.data[key];
|
|
929
|
+
return actual === value;
|
|
930
|
+
});
|
|
931
|
+
if (matches) {
|
|
932
|
+
result.status = 'passed';
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
result.error = `Unexpected parsed result: ${JSON.stringify(parsed.data)}`;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (result.status === 'passed') {
|
|
939
|
+
passed++;
|
|
940
|
+
console.log(`✅ ${result.name}`);
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
failed++;
|
|
944
|
+
console.log(`❌ ${result.name}: ${result.error}`);
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
948
|
+
return { passed, failed };
|
|
949
|
+
}
|
|
656
950
|
function runGitLabTreeItemSchemaTests() {
|
|
657
951
|
console.log('\n=== GitLabTreeItem Schema Tests ===');
|
|
658
952
|
const cases = [
|
|
@@ -778,15 +1072,18 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
778
1072
|
const getFileContentsResult = runGetFileContentsSchemaTests();
|
|
779
1073
|
const fileContentResult = runGitLabFileContentSchemaTests();
|
|
780
1074
|
const createPipelineResult = runCreatePipelineSchemaTests();
|
|
1075
|
+
const commitStatusResult = runCommitStatusSchemaTests();
|
|
781
1076
|
const createIssueNoteResult = runCreateIssueNoteSchemaTests();
|
|
782
1077
|
const getMergeRequestResult = runGetMergeRequestSchemaTests();
|
|
1078
|
+
const gitLabMergeRequestResult = runGitLabMergeRequestSchemaTests();
|
|
783
1079
|
const emojiReactionResult = runEmojiReactionSchemaTests();
|
|
784
1080
|
const repositorySchemaResult = runGitLabRepositorySchemaTests();
|
|
785
1081
|
const labelsCoercionResult = runLabelsCoercionSchemaTests();
|
|
1082
|
+
const listLabelsResult = runListLabelsSchemaTests();
|
|
786
1083
|
const treeItemResult = runGitLabTreeItemSchemaTests();
|
|
787
1084
|
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;
|
|
1085
|
+
const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + commitStatusResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + gitLabMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + listLabelsResult.passed + treeItemResult.passed + repositoryTreeResult.passed;
|
|
1086
|
+
const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + commitStatusResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + gitLabMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + listLabelsResult.failed + treeItemResult.failed + repositoryTreeResult.failed;
|
|
790
1087
|
console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
|
|
791
1088
|
if (totalFailed > 0) {
|
|
792
1089
|
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
|
+
});
|
|
@@ -124,6 +124,24 @@ function runTests() {
|
|
|
124
124
|
error: error.message,
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
|
+
// Test 7: Effects fields
|
|
128
|
+
try {
|
|
129
|
+
const schema = z.object({
|
|
130
|
+
effectRequired: z.preprocess(value => value, z.string()),
|
|
131
|
+
effectOptional: z.preprocess(value => value, z.string().optional()),
|
|
132
|
+
});
|
|
133
|
+
const result = toJSONSchema(schema);
|
|
134
|
+
assert(result.required?.includes("effectRequired"), "effectRequired should be in required array");
|
|
135
|
+
assert(!result.required?.includes("effectOptional"), "effectOptional should NOT be in required array");
|
|
136
|
+
results.push({ name: "effects fields", status: "passed" });
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
results.push({
|
|
140
|
+
name: "effects fields",
|
|
141
|
+
status: "failed",
|
|
142
|
+
error: error.message,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
127
145
|
// Print results
|
|
128
146
|
const passed = results.filter((r) => r.status === "passed").length;
|
|
129
147
|
const failed = results.filter((r) => r.status === "failed").length;
|
|
@@ -19,7 +19,7 @@ const TOOLSET_TOOL_COUNTS = {
|
|
|
19
19
|
merge_requests: 40,
|
|
20
20
|
issues: 23,
|
|
21
21
|
repositories: 7,
|
|
22
|
-
branches:
|
|
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"],
|
package/build/tools/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
2
|
import { toJSONSchema } from "../utils/schema.js";
|
|
3
3
|
import { USE_GITLAB_WIKI, USE_MILESTONE, USE_PIPELINE, } from "../config.js";
|
|
4
|
-
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, 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/build/utils/schema.js
CHANGED
|
@@ -6,6 +6,23 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|
|
6
6
|
*/
|
|
7
7
|
export const toJSONSchema = (schema) => {
|
|
8
8
|
const jsonSchema = zodToJsonSchema(schema, { $refStrategy: "none" });
|
|
9
|
+
const isOptionalLikeField = (zodType) => {
|
|
10
|
+
const def = zodType._def;
|
|
11
|
+
const typeName = def?.typeName;
|
|
12
|
+
if (["ZodOptional", "ZodNullable", "ZodDefault", "ZodCatch"].includes(typeName)) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (typeName === "ZodEffects") {
|
|
16
|
+
return isOptionalLikeField(def.schema);
|
|
17
|
+
}
|
|
18
|
+
if (typeName === "ZodBranded") {
|
|
19
|
+
return isOptionalLikeField(def.type);
|
|
20
|
+
}
|
|
21
|
+
if (typeName === "ZodPipeline") {
|
|
22
|
+
return isOptionalLikeField(def.in);
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
};
|
|
9
26
|
// Extract required fields from Zod schema
|
|
10
27
|
const zodRequiredFields = (() => {
|
|
11
28
|
if (schema instanceof z.ZodObject) {
|
|
@@ -13,17 +30,7 @@ export const toJSONSchema = (schema) => {
|
|
|
13
30
|
const requiredFields = [];
|
|
14
31
|
Object.entries(shape).forEach(([key, fieldDef]) => {
|
|
15
32
|
const zodType = fieldDef;
|
|
16
|
-
|
|
17
|
-
// Check if field is wrapped in zod required types
|
|
18
|
-
const isRequired = [
|
|
19
|
-
"ZodOptional",
|
|
20
|
-
"ZodNullable",
|
|
21
|
-
"ZodDefault",
|
|
22
|
-
"ZodEffects",
|
|
23
|
-
"ZodCatch",
|
|
24
|
-
"ZodBranded",
|
|
25
|
-
].includes(typeName);
|
|
26
|
-
if (!isRequired) {
|
|
33
|
+
if (!isOptionalLikeField(zodType)) {
|
|
27
34
|
requiredFields.push(key);
|
|
28
35
|
}
|
|
29
36
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.10",
|
|
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",
|