@zereight/mcp-gitlab 2.1.1 โ†’ 2.1.3

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/build/schemas.js CHANGED
@@ -527,7 +527,7 @@ export const GitLabRepositorySchema = z.object({
527
527
  http_url_to_repo: z.string().optional(),
528
528
  created_at: z.string().optional(),
529
529
  last_activity_at: z.string().optional(),
530
- default_branch: z.string().optional(),
530
+ default_branch: z.string().nullable().optional(),
531
531
  namespace: z
532
532
  .object({
533
533
  id: z.coerce.string(),
@@ -621,7 +621,7 @@ export const FileOperationSchema = z.object({
621
621
  export const GitLabTreeItemSchema = z.object({
622
622
  id: z.string(),
623
623
  name: z.string(),
624
- type: z.enum(["tree", "blob"]),
624
+ type: z.enum(["tree", "blob", "commit"]),
625
625
  path: z.string(),
626
626
  mode: z.string(),
627
627
  });
@@ -1219,7 +1219,7 @@ export const CreateIssueSchema = ProjectParamsSchema.extend({
1219
1219
  title: z.string().describe("Issue title"),
1220
1220
  description: z.string().optional().describe("Issue description"),
1221
1221
  assignee_ids: z.array(z.coerce.number()).optional().describe("Array of user IDs to assign"),
1222
- labels: z.array(z.string()).optional().describe("Array of label names"),
1222
+ labels: coerceStringArray.optional().describe("Array of label names"),
1223
1223
  milestone_id: z.coerce.string().optional().describe("Milestone ID to assign"),
1224
1224
  issue_type: z
1225
1225
  .enum(["issue", "incident", "test_case", "task"])
@@ -1239,7 +1239,7 @@ const MergeRequestOptionsSchema = {
1239
1239
  .array(z.coerce.number())
1240
1240
  .optional()
1241
1241
  .describe("The ID of the users to assign as reviewers of the MR"),
1242
- labels: z.array(z.string()).optional().describe("Labels for the MR"),
1242
+ labels: coerceStringArray.optional().describe("Labels for the MR"),
1243
1243
  draft: z.coerce.boolean().optional().describe("Create as draft merge request"),
1244
1244
  allow_collaboration: z.coerce.boolean().optional().describe("Allow commits from upstream members"),
1245
1245
  remove_source_branch: z
@@ -1288,7 +1288,7 @@ export const UpdateMergeRequestSchema = GetMergeRequestSchema.extend({
1288
1288
  .array(z.coerce.number())
1289
1289
  .optional()
1290
1290
  .describe("The ID of the users to assign as reviewers of the MR"),
1291
- labels: z.array(z.string()).optional().describe("Labels for the MR"),
1291
+ labels: coerceStringArray.optional().describe("Labels for the MR"),
1292
1292
  state_event: z
1293
1293
  .enum(["close", "reopen"])
1294
1294
  .optional()
@@ -1475,7 +1475,7 @@ export const ListIssuesSchema = z
1475
1475
  created_after: z.string().optional().describe("Return issues created after the given time"),
1476
1476
  created_before: z.string().optional().describe("Return issues created before the given time"),
1477
1477
  due_date: z.string().optional().describe("Return issues that have the due date"),
1478
- labels: z.array(z.string()).optional().describe("Array of label names"),
1478
+ labels: coerceStringArray.optional().describe("Array of label names"),
1479
1479
  milestone: z.string().optional().describe("Milestone title"),
1480
1480
  issue_type: z
1481
1481
  .enum(["issue", "incident", "test_case", "task"])
@@ -1546,7 +1546,7 @@ export const ListMergeRequestsSchema = z
1546
1546
  .string()
1547
1547
  .optional()
1548
1548
  .describe("Return merge requests updated before the given time"),
1549
- labels: z.array(z.string()).optional().describe("Array of label names"),
1549
+ labels: coerceStringArray.optional().describe("Array of label names"),
1550
1550
  milestone: z.string().optional().describe("Milestone title"),
1551
1551
  scope: z
1552
1552
  .enum(["created_by_me", "assigned_to_me", "all"])
@@ -2136,7 +2136,7 @@ export const MyIssuesSchema = z.object({
2136
2136
  .enum(["opened", "closed", "all"])
2137
2137
  .optional()
2138
2138
  .describe("Return issues with a specific state (default: opened)"),
2139
- labels: z.array(z.string()).optional().describe("Array of label names to filter by"),
2139
+ labels: coerceStringArray.optional().describe("Array of label names to filter by"),
2140
2140
  milestone: z.string().optional().describe("Milestone title to filter by"),
2141
2141
  search: z.string().optional().describe("Search for specific terms in title and description"),
2142
2142
  created_after: z
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ts-node
2
- import { GetFileContentsSchema, GitLabFileContentSchema, CreatePipelineSchema, CreateIssueNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateIssueEmojiReactionSchema, DeleteMergeRequestEmojiReactionSchema, DeleteIssueEmojiReactionSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema } from '../schemas.js';
2
+ import { GetFileContentsSchema, GitLabFileContentSchema, GitLabRepositorySchema, CreatePipelineSchema, CreateIssueNoteSchema, CreateMergeRequestEmojiReactionSchema, CreateIssueEmojiReactionSchema, DeleteMergeRequestEmojiReactionSchema, DeleteIssueEmojiReactionSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, CreateIssueSchema, ListIssuesSchema, ListMergeRequestsSchema, GitLabTreeItemSchema } from '../schemas.js';
3
3
  function runGetFileContentsSchemaTests() {
4
4
  console.log('๐Ÿงช Testing GetFileContentsSchema...');
5
5
  const cases = [
@@ -426,14 +426,216 @@ function runEmojiReactionSchemaTests() {
426
426
  console.log(`\nResults: ${passed} passed, ${failed} failed`);
427
427
  return { passed, failed };
428
428
  }
429
+ function runGitLabRepositorySchemaTests() {
430
+ console.log('๐Ÿงช Testing GitLabRepositorySchema...');
431
+ const baseProject = {
432
+ id: '42',
433
+ name: 'my-project',
434
+ path_with_namespace: 'group/my-project',
435
+ description: null,
436
+ };
437
+ const cases = [
438
+ {
439
+ name: 'schema:repository:default_branch-null',
440
+ input: { ...baseProject, default_branch: null },
441
+ shouldFail: false,
442
+ },
443
+ {
444
+ name: 'schema:repository:default_branch-string',
445
+ input: { ...baseProject, default_branch: 'main' },
446
+ shouldFail: false,
447
+ },
448
+ {
449
+ name: 'schema:repository:default_branch-omitted',
450
+ input: { ...baseProject },
451
+ shouldFail: false,
452
+ },
453
+ ];
454
+ let passed = 0;
455
+ let failed = 0;
456
+ cases.forEach(testCase => {
457
+ const result = { name: testCase.name, status: 'failed' };
458
+ const parsed = GitLabRepositorySchema.safeParse(testCase.input);
459
+ if (testCase.shouldFail) {
460
+ if (parsed.success) {
461
+ result.error = 'Expected schema validation to fail';
462
+ }
463
+ else {
464
+ result.status = 'passed';
465
+ }
466
+ }
467
+ else if (parsed.success) {
468
+ result.status = 'passed';
469
+ }
470
+ else {
471
+ result.error = parsed.error?.message || 'Schema validation failed';
472
+ }
473
+ if (result.status === 'passed') {
474
+ passed++;
475
+ console.log(`โœ… ${result.name}`);
476
+ }
477
+ else {
478
+ failed++;
479
+ console.log(`โŒ ${result.name}: ${result.error}`);
480
+ }
481
+ });
482
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
483
+ return { passed, failed };
484
+ }
485
+ function runLabelsCoercionSchemaTests() {
486
+ console.log('\n=== Labels Coercion Schema Tests ===');
487
+ const cases = [
488
+ {
489
+ name: 'schema:create_issue:labels-native-array',
490
+ schema: CreateIssueSchema,
491
+ input: { project_id: 'my/project', title: 'Test', labels: ['bug', 'enhancement'] },
492
+ expectedLabels: ['bug', 'enhancement'],
493
+ },
494
+ {
495
+ name: 'schema:create_issue:labels-stringified-array',
496
+ schema: CreateIssueSchema,
497
+ input: { project_id: 'my/project', title: 'Test', labels: '["bug","enhancement"]' },
498
+ expectedLabels: ['bug', 'enhancement'],
499
+ },
500
+ {
501
+ name: 'schema:create_issue:labels-omitted',
502
+ schema: CreateIssueSchema,
503
+ input: { project_id: 'my/project', title: 'Test' },
504
+ expectedLabels: undefined,
505
+ },
506
+ {
507
+ name: 'schema:list_issues:labels-native-array',
508
+ schema: ListIssuesSchema,
509
+ input: { project_id: 'my/project', labels: ['bug'] },
510
+ expectedLabels: ['bug'],
511
+ },
512
+ {
513
+ name: 'schema:list_issues:labels-stringified-array',
514
+ schema: ListIssuesSchema,
515
+ input: { project_id: 'my/project', labels: '["bug","enhancement"]' },
516
+ expectedLabels: ['bug', 'enhancement'],
517
+ },
518
+ {
519
+ name: 'schema:list_merge_requests:labels-native-array',
520
+ schema: ListMergeRequestsSchema,
521
+ input: { labels: ['feature'] },
522
+ expectedLabels: ['feature'],
523
+ },
524
+ {
525
+ name: 'schema:list_merge_requests:labels-stringified-array',
526
+ schema: ListMergeRequestsSchema,
527
+ input: { labels: '["feature","bugfix"]' },
528
+ expectedLabels: ['feature', 'bugfix'],
529
+ },
530
+ ];
531
+ let passed = 0;
532
+ let failed = 0;
533
+ function checkLabelResult(testCase, parsed) {
534
+ const result = { name: testCase.name, status: 'failed' };
535
+ if (!parsed.success) {
536
+ result.error = parsed.error?.message || 'Schema validation failed';
537
+ return result;
538
+ }
539
+ const actualLabels = parsed.data['labels'];
540
+ if (testCase.expectedLabels === undefined) {
541
+ result.status = actualLabels === undefined ? 'passed' : 'failed';
542
+ if (actualLabels !== undefined) {
543
+ result.error = `Expected labels to be undefined, got ${JSON.stringify(actualLabels)}`;
544
+ }
545
+ return result;
546
+ }
547
+ const match = Array.isArray(actualLabels) &&
548
+ actualLabels.length === testCase.expectedLabels.length &&
549
+ testCase.expectedLabels.every((v, i) => actualLabels[i] === v);
550
+ result.status = match ? 'passed' : 'failed';
551
+ if (!match) {
552
+ result.error = `Expected ${JSON.stringify(testCase.expectedLabels)}, got ${JSON.stringify(actualLabels)}`;
553
+ }
554
+ return result;
555
+ }
556
+ cases.forEach(testCase => {
557
+ const parsed = testCase.schema.safeParse(testCase.input);
558
+ let result;
559
+ if (testCase.shouldFail) {
560
+ result = { name: testCase.name, status: parsed.success ? 'failed' : 'passed' };
561
+ if (parsed.success)
562
+ result.error = 'Expected schema validation to fail';
563
+ }
564
+ else {
565
+ result = checkLabelResult(testCase, parsed);
566
+ }
567
+ if (result.status === 'passed') {
568
+ passed++;
569
+ console.log(`โœ… ${result.name}`);
570
+ }
571
+ else {
572
+ failed++;
573
+ console.log(`โŒ ${result.name}: ${result.error}`);
574
+ }
575
+ });
576
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
577
+ return { passed, failed };
578
+ }
579
+ function runGitLabTreeItemSchemaTests() {
580
+ console.log('\n=== GitLabTreeItem Schema Tests ===');
581
+ const cases = [
582
+ {
583
+ name: 'schema:tree_item:type-blob',
584
+ input: { id: 'abc123', name: 'README.md', type: 'blob', path: 'README.md', mode: '100644' },
585
+ },
586
+ {
587
+ name: 'schema:tree_item:type-tree',
588
+ input: { id: 'def456', name: 'src', type: 'tree', path: 'src', mode: '040000' },
589
+ },
590
+ {
591
+ name: 'schema:tree_item:type-commit-submodule',
592
+ input: { id: 'ghi789', name: 'vendor', type: 'commit', path: 'vendor', mode: '160000' },
593
+ },
594
+ {
595
+ name: 'schema:tree_item:reject-unknown-type',
596
+ input: { id: 'xyz', name: 'foo', type: 'symlink', path: 'foo', mode: '120000' },
597
+ shouldFail: true,
598
+ },
599
+ ];
600
+ let passed = 0;
601
+ let failed = 0;
602
+ cases.forEach(testCase => {
603
+ const result = { name: testCase.name, status: 'failed' };
604
+ const parsed = GitLabTreeItemSchema.safeParse(testCase.input);
605
+ if (testCase.shouldFail) {
606
+ result.status = parsed.success ? 'failed' : 'passed';
607
+ if (parsed.success)
608
+ result.error = 'Expected schema validation to fail';
609
+ }
610
+ else if (parsed.success) {
611
+ result.status = 'passed';
612
+ }
613
+ else {
614
+ result.error = parsed.error?.message || 'Schema validation failed';
615
+ }
616
+ if (result.status === 'passed') {
617
+ passed++;
618
+ console.log(`โœ… ${result.name}`);
619
+ }
620
+ else {
621
+ failed++;
622
+ console.log(`โŒ ${result.name}: ${result.error}`);
623
+ }
624
+ });
625
+ console.log(`\nResults: ${passed} passed, ${failed} failed`);
626
+ return { passed, failed };
627
+ }
429
628
  if (import.meta.url === `file://${process.argv[1]}`) {
430
629
  const getFileContentsResult = runGetFileContentsSchemaTests();
431
630
  const fileContentResult = runGitLabFileContentSchemaTests();
432
631
  const createPipelineResult = runCreatePipelineSchemaTests();
433
632
  const createIssueNoteResult = runCreateIssueNoteSchemaTests();
434
633
  const emojiReactionResult = runEmojiReactionSchemaTests();
435
- const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + createIssueNoteResult.passed + emojiReactionResult.passed;
436
- const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + createIssueNoteResult.failed + emojiReactionResult.failed;
634
+ const repositorySchemaResult = runGitLabRepositorySchemaTests();
635
+ const labelsCoercionResult = runLabelsCoercionSchemaTests();
636
+ const treeItemResult = runGitLabTreeItemSchemaTests();
637
+ const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + createIssueNoteResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + treeItemResult.passed;
638
+ const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + createIssueNoteResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + treeItemResult.failed;
437
639
  console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
438
640
  if (totalFailed > 0) {
439
641
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
+ "mcpName": "io.github.zereight/gitlab-mcp",
4
5
  "description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
5
6
  "keywords": [
6
7
  "gitlab",
@@ -24,7 +25,9 @@
24
25
  "license": "MIT",
25
26
  "author": "zereight",
26
27
  "type": "module",
27
- "bin": "./build/index.js",
28
+ "bin": {
29
+ "mcp-gitlab": "build/index.js"
30
+ },
28
31
  "files": [
29
32
  "build"
30
33
  ],
@@ -59,6 +62,7 @@
59
62
  "lint": "eslint . --ext .ts",
60
63
  "lint:fix": "eslint . --ext .ts --fix",
61
64
  "release": "bash scripts/release.sh",
65
+ "release:mcp-registry": "bash scripts/publish_mcp_registry.sh",
62
66
  "format": "prettier --write \"**/*.{js,ts,json,md}\"",
63
67
  "format:check": "prettier --check \"**/*.{js,ts,json,md}\""
64
68
  },