diff --git a/README.md b/README.md index 56f49d6..8b34cc4 100644 --- a/README.md +++ b/README.md @@ -93,30 +93,32 @@ When using with the Claude App, you need to set up your API key and URLs directl 15. `mr_discussions` - List discussion items for a merge request 16. `update_merge_request_note` - Modify an existing merge request thread note 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 -21. `delete_issue` - Delete an issue from a GitLab project -22. `list_issue_links` - List all issue links for a specific issue -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) +18. `update_issue_note` - Update the content of an existing issue note +19. `create_issue_note` - Add a new note to an existing issue thread +20. `list_issues` - List issues in a GitLab project with filtering options +21. `get_issue` - Get details of a specific issue in a GitLab project +22. `update_issue` - Update an issue in a GitLab project +23. `delete_issue` - Delete an issue from a GitLab project +24. `list_issue_links` - List all issue links for a specific issue +25. `list_issue_discussions` - List discussions for an issue in a GitLab project +26. `get_issue_link` - Get a specific issue link +27. `create_issue_link` - Create an issue link between two issues +28. `delete_issue_link` - Delete an issue link +29. `list_namespaces` - List all namespaces available to the current user +30. `get_namespace` - Get details of a namespace by ID or path +31. `verify_namespace` - Verify if a namespace path exists +32. `get_project` - Get details of a specific project +33. `list_projects` - List projects accessible by the current user +34. `list_labels` - List labels for a project +35. `get_label` - Get a single label from a project +36. `create_label` - Create a new label in a project +37. `update_label` - Update an existing label in a project +38. `delete_label` - Delete a label from a project +39. `list_group_projects` - List projects in a GitLab group with filtering options +40. `list_wiki_pages` - List wiki pages in a GitLab project +41. `get_wiki_page` - Get details of a specific wiki page +42. `create_wiki_page` - Create a new wiki page in a GitLab project +43. `update_wiki_page` - Update an existing wiki page in a GitLab project +44. `delete_wiki_page` - Delete a wiki page from a GitLab project +45. `get_repository_tree` - Get the repository tree for a GitLab project (list files and directories) diff --git a/index.ts b/index.ts index 1aaebee..5a7a045 100644 --- a/index.ts +++ b/index.ts @@ -121,6 +121,8 @@ import { GetRepositoryTreeSchema, type GitLabTreeItem, type GetRepositoryTreeOptions, + UpdateIssueNoteSchema, + CreateIssueNoteSchema, } from "./schemas.js"; /** @@ -287,6 +289,16 @@ const allTools = [ description: "Add a new note to an existing merge request thread", inputSchema: zodToJsonSchema(CreateMergeRequestNoteSchema), }, + { + name: "update_issue_note", + description: "Modify an existing issue thread note", + inputSchema: zodToJsonSchema(UpdateIssueNoteSchema), + }, + { + name: "create_issue_note", + description: "Add a new note to an existing issue thread", + inputSchema: zodToJsonSchema(CreateIssueNoteSchema), + }, { name: "list_issues", description: "List issues in a GitLab project with filtering options", @@ -1126,6 +1138,81 @@ async function updateMergeRequestNote( return GitLabDiscussionNoteSchema.parse(data); } +/** + * Update an issue discussion note + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {number} issueIid - The IID of an issue + * @param {string} discussionId - The ID of a thread + * @param {number} noteId - The ID of a thread note + * @param {string} body - The new content of the note + * @returns {Promise} The updated note + */ +async function updateIssueNote( + projectId: string, + issueIid: number, + discussionId: string, + noteId: number, + body: string +): Promise { + projectId = decodeURIComponent(projectId); // Decode project ID + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent( + projectId + )}/issues/${issueIid}/discussions/${discussionId}/notes/${noteId}` + ); + + const payload = { body }; + + const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, + method: "PUT", + body: JSON.stringify(payload), + }); + + await handleGitLabError(response); + const data = await response.json(); + return GitLabDiscussionNoteSchema.parse(data); +} + +/** + * Create a note in an issue discussion + * @param {string} projectId - The ID or URL-encoded path of the project + * @param {number} issueIid - The IID of an issue + * @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 createIssueNote( + projectId: string, + issueIid: number, + discussionId: string, + body: string, + createdAt?: string +): Promise { + projectId = decodeURIComponent(projectId); // Decode project ID + const url = new URL( + `${GITLAB_API_URL}/projects/${encodeURIComponent( + projectId + )}/issues/${issueIid}/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); +} + /** * Add a new note to an existing merge request thread * 기존 병합 요청 스레드에 새 노트 추가 @@ -2461,6 +2548,38 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { }; } + case "update_issue_note": { + const args = UpdateIssueNoteSchema.parse( + request.params.arguments + ); + const note = await updateIssueNote( + args.project_id, + args.issue_iid, + args.discussion_id, + args.note_id, + args.body + ); + return { + content: [{ type: "text", text: JSON.stringify(note, null, 2) }], + }; + } + + case "create_issue_note": { + const args = CreateIssueNoteSchema.parse( + request.params.arguments + ); + const note = await createIssueNote( + args.project_id, + args.issue_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); const mergeRequest = await getMergeRequest( diff --git a/schemas.ts b/schemas.ts index 071ee5f..6ae2fce 100644 --- a/schemas.ts +++ b/schemas.ts @@ -491,6 +491,22 @@ export const CreateMergeRequestNoteSchema = ProjectParamsSchema.extend({ created_at: z.string().optional().describe("Date the note was created at (ISO 8601 format)"), }); +// Input schema for updating an issue discussion note +export const UpdateIssueNoteSchema = ProjectParamsSchema.extend({ + issue_iid: z.number().describe("The IID of an issue"), + discussion_id: z.string().describe("The ID of a thread"), + note_id: z.number().describe("The ID of a thread note"), + body: z.string().describe("The content of the note or reply"), +}); + +// Input schema for adding a note to an existing issue discussion +export const CreateIssueNoteSchema = ProjectParamsSchema.extend({ + issue_iid: z.number().describe("The IID of an issue"), + 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({ @@ -1085,6 +1101,8 @@ export type GitLabMergeRequestDiff = z.infer< export type CreateNoteOptions = z.infer; export type GitLabIssueLink = z.infer; export type ListIssueDiscussionsOptions = z.infer; +export type UpdateIssueNoteOptions = z.infer; +export type CreateIssueNoteOptions = z.infer; export type GitLabNamespace = z.infer; export type GitLabNamespaceExistsResponse = z.infer< typeof GitLabNamespaceExistsResponseSchema