@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 +8 -8
- package/build/test/schema-tests.js +205 -3
- package/package.json +6 -2
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
436
|
-
const
|
|
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.
|
|
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":
|
|
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
|
},
|