@zereight/mcp-gitlab 2.1.10 → 2.1.11
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 +1 -0
- package/build/index.js +89 -5
- package/build/schemas.js +93 -0
- package/build/test/schema-tests.js +140 -3
- package/build/test/test-merge-request-pipelines.js +106 -0
- package/build/test/test-toolset-filtering.js +3 -3
- package/build/test/utils/server-launcher.js +2 -1
- package/build/tools/registry.js +29 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -644,6 +644,7 @@ Register the skill directory in your AI client to get optimal tool usage guidanc
|
|
|
644
644
|
151. `search_project_code` - Search for code within a specific GitLab project (requires advanced search or exact code search to be enabled)
|
|
645
645
|
152. `search_group_code` - Search for code within a specific GitLab group (requires advanced search or exact code search to be enabled)
|
|
646
646
|
153. `execute_graphql` - Execute a GitLab GraphQL query
|
|
647
|
+
154. `list_merge_request_pipelines` - List pipelines for a merge request with pagination support
|
|
647
648
|
|
|
648
649
|
<!-- TOOLS-END -->
|
|
649
650
|
|
package/build/index.js
CHANGED
|
@@ -25,13 +25,13 @@ import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middlew
|
|
|
25
25
|
import { GitLabClientPool } from "./gitlab-client-pool.js";
|
|
26
26
|
import { allTools, readOnlyTools, destructiveTools, parseEnabledToolsets, parseIndividualTools, buildFeatureFlagOverrides, isToolInEnabledToolset, TOOLSET_DEFINITIONS, ALL_TOOLSET_IDS, } from "./tools/registry.js";
|
|
27
27
|
import { BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, // Added
|
|
28
|
-
CreateMergeRequestNoteSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateMergeRequestNoteEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, 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,
|
|
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, GetUserSchema, GitLabUserFullSchema, WhoAmISchema, GitLabCurrentUserSchema, 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, ListCommitStatusesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
34
|
-
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, } from "./schemas.js";
|
|
33
|
+
GitLabDraftNoteSchema, GitLabForkSchema, GitLabIssueLinkSchema, GitLabIssueSchema, GitLabIssueWithLinkDetailsSchema, GitLabMarkdownUploadSchema, GitLabMergeRequestPipelineSchema, GitLabMergeRequestSchema, GitLabMilestonesSchema, GitLabNamespaceExistsResponseSchema, GitLabNamespaceSchema, GitLabPipelineJobSchema, GitLabDeploymentSchema, GitLabEnvironmentSchema, GitLabPipelineSchema, GitLabPipelineTriggerJobSchema, GitLabProjectMemberSchema, GitLabProjectSchema, GitLabTodoSchema, GitLabReferenceSchema, GitLabRepositorySchema, GitLabSearchBlobResultSchema, GitLabSearchResponseSchema, GitLabTreeItemSchema, GitLabUserSchema, GitLabUsersResponseSchema, GitLabWikiPageSchema, GroupIteration, ListCommitStatusesSchema, ListCommitsSchema, ListDraftNotesSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListTodosSchema, ListLabelsSchema, ListMergeRequestDiffsSchema, // Added
|
|
34
|
+
GetMergeRequestFileDiffSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestsSchema, ListMergeRequestVersionsSchema, GetMergeRequestVersionSchema, GitLabMergeRequestVersionSchema, GitLabMergeRequestVersionDetailSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelinesSchema, ListDeploymentsSchema, ListEnvironmentsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListWikiPagesSchema, GetGroupWikiPageSchema, ListGroupWikiPagesSchema, UpdateGroupWikiPageSchema, MarkdownUploadSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, GetJobArtifactFileSchema, GitLabArtifactEntrySchema, ListJobArtifactsSchema, MergeMergeRequestSchema, ApproveMergeRequestSchema, UnapproveMergeRequestSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GitLabMergeRequestApprovalsResponseSchema, GitLabMergeRequestApprovalStateSchema, MyIssuesSchema, MarkAllTodosDoneSchema, MarkTodoDoneSchema, PaginatedDiscussionsResponseSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PlayPipelineJobSchema, PushFilesSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UpdateDraftNoteSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestSchema, UpdateWikiPageSchema, VerifyNamespaceSchema, GitLabEventSchema, ListEventsSchema, GetProjectEventsSchema, ExecuteGraphQLSchema, GitLabReleaseSchema, ListReleasesSchema, GetReleaseSchema, CreateReleaseSchema, UpdateReleaseSchema, DeleteReleaseSchema, CreateReleaseEvidenceSchema, DownloadReleaseAssetSchema, ListTagsSchema, GetTagSchema, CreateTagSchema, DeleteTagSchema, GetTagSignatureSchema, GitLabTagSchema, GitLabTagSignatureSchema, GetMergeRequestNotesSchema, GetMergeRequestNoteSchema, DeleteMergeRequestDiscussionNoteSchema, ResolveMergeRequestThreadSchema, GetWorkItemSchema, ListWorkItemsSchema, CreateWorkItemSchema, UpdateWorkItemSchema, ConvertWorkItemTypeSchema, ListWorkItemStatusesSchema, ListWorkItemNotesSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, MoveWorkItemSchema, ListCustomFieldDefinitionsSchema, GetTimelineEventsSchema, CreateTimelineEventSchema, ListWebhooksSchema, ListWebhookEventsSchema, GetWebhookEventSchema, HealthCheckSchema, } from "./schemas.js";
|
|
35
35
|
import { randomUUID } from "node:crypto";
|
|
36
36
|
import { pino } from "pino";
|
|
37
37
|
const logger = pino({
|
|
@@ -783,6 +783,15 @@ function buildAuthHeaders() {
|
|
|
783
783
|
}
|
|
784
784
|
return {};
|
|
785
785
|
}
|
|
786
|
+
function usesJobTokenHeader() {
|
|
787
|
+
if (GITLAB_JOB_TOKEN)
|
|
788
|
+
return true;
|
|
789
|
+
if (REMOTE_AUTHORIZATION || GITLAB_MCP_OAUTH) {
|
|
790
|
+
const ctx = sessionAuthStore.getStore();
|
|
791
|
+
return ctx?.header === "JOB-TOKEN";
|
|
792
|
+
}
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
786
795
|
/**
|
|
787
796
|
* Get the effective GitLab API URL for the current request
|
|
788
797
|
* In REMOTE_AUTHORIZATION mode with ENABLE_DYNAMIC_API_URL, reads from session context
|
|
@@ -3263,6 +3272,21 @@ async function getMergeRequestSourceCommitCount(projectId, mergeRequestIid) {
|
|
|
3263
3272
|
}
|
|
3264
3273
|
return totalCount;
|
|
3265
3274
|
}
|
|
3275
|
+
async function listMergeRequestPipelines(projectId, mergeRequestIid, options = {}) {
|
|
3276
|
+
projectId = decodeURIComponent(projectId);
|
|
3277
|
+
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}/merge_requests/${mergeRequestIid}/pipelines`);
|
|
3278
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
3279
|
+
if (value !== undefined) {
|
|
3280
|
+
url.searchParams.append(key, value.toString());
|
|
3281
|
+
}
|
|
3282
|
+
});
|
|
3283
|
+
const response = await fetch(url.toString(), {
|
|
3284
|
+
...getFetchConfig(),
|
|
3285
|
+
});
|
|
3286
|
+
await handleGitLabError(response);
|
|
3287
|
+
const data = await response.json();
|
|
3288
|
+
return z.array(GitLabMergeRequestPipelineSchema).parse(data);
|
|
3289
|
+
}
|
|
3266
3290
|
async function getProjectMergeMethod(projectId) {
|
|
3267
3291
|
const url = new URL(`${getEffectiveApiUrl()}/projects/${encodeURIComponent(getEffectiveProjectId(projectId))}`);
|
|
3268
3292
|
const response = await fetch(url.toString(), {
|
|
@@ -5449,9 +5473,21 @@ async function createCommitStatus(projectId, sha, options) {
|
|
|
5449
5473
|
*/
|
|
5450
5474
|
async function getCurrentUser() {
|
|
5451
5475
|
const response = await fetch(`${getEffectiveApiUrl()}/user`, getFetchConfig());
|
|
5476
|
+
if (response.ok) {
|
|
5477
|
+
const data = await response.json();
|
|
5478
|
+
return GitLabUserSchema.parse(data);
|
|
5479
|
+
}
|
|
5480
|
+
if ((response.status === 401 || response.status === 403) && usesJobTokenHeader()) {
|
|
5481
|
+
const jobResponse = await fetch(`${getEffectiveApiUrl()}/job`, getFetchConfig());
|
|
5482
|
+
if (jobResponse.ok) {
|
|
5483
|
+
const jobData = await jobResponse.json();
|
|
5484
|
+
if (jobData.user) {
|
|
5485
|
+
return GitLabUserSchema.parse(jobData.user);
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5452
5489
|
await handleGitLabError(response);
|
|
5453
|
-
|
|
5454
|
-
return GitLabUserSchema.parse(data);
|
|
5490
|
+
throw new Error(`GitLab API error: ${response.status} ${response.statusText}`);
|
|
5455
5491
|
}
|
|
5456
5492
|
/**
|
|
5457
5493
|
* List issues assigned to the current authenticated user
|
|
@@ -6329,6 +6365,14 @@ async function handleToolCall(params) {
|
|
|
6329
6365
|
content: [{ type: "text", text: JSON.stringify(files, null, 2) }],
|
|
6330
6366
|
};
|
|
6331
6367
|
}
|
|
6368
|
+
case "list_merge_request_pipelines": {
|
|
6369
|
+
const args = ListMergeRequestPipelinesSchema.parse(params.arguments);
|
|
6370
|
+
const { project_id, merge_request_iid, ...options } = args;
|
|
6371
|
+
const pipelines = await listMergeRequestPipelines(project_id, merge_request_iid, options);
|
|
6372
|
+
return {
|
|
6373
|
+
content: [{ type: "text", text: JSON.stringify(pipelines, null, 2) }],
|
|
6374
|
+
};
|
|
6375
|
+
}
|
|
6332
6376
|
case "list_merge_request_diffs": {
|
|
6333
6377
|
const args = ListMergeRequestDiffsSchema.parse(params.arguments);
|
|
6334
6378
|
const changes = await listMergeRequestDiffs(args.project_id, args.merge_request_iid, args.source_branch, args.page, args.per_page, args.unidiff);
|
|
@@ -6503,6 +6547,32 @@ async function handleToolCall(params) {
|
|
|
6503
6547
|
content: [{ type: "text", text: JSON.stringify(usersMap, null, 2) }],
|
|
6504
6548
|
};
|
|
6505
6549
|
}
|
|
6550
|
+
case "get_user": {
|
|
6551
|
+
const args = GetUserSchema.parse(params.arguments);
|
|
6552
|
+
const url = new URL(`${getEffectiveApiUrl()}/users/${encodeURIComponent(args.user_id)}`);
|
|
6553
|
+
const response = await fetch(url.toString(), {
|
|
6554
|
+
...getFetchConfig(),
|
|
6555
|
+
});
|
|
6556
|
+
await handleGitLabError(response);
|
|
6557
|
+
const data = await response.json();
|
|
6558
|
+
const user = GitLabUserFullSchema.parse(data);
|
|
6559
|
+
return {
|
|
6560
|
+
content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
|
|
6561
|
+
};
|
|
6562
|
+
}
|
|
6563
|
+
case "whoami": {
|
|
6564
|
+
WhoAmISchema.parse(params.arguments ?? {});
|
|
6565
|
+
const url = new URL(`${getEffectiveApiUrl()}/user`);
|
|
6566
|
+
const response = await fetch(url.toString(), {
|
|
6567
|
+
...getFetchConfig(),
|
|
6568
|
+
});
|
|
6569
|
+
await handleGitLabError(response);
|
|
6570
|
+
const data = await response.json();
|
|
6571
|
+
const user = GitLabCurrentUserSchema.parse(data);
|
|
6572
|
+
return {
|
|
6573
|
+
content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
|
|
6574
|
+
};
|
|
6575
|
+
}
|
|
6506
6576
|
case "create_note": {
|
|
6507
6577
|
const args = CreateNoteSchema.parse(params.arguments);
|
|
6508
6578
|
const { project_id, noteable_type, noteable_iid, body } = args;
|
|
@@ -7528,6 +7598,20 @@ async function handleToolCall(params) {
|
|
|
7528
7598
|
content: [{ type: "text", text: JSON.stringify(event, null, 2) }],
|
|
7529
7599
|
};
|
|
7530
7600
|
}
|
|
7601
|
+
case "health_check": {
|
|
7602
|
+
HealthCheckSchema.parse(params.arguments ?? {});
|
|
7603
|
+
const url = new URL(`${getEffectiveApiUrl()}/user`);
|
|
7604
|
+
const response = await fetch(url.toString(), getFetchConfig());
|
|
7605
|
+
let authenticated = response.ok;
|
|
7606
|
+
if (!authenticated && (response.status === 401 || response.status === 403) && (GITLAB_JOB_TOKEN || usesJobTokenHeader())) {
|
|
7607
|
+
const jobUrl = new URL(`${getEffectiveApiUrl()}/job`);
|
|
7608
|
+
const jobResponse = await fetch(jobUrl.toString(), getFetchConfig());
|
|
7609
|
+
authenticated = jobResponse.ok;
|
|
7610
|
+
}
|
|
7611
|
+
return {
|
|
7612
|
+
content: [{ type: "text", text: JSON.stringify({ status: authenticated ? "ok" : "error", authenticated, gitlab_url: getEffectiveApiUrl() }) }],
|
|
7613
|
+
};
|
|
7614
|
+
}
|
|
7531
7615
|
default:
|
|
7532
7616
|
throw new Error(`Unknown tool: ${params.name}`);
|
|
7533
7617
|
}
|
package/build/schemas.js
CHANGED
|
@@ -248,6 +248,17 @@ export const GetPipelineSchema = z.object({
|
|
|
248
248
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
249
249
|
pipeline_id: z.coerce.string().describe("The ID of the pipeline"),
|
|
250
250
|
});
|
|
251
|
+
export const GitLabMergeRequestPipelineSchema = z.object({
|
|
252
|
+
id: z.coerce.string(),
|
|
253
|
+
sha: z.string(),
|
|
254
|
+
ref: z.string(),
|
|
255
|
+
status: z.string(),
|
|
256
|
+
project_id: z.coerce.string().optional(),
|
|
257
|
+
source: z.string().optional(),
|
|
258
|
+
created_at: z.string().optional(),
|
|
259
|
+
updated_at: z.string().optional(),
|
|
260
|
+
web_url: z.string().optional(),
|
|
261
|
+
});
|
|
251
262
|
// Schema for listing jobs in a pipeline
|
|
252
263
|
export const ListPipelineJobsSchema = z
|
|
253
264
|
.object({
|
|
@@ -514,6 +525,76 @@ export const GitLabUsersResponseSchema = z.record(z.string(), z
|
|
|
514
525
|
web_url: z.string(),
|
|
515
526
|
})
|
|
516
527
|
.nullable());
|
|
528
|
+
export const GetUserSchema = z.object({
|
|
529
|
+
user_id: z.coerce.string().describe("The ID of the user"),
|
|
530
|
+
});
|
|
531
|
+
export const GitLabUserFullSchema = z.object({
|
|
532
|
+
id: z.coerce.string(),
|
|
533
|
+
username: z.string(),
|
|
534
|
+
name: z.string(),
|
|
535
|
+
state: z.string(),
|
|
536
|
+
avatar_url: z.string().nullable(),
|
|
537
|
+
web_url: z.string(),
|
|
538
|
+
created_at: z.string(),
|
|
539
|
+
bio: z.string().nullable(),
|
|
540
|
+
location: z.string().nullable(),
|
|
541
|
+
public_email: z.string().nullable(),
|
|
542
|
+
website_url: z.string().nullable(),
|
|
543
|
+
organization: z.string().nullable(),
|
|
544
|
+
job_title: z.string().nullable(),
|
|
545
|
+
pronouns: z.string().nullable(),
|
|
546
|
+
bot: z.boolean().optional(),
|
|
547
|
+
work_information: z.string().nullable(),
|
|
548
|
+
followers: z.number().optional(),
|
|
549
|
+
following: z.number().optional(),
|
|
550
|
+
is_followed: z.boolean().optional(),
|
|
551
|
+
local_time: z.string().nullable().optional(),
|
|
552
|
+
last_sign_in_at: z.string().nullable().optional(),
|
|
553
|
+
confirmed_at: z.string().nullable().optional(),
|
|
554
|
+
last_activity_on: z.string().nullable().optional(),
|
|
555
|
+
email: z.string().nullable().optional(),
|
|
556
|
+
theme_id: z.number().nullable().optional(),
|
|
557
|
+
color_scheme_id: z.number().nullable().optional(),
|
|
558
|
+
projects_limit: z.number().nullable().optional(),
|
|
559
|
+
current_sign_in_at: z.string().nullable().optional(),
|
|
560
|
+
identities: z.array(z.object({
|
|
561
|
+
provider: z.string(),
|
|
562
|
+
extern_uid: z.string(),
|
|
563
|
+
})).optional(),
|
|
564
|
+
can_create_group: z.boolean().nullable().optional(),
|
|
565
|
+
can_create_project: z.boolean().nullable().optional(),
|
|
566
|
+
two_factor_enabled: z.boolean().nullable().optional(),
|
|
567
|
+
external: z.boolean().nullable().optional(),
|
|
568
|
+
private_profile: z.boolean().nullable().optional(),
|
|
569
|
+
is_admin: z.boolean().nullable().optional(),
|
|
570
|
+
}).passthrough();
|
|
571
|
+
export const WhoAmISchema = z.object({});
|
|
572
|
+
export const GitLabCurrentUserSchema = z.object({
|
|
573
|
+
id: z.coerce.string(),
|
|
574
|
+
username: z.string(),
|
|
575
|
+
name: z.string(),
|
|
576
|
+
state: z.string(),
|
|
577
|
+
avatar_url: z.string().nullable(),
|
|
578
|
+
web_url: z.string(),
|
|
579
|
+
created_at: z.string(),
|
|
580
|
+
bio: z.string().nullable(),
|
|
581
|
+
location: z.string().nullable(),
|
|
582
|
+
public_email: z.string().nullable(),
|
|
583
|
+
website_url: z.string().nullable(),
|
|
584
|
+
organization: z.string().nullable(),
|
|
585
|
+
job_title: z.string().nullable(),
|
|
586
|
+
email: z.string().nullable(),
|
|
587
|
+
last_sign_in_at: z.string().nullable(),
|
|
588
|
+
confirmed_at: z.string().nullable(),
|
|
589
|
+
last_activity_on: z.string().nullable(),
|
|
590
|
+
is_admin: z.boolean().optional(),
|
|
591
|
+
can_create_group: z.boolean().optional(),
|
|
592
|
+
can_create_project: z.boolean().optional(),
|
|
593
|
+
identities: z.array(z.object({
|
|
594
|
+
provider: z.string(),
|
|
595
|
+
extern_uid: z.string(),
|
|
596
|
+
})).optional(),
|
|
597
|
+
}).passthrough();
|
|
517
598
|
// Namespace related schemas
|
|
518
599
|
// Base schema for project-related operations
|
|
519
600
|
const ProjectParamsSchema = z.object({
|
|
@@ -1541,6 +1622,17 @@ export const GetMergeRequestApprovalStateSchema = ProjectParamsSchema.extend({
|
|
|
1541
1622
|
export const GetMergeRequestConflictsSchema = ProjectParamsSchema.extend({
|
|
1542
1623
|
merge_request_iid: z.coerce.string().describe("The IID of the merge request"),
|
|
1543
1624
|
});
|
|
1625
|
+
export const ListMergeRequestPipelinesSchema = ProjectParamsSchema.extend({
|
|
1626
|
+
merge_request_iid: z
|
|
1627
|
+
.preprocess(value => (value === undefined || value === null ? value : String(value)), z
|
|
1628
|
+
.string({
|
|
1629
|
+
required_error: "merge_request_iid is required",
|
|
1630
|
+
invalid_type_error: "merge_request_iid is required",
|
|
1631
|
+
})
|
|
1632
|
+
.refine(value => value.trim().length > 0, "merge_request_iid is required")
|
|
1633
|
+
.transform(value => value.trim()))
|
|
1634
|
+
.describe("The internal ID of the merge request"),
|
|
1635
|
+
}).merge(PaginationOptionsSchema);
|
|
1544
1636
|
export const GetMergeRequestDiffsSchema = GetMergeRequestSchema.extend({
|
|
1545
1637
|
view: z.enum(["inline", "parallel"]).optional().describe("Diff view type"),
|
|
1546
1638
|
excluded_file_patterns: z
|
|
@@ -3141,3 +3233,4 @@ export const GetWebhookEventSchema = z
|
|
|
3141
3233
|
.refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
|
|
3142
3234
|
message: "Provide exactly one of project_id or group_id",
|
|
3143
3235
|
});
|
|
3236
|
+
export const HealthCheckSchema = z.object({});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
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';
|
|
2
|
+
import { GetFileContentsSchema, GitLabFileContentSchema, GitLabRepositorySchema, CreatePipelineSchema, CreateCommitStatusSchema, ListCommitStatusesSchema, CreateIssueNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateIssueEmojiReactionSchema, DeleteMergeRequestEmojiReactionSchema, DeleteIssueEmojiReactionSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, CreateIssueSchema, ListIssuesSchema, ListMergeRequestsSchema, ListLabelsSchema, GitLabMergeRequestSchema, GitLabTreeItemSchema, GetMergeRequestSchema, ListMergeRequestPipelinesSchema, GetRepositoryTreeSchema, GitLabUserFullSchema } from '../schemas.js';
|
|
3
3
|
function runGetFileContentsSchemaTests() {
|
|
4
4
|
console.log('🧪 Testing GetFileContentsSchema...');
|
|
5
5
|
const cases = [
|
|
@@ -609,6 +609,60 @@ function runGetMergeRequestSchemaTests() {
|
|
|
609
609
|
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
610
610
|
return { passed, failed };
|
|
611
611
|
}
|
|
612
|
+
function runListMergeRequestPipelinesSchemaTests() {
|
|
613
|
+
console.log('\n🧪 Testing ListMergeRequestPipelinesSchema...');
|
|
614
|
+
const cases = [
|
|
615
|
+
{
|
|
616
|
+
name: 'schema:list_merge_request_pipelines:minimal-required-fields',
|
|
617
|
+
input: { project_id: 'my/project', merge_request_iid: '42' },
|
|
618
|
+
expected: { project_id: 'my/project', merge_request_iid: '42' },
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: 'schema:list_merge_request_pipelines:coerces-pagination',
|
|
622
|
+
input: { project_id: 123, merge_request_iid: 42, page: '2', per_page: '10' },
|
|
623
|
+
expected: { project_id: '123', merge_request_iid: '42', page: 2, per_page: 10 },
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
name: 'schema:list_merge_request_pipelines:reject-missing-merge-request-iid',
|
|
627
|
+
input: { project_id: 'my/project' },
|
|
628
|
+
shouldFail: true,
|
|
629
|
+
},
|
|
630
|
+
];
|
|
631
|
+
let passed = 0;
|
|
632
|
+
let failed = 0;
|
|
633
|
+
cases.forEach(testCase => {
|
|
634
|
+
const result = { name: testCase.name, status: 'failed' };
|
|
635
|
+
const parsed = ListMergeRequestPipelinesSchema.safeParse(testCase.input);
|
|
636
|
+
if (testCase.shouldFail) {
|
|
637
|
+
result.status = parsed.success ? 'failed' : 'passed';
|
|
638
|
+
if (parsed.success)
|
|
639
|
+
result.error = 'Expected schema validation to fail';
|
|
640
|
+
}
|
|
641
|
+
else if (parsed.success) {
|
|
642
|
+
const expected = testCase.expected || {};
|
|
643
|
+
const matches = Object.entries(expected).every(([key, value]) => {
|
|
644
|
+
const actual = parsed.data[key];
|
|
645
|
+
return actual === value;
|
|
646
|
+
});
|
|
647
|
+
result.status = matches ? 'passed' : 'failed';
|
|
648
|
+
if (!matches)
|
|
649
|
+
result.error = `Unexpected parsed result: ${JSON.stringify(parsed.data)}`;
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
result.error = parsed.error?.message || 'Schema validation failed';
|
|
653
|
+
}
|
|
654
|
+
if (result.status === 'passed') {
|
|
655
|
+
passed++;
|
|
656
|
+
console.log(`✅ ${result.name}`);
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
failed++;
|
|
660
|
+
console.log(`❌ ${result.name}: ${result.error}`);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
664
|
+
return { passed, failed };
|
|
665
|
+
}
|
|
612
666
|
function runGitLabMergeRequestSchemaTests() {
|
|
613
667
|
console.log('\n🧪 Testing GitLabMergeRequestSchema...');
|
|
614
668
|
const baseMergeRequest = {
|
|
@@ -1068,6 +1122,87 @@ function runGetRepositoryTreeSchemaTests() {
|
|
|
1068
1122
|
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
1069
1123
|
return { passed, failed };
|
|
1070
1124
|
}
|
|
1125
|
+
function runGitLabUserFullSchemaTests() {
|
|
1126
|
+
console.log('🧪 Testing GitLabUserFullSchema...');
|
|
1127
|
+
const adminResponse = {
|
|
1128
|
+
id: 1,
|
|
1129
|
+
username: 'root',
|
|
1130
|
+
name: 'Administrator',
|
|
1131
|
+
state: 'active',
|
|
1132
|
+
avatar_url: 'https://gitlab.example.com/uploads/-/system/user/avatar/1/avatar.png',
|
|
1133
|
+
web_url: 'https://gitlab.example.com/root',
|
|
1134
|
+
created_at: '2012-09-22T16:50:56.000Z',
|
|
1135
|
+
bio: null,
|
|
1136
|
+
location: null,
|
|
1137
|
+
public_email: '',
|
|
1138
|
+
website_url: '',
|
|
1139
|
+
organization: null,
|
|
1140
|
+
job_title: null,
|
|
1141
|
+
pronouns: null,
|
|
1142
|
+
work_information: null,
|
|
1143
|
+
followers: 0,
|
|
1144
|
+
following: 0,
|
|
1145
|
+
is_followed: false,
|
|
1146
|
+
local_time: null,
|
|
1147
|
+
last_sign_in_at: null,
|
|
1148
|
+
confirmed_at: '2012-09-22T16:50:56.000Z',
|
|
1149
|
+
last_activity_on: '2026-05-12',
|
|
1150
|
+
email: 'admin@example.com',
|
|
1151
|
+
theme_id: 1,
|
|
1152
|
+
color_scheme_id: 1,
|
|
1153
|
+
projects_limit: 100000,
|
|
1154
|
+
current_sign_in_at: '2026-05-12T08:14:22.885Z',
|
|
1155
|
+
identities: [],
|
|
1156
|
+
can_create_group: true,
|
|
1157
|
+
can_create_project: true,
|
|
1158
|
+
two_factor_enabled: false,
|
|
1159
|
+
external: false,
|
|
1160
|
+
private_profile: false,
|
|
1161
|
+
is_admin: true,
|
|
1162
|
+
};
|
|
1163
|
+
const cases = [
|
|
1164
|
+
{
|
|
1165
|
+
name: 'schema:user_full:admin-response-no-bot',
|
|
1166
|
+
input: { ...adminResponse },
|
|
1167
|
+
shouldFail: false,
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
name: 'schema:user_full:admin-response-with-bot',
|
|
1171
|
+
input: { ...adminResponse, bot: false },
|
|
1172
|
+
shouldFail: false,
|
|
1173
|
+
},
|
|
1174
|
+
];
|
|
1175
|
+
let passed = 0;
|
|
1176
|
+
let failed = 0;
|
|
1177
|
+
cases.forEach(testCase => {
|
|
1178
|
+
const result = { name: testCase.name, status: 'failed' };
|
|
1179
|
+
const parsed = GitLabUserFullSchema.safeParse(testCase.input);
|
|
1180
|
+
if (testCase.shouldFail) {
|
|
1181
|
+
if (parsed.success) {
|
|
1182
|
+
result.error = 'Expected schema validation to fail';
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
result.status = 'passed';
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
else if (parsed.success) {
|
|
1189
|
+
result.status = 'passed';
|
|
1190
|
+
}
|
|
1191
|
+
else {
|
|
1192
|
+
result.error = parsed.error?.message || 'Schema validation failed';
|
|
1193
|
+
}
|
|
1194
|
+
if (result.status === 'passed') {
|
|
1195
|
+
passed++;
|
|
1196
|
+
console.log(`✅ ${result.name}`);
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
failed++;
|
|
1200
|
+
console.log(`❌ ${result.name}: ${result.error}`);
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
1204
|
+
return { passed, failed };
|
|
1205
|
+
}
|
|
1071
1206
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1072
1207
|
const getFileContentsResult = runGetFileContentsSchemaTests();
|
|
1073
1208
|
const fileContentResult = runGitLabFileContentSchemaTests();
|
|
@@ -1075,6 +1210,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
1075
1210
|
const commitStatusResult = runCommitStatusSchemaTests();
|
|
1076
1211
|
const createIssueNoteResult = runCreateIssueNoteSchemaTests();
|
|
1077
1212
|
const getMergeRequestResult = runGetMergeRequestSchemaTests();
|
|
1213
|
+
const listMergeRequestPipelinesResult = runListMergeRequestPipelinesSchemaTests();
|
|
1078
1214
|
const gitLabMergeRequestResult = runGitLabMergeRequestSchemaTests();
|
|
1079
1215
|
const emojiReactionResult = runEmojiReactionSchemaTests();
|
|
1080
1216
|
const repositorySchemaResult = runGitLabRepositorySchemaTests();
|
|
@@ -1082,8 +1218,9 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
1082
1218
|
const listLabelsResult = runListLabelsSchemaTests();
|
|
1083
1219
|
const treeItemResult = runGitLabTreeItemSchemaTests();
|
|
1084
1220
|
const repositoryTreeResult = runGetRepositoryTreeSchemaTests();
|
|
1085
|
-
const
|
|
1086
|
-
const
|
|
1221
|
+
const gitLabUserFullResult = runGitLabUserFullSchemaTests();
|
|
1222
|
+
const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + commitStatusResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + listMergeRequestPipelinesResult.passed + gitLabMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + listLabelsResult.passed + treeItemResult.passed + repositoryTreeResult.passed + gitLabUserFullResult.passed;
|
|
1223
|
+
const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + commitStatusResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + listMergeRequestPipelinesResult.failed + gitLabMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + listLabelsResult.failed + treeItemResult.failed + repositoryTreeResult.failed + gitLabUserFullResult.failed;
|
|
1087
1224
|
console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
|
|
1088
1225
|
if (totalFailed > 0) {
|
|
1089
1226
|
process.exit(1);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { describe, test, before, after } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { MockGitLabServer, findMockServerPort } from "./utils/mock-gitlab-server.js";
|
|
5
|
+
const MOCK_TOKEN = "glpat-mr-pipelines-test-token";
|
|
6
|
+
const TEST_PROJECT_ID = "123";
|
|
7
|
+
const TEST_MR_IID = "1";
|
|
8
|
+
async function callTool(toolName, args, env) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const proc = spawn("node", ["build/index.js"], {
|
|
11
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
12
|
+
env: {
|
|
13
|
+
...process.env,
|
|
14
|
+
...env,
|
|
15
|
+
GITLAB_READ_ONLY_MODE: "true",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
let output = "";
|
|
19
|
+
let errorOutput = "";
|
|
20
|
+
proc.stdout?.on("data", d => output += d);
|
|
21
|
+
proc.stderr?.on("data", d => errorOutput += d);
|
|
22
|
+
proc.on("close", code => {
|
|
23
|
+
if (code !== 0) {
|
|
24
|
+
reject(new Error(`Process exited with code ${code}: ${errorOutput}`));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const line = output.split("\n").find(l => l.startsWith("{"));
|
|
28
|
+
if (!line) {
|
|
29
|
+
reject(new Error("No JSON output found"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const response = JSON.parse(line);
|
|
34
|
+
if (response.error) {
|
|
35
|
+
reject(response.error);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const content = response.result?.content?.[0]?.text;
|
|
39
|
+
resolve(content ? JSON.parse(content) : response.result);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
reject(error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
proc.stdin?.end(JSON.stringify({
|
|
46
|
+
jsonrpc: "2.0",
|
|
47
|
+
id: 1,
|
|
48
|
+
method: "tools/call",
|
|
49
|
+
params: { name: toolName, arguments: args },
|
|
50
|
+
}) + "\n");
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
describe("list_merge_request_pipelines", () => {
|
|
54
|
+
let mockGitLab;
|
|
55
|
+
let mockGitLabUrl;
|
|
56
|
+
let lastQuery = {};
|
|
57
|
+
before(async () => {
|
|
58
|
+
const mockPort = await findMockServerPort(9250);
|
|
59
|
+
mockGitLab = new MockGitLabServer({
|
|
60
|
+
port: mockPort,
|
|
61
|
+
validTokens: [MOCK_TOKEN],
|
|
62
|
+
});
|
|
63
|
+
mockGitLab.addMockHandler("get", `/projects/${TEST_PROJECT_ID}/merge_requests/${TEST_MR_IID}/pipelines`, (req, res) => {
|
|
64
|
+
lastQuery = { ...req.query };
|
|
65
|
+
res.json([
|
|
66
|
+
{
|
|
67
|
+
id: 77,
|
|
68
|
+
sha: "959e04d7c7a30600c894bd3c0cd0e1ce7f42c11d",
|
|
69
|
+
ref: "main",
|
|
70
|
+
status: "success",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 78,
|
|
74
|
+
sha: "a59e04d7c7a30600c894bd3c0cd0e1ce7f42c22e",
|
|
75
|
+
ref: "refs/merge-requests/1/head",
|
|
76
|
+
status: "running",
|
|
77
|
+
source: "merge_request_event",
|
|
78
|
+
web_url: "https://gitlab.mock/test/project/-/pipelines/78",
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
});
|
|
82
|
+
await mockGitLab.start();
|
|
83
|
+
mockGitLabUrl = mockGitLab.getUrl();
|
|
84
|
+
});
|
|
85
|
+
after(async () => {
|
|
86
|
+
await mockGitLab.stop();
|
|
87
|
+
});
|
|
88
|
+
test("lists pipelines for a merge request", async () => {
|
|
89
|
+
const pipelines = await callTool("list_merge_request_pipelines", {
|
|
90
|
+
project_id: TEST_PROJECT_ID,
|
|
91
|
+
merge_request_iid: TEST_MR_IID,
|
|
92
|
+
page: 2,
|
|
93
|
+
per_page: 10,
|
|
94
|
+
}, {
|
|
95
|
+
GITLAB_API_URL: `${mockGitLabUrl}/api/v4`,
|
|
96
|
+
GITLAB_PERSONAL_ACCESS_TOKEN: MOCK_TOKEN,
|
|
97
|
+
});
|
|
98
|
+
assert.ok(Array.isArray(pipelines), "Response should be an array");
|
|
99
|
+
assert.strictEqual(pipelines.length, 2);
|
|
100
|
+
assert.strictEqual(pipelines[0].id, "77");
|
|
101
|
+
assert.strictEqual(pipelines[0].status, "success");
|
|
102
|
+
assert.strictEqual(pipelines[1].source, "merge_request_event");
|
|
103
|
+
assert.strictEqual(lastQuery.page, "2");
|
|
104
|
+
assert.strictEqual(lastQuery.per_page, "10");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -16,11 +16,11 @@ const MOCK_PORT_BASE = 9200;
|
|
|
16
16
|
const MCP_PORT_BASE = 3200;
|
|
17
17
|
// Known tool counts per toolset (from TOOLSET_DEFINITIONS)
|
|
18
18
|
const TOOLSET_TOOL_COUNTS = {
|
|
19
|
-
merge_requests:
|
|
19
|
+
merge_requests: 41,
|
|
20
20
|
issues: 23,
|
|
21
21
|
repositories: 7,
|
|
22
22
|
branches: 6,
|
|
23
|
-
projects:
|
|
23
|
+
projects: 9,
|
|
24
24
|
labels: 5,
|
|
25
25
|
ci: 2,
|
|
26
26
|
pipelines: 19,
|
|
@@ -28,7 +28,7 @@ const TOOLSET_TOOL_COUNTS = {
|
|
|
28
28
|
wiki: 10,
|
|
29
29
|
releases: 7,
|
|
30
30
|
tags: 5,
|
|
31
|
-
users:
|
|
31
|
+
users: 7,
|
|
32
32
|
search: 3,
|
|
33
33
|
workitems: 18,
|
|
34
34
|
webhooks: 3,
|
|
@@ -78,11 +78,12 @@ export async function launchServer(config) {
|
|
|
78
78
|
if (!serverProcess.killed) {
|
|
79
79
|
serverProcess.kill("SIGTERM");
|
|
80
80
|
// Force kill if not terminated within 5 seconds
|
|
81
|
-
setTimeout(() => {
|
|
81
|
+
const forceKillTimer = setTimeout(() => {
|
|
82
82
|
if (!serverProcess.killed) {
|
|
83
83
|
serverProcess.kill("SIGKILL");
|
|
84
84
|
}
|
|
85
85
|
}, 5000);
|
|
86
|
+
forceKillTimer.unref();
|
|
86
87
|
}
|
|
87
88
|
},
|
|
88
89
|
};
|
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, 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";
|
|
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, HealthCheckSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
|
|
5
5
|
// Define all available tools
|
|
6
6
|
export const allTools = [
|
|
7
7
|
{
|
|
@@ -29,6 +29,11 @@ export const allTools = [
|
|
|
29
29
|
description: "Get the conflicts of a merge request",
|
|
30
30
|
inputSchema: toJSONSchema(GetMergeRequestConflictsSchema),
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
name: "list_merge_request_pipelines",
|
|
34
|
+
description: "List pipelines for a merge request with pagination",
|
|
35
|
+
inputSchema: toJSONSchema(ListMergeRequestPipelinesSchema),
|
|
36
|
+
},
|
|
32
37
|
{
|
|
33
38
|
name: "execute_graphql",
|
|
34
39
|
description: "Execute a GitLab GraphQL query",
|
|
@@ -631,6 +636,16 @@ export const allTools = [
|
|
|
631
636
|
description: "Get GitLab user details by usernames",
|
|
632
637
|
inputSchema: toJSONSchema(GetUsersSchema),
|
|
633
638
|
},
|
|
639
|
+
{
|
|
640
|
+
name: "get_user",
|
|
641
|
+
description: "Get user details by ID",
|
|
642
|
+
inputSchema: toJSONSchema(GetUserSchema),
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
name: "whoami",
|
|
646
|
+
description: "Get current authenticated user details",
|
|
647
|
+
inputSchema: toJSONSchema(WhoAmISchema),
|
|
648
|
+
},
|
|
634
649
|
{
|
|
635
650
|
name: "list_commits",
|
|
636
651
|
description: "List repository commits with filtering options",
|
|
@@ -671,6 +686,11 @@ export const allTools = [
|
|
|
671
686
|
description: "Download an uploaded file from a project (images returned as base64; use local_path to save to disk)",
|
|
672
687
|
inputSchema: toJSONSchema(DownloadAttachmentSchema),
|
|
673
688
|
},
|
|
689
|
+
{
|
|
690
|
+
name: "health_check",
|
|
691
|
+
description: "Verify server status and authentication",
|
|
692
|
+
inputSchema: toJSONSchema(HealthCheckSchema),
|
|
693
|
+
},
|
|
674
694
|
{
|
|
675
695
|
name: "list_events",
|
|
676
696
|
description: "List events for the authenticated user (before/after: YYYY-MM-DD)",
|
|
@@ -882,6 +902,7 @@ export const allTools = [
|
|
|
882
902
|
// Define which tools are read-only
|
|
883
903
|
export const readOnlyTools = new Set([
|
|
884
904
|
"discover_tools",
|
|
905
|
+
"health_check",
|
|
885
906
|
"search_repositories",
|
|
886
907
|
"search_code",
|
|
887
908
|
"search_project_code",
|
|
@@ -896,6 +917,7 @@ export const readOnlyTools = new Set([
|
|
|
896
917
|
"list_merge_request_versions",
|
|
897
918
|
"get_merge_request_version",
|
|
898
919
|
"get_branch_diffs",
|
|
920
|
+
"list_merge_request_pipelines",
|
|
899
921
|
"get_merge_request_note",
|
|
900
922
|
"get_merge_request_notes",
|
|
901
923
|
"get_draft_note",
|
|
@@ -944,6 +966,8 @@ export const readOnlyTools = new Set([
|
|
|
944
966
|
"list_group_wiki_pages",
|
|
945
967
|
"get_group_wiki_page",
|
|
946
968
|
"get_users",
|
|
969
|
+
"get_user",
|
|
970
|
+
"whoami",
|
|
947
971
|
"list_commits",
|
|
948
972
|
"get_commit",
|
|
949
973
|
"get_commit_diff",
|
|
@@ -1059,6 +1083,7 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1059
1083
|
"unapprove_merge_request",
|
|
1060
1084
|
"get_merge_request_approval_state",
|
|
1061
1085
|
"get_merge_request_conflicts",
|
|
1086
|
+
"list_merge_request_pipelines",
|
|
1062
1087
|
"get_merge_request",
|
|
1063
1088
|
"get_merge_request_diffs",
|
|
1064
1089
|
"list_merge_request_changed_files",
|
|
@@ -1162,6 +1187,7 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1162
1187
|
"verify_namespace",
|
|
1163
1188
|
"list_group_projects",
|
|
1164
1189
|
"list_group_iterations",
|
|
1190
|
+
"health_check",
|
|
1165
1191
|
]),
|
|
1166
1192
|
},
|
|
1167
1193
|
{
|
|
@@ -1265,6 +1291,8 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1265
1291
|
isDefault: true,
|
|
1266
1292
|
tools: new Set([
|
|
1267
1293
|
"get_users",
|
|
1294
|
+
"get_user",
|
|
1295
|
+
"whoami",
|
|
1268
1296
|
"list_events",
|
|
1269
1297
|
"get_project_events",
|
|
1270
1298
|
"upload_markdown",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.11",
|
|
4
4
|
"mcpName": "io.github.zereight/gitlab-mcp",
|
|
5
5
|
"description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"changelog": "auto-changelog -p",
|
|
52
52
|
"test": "npm run test:all",
|
|
53
53
|
"test:all": "npm run build && npm run test:mock && npm run test:live",
|
|
54
|
-
"test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && 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 && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/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",
|