From 026dd58887079bb60187d6acacaafc6fa28d0c3d Mon Sep 17 00:00:00 2001 From: Nicholas Crum Date: Tue, 13 May 2025 15:45:43 -0600 Subject: [PATCH 1/3] feat: Add create_merge_request_thread tool for diff notes - Implement new tool for creating MR threads with positioning support - Create schemas to handle diff notes with file and line number positions - Support optional created_at timestamp parameter - Update README with the new tool information --- README.md | 55 ++++++++++++++++++++------------------- index.ts | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ schemas.ts | 26 +++++++++++++++++++ 3 files changed, 130 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index f4b6aaf..6d72db2 100644 --- a/README.md +++ b/README.md @@ -55,31 +55,32 @@ When using with the Claude App, you need to set up your API key and URLs directl 11. `get_merge_request_diffs` - Get the changes/diffs of a merge request (Either mergeRequestIid or branchName must be provided) 12. `update_merge_request` - Update a merge request (Either mergeRequestIid or branchName must be provided) 13. `create_note` - Create a new note (comment) to an issue or merge request -14. `mr_discussions` - List discussion items for a merge request -15. `update_merge_request_note` - Modify an existing merge request thread note -16. `list_issues` - List issues in a GitLab project with filtering options -17. `get_issue` - Get details of a specific issue in a GitLab project -18. `update_issue` - Update an issue in a GitLab project -19. `delete_issue` - Delete an issue from a GitLab project -20. `list_issue_links` - List all issue links for a specific issue -21. `get_issue_link` - Get a specific issue link -22. `create_issue_link` - Create an issue link between two issues -23. `delete_issue_link` - Delete an issue link -24. `list_namespaces` - List all namespaces available to the current user -25. `get_namespace` - Get details of a namespace by ID or path -26. `verify_namespace` - Verify if a namespace path exists -27. `get_project` - Get details of a specific project -28. `list_projects` - List projects accessible by the current user -29. `list_labels` - List labels for a project -30. `get_label` - Get a single label from a project -31. `create_label` - Create a new label in a project -32. `update_label` - Update an existing label in a project -33. `delete_label` - Delete a label from a project -34. `list_group_projects` - List projects in a GitLab group with filtering options -35. `list_wiki_pages` - List wiki pages in a GitLab project -36. `get_wiki_page` - Get details of a specific wiki page -37. `create_wiki_page` - Create a new wiki page in a GitLab project -38. `update_wiki_page` - Update an existing wiki page in a GitLab project -39. `delete_wiki_page` - Delete a wiki page from a GitLab project -40. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories) +14. `create_merge_request_thread` - Create a new thread on a merge request +15. `mr_discussions` - List discussion items for a merge request +16. `update_merge_request_note` - Modify an existing merge request thread note +17. `list_issues` - List issues in a GitLab project with filtering options +18. `get_issue` - Get details of a specific issue in a GitLab project +19. `update_issue` - Update an issue in a GitLab project +20. `delete_issue` - Delete an issue from a GitLab project +21. `list_issue_links` - List all issue links for a specific issue +22. `get_issue_link` - Get a specific issue link +23. `create_issue_link` - Create an issue link between two issues +24. `delete_issue_link` - Delete an issue link +25. `list_namespaces` - List all namespaces available to the current user +26. `get_namespace` - Get details of a namespace by ID or path +27. `verify_namespace` - Verify if a namespace path exists +28. `get_project` - Get details of a specific project +29. `list_projects` - List projects accessible by the current user +30. `list_labels` - List labels for a project +31. `get_label` - Get a single label from a project +32. `create_label` - Create a new label in a project +33. `update_label` - Update an existing label in a project +34. `delete_label` - Delete a label from a project +35. `list_group_projects` - List projects in a GitLab group with filtering options +36. `list_wiki_pages` - List wiki pages in a GitLab project +37. `get_wiki_page` - Get details of a specific wiki page +38. `create_wiki_page` - Create a new wiki page in a GitLab project +39. `update_wiki_page` - Update an existing wiki page in a GitLab project +40. `delete_wiki_page` - Delete a wiki page from a GitLab project +41. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories) diff --git a/index.ts b/index.ts index f95d862..c21aab6 100644 --- a/index.ts +++ b/index.ts @@ -75,6 +75,7 @@ import { UpdateLabelSchema, DeleteLabelSchema, CreateNoteSchema, + CreateMergeRequestThreadSchema, ListGroupProjectsSchema, ListWikiPagesSchema, GetWikiPageSchema, @@ -108,6 +109,7 @@ import { // Discussion Types type GitLabDiscussionNote, // Added type GitLabDiscussion, + type MergeRequestThreadPosition, type GetWikiPageOptions, type CreateWikiPageOptions, type UpdateWikiPageOptions, @@ -263,6 +265,11 @@ const allTools = [ description: "Create a new note (comment) to an issue or merge request", inputSchema: zodToJsonSchema(CreateNoteSchema), }, + { + name: "create_merge_request_thread", + description: "Create a new thread on a merge request", + inputSchema: zodToJsonSchema(CreateMergeRequestThreadSchema), + }, { name: "mr_discussions", description: "List discussion items for a merge request", @@ -1520,6 +1527,59 @@ async function createNote( return await response.json(); } +/** + * Create a new thread on a merge request + * πŸ“¦ μƒˆλ‘œμš΄ ν•¨μˆ˜: createMergeRequestThread - 병합 μš”μ²­μ— μƒˆλ‘œμš΄ μŠ€λ ˆλ“œ(ν† λ‘ )λ₯Ό μƒμ„±ν•˜λŠ” ν•¨μˆ˜ + * (New function: createMergeRequestThread - Function to create a new thread (discussion) on a merge request) + * + * This function provides more capabilities than createNote, including the ability to: + * - Create diff notes (comments on specific lines of code) + * - Specify exact positions for comments + * - Set creation timestamps + * + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {number} mergeRequestIid - The internal ID of the merge request + * @param {string} body - The content of the thread + * @param {MergeRequestThreadPosition} [position] - Position information for diff notes + * @param {string} [createdAt] - ISO 8601 formatted creation date + * @returns {Promise} The created discussion thread + */ +async function createMergeRequestThread( + projectId: string, + mergeRequestIid: number, + body: string, + position?: MergeRequestThreadPosition, + createdAt?: string +): Promise { + projectId = decodeURIComponent(projectId); // Decode project ID + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent( + projectId + )}/merge_requests/${mergeRequestIid}/discussions` + ); + + const payload: Record = { body }; + + // Add optional parameters if provided + if (position) { + payload.position = position; + } + + if (createdAt) { + payload.created_at = createdAt; + } + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + method: "POST", + body: JSON.stringify(payload), + }); + + await handleGitLabError(response); + const data = await response.json(); + return GitLabDiscussionSchema.parse(data); +} + /** * List all namespaces * μ‚¬μš© κ°€λŠ₯ν•œ λͺ¨λ“  λ„€μž„μŠ€νŽ˜μ΄μŠ€ λͺ©λ‘ 쑰회 @@ -2454,6 +2514,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } + case "create_merge_request_thread": { + const args = CreateMergeRequestThreadSchema.parse(request.params.arguments); + const { project_id, merge_request_iid, body, position, created_at } = args; + + const thread = await createMergeRequestThread( + project_id, + merge_request_iid, + body, + position, + created_at + ); + return { + content: [{ type: "text", text: JSON.stringify(thread, null, 2) }], + }; + } + case "list_issues": { const args = ListIssuesSchema.parse(request.params.arguments); const { project_id, ...options } = args; diff --git a/schemas.ts b/schemas.ts index b2dd58a..2e27c00 100644 --- a/schemas.ts +++ b/schemas.ts @@ -1004,6 +1004,30 @@ export const GitLabWikiPageSchema = z.object({ updated_at: z.string().optional(), }); +// Merge Request Thread position schema - used for diff notes +export const MergeRequestThreadPositionSchema = z.object({ + base_sha: z.string().describe("Base commit SHA in the source branch"), + head_sha: z.string().describe("SHA referencing HEAD of the source branch"), + start_sha: z.string().describe("SHA referencing the start commit of the source branch"), + position_type: z.enum(["text", "image", "file"]).describe("Type of position reference"), + new_path: z.string().optional().describe("File path after change"), + old_path: z.string().optional().describe("File path before change"), + new_line: z.number().nullable().optional().describe("Line number after change"), + old_line: z.number().nullable().optional().describe("Line number before change"), + width: z.number().optional().describe("Width of the image (for image diffs)"), + height: z.number().optional().describe("Height of the image (for image diffs)"), + x: z.number().optional().describe("X coordinate on the image (for image diffs)"), + y: z.number().optional().describe("Y coordinate on the image (for image diffs)"), +}); + +// Schema for creating a new merge request thread +export const CreateMergeRequestThreadSchema = ProjectParamsSchema.extend({ + merge_request_iid: z.number().describe("The IID of a merge request"), + body: z.string().describe("The content of the thread"), + position: MergeRequestThreadPositionSchema.optional().describe("Position when creating a diff note"), + created_at: z.string().optional().describe("Date the thread was created at (ISO 8601 format)"), +}); + // Export types export type GitLabAuthor = z.infer; export type GitLabFork = z.infer; @@ -1053,3 +1077,5 @@ export type DeleteWikiPageOptions = z.infer; export type GitLabWikiPage = z.infer; export type GitLabTreeItem = z.infer; export type GetRepositoryTreeOptions = z.infer; +export type MergeRequestThreadPosition = z.infer; +export type CreateMergeRequestThreadOptions = z.infer; From 3f2b35535ee93b14a6649074608842d1ff8de208 Mon Sep 17 00:00:00 2001 From: Nicholas Crum Date: Tue, 13 May 2025 16:20:21 -0600 Subject: [PATCH 2/3] feat: Implement add_merge_request_thread_note function for adding notes to existing MR threads --- README.md | 51 +++++++++++++++++++++---------------------- index.ts | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ schemas.ts | 9 ++++++++ 3 files changed, 98 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 6d72db2..68fb3f5 100644 --- a/README.md +++ b/README.md @@ -58,29 +58,30 @@ When using with the Claude App, you need to set up your API key and URLs directl 14. `create_merge_request_thread` - Create a new thread on a merge request 15. `mr_discussions` - List discussion items for a merge request 16. `update_merge_request_note` - Modify an existing merge request thread note -17. `list_issues` - List issues in a GitLab project with filtering options -18. `get_issue` - Get details of a specific issue in a GitLab project -19. `update_issue` - Update an issue in a GitLab project -20. `delete_issue` - Delete an issue from a GitLab project -21. `list_issue_links` - List all issue links for a specific issue -22. `get_issue_link` - Get a specific issue link -23. `create_issue_link` - Create an issue link between two issues -24. `delete_issue_link` - Delete an issue link -25. `list_namespaces` - List all namespaces available to the current user -26. `get_namespace` - Get details of a namespace by ID or path -27. `verify_namespace` - Verify if a namespace path exists -28. `get_project` - Get details of a specific project -29. `list_projects` - List projects accessible by the current user -30. `list_labels` - List labels for a project -31. `get_label` - Get a single label from a project -32. `create_label` - Create a new label in a project -33. `update_label` - Update an existing label in a project -34. `delete_label` - Delete a label from a project -35. `list_group_projects` - List projects in a GitLab group with filtering options -36. `list_wiki_pages` - List wiki pages in a GitLab project -37. `get_wiki_page` - Get details of a specific wiki page -38. `create_wiki_page` - Create a new wiki page in a GitLab project -39. `update_wiki_page` - Update an existing wiki page in a GitLab project -40. `delete_wiki_page` - Delete a wiki page from a GitLab project -41. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories) +17. `add_merge_request_thread_note` - Add a new note to an existing merge request thread +18. `list_issues` - List issues in a GitLab project with filtering options +19. `get_issue` - Get details of a specific issue in a GitLab project +20. `update_issue` - Update an issue in a GitLab project +21. `delete_issue` - Delete an issue from a GitLab project +22. `list_issue_links` - List all issue links for a specific issue +23. `get_issue_link` - Get a specific issue link +24. `create_issue_link` - Create an issue link between two issues +25. `delete_issue_link` - Delete an issue link +26. `list_namespaces` - List all namespaces available to the current user +27. `get_namespace` - Get details of a namespace by ID or path +28. `verify_namespace` - Verify if a namespace path exists +29. `get_project` - Get details of a specific project +30. `list_projects` - List projects accessible by the current user +31. `list_labels` - List labels for a project +32. `get_label` - Get a single label from a project +33. `create_label` - Create a new label in a project +34. `update_label` - Update an existing label in a project +35. `delete_label` - Delete a label from a project +36. `list_group_projects` - List projects in a GitLab group with filtering options +37. `list_wiki_pages` - List wiki pages in a GitLab project +38. `get_wiki_page` - Get details of a specific wiki page +39. `create_wiki_page` - Create a new wiki page in a GitLab project +40. `update_wiki_page` - Update an existing wiki page in a GitLab project +41. `delete_wiki_page` - Delete a wiki page from a GitLab project +42. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories) diff --git a/index.ts b/index.ts index c21aab6..f7d8b74 100644 --- a/index.ts +++ b/index.ts @@ -87,6 +87,7 @@ import { GitLabDiscussionNoteSchema, // Added GitLabDiscussionSchema, UpdateMergeRequestNoteSchema, // Added + AddMergeRequestThreadNoteSchema, // Added ListMergeRequestDiscussionsSchema, type GitLabFork, type GitLabReference, @@ -280,6 +281,11 @@ const allTools = [ description: "Modify an existing merge request thread note", inputSchema: zodToJsonSchema(UpdateMergeRequestNoteSchema), }, + { + name: "add_merge_request_thread_note", + description: "Add a new note to an existing merge request thread", + inputSchema: zodToJsonSchema(AddMergeRequestThreadNoteSchema), + }, { name: "list_issues", description: "List issues in a GitLab project with filtering options", @@ -1060,6 +1066,47 @@ async function updateMergeRequestNote( return GitLabDiscussionNoteSchema.parse(data); } +/** + * Add a new note to an existing merge request thread + * κΈ°μ‘΄ 병합 μš”μ²­ μŠ€λ ˆλ“œμ— μƒˆ λ…ΈνŠΈ μΆ”κ°€ + * + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {number} mergeRequestIid - The IID of a merge request + * @param {string} discussionId - The ID of a thread + * @param {string} body - The content of the new note + * @param {string} [createdAt] - The creation date of the note (ISO 8601 format) + * @returns {Promise} The created note + */ +async function addMergeRequestThreadNote( + projectId: string, + mergeRequestIid: number, + discussionId: string, + body: string, + createdAt?: string +): Promise { + projectId = decodeURIComponent(projectId); // Decode project ID + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent( + projectId + )}/merge_requests/${mergeRequestIid}/discussions/${discussionId}/notes` + ); + + const payload: { body: string; created_at?: string } = { body }; + if (createdAt) { + payload.created_at = createdAt; + } + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + method: "POST", + body: JSON.stringify(payload), + }); + + await handleGitLabError(response); + const data = await response.json(); + return GitLabDiscussionNoteSchema.parse(data); +} + /** * Create or update a file in a GitLab project * 파일 생성 λ˜λŠ” μ—…λ°μ΄νŠΈ @@ -2337,6 +2384,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { content: [{ type: "text", text: JSON.stringify(note, null, 2) }], }; } + + case "add_merge_request_thread_note": { + const args = AddMergeRequestThreadNoteSchema.parse( + request.params.arguments + ); + const note = await addMergeRequestThreadNote( + args.project_id, + args.merge_request_iid, + args.discussion_id, + args.body, + args.created_at + ); + return { + content: [{ type: "text", text: JSON.stringify(note, null, 2) }], + }; + } case "get_merge_request": { const args = GetMergeRequestSchema.parse(request.params.arguments); diff --git a/schemas.ts b/schemas.ts index 2e27c00..d779592 100644 --- a/schemas.ts +++ b/schemas.ts @@ -479,6 +479,14 @@ export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({ resolved: z.boolean().optional().describe("Resolve or unresolve the note"), // Optional based on API docs }); +// Input schema for adding a note to an existing merge request discussion +export const AddMergeRequestThreadNoteSchema = ProjectParamsSchema.extend({ + merge_request_iid: z.number().describe("The IID of a merge request"), + discussion_id: z.string().describe("The ID of a thread"), + body: z.string().describe("The content of the note or reply"), + created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"), +}); + // API Operation Parameter Schemas export const CreateOrUpdateFileSchema = ProjectParamsSchema.extend({ @@ -1079,3 +1087,4 @@ export type GitLabTreeItem = z.infer; export type GetRepositoryTreeOptions = z.infer; export type MergeRequestThreadPosition = z.infer; export type CreateMergeRequestThreadOptions = z.infer; +export type AddMergeRequestThreadNoteOptions = z.infer; From 353e62a40177ddd0646515e69cbe2f5d5e758f36 Mon Sep 17 00:00:00 2001 From: Nicholas Crum Date: Tue, 13 May 2025 16:52:17 -0600 Subject: [PATCH 3/3] refactor: rename add_merge_request_thread_note to create_merge_request_note for consistency --- README.md | 2 +- index.ts | 14 +++++++------- schemas.ts | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 68fb3f5..b58051c 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ When using with the Claude App, you need to set up your API key and URLs directl 14. `create_merge_request_thread` - Create a new thread on a merge request 15. `mr_discussions` - List discussion items for a merge request 16. `update_merge_request_note` - Modify an existing merge request thread note -17. `add_merge_request_thread_note` - Add a new note to an existing merge request thread +17. `create_merge_request_note` - Add a new note to an existing merge request thread 18. `list_issues` - List issues in a GitLab project with filtering options 19. `get_issue` - Get details of a specific issue in a GitLab project 20. `update_issue` - Update an issue in a GitLab project diff --git a/index.ts b/index.ts index f7d8b74..422d96f 100644 --- a/index.ts +++ b/index.ts @@ -87,7 +87,7 @@ import { GitLabDiscussionNoteSchema, // Added GitLabDiscussionSchema, UpdateMergeRequestNoteSchema, // Added - AddMergeRequestThreadNoteSchema, // Added + CreateMergeRequestNoteSchema, // Added ListMergeRequestDiscussionsSchema, type GitLabFork, type GitLabReference, @@ -282,9 +282,9 @@ const allTools = [ inputSchema: zodToJsonSchema(UpdateMergeRequestNoteSchema), }, { - name: "add_merge_request_thread_note", + name: "create_merge_request_note", description: "Add a new note to an existing merge request thread", - inputSchema: zodToJsonSchema(AddMergeRequestThreadNoteSchema), + inputSchema: zodToJsonSchema(CreateMergeRequestNoteSchema), }, { name: "list_issues", @@ -1077,7 +1077,7 @@ async function updateMergeRequestNote( * @param {string} [createdAt] - The creation date of the note (ISO 8601 format) * @returns {Promise} The created note */ -async function addMergeRequestThreadNote( +async function createMergeRequestNote( projectId: string, mergeRequestIid: number, discussionId: string, @@ -2385,11 +2385,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } - case "add_merge_request_thread_note": { - const args = AddMergeRequestThreadNoteSchema.parse( + case "create_merge_request_note": { + const args = CreateMergeRequestNoteSchema.parse( request.params.arguments ); - const note = await addMergeRequestThreadNote( + const note = await createMergeRequestNote( args.project_id, args.merge_request_iid, args.discussion_id, diff --git a/schemas.ts b/schemas.ts index d779592..a6857b3 100644 --- a/schemas.ts +++ b/schemas.ts @@ -480,7 +480,7 @@ export const UpdateMergeRequestNoteSchema = ProjectParamsSchema.extend({ }); // Input schema for adding a note to an existing merge request discussion -export const AddMergeRequestThreadNoteSchema = ProjectParamsSchema.extend({ +export const CreateMergeRequestNoteSchema = ProjectParamsSchema.extend({ merge_request_iid: z.number().describe("The IID of a merge request"), discussion_id: z.string().describe("The ID of a thread"), body: z.string().describe("The content of the note or reply"), @@ -1087,4 +1087,4 @@ export type GitLabTreeItem = z.infer; export type GetRepositoryTreeOptions = z.infer; export type MergeRequestThreadPosition = z.infer; export type CreateMergeRequestThreadOptions = z.infer; -export type AddMergeRequestThreadNoteOptions = z.infer; +export type CreateMergeRequestNoteOptions = z.infer;