From 8f0c3d20e31fc903f7beeb413016027b0d44bfc2 Mon Sep 17 00:00:00 2001 From: Meir Blachman Date: Wed, 12 Nov 2025 15:27:20 +0200 Subject: [PATCH] Add label support to update_pull_request tool - Add optional labels parameter to update_pull_request schema - Implement labels mapping to WebApiTagDefinition format - Handle undefined (no change), empty array (remove all), and array with values (replace) - Add comprehensive test coverage for all label scenarios Fixes #697 --- src/tools/repositories.ts | 7 +- test/src/tools/repositories.test.ts | 125 ++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/src/tools/repositories.ts b/src/tools/repositories.ts index f89fa8c9..e93bf9bf 100644 --- a/src/tools/repositories.ts +++ b/src/tools/repositories.ts @@ -296,8 +296,9 @@ function configureRepoTools(server: McpServer, tokenProvider: () => Promise { + async ({ repositoryId, pullRequestId, title, description, isDraft, targetRefName, status, autoComplete, mergeStrategy, deleteSourceBranch, transitionWorkItems, bypassReason, labels }) => { const connection = await connectionProvider(); const gitApi = await connection.getGitApi(); @@ -312,6 +313,10 @@ function configureRepoTools(server: McpServer, tokenProvider: () => Promise ({ name: label })); + } + if (autoComplete !== undefined) { if (autoComplete) { const data = await getCurrentUserDetails(tokenProvider, connectionProvider, userAgentProvider); diff --git a/test/src/tools/repositories.test.ts b/test/src/tools/repositories.test.ts index ae06bd82..41b12298 100644 --- a/test/src/tools/repositories.test.ts +++ b/test/src/tools/repositories.test.ts @@ -607,6 +607,131 @@ describe("repos tools", () => { // Validation should fail due to description being too long await expect(handler(params)).rejects.toThrow(); }); + + it("should not update labels when labels parameter is undefined", async () => { + configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider); + + const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === REPO_TOOLS.update_pull_request); + if (!call) throw new Error("repo_update_pull_request tool not registered"); + const [, , , handler] = call; + + const mockUpdatedPR = { + pullRequestId: 123, + codeReviewId: 123, + repository: { name: "test-repo" }, + status: PullRequestStatus.Active, + createdBy: { + displayName: "Test User", + uniqueName: "testuser@example.com", + }, + creationDate: "2023-01-01T00:00:00Z", + title: "Updated Title", + isDraft: false, + sourceRefName: "refs/heads/feature", + targetRefName: "refs/heads/main", + }; + mockGitApi.updatePullRequest.mockResolvedValue(mockUpdatedPR); + + const params = { + repositoryId: "repo123", + pullRequestId: 123, + title: "Updated Title", + // labels is undefined (not provided) + }; + + await handler(params); + + // Verify that labels field is not included in the update request + const updateRequestArg = mockGitApi.updatePullRequest.mock.calls[0][0]; + expect(updateRequestArg).not.toHaveProperty("labels"); + expect(updateRequestArg).toEqual({ + title: "Updated Title", + }); + }); + + it("should remove all labels when empty array is provided", async () => { + configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider); + + const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === REPO_TOOLS.update_pull_request); + if (!call) throw new Error("repo_update_pull_request tool not registered"); + const [, , , handler] = call; + + const mockUpdatedPR = { + pullRequestId: 123, + codeReviewId: 123, + repository: { name: "test-repo" }, + status: PullRequestStatus.Active, + createdBy: { + displayName: "Test User", + uniqueName: "testuser@example.com", + }, + creationDate: "2023-01-01T00:00:00Z", + title: "Test PR", + isDraft: false, + sourceRefName: "refs/heads/feature", + targetRefName: "refs/heads/main", + }; + mockGitApi.updatePullRequest.mockResolvedValue(mockUpdatedPR); + + const params = { + repositoryId: "repo123", + pullRequestId: 123, + labels: [], + }; + + await handler(params); + + // Verify that labels field is included with empty array + expect(mockGitApi.updatePullRequest).toHaveBeenCalledWith( + { + labels: [], + }, + "repo123", + 123 + ); + }); + + it("should replace labels when array with values is provided", async () => { + configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider); + + const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === REPO_TOOLS.update_pull_request); + if (!call) throw new Error("repo_update_pull_request tool not registered"); + const [, , , handler] = call; + + const mockUpdatedPR = { + pullRequestId: 123, + codeReviewId: 123, + repository: { name: "test-repo" }, + status: PullRequestStatus.Active, + createdBy: { + displayName: "Test User", + uniqueName: "testuser@example.com", + }, + creationDate: "2023-01-01T00:00:00Z", + title: "Test PR", + isDraft: false, + sourceRefName: "refs/heads/feature", + targetRefName: "refs/heads/main", + }; + mockGitApi.updatePullRequest.mockResolvedValue(mockUpdatedPR); + + const params = { + repositoryId: "repo123", + pullRequestId: 123, + labels: ["bug", "urgent"], + }; + + await handler(params); + + // Verify that labels are mapped to WebApiTagDefinition format + expect(mockGitApi.updatePullRequest).toHaveBeenCalledWith( + { + labels: [{ name: "bug" }, { name: "urgent" }], + }, + "repo123", + 123 + ); + }); }); describe("repo_create_pull_request", () => {