@zereight/mcp-gitlab 2.1.11 → 2.1.13
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 +152 -150
- package/build/index.js +205 -9
- package/build/schemas.js +124 -0
- package/build/test/test-get-file-blame.js +145 -0
- package/build/test/test-geteffectiveprojectid.js +230 -6
- package/build/test/test-issue-description-patch.js +256 -0
- package/build/test/test-token-optimizations.js +1 -1
- package/build/test/test-toolset-filtering.js +7 -3
- package/build/test/utils/mock-gitlab-server.js +46 -0
- package/build/tools/registry.js +52 -3
- package/build/utils/patch-helper.js +145 -0
- package/package.json +3 -2
package/build/tools/registry.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
2
|
import { toJSONSchema } from "../utils/schema.js";
|
|
3
3
|
import { USE_GITLAB_WIKI, USE_MILESTONE, USE_PIPELINE, } from "../config.js";
|
|
4
|
-
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
|
|
4
|
+
import { ApproveMergeRequestSchema, BulkPublishDraftNotesSchema, CancelPipelineJobSchema, CancelPipelineSchema, ConvertWorkItemTypeSchema, CreateBranchSchema, CreateDraftNoteSchema, CreateGroupSchema, CreateGroupWikiPageSchema, CreateIssueLinkSchema, CreateIssueNoteSchema, CreateIssueSchema, CreateIssueEmojiReactionSchema, CreateIssueNoteEmojiReactionSchema, ListIssueEmojiReactionsSchema, ListIssueNoteEmojiReactionsSchema, CreateLabelSchema, MarkAllTodosDoneSchema, ListTodosSchema, MarkTodoDoneSchema, CreateMergeRequestDiscussionNoteSchema, CreateMergeRequestEmojiReactionSchema, ListMergeRequestEmojiReactionsSchema, ListMergeRequestNoteEmojiReactionsSchema, CreateMergeRequestNoteSchema, CreateMergeRequestNoteEmojiReactionSchema, CreateMergeRequestSchema, CreateMergeRequestThreadSchema, CreateNoteSchema, CreateCommitStatusSchema, CreateOrUpdateFileSchema, CreatePipelineSchema, CreateProjectMilestoneSchema, CreateReleaseEvidenceSchema, CreateReleaseSchema, CreateRepositorySchema, CreateTagSchema, CreateTimelineEventSchema, CreateWikiPageSchema, CreateWorkItemNoteSchema, CreateWorkItemEmojiReactionSchema, CreateWorkItemNoteEmojiReactionSchema, ListWorkItemEmojiReactionsSchema, ListWorkItemNoteEmojiReactionsSchema, CreateWorkItemSchema, DeleteBranchSchema, DeleteDraftNoteSchema, DeleteGroupWikiPageSchema, DeleteIssueLinkSchema, DeleteIssueSchema, DeleteIssueEmojiReactionSchema, DeleteIssueNoteEmojiReactionSchema, DeleteLabelSchema, DeleteMergeRequestDiscussionNoteSchema, DeleteMergeRequestNoteSchema, DeleteMergeRequestEmojiReactionSchema, DeleteMergeRequestNoteEmojiReactionSchema, DeleteProjectMilestoneSchema, DeleteReleaseSchema, DeleteTagSchema, DeleteWikiPageSchema, DeleteWorkItemEmojiReactionSchema, DeleteWorkItemNoteEmojiReactionSchema, DownloadAttachmentSchema, DownloadJobArtifactsSchema, DownloadReleaseAssetSchema, EditProjectMilestoneSchema, ExecuteGraphQLSchema, ForkRepositorySchema, HealthCheckSchema, GetBranchSchema, GetBranchDiffsSchema, GetCommitDiffSchema, GetCommitSchema, GetFileBlameSchema, GetDeploymentSchema, GetDraftNoteSchema, GetEnvironmentSchema, GetFileContentsSchema, GetGroupWikiPageSchema, GetIssueLinkSchema, GetIssueSchema, GetJobArtifactFileSchema, GetLabelSchema, GetMergeRequestApprovalStateSchema, GetMergeRequestConflictsSchema, GetMergeRequestDiffsSchema, GetMergeRequestFileDiffSchema, GetMergeRequestNoteSchema, GetMergeRequestNotesSchema, GetMergeRequestSchema, GetMergeRequestVersionSchema, GetMilestoneBurndownEventsSchema, GetMilestoneIssuesSchema, GetMilestoneMergeRequestsSchema, GetNamespaceSchema, GetPipelineJobOutputSchema, GetPipelineSchema, GetProjectEventsSchema, GetProjectMilestoneSchema, GetProjectSchema, GetReleaseSchema, GetRepositoryTreeSchema, GetTagSchema, GetTagSignatureSchema, GetTimelineEventsSchema, GetUsersSchema, GetUserSchema, WhoAmISchema, GetWebhookEventSchema, GetWikiPageSchema, GetWorkItemSchema, ListBranchesSchema, ListCommitsSchema, ListCommitStatusesSchema, ListCustomFieldDefinitionsSchema, ListDeploymentsSchema, ListDraftNotesSchema, ListEnvironmentsSchema, ListEventsSchema, ListGroupIterationsSchema, ListGroupProjectsSchema, ListGroupWikiPagesSchema, ListIssueDiscussionsSchema, ListIssueLinksSchema, ListIssuesSchema, ListJobArtifactsSchema, ListLabelsSchema, ListMergeRequestChangedFilesSchema, ListMergeRequestDiffsSchema, ListMergeRequestDiscussionsSchema, ListMergeRequestPipelinesSchema, ListMergeRequestVersionsSchema, ListMergeRequestsSchema, ListNamespacesSchema, ListPipelineJobsSchema, ListPipelineTriggerJobsSchema, ValidateCiLintSchema, ValidateProjectCiLintSchema, ListPipelinesSchema, ListProjectMembersSchema, ListProjectMilestonesSchema, ListProjectsSchema, ListReleasesSchema, ListTagsSchema, ListWebhookEventsSchema, ListWebhooksSchema, ListWikiPagesSchema, ListWorkItemNotesSchema, ListWorkItemStatusesSchema, ListWorkItemsSchema, MarkdownUploadSchema, MergeMergeRequestSchema, MoveWorkItemSchema, MyIssuesSchema, PlayPipelineJobSchema, PromoteProjectMilestoneSchema, PublishDraftNoteSchema, PushFilesSchema, ResolveMergeRequestThreadSchema, RetryPipelineJobSchema, RetryPipelineSchema, SearchCodeSchema, SearchGroupCodeSchema, SearchProjectCodeSchema, SearchRepositoriesSchema, UnapproveMergeRequestSchema, UpdateDraftNoteSchema, UpdateGroupWikiPageSchema, UpdateIssueNoteSchema, UpdateIssueSchema, UpdateIssueDescriptionPatchSchema, UpdateLabelSchema, UpdateMergeRequestDiscussionNoteSchema, UpdateMergeRequestNoteSchema, UpdateMergeRequestSchema, UpdateReleaseSchema, UpdateWikiPageSchema, UpdateWorkItemSchema, VerifyNamespaceSchema, } from "../schemas.js";
|
|
5
5
|
// Define all available tools
|
|
6
6
|
export const allTools = [
|
|
7
7
|
{
|
|
@@ -54,6 +54,11 @@ export const allTools = [
|
|
|
54
54
|
description: "Create a new GitLab project",
|
|
55
55
|
inputSchema: toJSONSchema(CreateRepositorySchema),
|
|
56
56
|
},
|
|
57
|
+
{
|
|
58
|
+
name: "create_group",
|
|
59
|
+
description: "Create new group or subgroup",
|
|
60
|
+
inputSchema: toJSONSchema(CreateGroupSchema),
|
|
61
|
+
},
|
|
57
62
|
{
|
|
58
63
|
name: "get_file_contents",
|
|
59
64
|
description: "Get contents of a file or directory from a GitLab project",
|
|
@@ -84,6 +89,21 @@ export const allTools = [
|
|
|
84
89
|
description: "Create a new branch",
|
|
85
90
|
inputSchema: toJSONSchema(CreateBranchSchema),
|
|
86
91
|
},
|
|
92
|
+
{
|
|
93
|
+
name: "get_branch",
|
|
94
|
+
description: "Get branch details (commit, protection status)",
|
|
95
|
+
inputSchema: toJSONSchema(GetBranchSchema),
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "list_branches",
|
|
99
|
+
description: "List branches in project with search filter",
|
|
100
|
+
inputSchema: toJSONSchema(ListBranchesSchema),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "delete_branch",
|
|
104
|
+
description: "Delete branch from project",
|
|
105
|
+
inputSchema: toJSONSchema(DeleteBranchSchema),
|
|
106
|
+
},
|
|
87
107
|
{
|
|
88
108
|
name: "get_merge_request",
|
|
89
109
|
description: "Get details of a merge request (mergeRequestIid or branchName required)",
|
|
@@ -316,6 +336,13 @@ export const allTools = [
|
|
|
316
336
|
description: "Update an issue",
|
|
317
337
|
inputSchema: toJSONSchema(UpdateIssueSchema),
|
|
318
338
|
},
|
|
339
|
+
{
|
|
340
|
+
name: "update_issue_description_patch",
|
|
341
|
+
description: "Apply a patch (search/replace or unified diff) to an issue description. " +
|
|
342
|
+
"Reduces token usage by allowing small changes without sending the full description. " +
|
|
343
|
+
"Supports dry_run to preview changes and create_note to summarize updates.",
|
|
344
|
+
inputSchema: toJSONSchema(UpdateIssueDescriptionPatchSchema),
|
|
345
|
+
},
|
|
319
346
|
{
|
|
320
347
|
name: "delete_issue",
|
|
321
348
|
description: "Delete an issue",
|
|
@@ -363,12 +390,12 @@ export const allTools = [
|
|
|
363
390
|
},
|
|
364
391
|
{
|
|
365
392
|
name: "list_namespaces",
|
|
366
|
-
description: "List all namespaces available to the current user",
|
|
393
|
+
description: "List all namespaces (users and groups) available to the current user. Filter by kind='group' for groups only.",
|
|
367
394
|
inputSchema: toJSONSchema(ListNamespacesSchema),
|
|
368
395
|
},
|
|
369
396
|
{
|
|
370
397
|
name: "get_namespace",
|
|
371
|
-
description: "Get details of a namespace by ID or path",
|
|
398
|
+
description: "Get details of a namespace (user or group) by ID or path. Groups are namespaces with kind='group'.",
|
|
372
399
|
inputSchema: toJSONSchema(GetNamespaceSchema),
|
|
373
400
|
},
|
|
374
401
|
{
|
|
@@ -661,6 +688,11 @@ export const allTools = [
|
|
|
661
688
|
description: "Get changes/diffs of a specific commit",
|
|
662
689
|
inputSchema: toJSONSchema(GetCommitDiffSchema),
|
|
663
690
|
},
|
|
691
|
+
{
|
|
692
|
+
name: "get_file_blame",
|
|
693
|
+
description: "Get git blame for a file at a given ref. Each entry maps a contiguous range of source lines to the commit that last changed them (id, author, authored_date, message). Use range_start/range_end to limit blame to specific lines.",
|
|
694
|
+
inputSchema: toJSONSchema(GetFileBlameSchema),
|
|
695
|
+
},
|
|
664
696
|
{
|
|
665
697
|
name: "list_commit_statuses",
|
|
666
698
|
description: "List statuses for a commit",
|
|
@@ -916,6 +948,8 @@ export const readOnlyTools = new Set([
|
|
|
916
948
|
"get_merge_request_file_diff",
|
|
917
949
|
"list_merge_request_versions",
|
|
918
950
|
"get_merge_request_version",
|
|
951
|
+
"get_branch",
|
|
952
|
+
"list_branches",
|
|
919
953
|
"get_branch_diffs",
|
|
920
954
|
"list_merge_request_pipelines",
|
|
921
955
|
"get_merge_request_note",
|
|
@@ -971,6 +1005,7 @@ export const readOnlyTools = new Set([
|
|
|
971
1005
|
"list_commits",
|
|
972
1006
|
"get_commit",
|
|
973
1007
|
"get_commit_diff",
|
|
1008
|
+
"get_file_blame",
|
|
974
1009
|
"list_commit_statuses",
|
|
975
1010
|
"list_group_iterations",
|
|
976
1011
|
"get_group_iteration",
|
|
@@ -1020,8 +1055,10 @@ export const destructiveTools = new Set([
|
|
|
1020
1055
|
"delete_issue_note_emoji_reaction",
|
|
1021
1056
|
"delete_work_item_emoji_reaction",
|
|
1022
1057
|
"delete_work_item_note_emoji_reaction",
|
|
1058
|
+
"delete_branch",
|
|
1023
1059
|
"merge_merge_request",
|
|
1024
1060
|
"push_files",
|
|
1061
|
+
"delete_branch",
|
|
1025
1062
|
]);
|
|
1026
1063
|
// Define which tools are related to wiki and can be toggled by USE_GITLAB_WIKI
|
|
1027
1064
|
export const wikiToolNames = new Set([
|
|
@@ -1082,6 +1119,8 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1082
1119
|
"approve_merge_request",
|
|
1083
1120
|
"unapprove_merge_request",
|
|
1084
1121
|
"get_merge_request_approval_state",
|
|
1122
|
+
"get_branch",
|
|
1123
|
+
"list_branches",
|
|
1085
1124
|
"get_merge_request_conflicts",
|
|
1086
1125
|
"list_merge_request_pipelines",
|
|
1087
1126
|
"get_merge_request",
|
|
@@ -1130,6 +1169,7 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1130
1169
|
"my_issues",
|
|
1131
1170
|
"get_issue",
|
|
1132
1171
|
"update_issue",
|
|
1172
|
+
"update_issue_description_patch",
|
|
1133
1173
|
"delete_issue",
|
|
1134
1174
|
"list_todos",
|
|
1135
1175
|
"mark_todo_done",
|
|
@@ -1168,9 +1208,13 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1168
1208
|
isDefault: true,
|
|
1169
1209
|
tools: new Set([
|
|
1170
1210
|
"create_branch",
|
|
1211
|
+
"get_branch",
|
|
1212
|
+
"list_branches",
|
|
1213
|
+
"delete_branch",
|
|
1171
1214
|
"list_commits",
|
|
1172
1215
|
"get_commit",
|
|
1173
1216
|
"get_commit_diff",
|
|
1217
|
+
"get_file_blame",
|
|
1174
1218
|
"list_commit_statuses",
|
|
1175
1219
|
"create_commit_status",
|
|
1176
1220
|
]),
|
|
@@ -1206,6 +1250,11 @@ export const TOOLSET_DEFINITIONS = [
|
|
|
1206
1250
|
isDefault: true,
|
|
1207
1251
|
tools: new Set(["validate_ci_lint", "validate_project_ci_lint"]),
|
|
1208
1252
|
},
|
|
1253
|
+
{
|
|
1254
|
+
id: "groups",
|
|
1255
|
+
isDefault: true,
|
|
1256
|
+
tools: new Set(["create_group"]),
|
|
1257
|
+
},
|
|
1209
1258
|
{
|
|
1210
1259
|
id: "pipelines",
|
|
1211
1260
|
isDefault: false,
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patch helper for issue description updates.
|
|
3
|
+
* Supports two patch formats:
|
|
4
|
+
* - search_replace: exact text search/replace blocks
|
|
5
|
+
* - unified_diff: standard unified diff via the `diff` library
|
|
6
|
+
*/
|
|
7
|
+
import { applyPatch, createTwoFilesPatch, parsePatch } from "diff";
|
|
8
|
+
/**
|
|
9
|
+
* Parse a search/replace patch string into blocks.
|
|
10
|
+
* Format:
|
|
11
|
+
* <<<<<<< SEARCH
|
|
12
|
+
* text to find
|
|
13
|
+
* =======
|
|
14
|
+
* text to replace with
|
|
15
|
+
* >>>>>>> REPLACE
|
|
16
|
+
*
|
|
17
|
+
* Supports multiple blocks.
|
|
18
|
+
*/
|
|
19
|
+
export function parseSearchReplaceBlocks(patch) {
|
|
20
|
+
const blocks = [];
|
|
21
|
+
// Match SEARCH...REPLACE blocks (greedy multiline)
|
|
22
|
+
const regex = /<<<<<<< SEARCH[^\S\n]*\n([\s\S]*?)=======[^\S\n]*\n([\s\S]*?)>>>>>>> REPLACE[^\S\n]*/g;
|
|
23
|
+
let match;
|
|
24
|
+
while ((match = regex.exec(patch)) !== null) {
|
|
25
|
+
let search = match[1];
|
|
26
|
+
let replace = match[2];
|
|
27
|
+
// Trim trailing newline from each side (the \n before ======= and before >>>>>>>)
|
|
28
|
+
if (search.endsWith("\n"))
|
|
29
|
+
search = search.slice(0, -1);
|
|
30
|
+
if (replace.endsWith("\n"))
|
|
31
|
+
replace = replace.slice(0, -1);
|
|
32
|
+
blocks.push({ search, replace });
|
|
33
|
+
}
|
|
34
|
+
// Detect malformed blocks: check if SEARCH/REPLACE markers exist but weren't captured
|
|
35
|
+
const searchMarkers = (patch.match(/<<<<<<<\s+SEARCH/g) || []).length;
|
|
36
|
+
const replaceMarkers = (patch.match(/>>>>>>>\s+REPLACE/g) || []).length;
|
|
37
|
+
if (searchMarkers !== blocks.length || replaceMarkers !== blocks.length) {
|
|
38
|
+
throw new Error(`Found ${searchMarkers} SEARCH marker(s) and ${replaceMarkers} REPLACE marker(s), ` +
|
|
39
|
+
`but only parsed ${blocks.length} valid block(s). ` +
|
|
40
|
+
"Some blocks may be malformed (e.g. missing ======= or >>>>>>> REPLACE). " +
|
|
41
|
+
"Each block must follow the exact format: <<<<<<< SEARCH\ntext\n=======\nnew text\n>>>>>>> REPLACE");
|
|
42
|
+
}
|
|
43
|
+
return blocks;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Apply search/replace blocks to source text.
|
|
47
|
+
*
|
|
48
|
+
* @param source - Current text
|
|
49
|
+
* @param blocks - Search/replace blocks
|
|
50
|
+
* @param allowMultiple - If true, replace all occurrences; if false, fail on duplicate match
|
|
51
|
+
* @returns PatchResult or throws on error
|
|
52
|
+
*/
|
|
53
|
+
export function applySearchReplace(source, blocks, allowMultiple = false) {
|
|
54
|
+
let current = source;
|
|
55
|
+
let totalChanges = 0;
|
|
56
|
+
const changeLines = [];
|
|
57
|
+
for (const block of blocks) {
|
|
58
|
+
// Empty SEARCH body would produce an empty regex that corrupts the description
|
|
59
|
+
if (block.search.length === 0) {
|
|
60
|
+
throw new Error("Empty SEARCH block is not allowed. " +
|
|
61
|
+
"Each SEARCH block must contain the text to find. " +
|
|
62
|
+
"Block:\n---\n" + block.replace.slice(0, 200) + "\n---");
|
|
63
|
+
}
|
|
64
|
+
// Count occurrences
|
|
65
|
+
const escapedSearch = escapeRegex(block.search);
|
|
66
|
+
const regex = new RegExp(escapedSearch, "g");
|
|
67
|
+
const occurrences = current.match(regex);
|
|
68
|
+
if (!occurrences || occurrences.length === 0) {
|
|
69
|
+
throw new Error(`Search text not found in issue description. Search block:\n---\n${block.search.slice(0, 200)}${block.search.length > 200 ? "..." : ""}\n---`);
|
|
70
|
+
}
|
|
71
|
+
if (occurrences.length > 1 && !allowMultiple) {
|
|
72
|
+
throw new Error(`Search text matches ${occurrences.length} times (expected exactly 1). ` +
|
|
73
|
+
"Use 'allow_multiple: true' to replace all occurrences.\n" +
|
|
74
|
+
`Search block:\n---\n${block.search.slice(0, 200)}${block.search.length > 200 ? "..." : ""}\n---`);
|
|
75
|
+
}
|
|
76
|
+
// Apply replacement
|
|
77
|
+
const replacement = block.replace;
|
|
78
|
+
const replaced = current.replace(regex, () => replacement);
|
|
79
|
+
// Detect no-op
|
|
80
|
+
if (replaced === current) {
|
|
81
|
+
throw new Error(`Replacement did not change the description (identical result). Search block:\n---\n${block.search.slice(0, 200)}${block.search.length > 200 ? "..." : ""}\n---`);
|
|
82
|
+
}
|
|
83
|
+
const count = occurrences.length;
|
|
84
|
+
totalChanges += count;
|
|
85
|
+
changeLines.push(`Replaced ${count} occurrence(s): "${truncate(block.search, 60)}" → "${truncate(block.replace, 60)}"`);
|
|
86
|
+
current = replaced;
|
|
87
|
+
}
|
|
88
|
+
const summary = changeLines.join("\n");
|
|
89
|
+
// Generate preview diff
|
|
90
|
+
const preview = createTwoFilesPatch("current", "updated", source, current);
|
|
91
|
+
return {
|
|
92
|
+
description: current,
|
|
93
|
+
changes: totalChanges,
|
|
94
|
+
summary,
|
|
95
|
+
preview,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Apply a unified diff patch to source text.
|
|
100
|
+
* Delegates to the `diff` library's applyPatch.
|
|
101
|
+
*/
|
|
102
|
+
export function applyUnifiedDiff(source, patch) {
|
|
103
|
+
// Validate the patch can be parsed first
|
|
104
|
+
const parsed = parsePatch(patch);
|
|
105
|
+
if (parsed.length === 0 || parsed.every((p) => p.hunks.length === 0)) {
|
|
106
|
+
throw new Error("Could not parse unified diff: no valid hunks found. " +
|
|
107
|
+
"Expected format: '--- old\\n+++ new\\n@@ -line,count +line,count @@\\n context\\n-old\\n+new\\n'");
|
|
108
|
+
}
|
|
109
|
+
const result = applyPatch(source, patch);
|
|
110
|
+
if (result === false) {
|
|
111
|
+
throw new Error("Unified diff could not be applied to the current issue description. " +
|
|
112
|
+
"The diff context may not match. Use 'dry_run: true' to debug.");
|
|
113
|
+
}
|
|
114
|
+
// Detect no-op: patch applied but nothing changed
|
|
115
|
+
if (result === source) {
|
|
116
|
+
throw new Error("Unified diff applied but did not change the issue description. " +
|
|
117
|
+
"The source text already matches the patched result.");
|
|
118
|
+
}
|
|
119
|
+
const changes = parsed.reduce((sum, p) => sum + p.hunks.reduce((hSum, h) => hSum + h.lines.filter((l) => l.startsWith("-") || l.startsWith("+")).length, 0), 0);
|
|
120
|
+
// Generate preview of what actually changed
|
|
121
|
+
const preview = createTwoFilesPatch("current", "updated", source, result);
|
|
122
|
+
// Build summary from hunk headers
|
|
123
|
+
const summaryLines = [];
|
|
124
|
+
for (const p of parsed) {
|
|
125
|
+
for (const hunk of p.hunks) {
|
|
126
|
+
const added = hunk.lines.filter((l) => l.startsWith("+")).length;
|
|
127
|
+
const removed = hunk.lines.filter((l) => l.startsWith("-")).length;
|
|
128
|
+
summaryLines.push(`Hunk at line ${hunk.oldStart}: ${removed} removed, ${added} added`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
description: result,
|
|
133
|
+
changes,
|
|
134
|
+
summary: summaryLines.join("\n"),
|
|
135
|
+
preview,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function escapeRegex(str) {
|
|
139
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
140
|
+
}
|
|
141
|
+
function truncate(str, maxLen) {
|
|
142
|
+
if (str.length <= maxLen)
|
|
143
|
+
return str;
|
|
144
|
+
return str.slice(0, maxLen) + "...";
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zereight/mcp-gitlab",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.13",
|
|
4
4
|
"mcpName": "io.github.zereight/gitlab-mcp",
|
|
5
5
|
"description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"changelog": "auto-changelog -p",
|
|
52
52
|
"test": "npm run test:all",
|
|
53
53
|
"test:all": "npm run build && npm run test:mock && npm run test:live",
|
|
54
|
-
"test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
54
|
+
"test:mock": "node --import tsx/esm --test test/remote-auth-simple-test.ts && node --import tsx/esm --test test/mcp-oauth-tests.ts && node --import tsx/esm --test test/streamable-http-static-token-auth.test.ts && tsx test/oauth-tests.ts && tsx test/test-list-merge-requests.ts && node --import tsx/esm --test test/test-merge-request-pipelines.ts && tsx test/test-list-project-members.ts && tsx test/test-download-attachment.ts && node --import tsx/esm --test test/test-job-artifacts.ts && node --import tsx/esm --test test/test-deployment-tools.ts && node --import tsx/esm --test test/test-merge-request-approval-state-tools.ts && node --import tsx/esm --test test/test-search-code.ts && node --import tsx/esm --test test/test-tags.ts && node --import tsx/esm --test test/test-toolset-filtering.ts && node --import tsx/esm --test test/test-ci-lint.ts && node --import tsx/esm --test test/test-todos.ts && node --import tsx/esm --test test/test-auth-retry.ts && node --import tsx/esm --test test/test-issue-description-patch.ts && node --import tsx/esm --test test/test-geteffectiveprojectid.ts && node --import tsx/esm --test test/test-get-file-blame.ts && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
55
55
|
"test:stateless": "npm run build && node --import tsx/esm --test test/stateless/codec.test.ts test/stateless/client-id.test.ts test/stateless/callback-proxy.test.ts test/stateless/session-id.test.ts test/stateless/session-id-integration.test.ts test/stateless/config-ttl.test.ts",
|
|
56
56
|
"test:mcp-oauth": "npm run build && node --import tsx/esm --test test/mcp-oauth-tests.ts",
|
|
57
57
|
"test:live": "node test/validate-api.js",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"@modelcontextprotocol/sdk": "^1.24.2",
|
|
72
72
|
"@types/node-fetch": "^2.6.12",
|
|
73
|
+
"diff": "^9.0.0",
|
|
73
74
|
"express": "^5.1.0",
|
|
74
75
|
"fetch-cookie": "^3.1.0",
|
|
75
76
|
"form-data": "^4.0.0",
|