@ttpears/gitlab-mcp-server 1.7.3 → 1.8.0
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 +25 -5
- package/dist/gitlab-client.d.ts +82 -9
- package/dist/gitlab-client.d.ts.map +1 -1
- package/dist/gitlab-client.js +1001 -40
- package/dist/gitlab-client.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +672 -31
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -81,10 +81,15 @@ const getProjectsTool = {
|
|
|
81
81
|
inputSchema: withUserAuth(z.object({
|
|
82
82
|
first: z.number().min(1).max(100).default(20).describe('Number of projects to retrieve'),
|
|
83
83
|
after: z.string().optional().describe('Cursor for pagination'),
|
|
84
|
+
sort: z.string().optional().describe('Sort order (e.g., UPDATED_DESC, CREATED_DESC, CREATED_ASC). Defaults to UPDATED_DESC for recency.'),
|
|
85
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
84
86
|
})),
|
|
85
87
|
handler: async (input, client, userConfig) => {
|
|
86
88
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
87
|
-
const result = await client.getProjects(input.first, input.after, credentials);
|
|
89
|
+
const result = await client.getProjects(input.first, input.after, input.fetchAll, credentials, input.sort);
|
|
90
|
+
if (input.fetchAll) {
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
88
93
|
return result.projects;
|
|
89
94
|
},
|
|
90
95
|
};
|
|
@@ -103,10 +108,15 @@ const getIssuesTool = {
|
|
|
103
108
|
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
104
109
|
first: z.number().min(1).max(100).default(20).describe('Number of issues to retrieve'),
|
|
105
110
|
after: z.string().optional().describe('Cursor for pagination'),
|
|
111
|
+
sort: z.string().optional().describe('Sort order (e.g., UPDATED_DESC, CREATED_DESC, CREATED_ASC). Defaults to UPDATED_DESC for recency.'),
|
|
112
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
106
113
|
})),
|
|
107
114
|
handler: async (input, client, userConfig) => {
|
|
108
115
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
109
|
-
const result = await client.getIssues(input.projectPath, input.first, input.after, credentials);
|
|
116
|
+
const result = await client.getIssues(input.projectPath, input.first, input.after, input.fetchAll, credentials, input.sort);
|
|
117
|
+
if (input.fetchAll) {
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
110
120
|
if (!result || !result.project || !result.project.issues) {
|
|
111
121
|
throw new Error('Project not found or issues are not accessible for the provided path');
|
|
112
122
|
}
|
|
@@ -128,10 +138,15 @@ const getMergeRequestsTool = {
|
|
|
128
138
|
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
129
139
|
first: z.number().min(1).max(100).default(20).describe('Number of merge requests to retrieve'),
|
|
130
140
|
after: z.string().optional().describe('Cursor for pagination'),
|
|
141
|
+
sort: z.string().optional().describe('Sort order (e.g., UPDATED_DESC, CREATED_DESC, CREATED_ASC). Defaults to UPDATED_DESC for recency.'),
|
|
142
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
131
143
|
})),
|
|
132
144
|
handler: async (input, client, userConfig) => {
|
|
133
145
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
134
|
-
const result = await client.getMergeRequests(input.projectPath, input.first, input.after, credentials);
|
|
146
|
+
const result = await client.getMergeRequests(input.projectPath, input.first, input.after, input.fetchAll, credentials, input.sort);
|
|
147
|
+
if (input.fetchAll) {
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
135
150
|
if (!result || !result.project || !result.project.mergeRequests) {
|
|
136
151
|
throw new Error('Project not found or merge requests are not accessible for the provided path');
|
|
137
152
|
}
|
|
@@ -245,21 +260,6 @@ const getAvailableQueriesTools = {
|
|
|
245
260
|
};
|
|
246
261
|
},
|
|
247
262
|
};
|
|
248
|
-
export const readOnlyTools = [
|
|
249
|
-
getProjectTool,
|
|
250
|
-
getIssuesTool,
|
|
251
|
-
getMergeRequestsTool,
|
|
252
|
-
executeCustomQueryTool,
|
|
253
|
-
getAvailableQueriesTools,
|
|
254
|
-
];
|
|
255
|
-
export const userAuthTools = [
|
|
256
|
-
getCurrentUserTool,
|
|
257
|
-
getProjectsTool,
|
|
258
|
-
];
|
|
259
|
-
export const writeTools = [
|
|
260
|
-
createIssueTool,
|
|
261
|
-
createMergeRequestTool,
|
|
262
|
-
];
|
|
263
263
|
const updateIssueTool = {
|
|
264
264
|
name: 'update_issue',
|
|
265
265
|
title: 'Update Issue',
|
|
@@ -410,15 +410,27 @@ const globalSearchTool = {
|
|
|
410
410
|
},
|
|
411
411
|
inputSchema: withUserAuth(z.object({
|
|
412
412
|
searchTerm: z.string().optional().transform(val => val?.trim() || undefined).describe('Search term (leave empty for recent activity)'),
|
|
413
|
+
first: z.number().min(1).max(100).default(20).describe('Number of results to retrieve per category'),
|
|
414
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
415
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results per category'),
|
|
413
416
|
})),
|
|
414
417
|
handler: async (input, client, userConfig) => {
|
|
415
418
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
416
|
-
|
|
419
|
+
if (input.fetchAll) {
|
|
420
|
+
const result = await client.globalSearchAll(input.searchTerm, undefined, credentials);
|
|
421
|
+
return {
|
|
422
|
+
searchTerm: input.searchTerm,
|
|
423
|
+
projects: result.projects,
|
|
424
|
+
issues: result.issues,
|
|
425
|
+
totalResults: result.projects.totalFetched + result.issues.totalFetched,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const result = await client.globalSearch(input.searchTerm, input.first, input.after, credentials);
|
|
417
429
|
return {
|
|
418
430
|
searchTerm: input.searchTerm,
|
|
419
|
-
projects: result.projects
|
|
420
|
-
issues: result.issues
|
|
421
|
-
totalResults: result.projects.nodes
|
|
431
|
+
projects: result.projects,
|
|
432
|
+
issues: result.issues,
|
|
433
|
+
totalResults: (result.projects.nodes?.length || 0) + (result.issues.nodes?.length || 0),
|
|
422
434
|
_note: 'This is a text search only. For filtering by assignee/author/labels, use search_issues or get_user_issues. For MRs, use search_merge_requests with username.'
|
|
423
435
|
};
|
|
424
436
|
},
|
|
@@ -441,10 +453,14 @@ const searchProjectsTool = {
|
|
|
441
453
|
.describe('Search term to find projects by name or description'),
|
|
442
454
|
first: z.number().min(1).max(100).default(20).describe('Number of projects to retrieve'),
|
|
443
455
|
after: z.string().optional().describe('Cursor for pagination'),
|
|
456
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
444
457
|
})),
|
|
445
458
|
handler: async (input, client, userConfig) => {
|
|
446
459
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
447
|
-
const result = await client.searchProjects(input.searchTerm, input.first, input.after, credentials);
|
|
460
|
+
const result = await client.searchProjects(input.searchTerm, input.first, input.after, input.fetchAll, credentials);
|
|
461
|
+
if (input.fetchAll) {
|
|
462
|
+
return result;
|
|
463
|
+
}
|
|
448
464
|
return result.projects;
|
|
449
465
|
},
|
|
450
466
|
};
|
|
@@ -468,10 +484,15 @@ const searchIssuesTool = {
|
|
|
468
484
|
labelNames: z.array(z.string()).optional().describe('Filter by label names (e.g., ["Priority::High", "bug"])'),
|
|
469
485
|
first: z.number().min(1).max(100).default(20).describe('Number of issues to retrieve'),
|
|
470
486
|
after: z.string().optional().describe('Cursor for pagination'),
|
|
487
|
+
sort: z.string().optional().describe('Sort order (e.g., UPDATED_DESC, CREATED_DESC, CREATED_ASC). Defaults to UPDATED_DESC for recency.'),
|
|
488
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
471
489
|
})),
|
|
472
490
|
handler: async (input, client, userConfig) => {
|
|
473
491
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
474
|
-
const result = await client.searchIssues(input.searchTerm, input.projectPath, input.state, input.first, input.after, credentials, input.assigneeUsernames, input.authorUsername, input.labelNames);
|
|
492
|
+
const result = await client.searchIssues(input.searchTerm, input.projectPath, input.state, input.first, input.after, input.fetchAll, credentials, input.assigneeUsernames, input.authorUsername, input.labelNames, input.sort);
|
|
493
|
+
if (input.fetchAll) {
|
|
494
|
+
return result;
|
|
495
|
+
}
|
|
475
496
|
// Return the issues from either project-specific or global search
|
|
476
497
|
if (input.projectPath) {
|
|
477
498
|
if (!result || !result.project || !result.project.issues) {
|
|
@@ -512,12 +533,17 @@ const searchMergeRequestsTool = {
|
|
|
512
533
|
state: z.string().default('all').describe('Filter by merge request state (opened, closed, merged, all)'),
|
|
513
534
|
first: z.number().min(1).max(100).default(20).describe('Number of merge requests to retrieve'),
|
|
514
535
|
after: z.string().optional().describe('Cursor for pagination'),
|
|
536
|
+
sort: z.string().optional().describe('Sort order (e.g., UPDATED_DESC, CREATED_DESC, CREATED_ASC). Defaults to UPDATED_DESC for recency.'),
|
|
537
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
515
538
|
})),
|
|
516
539
|
handler: async (input, client, userConfig) => {
|
|
517
540
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
518
541
|
// If projectPath provided, search in that project
|
|
519
542
|
// Otherwise, intelligently find projects matching search term and search their MRs
|
|
520
|
-
const result = await client.searchMergeRequests(input.searchTerm, input.projectPath, input.state, input.first, input.after, credentials);
|
|
543
|
+
const result = await client.searchMergeRequests(input.searchTerm, input.projectPath, input.state, input.first, input.after, input.fetchAll, credentials, input.sort);
|
|
544
|
+
if (input.fetchAll) {
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
521
547
|
// Handle project-specific search
|
|
522
548
|
if (input.projectPath) {
|
|
523
549
|
if (!result || !result.project || !result.project.mergeRequests) {
|
|
@@ -553,10 +579,15 @@ const searchUsersTool = {
|
|
|
553
579
|
.refine(val => val.length > 0, { message: 'Search term cannot be empty' })
|
|
554
580
|
.describe('Search term to find users by username or name'),
|
|
555
581
|
first: z.number().min(1).max(100).default(20).describe('Number of users to retrieve'),
|
|
582
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
583
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
556
584
|
})),
|
|
557
585
|
handler: async (input, client, userConfig) => {
|
|
558
586
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
559
|
-
const result = await client.searchUsers(input.searchTerm, input.first, credentials);
|
|
587
|
+
const result = await client.searchUsers(input.searchTerm, input.first, input.after, input.fetchAll, credentials);
|
|
588
|
+
if (input.fetchAll) {
|
|
589
|
+
return result;
|
|
590
|
+
}
|
|
560
591
|
return result.users;
|
|
561
592
|
},
|
|
562
593
|
};
|
|
@@ -577,10 +608,15 @@ const searchGroupsTool = {
|
|
|
577
608
|
.refine(val => val.length > 0, { message: 'Search term cannot be empty' })
|
|
578
609
|
.describe('Search term to find groups by name or path'),
|
|
579
610
|
first: z.number().min(1).max(100).default(20).describe('Number of groups to retrieve'),
|
|
611
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
612
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
580
613
|
})),
|
|
581
614
|
handler: async (input, client, userConfig) => {
|
|
582
615
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
583
|
-
const result = await client.searchGroups(input.searchTerm, input.first, credentials);
|
|
616
|
+
const result = await client.searchGroups(input.searchTerm, input.first, input.after, input.fetchAll, credentials);
|
|
617
|
+
if (input.fetchAll) {
|
|
618
|
+
return result;
|
|
619
|
+
}
|
|
584
620
|
return result.groups;
|
|
585
621
|
},
|
|
586
622
|
};
|
|
@@ -660,6 +696,461 @@ const getFileContentTool = {
|
|
|
660
696
|
};
|
|
661
697
|
},
|
|
662
698
|
};
|
|
699
|
+
// CI/CD Pipeline tools
|
|
700
|
+
const getMergeRequestPipelinesTool = {
|
|
701
|
+
name: 'get_merge_request_pipelines',
|
|
702
|
+
title: 'MR Pipelines',
|
|
703
|
+
description: 'Get CI/CD pipelines for a merge request, including status, duration, and stages',
|
|
704
|
+
requiresAuth: false,
|
|
705
|
+
requiresWrite: false,
|
|
706
|
+
annotations: {
|
|
707
|
+
readOnlyHint: true,
|
|
708
|
+
destructiveHint: false,
|
|
709
|
+
idempotentHint: true,
|
|
710
|
+
},
|
|
711
|
+
inputSchema: withUserAuth(z.object({
|
|
712
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
713
|
+
iid: z.string().describe('Merge request IID'),
|
|
714
|
+
first: z.number().min(1).max(100).default(20).describe('Number of pipelines to retrieve'),
|
|
715
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
716
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
717
|
+
})),
|
|
718
|
+
handler: async (input, client, userConfig) => {
|
|
719
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
720
|
+
const result = await client.getMergeRequestPipelines(input.projectPath, input.iid, input.first, input.after, input.fetchAll, credentials);
|
|
721
|
+
if (input.fetchAll) {
|
|
722
|
+
return result;
|
|
723
|
+
}
|
|
724
|
+
if (!result?.project?.mergeRequest) {
|
|
725
|
+
throw new Error('Merge request not found');
|
|
726
|
+
}
|
|
727
|
+
return result.project.mergeRequest.pipelines;
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
const getPipelineJobsTool = {
|
|
731
|
+
name: 'get_pipeline_jobs',
|
|
732
|
+
title: 'Pipeline Jobs',
|
|
733
|
+
description: 'Get jobs for a specific pipeline, including status, stage, duration, and retry/cancel info',
|
|
734
|
+
requiresAuth: false,
|
|
735
|
+
requiresWrite: false,
|
|
736
|
+
annotations: {
|
|
737
|
+
readOnlyHint: true,
|
|
738
|
+
destructiveHint: false,
|
|
739
|
+
idempotentHint: true,
|
|
740
|
+
},
|
|
741
|
+
inputSchema: withUserAuth(z.object({
|
|
742
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
743
|
+
pipelineIid: z.string().describe('Pipeline IID'),
|
|
744
|
+
first: z.number().min(1).max(100).default(20).describe('Number of jobs to retrieve'),
|
|
745
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
746
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
747
|
+
})),
|
|
748
|
+
handler: async (input, client, userConfig) => {
|
|
749
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
750
|
+
const result = await client.getPipelineJobs(input.projectPath, input.pipelineIid, input.first, input.after, input.fetchAll, credentials);
|
|
751
|
+
if (input.fetchAll) {
|
|
752
|
+
return result;
|
|
753
|
+
}
|
|
754
|
+
if (!result?.project?.pipeline) {
|
|
755
|
+
throw new Error('Pipeline not found');
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
pipeline: {
|
|
759
|
+
id: result.project.pipeline.id,
|
|
760
|
+
iid: result.project.pipeline.iid,
|
|
761
|
+
status: result.project.pipeline.status,
|
|
762
|
+
},
|
|
763
|
+
jobs: result.project.pipeline.jobs,
|
|
764
|
+
};
|
|
765
|
+
},
|
|
766
|
+
};
|
|
767
|
+
const managePipelineTool = {
|
|
768
|
+
name: 'manage_pipeline',
|
|
769
|
+
title: 'Manage Pipeline',
|
|
770
|
+
description: 'Retry or cancel a CI/CD pipeline (requires user authentication with write permissions)',
|
|
771
|
+
requiresAuth: true,
|
|
772
|
+
requiresWrite: true,
|
|
773
|
+
annotations: {
|
|
774
|
+
readOnlyHint: false,
|
|
775
|
+
destructiveHint: false,
|
|
776
|
+
idempotentHint: false,
|
|
777
|
+
},
|
|
778
|
+
inputSchema: withUserAuth(z.object({
|
|
779
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
780
|
+
pipelineIid: z.string().describe('Pipeline IID'),
|
|
781
|
+
action: z.enum(['retry', 'cancel']).describe('Action to perform on the pipeline'),
|
|
782
|
+
})),
|
|
783
|
+
handler: async (input, client, userConfig) => {
|
|
784
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
785
|
+
if (!credentials) {
|
|
786
|
+
throw new Error('User authentication is required for pipeline management. Please provide your GitLab credentials.');
|
|
787
|
+
}
|
|
788
|
+
return await client.managePipeline(input.projectPath, input.pipelineIid, input.action, credentials);
|
|
789
|
+
},
|
|
790
|
+
};
|
|
791
|
+
// MR Diffs & Commits tools
|
|
792
|
+
const getMergeRequestDiffsTool = {
|
|
793
|
+
name: 'get_merge_request_diffs',
|
|
794
|
+
title: 'MR Diffs',
|
|
795
|
+
description: 'Get diff statistics for a merge request, including per-file additions/deletions and diff refs',
|
|
796
|
+
requiresAuth: false,
|
|
797
|
+
requiresWrite: false,
|
|
798
|
+
annotations: {
|
|
799
|
+
readOnlyHint: true,
|
|
800
|
+
destructiveHint: false,
|
|
801
|
+
idempotentHint: true,
|
|
802
|
+
},
|
|
803
|
+
inputSchema: withUserAuth(z.object({
|
|
804
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
805
|
+
iid: z.string().describe('Merge request IID'),
|
|
806
|
+
})),
|
|
807
|
+
handler: async (input, client, userConfig) => {
|
|
808
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
809
|
+
const result = await client.getMergeRequestDiffs(input.projectPath, input.iid, credentials);
|
|
810
|
+
if (!result?.project?.mergeRequest) {
|
|
811
|
+
throw new Error('Merge request not found');
|
|
812
|
+
}
|
|
813
|
+
return result.project.mergeRequest;
|
|
814
|
+
},
|
|
815
|
+
};
|
|
816
|
+
const getMergeRequestCommitsTool = {
|
|
817
|
+
name: 'get_merge_request_commits',
|
|
818
|
+
title: 'MR Commits',
|
|
819
|
+
description: 'Get commits for a merge request (excluding merge commits), with commit count and details',
|
|
820
|
+
requiresAuth: false,
|
|
821
|
+
requiresWrite: false,
|
|
822
|
+
annotations: {
|
|
823
|
+
readOnlyHint: true,
|
|
824
|
+
destructiveHint: false,
|
|
825
|
+
idempotentHint: true,
|
|
826
|
+
},
|
|
827
|
+
inputSchema: withUserAuth(z.object({
|
|
828
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
829
|
+
iid: z.string().describe('Merge request IID'),
|
|
830
|
+
first: z.number().min(1).max(100).default(20).describe('Number of commits to retrieve'),
|
|
831
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
832
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
833
|
+
})),
|
|
834
|
+
handler: async (input, client, userConfig) => {
|
|
835
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
836
|
+
const result = await client.getMergeRequestCommits(input.projectPath, input.iid, input.first, input.after, input.fetchAll, credentials);
|
|
837
|
+
if (input.fetchAll) {
|
|
838
|
+
return result;
|
|
839
|
+
}
|
|
840
|
+
if (!result?.project?.mergeRequest) {
|
|
841
|
+
throw new Error('Merge request not found');
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
commitCount: result.project.mergeRequest.commitCount,
|
|
845
|
+
commits: result.project.mergeRequest.commitsWithoutMergeCommits,
|
|
846
|
+
};
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
// Work Item Notes tools
|
|
850
|
+
const getNotesTool = {
|
|
851
|
+
name: 'get_notes',
|
|
852
|
+
title: 'Notes/Comments',
|
|
853
|
+
description: 'Get notes (comments) on an issue or merge request, including system notes and inline MR comments',
|
|
854
|
+
requiresAuth: false,
|
|
855
|
+
requiresWrite: false,
|
|
856
|
+
annotations: {
|
|
857
|
+
readOnlyHint: true,
|
|
858
|
+
destructiveHint: false,
|
|
859
|
+
idempotentHint: true,
|
|
860
|
+
},
|
|
861
|
+
inputSchema: withUserAuth(z.object({
|
|
862
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
863
|
+
noteableType: z.enum(['issue', 'merge_request']).describe('Type of item to get notes for'),
|
|
864
|
+
iid: z.string().describe('Issue or merge request IID'),
|
|
865
|
+
first: z.number().min(1).max(100).default(20).describe('Number of notes to retrieve'),
|
|
866
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
867
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
868
|
+
})),
|
|
869
|
+
handler: async (input, client, userConfig) => {
|
|
870
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
871
|
+
const result = await client.getNotes(input.projectPath, input.noteableType, input.iid, input.first, input.after, input.fetchAll, credentials);
|
|
872
|
+
if (input.fetchAll) {
|
|
873
|
+
return result;
|
|
874
|
+
}
|
|
875
|
+
const noteable = input.noteableType === 'issue'
|
|
876
|
+
? result?.project?.issue
|
|
877
|
+
: result?.project?.mergeRequest;
|
|
878
|
+
if (!noteable) {
|
|
879
|
+
throw new Error(`${input.noteableType === 'issue' ? 'Issue' : 'Merge request'} not found`);
|
|
880
|
+
}
|
|
881
|
+
return noteable.notes;
|
|
882
|
+
},
|
|
883
|
+
};
|
|
884
|
+
const createNoteTool = {
|
|
885
|
+
name: 'create_note',
|
|
886
|
+
title: 'Create Note',
|
|
887
|
+
description: 'Add a comment/note to an issue or merge request (requires user authentication)',
|
|
888
|
+
requiresAuth: true,
|
|
889
|
+
requiresWrite: true,
|
|
890
|
+
annotations: {
|
|
891
|
+
readOnlyHint: false,
|
|
892
|
+
destructiveHint: false,
|
|
893
|
+
idempotentHint: false,
|
|
894
|
+
},
|
|
895
|
+
inputSchema: withUserAuth(z.object({
|
|
896
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
897
|
+
noteableType: z.enum(['issue', 'merge_request']).describe('Type of item to add a note to'),
|
|
898
|
+
iid: z.string().describe('Issue or merge request IID'),
|
|
899
|
+
body: z.string().min(1).describe('Note body (supports Markdown)'),
|
|
900
|
+
internal: z.boolean().default(false).describe('Whether the note is internal/confidential (only visible to project members)'),
|
|
901
|
+
})),
|
|
902
|
+
handler: async (input, client, userConfig) => {
|
|
903
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
904
|
+
if (!credentials) {
|
|
905
|
+
throw new Error('User authentication is required for creating notes. Please provide your GitLab credentials.');
|
|
906
|
+
}
|
|
907
|
+
const result = await client.createNote(input.projectPath, input.noteableType, input.iid, input.body, input.internal, credentials);
|
|
908
|
+
if (result.errors && result.errors.length > 0) {
|
|
909
|
+
throw new Error(`Failed to create note: ${result.errors.join(', ')}`);
|
|
910
|
+
}
|
|
911
|
+
return result.note;
|
|
912
|
+
},
|
|
913
|
+
};
|
|
914
|
+
// ── Project Tracking & User Reporting tools ─────────────────────────
|
|
915
|
+
const listMilestonesTool = {
|
|
916
|
+
name: 'list_milestones',
|
|
917
|
+
title: 'Milestones',
|
|
918
|
+
description: 'List milestones for a project or group with progress statistics (total/closed issue counts)',
|
|
919
|
+
requiresAuth: false,
|
|
920
|
+
requiresWrite: false,
|
|
921
|
+
annotations: {
|
|
922
|
+
readOnlyHint: true,
|
|
923
|
+
destructiveHint: false,
|
|
924
|
+
idempotentHint: true,
|
|
925
|
+
},
|
|
926
|
+
inputSchema: withUserAuth(z.object({
|
|
927
|
+
fullPath: z.string().describe('Full path of the project or group (e.g., "group/project-name" or "group")'),
|
|
928
|
+
isProject: z.boolean().describe('Whether the path is a project (true) or group (false)'),
|
|
929
|
+
state: z.string().optional().describe('Filter by state: active, closed (omit for all)'),
|
|
930
|
+
search: z.string().optional().describe('Search milestones by title'),
|
|
931
|
+
includeAncestors: z.boolean().default(false).describe('Include milestones from ancestor groups'),
|
|
932
|
+
first: z.number().min(1).max(100).default(20).describe('Number of milestones to retrieve'),
|
|
933
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
934
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
935
|
+
})),
|
|
936
|
+
handler: async (input, client, userConfig) => {
|
|
937
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
938
|
+
const result = await client.listMilestones(input.fullPath, input.isProject, input.state, input.search, input.includeAncestors, input.first, input.after, input.fetchAll, credentials);
|
|
939
|
+
if (input.fetchAll) {
|
|
940
|
+
return result;
|
|
941
|
+
}
|
|
942
|
+
const container = input.isProject ? result?.project : result?.group;
|
|
943
|
+
if (!container) {
|
|
944
|
+
throw new Error(`${input.isProject ? 'Project' : 'Group'} not found: ${input.fullPath}`);
|
|
945
|
+
}
|
|
946
|
+
return container.milestones;
|
|
947
|
+
},
|
|
948
|
+
};
|
|
949
|
+
const listIterationsTool = {
|
|
950
|
+
name: 'list_iterations',
|
|
951
|
+
title: 'Iterations',
|
|
952
|
+
description: 'List iterations (sprints) for a group with cadence info. Requires GitLab Premium/Ultimate.',
|
|
953
|
+
requiresAuth: false,
|
|
954
|
+
requiresWrite: false,
|
|
955
|
+
annotations: {
|
|
956
|
+
readOnlyHint: true,
|
|
957
|
+
destructiveHint: false,
|
|
958
|
+
idempotentHint: true,
|
|
959
|
+
},
|
|
960
|
+
inputSchema: withUserAuth(z.object({
|
|
961
|
+
groupPath: z.string().describe('Full path of the group (e.g., "my-group" or "parent/child-group")'),
|
|
962
|
+
state: z.string().optional().describe('Filter by state: upcoming, current, opened, closed (omit for all)'),
|
|
963
|
+
first: z.number().min(1).max(100).default(20).describe('Number of iterations to retrieve'),
|
|
964
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
965
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
966
|
+
})),
|
|
967
|
+
handler: async (input, client, userConfig) => {
|
|
968
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
969
|
+
try {
|
|
970
|
+
const result = await client.listIterations(input.groupPath, input.state, input.first, input.after, input.fetchAll, credentials);
|
|
971
|
+
if (input.fetchAll) {
|
|
972
|
+
return result;
|
|
973
|
+
}
|
|
974
|
+
if (!result?.group) {
|
|
975
|
+
throw new Error(`Group not found: ${input.groupPath}`);
|
|
976
|
+
}
|
|
977
|
+
return result.group.iterations;
|
|
978
|
+
}
|
|
979
|
+
catch (error) {
|
|
980
|
+
if (error.message?.includes('iterations') || error.message?.includes('does not exist')) {
|
|
981
|
+
throw new Error(`Iterations are not available for "${input.groupPath}". ` +
|
|
982
|
+
`This feature requires GitLab Premium or Ultimate. ` +
|
|
983
|
+
`Original error: ${error.message}`);
|
|
984
|
+
}
|
|
985
|
+
throw error;
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
};
|
|
989
|
+
const getTimeTrackingTool = {
|
|
990
|
+
name: 'get_time_tracking',
|
|
991
|
+
title: 'Time Tracking',
|
|
992
|
+
description: 'Get time tracking data (estimate, spent, timelogs) for an issue or merge request',
|
|
993
|
+
requiresAuth: false,
|
|
994
|
+
requiresWrite: false,
|
|
995
|
+
annotations: {
|
|
996
|
+
readOnlyHint: true,
|
|
997
|
+
destructiveHint: false,
|
|
998
|
+
idempotentHint: true,
|
|
999
|
+
},
|
|
1000
|
+
inputSchema: withUserAuth(z.object({
|
|
1001
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
1002
|
+
resourceType: z.enum(['issue', 'merge_request']).describe('Type of resource to get time tracking for'),
|
|
1003
|
+
iid: z.string().describe('Issue or merge request IID'),
|
|
1004
|
+
includeTimelogs: z.boolean().default(true).describe('Whether to include individual timelog entries'),
|
|
1005
|
+
first: z.number().min(1).max(100).default(20).describe('Number of timelog entries to retrieve'),
|
|
1006
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
1007
|
+
})),
|
|
1008
|
+
handler: async (input, client, userConfig) => {
|
|
1009
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1010
|
+
const result = await client.getTimeTracking(input.projectPath, input.resourceType, input.iid, input.includeTimelogs, input.first, input.after, credentials);
|
|
1011
|
+
const resource = input.resourceType === 'issue'
|
|
1012
|
+
? result?.project?.issue
|
|
1013
|
+
: result?.project?.mergeRequest;
|
|
1014
|
+
if (!resource) {
|
|
1015
|
+
throw new Error(`${input.resourceType === 'issue' ? 'Issue' : 'Merge request'} not found`);
|
|
1016
|
+
}
|
|
1017
|
+
return resource;
|
|
1018
|
+
},
|
|
1019
|
+
};
|
|
1020
|
+
const getMergeRequestReviewersTool = {
|
|
1021
|
+
name: 'get_merge_request_reviewers',
|
|
1022
|
+
title: 'MR Reviewers',
|
|
1023
|
+
description: 'Get approval and reviewer status for a merge request, including who approved and review states',
|
|
1024
|
+
requiresAuth: false,
|
|
1025
|
+
requiresWrite: false,
|
|
1026
|
+
annotations: {
|
|
1027
|
+
readOnlyHint: true,
|
|
1028
|
+
destructiveHint: false,
|
|
1029
|
+
idempotentHint: true,
|
|
1030
|
+
},
|
|
1031
|
+
inputSchema: withUserAuth(z.object({
|
|
1032
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
1033
|
+
iid: z.string().describe('Merge request IID'),
|
|
1034
|
+
})),
|
|
1035
|
+
handler: async (input, client, userConfig) => {
|
|
1036
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1037
|
+
const result = await client.getMergeRequestReviewers(input.projectPath, input.iid, credentials);
|
|
1038
|
+
if (!result?.project?.mergeRequest) {
|
|
1039
|
+
throw new Error('Merge request not found');
|
|
1040
|
+
}
|
|
1041
|
+
return result.project.mergeRequest;
|
|
1042
|
+
},
|
|
1043
|
+
};
|
|
1044
|
+
const getProjectStatisticsTool = {
|
|
1045
|
+
name: 'get_project_statistics',
|
|
1046
|
+
title: 'Project Statistics',
|
|
1047
|
+
description: 'Get aggregate project statistics: open issues/MRs, star/fork counts, storage sizes, commit count, last pipeline status, release count, and language breakdown',
|
|
1048
|
+
requiresAuth: false,
|
|
1049
|
+
requiresWrite: false,
|
|
1050
|
+
annotations: {
|
|
1051
|
+
readOnlyHint: true,
|
|
1052
|
+
destructiveHint: false,
|
|
1053
|
+
idempotentHint: true,
|
|
1054
|
+
},
|
|
1055
|
+
inputSchema: withUserAuth(z.object({
|
|
1056
|
+
projectPath: z.string().describe('Full path of the project (e.g., "group/project-name")'),
|
|
1057
|
+
})),
|
|
1058
|
+
handler: async (input, client, userConfig) => {
|
|
1059
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1060
|
+
const result = await client.getProjectStatistics(input.projectPath, credentials);
|
|
1061
|
+
if (!result?.project) {
|
|
1062
|
+
throw new Error(`Project not found: ${input.projectPath}`);
|
|
1063
|
+
}
|
|
1064
|
+
const p = result.project;
|
|
1065
|
+
return {
|
|
1066
|
+
id: p.id,
|
|
1067
|
+
name: p.name,
|
|
1068
|
+
fullPath: p.fullPath,
|
|
1069
|
+
webUrl: p.webUrl,
|
|
1070
|
+
starCount: p.starCount,
|
|
1071
|
+
forksCount: p.forksCount,
|
|
1072
|
+
openIssuesCount: p.openIssuesCount,
|
|
1073
|
+
openMergeRequestsCount: p.openMergeRequests?.count ?? null,
|
|
1074
|
+
commitCount: p.statistics?.commitCount ?? null,
|
|
1075
|
+
storage: p.statistics ? {
|
|
1076
|
+
repositorySize: p.statistics.repositorySize,
|
|
1077
|
+
lfsObjectsSize: p.statistics.lfsObjectsSize,
|
|
1078
|
+
buildArtifactsSize: p.statistics.buildArtifactsSize,
|
|
1079
|
+
packagesSize: p.statistics.packagesSize,
|
|
1080
|
+
wikiSize: p.statistics.wikiSize,
|
|
1081
|
+
snippetsSize: p.statistics.snippetsSize,
|
|
1082
|
+
uploadsSize: p.statistics.uploadsSize,
|
|
1083
|
+
containerRegistrySize: p.statistics.containerRegistrySize,
|
|
1084
|
+
} : null,
|
|
1085
|
+
lastPipeline: p.lastPipeline?.nodes?.[0] ?? null,
|
|
1086
|
+
releaseCount: p.releaseCount?.count ?? null,
|
|
1087
|
+
languages: p.languages ?? [],
|
|
1088
|
+
};
|
|
1089
|
+
},
|
|
1090
|
+
};
|
|
1091
|
+
const listGroupMembersTool = {
|
|
1092
|
+
name: 'list_group_members',
|
|
1093
|
+
title: 'Group Members',
|
|
1094
|
+
description: 'List group members with access levels, optionally filtered by search term',
|
|
1095
|
+
requiresAuth: false,
|
|
1096
|
+
requiresWrite: false,
|
|
1097
|
+
annotations: {
|
|
1098
|
+
readOnlyHint: true,
|
|
1099
|
+
destructiveHint: false,
|
|
1100
|
+
idempotentHint: true,
|
|
1101
|
+
},
|
|
1102
|
+
inputSchema: withUserAuth(z.object({
|
|
1103
|
+
groupPath: z.string().describe('Full path of the group (e.g., "my-group" or "parent/child-group")'),
|
|
1104
|
+
search: z.string().optional().describe('Optional search term to filter members by name or username'),
|
|
1105
|
+
first: z.number().min(1).max(100).default(20).describe('Number of members to retrieve'),
|
|
1106
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
1107
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
1108
|
+
})),
|
|
1109
|
+
handler: async (input, client, userConfig) => {
|
|
1110
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1111
|
+
const result = await client.listGroupMembers(input.groupPath, input.search, input.first, input.after, input.fetchAll, credentials);
|
|
1112
|
+
if (input.fetchAll) {
|
|
1113
|
+
return result;
|
|
1114
|
+
}
|
|
1115
|
+
if (!result?.group) {
|
|
1116
|
+
throw new Error(`Group not found: ${input.groupPath}`);
|
|
1117
|
+
}
|
|
1118
|
+
return result.group.groupMembers;
|
|
1119
|
+
},
|
|
1120
|
+
};
|
|
1121
|
+
// Label Search tool
|
|
1122
|
+
const searchLabelsTool = {
|
|
1123
|
+
name: 'search_labels',
|
|
1124
|
+
title: 'Search Labels',
|
|
1125
|
+
description: 'Search for labels in a project or group, with optional text filtering',
|
|
1126
|
+
requiresAuth: false,
|
|
1127
|
+
requiresWrite: false,
|
|
1128
|
+
annotations: {
|
|
1129
|
+
readOnlyHint: true,
|
|
1130
|
+
destructiveHint: false,
|
|
1131
|
+
idempotentHint: true,
|
|
1132
|
+
},
|
|
1133
|
+
inputSchema: withUserAuth(z.object({
|
|
1134
|
+
fullPath: z.string().describe('Full path of the project or group (e.g., "group/project-name" or "group")'),
|
|
1135
|
+
isProject: z.boolean().describe('Whether the path is a project (true) or group (false)'),
|
|
1136
|
+
search: z.string().optional().describe('Optional search term to filter labels'),
|
|
1137
|
+
first: z.number().min(1).max(100).default(20).describe('Number of labels to retrieve'),
|
|
1138
|
+
after: z.string().optional().describe('Cursor for pagination'),
|
|
1139
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
1140
|
+
})),
|
|
1141
|
+
handler: async (input, client, userConfig) => {
|
|
1142
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1143
|
+
const result = await client.searchLabels(input.fullPath, input.isProject, input.search, input.first, input.after, input.fetchAll, credentials);
|
|
1144
|
+
if (input.fetchAll) {
|
|
1145
|
+
return result;
|
|
1146
|
+
}
|
|
1147
|
+
const container = input.isProject ? result?.project : result?.group;
|
|
1148
|
+
if (!container) {
|
|
1149
|
+
throw new Error(`${input.isProject ? 'Project' : 'Group'} not found: ${input.fullPath}`);
|
|
1150
|
+
}
|
|
1151
|
+
return container.labels;
|
|
1152
|
+
},
|
|
1153
|
+
};
|
|
663
1154
|
// Helper functions for common user queries
|
|
664
1155
|
const getUserIssuesTool = {
|
|
665
1156
|
name: 'get_user_issues',
|
|
@@ -676,17 +1167,21 @@ const getUserIssuesTool = {
|
|
|
676
1167
|
username: z.string().describe('Username to find issues for (e.g., "cdhanlon")'),
|
|
677
1168
|
state: z.string().default('opened').describe('Filter by issue state (opened, closed, all)'),
|
|
678
1169
|
projectPath: z.string().optional().describe('Optional: limit search to a specific project'),
|
|
679
|
-
first: z.number().min(1).max(100).default(
|
|
1170
|
+
first: z.number().min(1).max(100).default(20).describe('Number of issues to retrieve'),
|
|
680
1171
|
after: z.string().optional().describe('Cursor for pagination'),
|
|
1172
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
681
1173
|
})),
|
|
682
1174
|
handler: async (input, client, userConfig) => {
|
|
683
1175
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
684
1176
|
// Use the searchIssues method with assigneeUsernames filter
|
|
685
1177
|
const result = await client.searchIssues(undefined, // No text search
|
|
686
|
-
input.projectPath, input.state, input.first, input.after, credentials, [input.username], // assigneeUsernames
|
|
1178
|
+
input.projectPath, input.state, input.first, input.after, input.fetchAll, credentials, [input.username], // assigneeUsernames
|
|
687
1179
|
undefined, // authorUsername
|
|
688
1180
|
undefined // labelNames
|
|
689
1181
|
);
|
|
1182
|
+
if (input.fetchAll) {
|
|
1183
|
+
return result;
|
|
1184
|
+
}
|
|
690
1185
|
if (input.projectPath) {
|
|
691
1186
|
if (!result || !result.project || !result.project.issues) {
|
|
692
1187
|
throw new Error('Project not found or issues are not accessible for the provided path');
|
|
@@ -723,14 +1218,18 @@ const getUserMergeRequestsTool = {
|
|
|
723
1218
|
role: z.enum(['author', 'assignee']).default('author').describe('Whether to find MRs authored by or assigned to the user'),
|
|
724
1219
|
state: z.string().default('opened').describe('Filter by MR state (opened, closed, merged, all)'),
|
|
725
1220
|
projectPath: z.string().optional().describe('Optional: limit search to a specific project'),
|
|
726
|
-
first: z.number().min(1).max(100).default(
|
|
1221
|
+
first: z.number().min(1).max(100).default(20).describe('Number of merge requests to retrieve'),
|
|
727
1222
|
after: z.string().optional().describe('Cursor for pagination'),
|
|
1223
|
+
fetchAll: z.boolean().default(false).describe('Fetch all pages up to 100 results'),
|
|
728
1224
|
})),
|
|
729
1225
|
handler: async (input, client, userConfig) => {
|
|
730
1226
|
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
731
1227
|
// Use the searchMergeRequests method with author: or assignee: prefix
|
|
732
1228
|
const searchTerm = input.role === 'author' ? `author:${input.username}` : `assignee:${input.username}`;
|
|
733
|
-
const result = await client.searchMergeRequests(searchTerm, input.projectPath, input.state, input.first, input.after, credentials);
|
|
1229
|
+
const result = await client.searchMergeRequests(searchTerm, input.projectPath, input.state, input.first, input.after, input.fetchAll, credentials);
|
|
1230
|
+
if (input.fetchAll) {
|
|
1231
|
+
return result;
|
|
1232
|
+
}
|
|
734
1233
|
if (input.projectPath) {
|
|
735
1234
|
if (!result || !result.project || !result.project.mergeRequests) {
|
|
736
1235
|
throw new Error(`Project "${input.projectPath}" not found or merge requests are not accessible`);
|
|
@@ -751,6 +1250,146 @@ const getUserMergeRequestsTool = {
|
|
|
751
1250
|
};
|
|
752
1251
|
},
|
|
753
1252
|
};
|
|
1253
|
+
const BroadcastMessageFields = {
|
|
1254
|
+
message: z.string().min(1).describe('Message text to display'),
|
|
1255
|
+
starts_at: z.string().datetime().optional().describe('ISO 8601 timestamp when the message starts'),
|
|
1256
|
+
ends_at: z.string().datetime().optional().describe('ISO 8601 timestamp when the message ends'),
|
|
1257
|
+
color: z.string().optional().describe('Background color in hex format, e.g. "#E75E40"'),
|
|
1258
|
+
font: z.string().optional().describe('Foreground (font) color in hex format'),
|
|
1259
|
+
target_access_levels: z.array(z.number().int()).optional().describe('Access levels to target: 10=Guest, 20=Reporter, 30=Developer, 40=Maintainer, 50=Owner'),
|
|
1260
|
+
target_path: z.string().optional().describe('Path glob for pages where the message should appear'),
|
|
1261
|
+
broadcast_type: z.enum(['banner', 'notification']).optional().describe('Broadcast type: "banner" or "notification"'),
|
|
1262
|
+
dismissable: z.boolean().optional().describe('Whether users can dismiss the broadcast message'),
|
|
1263
|
+
theme: z.string().optional().describe('Theme name (GitLab 16.9+), e.g. "indigo", "red"'),
|
|
1264
|
+
};
|
|
1265
|
+
const listBroadcastMessagesTool = {
|
|
1266
|
+
name: 'list_broadcast_messages',
|
|
1267
|
+
title: 'List Broadcast Messages',
|
|
1268
|
+
description: 'List all GitLab broadcast messages (instance-wide announcements). Read-only.',
|
|
1269
|
+
requiresAuth: false,
|
|
1270
|
+
requiresWrite: false,
|
|
1271
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
1272
|
+
inputSchema: withUserAuth(z.object({
|
|
1273
|
+
page: z.number().int().min(1).default(1).describe('Page number (1-based)'),
|
|
1274
|
+
perPage: z.number().int().min(1).max(100).default(20).describe('Results per page'),
|
|
1275
|
+
})),
|
|
1276
|
+
handler: async (input, client, userConfig) => {
|
|
1277
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1278
|
+
return client.listBroadcastMessages(input.page, input.perPage, credentials);
|
|
1279
|
+
},
|
|
1280
|
+
};
|
|
1281
|
+
const getBroadcastMessageTool = {
|
|
1282
|
+
name: 'get_broadcast_message',
|
|
1283
|
+
title: 'Get Broadcast Message',
|
|
1284
|
+
description: 'Get a specific GitLab broadcast message by ID.',
|
|
1285
|
+
requiresAuth: false,
|
|
1286
|
+
requiresWrite: false,
|
|
1287
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true },
|
|
1288
|
+
inputSchema: withUserAuth(z.object({
|
|
1289
|
+
id: z.number().int().describe('Broadcast message ID'),
|
|
1290
|
+
})),
|
|
1291
|
+
handler: async (input, client, userConfig) => {
|
|
1292
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1293
|
+
return client.getBroadcastMessage(input.id, credentials);
|
|
1294
|
+
},
|
|
1295
|
+
};
|
|
1296
|
+
const createBroadcastMessageTool = {
|
|
1297
|
+
name: 'create_broadcast_message',
|
|
1298
|
+
title: 'Create Broadcast Message',
|
|
1299
|
+
description: 'Create a GitLab broadcast message. Requires administrator privileges on the GitLab instance.',
|
|
1300
|
+
requiresAuth: true,
|
|
1301
|
+
requiresWrite: true,
|
|
1302
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },
|
|
1303
|
+
inputSchema: withUserAuth(z.object(BroadcastMessageFields)),
|
|
1304
|
+
handler: async (input, client, userConfig) => {
|
|
1305
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1306
|
+
if (!credentials) {
|
|
1307
|
+
throw new Error('User authentication is required for creating broadcast messages.');
|
|
1308
|
+
}
|
|
1309
|
+
const { userCredentials, ...body } = input;
|
|
1310
|
+
return client.createBroadcastMessage(body, credentials);
|
|
1311
|
+
},
|
|
1312
|
+
};
|
|
1313
|
+
const updateBroadcastMessageTool = {
|
|
1314
|
+
name: 'update_broadcast_message',
|
|
1315
|
+
title: 'Update Broadcast Message',
|
|
1316
|
+
description: 'Update an existing GitLab broadcast message. Requires administrator privileges.',
|
|
1317
|
+
requiresAuth: true,
|
|
1318
|
+
requiresWrite: true,
|
|
1319
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
|
|
1320
|
+
inputSchema: withUserAuth(z.object({
|
|
1321
|
+
id: z.number().int().describe('Broadcast message ID'),
|
|
1322
|
+
message: BroadcastMessageFields.message.optional(),
|
|
1323
|
+
starts_at: BroadcastMessageFields.starts_at,
|
|
1324
|
+
ends_at: BroadcastMessageFields.ends_at,
|
|
1325
|
+
color: BroadcastMessageFields.color,
|
|
1326
|
+
font: BroadcastMessageFields.font,
|
|
1327
|
+
target_access_levels: BroadcastMessageFields.target_access_levels,
|
|
1328
|
+
target_path: BroadcastMessageFields.target_path,
|
|
1329
|
+
broadcast_type: BroadcastMessageFields.broadcast_type,
|
|
1330
|
+
dismissable: BroadcastMessageFields.dismissable,
|
|
1331
|
+
theme: BroadcastMessageFields.theme,
|
|
1332
|
+
})),
|
|
1333
|
+
handler: async (input, client, userConfig) => {
|
|
1334
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1335
|
+
if (!credentials) {
|
|
1336
|
+
throw new Error('User authentication is required for updating broadcast messages.');
|
|
1337
|
+
}
|
|
1338
|
+
const { id, userCredentials, ...body } = input;
|
|
1339
|
+
return client.updateBroadcastMessage(id, body, credentials);
|
|
1340
|
+
},
|
|
1341
|
+
};
|
|
1342
|
+
const deleteBroadcastMessageTool = {
|
|
1343
|
+
name: 'delete_broadcast_message',
|
|
1344
|
+
title: 'Delete Broadcast Message',
|
|
1345
|
+
description: 'Delete a GitLab broadcast message by ID. Requires administrator privileges.',
|
|
1346
|
+
requiresAuth: true,
|
|
1347
|
+
requiresWrite: true,
|
|
1348
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true },
|
|
1349
|
+
inputSchema: withUserAuth(z.object({
|
|
1350
|
+
id: z.number().int().describe('Broadcast message ID'),
|
|
1351
|
+
})),
|
|
1352
|
+
handler: async (input, client, userConfig) => {
|
|
1353
|
+
const credentials = input.userCredentials ? validateUserConfig(input.userCredentials) : userConfig;
|
|
1354
|
+
if (!credentials) {
|
|
1355
|
+
throw new Error('User authentication is required for deleting broadcast messages.');
|
|
1356
|
+
}
|
|
1357
|
+
await client.deleteBroadcastMessage(input.id, credentials);
|
|
1358
|
+
return { id: input.id, deleted: true };
|
|
1359
|
+
},
|
|
1360
|
+
};
|
|
1361
|
+
export const readOnlyTools = [
|
|
1362
|
+
getProjectTool,
|
|
1363
|
+
getIssuesTool,
|
|
1364
|
+
getMergeRequestsTool,
|
|
1365
|
+
executeCustomQueryTool,
|
|
1366
|
+
getAvailableQueriesTools,
|
|
1367
|
+
getMergeRequestPipelinesTool,
|
|
1368
|
+
getPipelineJobsTool,
|
|
1369
|
+
getMergeRequestDiffsTool,
|
|
1370
|
+
getMergeRequestCommitsTool,
|
|
1371
|
+
getNotesTool,
|
|
1372
|
+
listMilestonesTool,
|
|
1373
|
+
listIterationsTool,
|
|
1374
|
+
getTimeTrackingTool,
|
|
1375
|
+
getMergeRequestReviewersTool,
|
|
1376
|
+
getProjectStatisticsTool,
|
|
1377
|
+
listBroadcastMessagesTool,
|
|
1378
|
+
getBroadcastMessageTool,
|
|
1379
|
+
];
|
|
1380
|
+
export const userAuthTools = [
|
|
1381
|
+
getCurrentUserTool,
|
|
1382
|
+
getProjectsTool,
|
|
1383
|
+
];
|
|
1384
|
+
export const writeTools = [
|
|
1385
|
+
createIssueTool,
|
|
1386
|
+
createMergeRequestTool,
|
|
1387
|
+
createNoteTool,
|
|
1388
|
+
managePipelineTool,
|
|
1389
|
+
createBroadcastMessageTool,
|
|
1390
|
+
updateBroadcastMessageTool,
|
|
1391
|
+
deleteBroadcastMessageTool,
|
|
1392
|
+
];
|
|
754
1393
|
export const searchTools = [
|
|
755
1394
|
globalSearchTool,
|
|
756
1395
|
searchProjectsTool,
|
|
@@ -760,8 +1399,10 @@ export const searchTools = [
|
|
|
760
1399
|
getUserMergeRequestsTool,
|
|
761
1400
|
searchUsersTool,
|
|
762
1401
|
searchGroupsTool,
|
|
1402
|
+
searchLabelsTool,
|
|
763
1403
|
browseRepositoryTool,
|
|
764
1404
|
getFileContentTool,
|
|
1405
|
+
listGroupMembersTool,
|
|
765
1406
|
];
|
|
766
1407
|
export const tools = [
|
|
767
1408
|
...readOnlyTools,
|