From 4c57c378886bf6e3eda8f815c654fda0d29dcd44 Mon Sep 17 00:00:00 2001 From: Simon Huerlimann Date: Tue, 20 May 2025 15:45:13 +0200 Subject: [PATCH] feat: add issue discussions support Added `list_issue_discussions` tool to support GitLab issue discussions similar to merge request discussions. --- README.md | 41 ++++++++++++++--------------- index.ts | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++--- schemas.ts | 10 ++++++++ 3 files changed, 102 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 9aea989..20f0327 100644 --- a/README.md +++ b/README.md @@ -98,24 +98,25 @@ When using with the Claude App, you need to set up your API key and URLs directl 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) +23. `list_issue_discussions` - List discussions for an issue in a GitLab project +24. `get_issue_link` - Get a specific issue link +25. `create_issue_link` - Create an issue link between two issues +26. `delete_issue_link` - Delete an issue link +27. `list_namespaces` - List all namespaces available to the current user +28. `get_namespace` - Get details of a namespace by ID or path +29. `verify_namespace` - Verify if a namespace path exists +30. `get_project` - Get details of a specific project +31. `list_projects` - List projects accessible by the current user +32. `list_labels` - List labels for a project +33. `get_label` - Get a single label from a project +34. `create_label` - Create a new label in a project +35. `update_label` - Update an existing label in a project +36. `delete_label` - Delete a label from a project +37. `list_group_projects` - List projects in a GitLab group with filtering options +38. `list_wiki_pages` - List wiki pages in a GitLab project +39. `get_wiki_page` - Get details of a specific wiki page +40. `create_wiki_page` - Create a new wiki page in a GitLab project +41. `update_wiki_page` - Update an existing wiki page in a GitLab project +42. `delete_wiki_page` - Delete a wiki page from a GitLab project +43. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories) diff --git a/index.ts b/index.ts index f949a40..1aaebee 100644 --- a/index.ts +++ b/index.ts @@ -61,6 +61,7 @@ import { GitLabIssueLinkSchema, GitLabIssueWithLinkDetailsSchema, ListIssueLinksSchema, + ListIssueDiscussionsSchema, GetIssueLinkSchema, CreateIssueLinkSchema, DeleteIssueLinkSchema, @@ -311,6 +312,11 @@ const allTools = [ description: "List all issue links for a specific issue", inputSchema: zodToJsonSchema(ListIssueLinksSchema), }, + { + name: "list_issue_discussions", + description: "List discussions for an issue in a GitLab project", + inputSchema: zodToJsonSchema(ListIssueDiscussionsSchema), + }, { name: "get_issue_link", description: "Get a specific issue link", @@ -424,6 +430,7 @@ const readOnlyTools = [ "list_issues", "get_issue", "list_issue_links", + "list_issue_discussions", "get_issue_link", "list_namespaces", "get_namespace", @@ -1023,6 +1030,56 @@ async function listMergeRequestDiscussions( return z.array(GitLabDiscussionSchema).parse(data); } +/** + * List discussions for an issue + * + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {number} issueIid - The internal ID of the project issue + * @param {Object} options - Pagination and sorting options + * @returns {Promise} List of issue discussions + */ +async function listIssueDiscussions( + projectId: string, + issueIid: number, + options: { + page?: number, + per_page?: number, + sort?: "asc" | "desc", + order_by?: "created_at" | "updated_at" + } = {} +): Promise { + projectId = decodeURIComponent(projectId); // Decode project ID + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent( + projectId + )}/issues/${issueIid}/discussions` + ); + + // Add query parameters for pagination and sorting + if (options.page) { + url.searchParams.append("page", options.page.toString()); + } + if (options.per_page) { + url.searchParams.append("per_page", options.per_page.toString()); + } + if (options.sort) { + url.searchParams.append("sort", options.sort); + } + if (options.order_by) { + url.searchParams.append("order_by", options.order_by); + } + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + }); + + await handleGitLabError(response); + const data = await response.json(); + + // Parse the response as an array of discussions + return z.array(GitLabDiscussionSchema).parse(data); +} + /** * Modify an existing merge request thread note * 병합 요청 토론 노트 수정 @@ -1581,7 +1638,7 @@ async function createNote( * 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 @@ -1609,12 +1666,12 @@ async function createMergeRequestThread( ); const payload: Record = { body }; - + // Add optional parameters if provided if (position) { payload.position = position; } - + if (createdAt) { payload.created_at = createdAt; } @@ -2387,7 +2444,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { content: [{ type: "text", text: JSON.stringify(note, null, 2) }], }; } - + case "create_merge_request_note": { const args = CreateMergeRequestNoteSchema.parse( request.params.arguments @@ -2647,6 +2704,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } + case "list_issue_discussions": { + const args = ListIssueDiscussionsSchema.parse(request.params.arguments); + const { project_id, issue_iid, ...options } = args; + + const discussions = await listIssueDiscussions(project_id, issue_iid, options); + return { + content: [{ type: "text", text: JSON.stringify(discussions, null, 2) }], + }; + } + case "get_issue_link": { const args = GetIssueLinkSchema.parse(request.params.arguments); const link = await getIssueLink( diff --git a/schemas.ts b/schemas.ts index 29c7718..071ee5f 100644 --- a/schemas.ts +++ b/schemas.ts @@ -757,6 +757,15 @@ export const ListIssueLinksSchema = z.object({ issue_iid: z.number().describe("The internal ID of a project's issue"), }); +export const ListIssueDiscussionsSchema = z.object({ + project_id: z.string().describe("Project ID or URL-encoded path"), + issue_iid: z.number().describe("The internal ID of the project issue"), + page: z.number().optional().describe("Page number for pagination"), + per_page: z.number().optional().describe("Number of items per page"), + sort: z.enum(["asc", "desc"]).optional().describe("Return issue discussions sorted in ascending or descending order"), + order_by: z.enum(["created_at", "updated_at"]).optional().describe("Return issue discussions ordered by created_at or updated_at fields"), +}); + export const GetIssueLinkSchema = z.object({ project_id: z.string().describe("Project ID or URL-encoded path"), issue_iid: z.number().describe("The internal ID of a project's issue"), @@ -1075,6 +1084,7 @@ export type GitLabMergeRequestDiff = z.infer< >; export type CreateNoteOptions = z.infer; export type GitLabIssueLink = z.infer; +export type ListIssueDiscussionsOptions = z.infer; export type GitLabNamespace = z.infer; export type GitLabNamespaceExistsResponse = z.infer< typeof GitLabNamespaceExistsResponseSchema