opencode-gitlab-plugin 2.5.0 → 2.6.1
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/CHANGELOG.md +20 -0
- package/dist/index.js +157 -77
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.cjs +158 -78
- package/dist/mcp-server.cjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [2.6.1](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.6.0...v2.6.1) (2026-08-17)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### 🐛 Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **merge-requests:** resolve MWPS 'checking' race in smartMerge/setAutoMerge ([b9a992f](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/b9a992f6ebec655fba271623bf5fc963cfe16f09))
|
|
11
|
+
* **work-items:** avoid nullability mismatch on widget inputs ([1ae3cd7](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/1ae3cd79627c6cca88955f90af13694472939331))
|
|
12
|
+
|
|
13
|
+
## [2.6.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.5.0...v2.6.0) (2026-05-20)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### ✨ Features
|
|
17
|
+
|
|
18
|
+
* **pagination:** expose pagination info in list MR and issue responses ([1c1722e](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/1c1722e62e0e41f00ab5409057bea1e76ec17a8d))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### ♻️ Code Refactoring
|
|
22
|
+
|
|
23
|
+
* **client:** address review feedback on !23 ([2e94e46](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/2e94e46706807bc8797f10884f71fc56ecf967b9)), closes [23#note_3135645102](https://gitlab.com/vglafirov/23/issues/note_3135645102) [23#note_3135645423](https://gitlab.com/vglafirov/23/issues/note_3135645423) [23#note_3135644923](https://gitlab.com/vglafirov/23/issues/note_3135644923)
|
|
24
|
+
|
|
5
25
|
## [2.5.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.4.0...v2.5.0) (2026-05-20)
|
|
6
26
|
|
|
7
27
|
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,13 @@ var GitLabApiClient = class {
|
|
|
26
26
|
}
|
|
27
27
|
return projectId;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Core HTTP request used by all REST helpers.
|
|
31
|
+
*
|
|
32
|
+
* Returns the raw `Response` after validating the status code so callers can
|
|
33
|
+
* inspect headers (e.g. for pagination) before parsing the body.
|
|
34
|
+
*/
|
|
35
|
+
async rawFetch(method, path2, body) {
|
|
30
36
|
const url = `${this.instanceUrl}/api/v4${path2}`;
|
|
31
37
|
const response = await fetch(url, {
|
|
32
38
|
method,
|
|
@@ -37,12 +43,53 @@ var GitLabApiClient = class {
|
|
|
37
43
|
const errorText = await response.text();
|
|
38
44
|
throw new Error(`GitLab API error ${response.status}: ${errorText}`);
|
|
39
45
|
}
|
|
46
|
+
return response;
|
|
47
|
+
}
|
|
48
|
+
async fetch(method, path2, body) {
|
|
49
|
+
const response = await this.rawFetch(method, path2, body);
|
|
40
50
|
const text = await response.text();
|
|
41
51
|
if (!text) {
|
|
42
52
|
return {};
|
|
43
53
|
}
|
|
44
54
|
return JSON.parse(text);
|
|
45
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse a numeric pagination header, guarding against empty strings.
|
|
58
|
+
*
|
|
59
|
+
* GitLab returns `""` (rather than omitting the header) for `X-Next-Page` and
|
|
60
|
+
* `X-Prev-Page` when there is no next/prev page, and bare `parseInt` without
|
|
61
|
+
* a radix on an empty string yields `NaN`. Treat empty/missing as `undefined`.
|
|
62
|
+
*/
|
|
63
|
+
parsePaginationHeader(response, name) {
|
|
64
|
+
const raw = response.headers.get(name);
|
|
65
|
+
if (raw == null || raw === "") return void 0;
|
|
66
|
+
const value = parseInt(raw, 10);
|
|
67
|
+
return Number.isNaN(value) ? void 0 : value;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Pagination info extracted from GitLab API response headers
|
|
71
|
+
*/
|
|
72
|
+
extractPaginationInfo(response) {
|
|
73
|
+
return {
|
|
74
|
+
total: this.parsePaginationHeader(response, "X-Total"),
|
|
75
|
+
totalPages: this.parsePaginationHeader(response, "X-Total-Pages"),
|
|
76
|
+
page: this.parsePaginationHeader(response, "X-Page"),
|
|
77
|
+
perPage: this.parsePaginationHeader(response, "X-Per-Page"),
|
|
78
|
+
nextPage: this.parsePaginationHeader(response, "X-Next-Page"),
|
|
79
|
+
prevPage: this.parsePaginationHeader(response, "X-Prev-Page")
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fetch with pagination info from response headers.
|
|
84
|
+
* Use this for list endpoints where knowing if more data exists is important.
|
|
85
|
+
*/
|
|
86
|
+
async fetchWithPagination(method, path2, body) {
|
|
87
|
+
const response = await this.rawFetch(method, path2, body);
|
|
88
|
+
const text = await response.text();
|
|
89
|
+
const data = text ? JSON.parse(text) : [];
|
|
90
|
+
const pagination = this.extractPaginationInfo(response);
|
|
91
|
+
return { data, pagination };
|
|
92
|
+
}
|
|
46
93
|
async fetchText(method, path2) {
|
|
47
94
|
const url = `${this.instanceUrl}/api/v4${path2}`;
|
|
48
95
|
const response = await fetch(url, {
|
|
@@ -294,7 +341,7 @@ var RESOLVE_DISCUSSION_MUTATION = `
|
|
|
294
341
|
}
|
|
295
342
|
}
|
|
296
343
|
`;
|
|
297
|
-
var MergeRequestsClient = class extends GitLabApiClient {
|
|
344
|
+
var MergeRequestsClient = class _MergeRequestsClient extends GitLabApiClient {
|
|
298
345
|
async getMergeRequest(projectId, mrIid, includeChanges) {
|
|
299
346
|
const encodedProject = this.encodeProjectId(projectId);
|
|
300
347
|
let path2 = `/projects/${encodedProject}/merge_requests/${mrIid}`;
|
|
@@ -306,6 +353,7 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
306
353
|
async listMergeRequests(options) {
|
|
307
354
|
const params = new URLSearchParams();
|
|
308
355
|
params.set("per_page", String(options.limit || 20));
|
|
356
|
+
if (options.page) params.set("page", String(options.page));
|
|
309
357
|
if (options.state) params.set("state", options.state);
|
|
310
358
|
if (options.scope) params.set("scope", options.scope);
|
|
311
359
|
if (options.search) params.set("search", options.search);
|
|
@@ -317,7 +365,7 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
317
365
|
} else {
|
|
318
366
|
path2 = `/merge_requests?${params}`;
|
|
319
367
|
}
|
|
320
|
-
return this.
|
|
368
|
+
return this.fetchWithPagination("GET", path2);
|
|
321
369
|
}
|
|
322
370
|
async getMrChanges(projectId, mrIid) {
|
|
323
371
|
const encodedProject = this.encodeProjectId(projectId);
|
|
@@ -515,20 +563,48 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
515
563
|
* Set auto-merge (MWPS) on a merge request using GraphQL API
|
|
516
564
|
* Uses the mergeRequestAccept mutation with a merge strategy
|
|
517
565
|
*/
|
|
518
|
-
async setAutoMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS") {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
566
|
+
async setAutoMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS", retries = 3) {
|
|
567
|
+
let lastError = null;
|
|
568
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
569
|
+
const result = await this.fetchGraphQL(SET_AUTO_MERGE_MUTATION, {
|
|
570
|
+
projectPath: projectId,
|
|
571
|
+
iid: String(mrIid),
|
|
572
|
+
sha,
|
|
573
|
+
strategy
|
|
574
|
+
});
|
|
575
|
+
if (result.mergeRequestAccept.errors.length > 0) {
|
|
576
|
+
const message = result.mergeRequestAccept.errors.join(", ");
|
|
577
|
+
lastError = new Error(`Failed to set auto-merge: ${message}`);
|
|
578
|
+
if (attempt < retries && _MergeRequestsClient.isTransientMergeError(message)) {
|
|
579
|
+
await _MergeRequestsClient.delay(750 * (attempt + 1));
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
throw lastError;
|
|
583
|
+
}
|
|
584
|
+
if (!result.mergeRequestAccept.mergeRequest) {
|
|
585
|
+
lastError = new Error("Failed to set auto-merge: No merge request returned");
|
|
586
|
+
if (attempt < retries) {
|
|
587
|
+
await _MergeRequestsClient.delay(750 * (attempt + 1));
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
throw lastError;
|
|
591
|
+
}
|
|
592
|
+
return result.mergeRequestAccept.mergeRequest;
|
|
530
593
|
}
|
|
531
|
-
|
|
594
|
+
throw lastError ?? new Error("Failed to set auto-merge: exhausted retries");
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* True for GitLab merge errors that are transient because mergeability is
|
|
598
|
+
* still being (re)computed. Such errors clear on their own within a second
|
|
599
|
+
* or two, so callers should retry rather than treat them as real failures.
|
|
600
|
+
*/
|
|
601
|
+
static isTransientMergeError(message) {
|
|
602
|
+
const m = message.toLowerCase();
|
|
603
|
+
return m.includes("being checked") || m.includes("being recomputed") || m.includes("cannot be merged") || m.includes("not mergeable") || m.includes("merge failed") || m.includes("try again");
|
|
604
|
+
}
|
|
605
|
+
/** Small awaitable delay helper (ms). */
|
|
606
|
+
static delay(ms) {
|
|
607
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
532
608
|
}
|
|
533
609
|
/**
|
|
534
610
|
* Approve a merge request
|
|
@@ -576,7 +652,13 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
576
652
|
* Returns detailed context about what happened
|
|
577
653
|
*/
|
|
578
654
|
async smartMerge(projectId, mrIid, sha, strategy = "MERGE_WHEN_CHECKS_PASS") {
|
|
579
|
-
|
|
655
|
+
let mr = await this.getMergeRequest(projectId, mrIid);
|
|
656
|
+
if (mr.detailed_merge_status === "checking") {
|
|
657
|
+
for (let attempt = 0; attempt < 5 && mr.detailed_merge_status === "checking"; attempt++) {
|
|
658
|
+
await _MergeRequestsClient.delay(1e3 * (attempt + 1));
|
|
659
|
+
mr = await this.getMergeRequest(projectId, mrIid);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
580
662
|
const context = {
|
|
581
663
|
state: mr.state,
|
|
582
664
|
detailedMergeStatus: mr.detailed_merge_status,
|
|
@@ -615,14 +697,13 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
615
697
|
};
|
|
616
698
|
}
|
|
617
699
|
}
|
|
618
|
-
const autoMergeStatuses = ["ci_still_running", "not_approved"
|
|
700
|
+
const autoMergeStatuses = ["ci_still_running", "not_approved"];
|
|
619
701
|
if (autoMergeStatuses.includes(mr.detailed_merge_status)) {
|
|
620
702
|
try {
|
|
621
703
|
const result = await this.setAutoMerge(projectId, mrIid, sha, strategy);
|
|
622
704
|
const autoMergeMessages = {
|
|
623
705
|
ci_still_running: "pipeline passes",
|
|
624
|
-
not_approved: "approved"
|
|
625
|
-
checking: "checks complete"
|
|
706
|
+
not_approved: "approved"
|
|
626
707
|
};
|
|
627
708
|
return {
|
|
628
709
|
action: "auto_merge_enabled",
|
|
@@ -729,6 +810,7 @@ var IssuesClient = class extends GitLabApiClient {
|
|
|
729
810
|
async listIssues(options) {
|
|
730
811
|
const params = new URLSearchParams();
|
|
731
812
|
params.set("per_page", String(options.limit || 20));
|
|
813
|
+
if (options.page) params.set("page", String(options.page));
|
|
732
814
|
if (options.state) params.set("state", options.state);
|
|
733
815
|
if (options.scope) params.set("scope", options.scope);
|
|
734
816
|
if (options.search) params.set("search", options.search);
|
|
@@ -741,7 +823,7 @@ var IssuesClient = class extends GitLabApiClient {
|
|
|
741
823
|
} else {
|
|
742
824
|
path2 = `/issues?${params}`;
|
|
743
825
|
}
|
|
744
|
-
return this.
|
|
826
|
+
return this.fetchWithPagination("GET", path2);
|
|
745
827
|
}
|
|
746
828
|
/**
|
|
747
829
|
* List notes on an issue using GraphQL API with pagination support
|
|
@@ -980,22 +1062,8 @@ var GET_WORK_ITEM_NOTES_QUERY = `
|
|
|
980
1062
|
}
|
|
981
1063
|
`;
|
|
982
1064
|
var CREATE_WORK_ITEM_MUTATION = `
|
|
983
|
-
mutation createWorkItem(
|
|
984
|
-
|
|
985
|
-
$title: String!
|
|
986
|
-
$workItemTypeId: WorkItemsTypeID!
|
|
987
|
-
$description: String
|
|
988
|
-
$labelIds: [LabelID!]
|
|
989
|
-
$assigneeIds: [UserID!]
|
|
990
|
-
) {
|
|
991
|
-
workItemCreate(input: {
|
|
992
|
-
projectPath: $projectPath
|
|
993
|
-
title: $title
|
|
994
|
-
workItemTypeId: $workItemTypeId
|
|
995
|
-
descriptionWidget: { description: $description }
|
|
996
|
-
labelsWidget: { labelIds: $labelIds }
|
|
997
|
-
assigneesWidget: { assigneeIds: $assigneeIds }
|
|
998
|
-
}) {
|
|
1065
|
+
mutation createWorkItem($input: WorkItemCreateInput!) {
|
|
1066
|
+
workItemCreate(input: $input) {
|
|
999
1067
|
workItem {
|
|
1000
1068
|
${WORK_ITEM_FIELDS}
|
|
1001
1069
|
}
|
|
@@ -1004,22 +1072,8 @@ var CREATE_WORK_ITEM_MUTATION = `
|
|
|
1004
1072
|
}
|
|
1005
1073
|
`;
|
|
1006
1074
|
var UPDATE_WORK_ITEM_MUTATION = `
|
|
1007
|
-
mutation updateWorkItem(
|
|
1008
|
-
|
|
1009
|
-
$title: String
|
|
1010
|
-
$description: String
|
|
1011
|
-
$stateEvent: WorkItemStateEvent
|
|
1012
|
-
$labelIds: [LabelID!]
|
|
1013
|
-
$assigneeIds: [UserID!]
|
|
1014
|
-
) {
|
|
1015
|
-
workItemUpdate(input: {
|
|
1016
|
-
id: $id
|
|
1017
|
-
title: $title
|
|
1018
|
-
descriptionWidget: { description: $description }
|
|
1019
|
-
stateEvent: $stateEvent
|
|
1020
|
-
labelsWidget: { labelIds: $labelIds }
|
|
1021
|
-
assigneesWidget: { assigneeIds: $assigneeIds }
|
|
1022
|
-
}) {
|
|
1075
|
+
mutation updateWorkItem($input: WorkItemUpdateInput!) {
|
|
1076
|
+
workItemUpdate(input: $input) {
|
|
1023
1077
|
workItem {
|
|
1024
1078
|
${WORK_ITEM_FIELDS}
|
|
1025
1079
|
}
|
|
@@ -1113,14 +1167,21 @@ var WorkItemsClient = class extends GitLabApiClient {
|
|
|
1113
1167
|
async createWorkItem(projectId, options) {
|
|
1114
1168
|
const labelIds = options.labels?.map((l) => toGid("Label", l));
|
|
1115
1169
|
const assigneeIds = options.assignee_ids?.map((id) => toGid("User", id));
|
|
1116
|
-
const
|
|
1170
|
+
const input = {
|
|
1117
1171
|
projectPath: projectId,
|
|
1118
1172
|
title: options.title,
|
|
1119
|
-
workItemTypeId: toGid("WorkItems::Type", options.work_item_type_id)
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
}
|
|
1173
|
+
workItemTypeId: toGid("WorkItems::Type", options.work_item_type_id)
|
|
1174
|
+
};
|
|
1175
|
+
if (options.description !== void 0) {
|
|
1176
|
+
input.descriptionWidget = { description: options.description };
|
|
1177
|
+
}
|
|
1178
|
+
if (labelIds?.length) {
|
|
1179
|
+
input.labelsWidget = { labelIds };
|
|
1180
|
+
}
|
|
1181
|
+
if (assigneeIds?.length) {
|
|
1182
|
+
input.assigneesWidget = { assigneeIds };
|
|
1183
|
+
}
|
|
1184
|
+
const result = await this.fetchGraphQL(CREATE_WORK_ITEM_MUTATION, { input });
|
|
1124
1185
|
if (result.workItemCreate.errors.length > 0) {
|
|
1125
1186
|
throw new Error(`Failed to create work item: ${result.workItemCreate.errors.join(", ")}`);
|
|
1126
1187
|
}
|
|
@@ -1138,14 +1199,23 @@ var WorkItemsClient = class extends GitLabApiClient {
|
|
|
1138
1199
|
const stateEvent = options.state_event ? options.state_event === "close" ? "CLOSE" : "REOPEN" : void 0;
|
|
1139
1200
|
const labelIds = options.labels?.map((l) => toGid("Label", l));
|
|
1140
1201
|
const assigneeIds = options.assignee_ids?.map((id) => toGid("User", id));
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
title
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1202
|
+
const input = { id: gid };
|
|
1203
|
+
if (options.title !== void 0) {
|
|
1204
|
+
input.title = options.title;
|
|
1205
|
+
}
|
|
1206
|
+
if (options.description !== void 0) {
|
|
1207
|
+
input.descriptionWidget = { description: options.description };
|
|
1208
|
+
}
|
|
1209
|
+
if (stateEvent !== void 0) {
|
|
1210
|
+
input.stateEvent = stateEvent;
|
|
1211
|
+
}
|
|
1212
|
+
if (labelIds?.length) {
|
|
1213
|
+
input.labelsWidget = { addLabelIds: labelIds };
|
|
1214
|
+
}
|
|
1215
|
+
if (assigneeIds?.length) {
|
|
1216
|
+
input.assigneesWidget = { assigneeIds };
|
|
1217
|
+
}
|
|
1218
|
+
const result = await this.fetchGraphQL(UPDATE_WORK_ITEM_MUTATION, { input });
|
|
1149
1219
|
if (result.workItemUpdate.errors.length > 0) {
|
|
1150
1220
|
throw new Error(`Failed to update work item: ${result.workItemUpdate.errors.join(", ")}`);
|
|
1151
1221
|
}
|
|
@@ -2647,26 +2717,31 @@ Returns: title, description, state, author, assignees, reviewers, labels, diff s
|
|
|
2647
2717
|
}),
|
|
2648
2718
|
gitlab_list_merge_requests: tool({
|
|
2649
2719
|
description: `List merge requests for a project or search globally.
|
|
2650
|
-
Can filter by state (opened, closed, merged, all), scope (assigned_to_me, created_by_me), and labels
|
|
2720
|
+
Can filter by state (opened, closed, merged, all), scope (assigned_to_me, created_by_me), and labels.
|
|
2721
|
+
|
|
2722
|
+
IMPORTANT: Response includes pagination info. If nextPage exists, there are more results.
|
|
2723
|
+
Always paginate until you get an empty response or no nextPage to ensure you have all data.`,
|
|
2651
2724
|
args: {
|
|
2652
2725
|
project_id: z.string().optional().describe("The project ID or path. If not provided, searches globally."),
|
|
2653
2726
|
state: z.enum(["opened", "closed", "merged", "all"]).optional().describe("Filter by MR state (default: opened)"),
|
|
2654
2727
|
scope: z.enum(["assigned_to_me", "created_by_me", "all"]).optional().describe("Filter by scope"),
|
|
2655
2728
|
search: z.string().optional().describe("Search MRs by title or description"),
|
|
2656
2729
|
labels: z.string().optional().describe("Comma-separated list of labels to filter by"),
|
|
2657
|
-
limit: z.number().optional().describe("Maximum number of results (default: 20)")
|
|
2730
|
+
limit: z.number().optional().describe("Maximum number of results per page (default: 20)"),
|
|
2731
|
+
page: z.number().optional().describe("Page number for pagination (default: 1)")
|
|
2658
2732
|
},
|
|
2659
2733
|
execute: async (args, _ctx) => {
|
|
2660
2734
|
const client = getGitLabClient();
|
|
2661
|
-
const
|
|
2735
|
+
const result = await client.listMergeRequests({
|
|
2662
2736
|
projectId: args.project_id,
|
|
2663
2737
|
state: args.state,
|
|
2664
2738
|
scope: args.scope,
|
|
2665
2739
|
search: args.search,
|
|
2666
2740
|
labels: args.labels,
|
|
2667
|
-
limit: args.limit
|
|
2741
|
+
limit: args.limit,
|
|
2742
|
+
page: args.page
|
|
2668
2743
|
});
|
|
2669
|
-
return JSON.stringify(
|
|
2744
|
+
return JSON.stringify(result, null, 2);
|
|
2670
2745
|
}
|
|
2671
2746
|
}),
|
|
2672
2747
|
gitlab_get_mr_changes: tool({
|
|
@@ -2998,7 +3073,10 @@ Returns: title, description, state, author, assignees, labels, milestone, weight
|
|
|
2998
3073
|
}),
|
|
2999
3074
|
gitlab_list_issues: tool2({
|
|
3000
3075
|
description: `List issues for a project or search globally.
|
|
3001
|
-
Can filter by state, labels, assignee, milestone
|
|
3076
|
+
Can filter by state, labels, assignee, milestone.
|
|
3077
|
+
|
|
3078
|
+
IMPORTANT: Response includes pagination info. If nextPage exists, there are more results.
|
|
3079
|
+
Always paginate until you get an empty response or no nextPage to ensure you have all data.`,
|
|
3002
3080
|
args: {
|
|
3003
3081
|
project_id: z2.string().optional().describe("The project ID or path. If not provided, searches globally."),
|
|
3004
3082
|
state: z2.enum(["opened", "closed", "all"]).optional().describe("Filter by issue state (default: opened)"),
|
|
@@ -3006,20 +3084,22 @@ Can filter by state, labels, assignee, milestone.`,
|
|
|
3006
3084
|
search: z2.string().optional().describe("Search issues by title or description"),
|
|
3007
3085
|
labels: z2.string().optional().describe("Comma-separated list of labels to filter by"),
|
|
3008
3086
|
milestone: z2.string().optional().describe("Filter by milestone title"),
|
|
3009
|
-
limit: z2.number().optional().describe("Maximum number of results (default: 20)")
|
|
3087
|
+
limit: z2.number().optional().describe("Maximum number of results per page (default: 20)"),
|
|
3088
|
+
page: z2.number().optional().describe("Page number for pagination (default: 1)")
|
|
3010
3089
|
},
|
|
3011
3090
|
execute: async (args, _ctx) => {
|
|
3012
3091
|
const client = getGitLabClient();
|
|
3013
|
-
const
|
|
3092
|
+
const result = await client.listIssues({
|
|
3014
3093
|
projectId: args.project_id,
|
|
3015
3094
|
state: args.state,
|
|
3016
3095
|
scope: args.scope,
|
|
3017
3096
|
search: args.search,
|
|
3018
3097
|
labels: args.labels,
|
|
3019
3098
|
milestone: args.milestone,
|
|
3020
|
-
limit: args.limit
|
|
3099
|
+
limit: args.limit,
|
|
3100
|
+
page: args.page
|
|
3021
3101
|
});
|
|
3022
|
-
return JSON.stringify(
|
|
3102
|
+
return JSON.stringify(result, null, 2);
|
|
3023
3103
|
}
|
|
3024
3104
|
})
|
|
3025
3105
|
};
|