@zereight/mcp-gitlab 2.1.9 โ 2.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/schemas.js
CHANGED
|
@@ -1404,6 +1404,15 @@ export const GetBranchDiffsSchema = ProjectParamsSchema.extend({
|
|
|
1404
1404
|
.describe('Array of regex patterns to exclude files from the diff results. Each pattern is a JavaScript-compatible regular expression that matches file paths to ignore. Examples: ["^vendor/", "^test/mocks/", "\\.spec\\.ts$", "package-lock\\.json"]'),
|
|
1405
1405
|
});
|
|
1406
1406
|
export const GetMergeRequestSchema = ProjectParamsSchema.extend({
|
|
1407
|
+
project_id: z
|
|
1408
|
+
.preprocess(value => (value === undefined || value === null ? value : String(value)), z
|
|
1409
|
+
.string({
|
|
1410
|
+
required_error: "project_id is required",
|
|
1411
|
+
invalid_type_error: "project_id is required",
|
|
1412
|
+
})
|
|
1413
|
+
.refine(value => value === "" || value.trim().length > 0, "project_id is required")
|
|
1414
|
+
.transform(value => (value === "" ? value : value.trim())))
|
|
1415
|
+
.describe("Project ID or complete URL-encoded path to project"),
|
|
1407
1416
|
merge_request_iid: z.coerce.string().optional().describe("The IID of a merge request"),
|
|
1408
1417
|
source_branch: z.string().optional().describe("Source branch name"),
|
|
1409
1418
|
});
|
|
@@ -1821,7 +1830,8 @@ export const ListProjectsSchema = z
|
|
|
1821
1830
|
})
|
|
1822
1831
|
.merge(PaginationOptionsSchema);
|
|
1823
1832
|
// Label operation schemas
|
|
1824
|
-
export const ListLabelsSchema = z
|
|
1833
|
+
export const ListLabelsSchema = z
|
|
1834
|
+
.object({
|
|
1825
1835
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1826
1836
|
with_counts: z
|
|
1827
1837
|
.coerce.boolean()
|
|
@@ -1829,7 +1839,8 @@ export const ListLabelsSchema = z.object({
|
|
|
1829
1839
|
.describe("Whether or not to include issue and merge request counts"),
|
|
1830
1840
|
include_ancestor_groups: z.coerce.boolean().optional().describe("Include ancestor groups"),
|
|
1831
1841
|
search: z.string().optional().describe("Keyword to filter labels by"),
|
|
1832
|
-
})
|
|
1842
|
+
})
|
|
1843
|
+
.merge(PaginationOptionsSchema);
|
|
1833
1844
|
export const GetLabelSchema = z.object({
|
|
1834
1845
|
project_id: z.coerce.string().describe("Project ID or URL-encoded path"),
|
|
1835
1846
|
label_id: z.coerce.string().describe("The ID or title of a project's label"),
|
|
@@ -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, GetRepositoryTreeSchema } 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;
|
|
@@ -876,6 +901,52 @@ function runLabelsCoercionSchemaTests() {
|
|
|
876
901
|
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
877
902
|
return { passed, failed };
|
|
878
903
|
}
|
|
904
|
+
function runListLabelsSchemaTests() {
|
|
905
|
+
console.log('\n=== List Labels Schema Tests ===');
|
|
906
|
+
const cases = [
|
|
907
|
+
{
|
|
908
|
+
name: 'schema:list_labels:pagination-coercion',
|
|
909
|
+
input: { project_id: 'my/project', page: '2', per_page: '100' },
|
|
910
|
+
expected: { project_id: 'my/project', page: 2, per_page: 100 },
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: 'schema:list_labels:filters-with-pagination',
|
|
914
|
+
input: { project_id: 'my/project', search: 'backend', with_counts: 'true', page: 3, per_page: 50 },
|
|
915
|
+
expected: { project_id: 'my/project', search: 'backend', with_counts: true, page: 3, per_page: 50 },
|
|
916
|
+
},
|
|
917
|
+
];
|
|
918
|
+
let passed = 0;
|
|
919
|
+
let failed = 0;
|
|
920
|
+
cases.forEach(testCase => {
|
|
921
|
+
const result = { name: testCase.name, status: 'failed' };
|
|
922
|
+
const parsed = ListLabelsSchema.safeParse(testCase.input);
|
|
923
|
+
if (!parsed.success) {
|
|
924
|
+
result.error = parsed.error?.message || 'Schema validation failed';
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
const matches = Object.entries(testCase.expected).every(([key, value]) => {
|
|
928
|
+
const actual = parsed.data[key];
|
|
929
|
+
return actual === value;
|
|
930
|
+
});
|
|
931
|
+
if (matches) {
|
|
932
|
+
result.status = 'passed';
|
|
933
|
+
}
|
|
934
|
+
else {
|
|
935
|
+
result.error = `Unexpected parsed result: ${JSON.stringify(parsed.data)}`;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (result.status === 'passed') {
|
|
939
|
+
passed++;
|
|
940
|
+
console.log(`โ
${result.name}`);
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
failed++;
|
|
944
|
+
console.log(`โ ${result.name}: ${result.error}`);
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
948
|
+
return { passed, failed };
|
|
949
|
+
}
|
|
879
950
|
function runGitLabTreeItemSchemaTests() {
|
|
880
951
|
console.log('\n=== GitLabTreeItem Schema Tests ===');
|
|
881
952
|
const cases = [
|
|
@@ -1008,10 +1079,11 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
1008
1079
|
const emojiReactionResult = runEmojiReactionSchemaTests();
|
|
1009
1080
|
const repositorySchemaResult = runGitLabRepositorySchemaTests();
|
|
1010
1081
|
const labelsCoercionResult = runLabelsCoercionSchemaTests();
|
|
1082
|
+
const listLabelsResult = runListLabelsSchemaTests();
|
|
1011
1083
|
const treeItemResult = runGitLabTreeItemSchemaTests();
|
|
1012
1084
|
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;
|
|
1085
|
+
const totalPassed = getFileContentsResult.passed + fileContentResult.passed + createPipelineResult.passed + commitStatusResult.passed + createIssueNoteResult.passed + getMergeRequestResult.passed + gitLabMergeRequestResult.passed + emojiReactionResult.passed + repositorySchemaResult.passed + labelsCoercionResult.passed + listLabelsResult.passed + treeItemResult.passed + repositoryTreeResult.passed;
|
|
1086
|
+
const totalFailed = getFileContentsResult.failed + fileContentResult.failed + createPipelineResult.failed + commitStatusResult.failed + createIssueNoteResult.failed + getMergeRequestResult.failed + gitLabMergeRequestResult.failed + emojiReactionResult.failed + repositorySchemaResult.failed + labelsCoercionResult.failed + listLabelsResult.failed + treeItemResult.failed + repositoryTreeResult.failed;
|
|
1015
1087
|
console.log(`\nTotal Results: ${totalPassed} passed, ${totalFailed} failed`);
|
|
1016
1088
|
if (totalFailed > 0) {
|
|
1017
1089
|
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;
|
package/build/utils/schema.js
CHANGED
|
@@ -6,6 +6,23 @@ import { zodToJsonSchema } from "zod-to-json-schema";
|
|
|
6
6
|
*/
|
|
7
7
|
export const toJSONSchema = (schema) => {
|
|
8
8
|
const jsonSchema = zodToJsonSchema(schema, { $refStrategy: "none" });
|
|
9
|
+
const isOptionalLikeField = (zodType) => {
|
|
10
|
+
const def = zodType._def;
|
|
11
|
+
const typeName = def?.typeName;
|
|
12
|
+
if (["ZodOptional", "ZodNullable", "ZodDefault", "ZodCatch"].includes(typeName)) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (typeName === "ZodEffects") {
|
|
16
|
+
return isOptionalLikeField(def.schema);
|
|
17
|
+
}
|
|
18
|
+
if (typeName === "ZodBranded") {
|
|
19
|
+
return isOptionalLikeField(def.type);
|
|
20
|
+
}
|
|
21
|
+
if (typeName === "ZodPipeline") {
|
|
22
|
+
return isOptionalLikeField(def.in);
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
};
|
|
9
26
|
// Extract required fields from Zod schema
|
|
10
27
|
const zodRequiredFields = (() => {
|
|
11
28
|
if (schema instanceof z.ZodObject) {
|
|
@@ -13,17 +30,7 @@ export const toJSONSchema = (schema) => {
|
|
|
13
30
|
const requiredFields = [];
|
|
14
31
|
Object.entries(shape).forEach(([key, fieldDef]) => {
|
|
15
32
|
const zodType = fieldDef;
|
|
16
|
-
|
|
17
|
-
// Check if field is wrapped in zod required types
|
|
18
|
-
const isRequired = [
|
|
19
|
-
"ZodOptional",
|
|
20
|
-
"ZodNullable",
|
|
21
|
-
"ZodDefault",
|
|
22
|
-
"ZodEffects",
|
|
23
|
-
"ZodCatch",
|
|
24
|
-
"ZodBranded",
|
|
25
|
-
].includes(typeName);
|
|
26
|
-
if (!isRequired) {
|
|
33
|
+
if (!isOptionalLikeField(zodType)) {
|
|
27
34
|
requiredFields.push(key);
|
|
28
35
|
}
|
|
29
36
|
});
|
package/package.json
CHANGED