@zereight/mcp-gitlab 2.1.9 → 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 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
- const data = await response.json();
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({
@@ -1404,6 +1485,15 @@ export const GetBranchDiffsSchema = ProjectParamsSchema.extend({
1404
1485
  .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"]'),
1405
1486
  });
1406
1487
  export const GetMergeRequestSchema = ProjectParamsSchema.extend({
1488
+ project_id: z
1489
+ .preprocess(value => (value === undefined || value === null ? value : String(value)), z
1490
+ .string({
1491
+ required_error: "project_id is required",
1492
+ invalid_type_error: "project_id is required",
1493
+ })
1494
+ .refine(value => value === "" || value.trim().length > 0, "project_id is required")
1495
+ .transform(value => (value === "" ? value : value.trim())))
1496
+ .describe("Project ID or complete URL-encoded path to project"),
1407
1497
  merge_request_iid: z.coerce.string().optional().describe("The IID of a merge request"),
1408
1498
  source_branch: z.string().optional().describe("Source branch name"),
1409
1499
  });
@@ -1532,6 +1622,17 @@ export const GetMergeRequestApprovalStateSchema = ProjectParamsSchema.extend({
1532
1622
  export const GetMergeRequestConflictsSchema = ProjectParamsSchema.extend({
1533
1623
  merge_request_iid: z.coerce.string().describe("The IID of the merge request"),
1534
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);
1535
1636
  export const GetMergeRequestDiffsSchema = GetMergeRequestSchema.extend({
1536
1637
  view: z.enum(["inline", "parallel"]).optional().describe("Diff view type"),
1537
1638
  excluded_file_patterns: z
@@ -1821,7 +1922,8 @@ export const ListProjectsSchema = z
1821
1922
  })
1822
1923
  .merge(PaginationOptionsSchema);
1823
1924
  // Label operation schemas
1824
- export const ListLabelsSchema = z.object({
1925
+ export const ListLabelsSchema = z
1926
+ .object({
1825
1927
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
1826
1928
  with_counts: z
1827
1929
  .coerce.boolean()
@@ -1829,7 +1931,8 @@ export const ListLabelsSchema = z.object({
1829
1931
  .describe("Whether or not to include issue and merge request counts"),
1830
1932
  include_ancestor_groups: z.coerce.boolean().optional().describe("Include ancestor groups"),
1831
1933
  search: z.string().optional().describe("Keyword to filter labels by"),
1832
- });
1934
+ })
1935
+ .merge(PaginationOptionsSchema);
1833
1936
  export const GetLabelSchema = z.object({
1834
1937
  project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
1835
1938
  label_id: z.coerce.string().describe("The ID or title of a project's label"),
@@ -3130,3 +3233,4 @@ export const GetWebhookEventSchema = z
3130
3233
  .refine(data => (data.project_id || data.group_id) && !(data.project_id && data.group_id), {
3131
3234
  message: "Provide exactly one of project_id or group_id",
3132
3235
  });
3236
+ export const HealthCheckSchema = z.object({});
@@ -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, CreateCommitStatusSchema, ListCommitStatusesSchema, CreateIssueNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateIssueEmojiReactionSchema, DeleteMergeRequestEmojiReactionSchema, DeleteIssueEmojiReactionSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, CreateIssueSchema, ListIssuesSchema, ListMergeRequestsSchema, 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 = [
@@ -534,11 +534,36 @@ function runGetMergeRequestSchemaTests() {
534
534
  input: { project_id: 'my/project', merge_request_iid: 24 },
535
535
  expected: { project_id: 'my/project', merge_request_iid: '24' },
536
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
+ },
537
542
  {
538
543
  name: 'schema:get_merge_request:coerced-source-branch',
539
544
  input: { project_id: 'my/project', source_branch: 'feature' },
540
545
  expected: { project_id: 'my/project', source_branch: 'feature' },
541
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
+ },
542
567
  ];
543
568
  let passed = 0;
544
569
  let failed = 0;
@@ -584,6 +609,60 @@ function runGetMergeRequestSchemaTests() {
584
609
  console.log(`\nResults: ${passed} passed, ${failed} failed`);
585
610
  return { passed, failed };
586
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
+ }
587
666
  function runGitLabMergeRequestSchemaTests() {
588
667
  console.log('\n🧪 Testing GitLabMergeRequestSchema...');
589
668
  const baseMergeRequest = {
@@ -876,6 +955,52 @@ function runLabelsCoercionSchemaTests() {
876
955
  console.log(`\nResults: ${passed} passed, ${failed} failed`);
877
956
  return { passed, failed };
878
957
  }
958
+ function runListLabelsSchemaTests() {
959
+ console.log('\n=== List Labels Schema Tests ===');
960
+ const cases = [
961
+ {
962
+ name: 'schema:list_labels:pagination-coercion',
963
+ input: { project_id: 'my/project', page: '2', per_page: '100' },
964
+ expected: { project_id: 'my/project', page: 2, per_page: 100 },
965
+ },
966
+ {
967
+ name: 'schema:list_labels:filters-with-pagination',
968
+ input: { project_id: 'my/project', search: 'backend', with_counts: 'true', page: 3, per_page: 50 },
969
+ expected: { project_id: 'my/project', search: 'backend', with_counts: true, page: 3, per_page: 50 },
970
+ },
971
+ ];
972
+ let passed = 0;
973
+ let failed = 0;
974
+ cases.forEach(testCase => {
975
+ const result = { name: testCase.name, status: 'failed' };
976
+ const parsed = ListLabelsSchema.safeParse(testCase.input);
977
+ if (!parsed.success) {
978
+ result.error = parsed.error?.message || 'Schema validation failed';
979
+ }
980
+ else {
981
+ const matches = Object.entries(testCase.expected).every(([key, value]) => {
982
+ const actual = parsed.data[key];
983
+ return actual === value;
984
+ });
985
+ if (matches) {
986
+ result.status = 'passed';
987
+ }
988
+ else {
989
+ result.error = `Unexpected parsed result: ${JSON.stringify(parsed.data)}`;
990
+ }
991
+ }
992
+ if (result.status === 'passed') {
993
+ passed++;
994
+ console.log(`✅ ${result.name}`);
995
+ }
996
+ else {
997
+ failed++;
998
+ console.log(`❌ ${result.name}: ${result.error}`);
999
+ }
1000
+ });
1001
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
1002
+ return { passed, failed };
1003
+ }
879
1004
  function runGitLabTreeItemSchemaTests() {
880
1005
  console.log('\n=== GitLabTreeItem Schema Tests ===');
881
1006
  const cases = [
@@ -997,6 +1122,87 @@ function runGetRepositoryTreeSchemaTests() {
997
1122
  console.log(`\nResults: ${passed} passed, ${failed} failed`);
998
1123
  return { passed, failed };
999
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
+ }
1000
1206
  if (import.meta.url === `file://${process.argv[1]}`) {
1001
1207
  const getFileContentsResult = runGetFileContentsSchemaTests();
1002
1208
  const fileContentResult = runGitLabFileContentSchemaTests();
@@ -1004,14 +1210,17 @@ if (import.meta.url === `file://${process.argv[1]}`) {
1004
1210
  const commitStatusResult = runCommitStatusSchemaTests();
1005
1211
  const createIssueNoteResult = runCreateIssueNoteSchemaTests();
1006
1212
  const getMergeRequestResult = runGetMergeRequestSchemaTests();
1213
+ const listMergeRequestPipelinesResult = runListMergeRequestPipelinesSchemaTests();
1007
1214
  const gitLabMergeRequestResult = runGitLabMergeRequestSchemaTests();
1008
1215
  const emojiReactionResult = runEmojiReactionSchemaTests();
1009
1216
  const repositorySchemaResult = runGitLabRepositorySchemaTests();
1010
1217
  const labelsCoercionResult = runLabelsCoercionSchemaTests();
1218
+ const listLabelsResult = runListLabelsSchemaTests();
1011
1219
  const treeItemResult = runGitLabTreeItemSchemaTests();
1012
1220
  const repositoryTreeResult = runGetRepositoryTreeSchemaTests();
1013
- const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + commitStatusResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + gitLabMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + treeItemResult.passed + repositoryTreeResult.passed;
1014
- const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + commitStatusResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + gitLabMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + treeItemResult.failed + repositoryTreeResult.failed;
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;
1015
1224
  console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
1016
1225
  if (totalFailed > 0) {
1017
1226
  process.exit(1);
@@ -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;
@@ -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: 40,
19
+ merge_requests: 41,
20
20
  issues: 23,
21
21
  repositories: 7,
22
22
  branches: 6,
23
- projects: 8,
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: 5,
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
  };
@@ -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",
@@ -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
- const typeName = zodType._def?.typeName;
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.9",
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",