From e8e9ede8d5745eaed96d5714bbbcc8dcdc43ab99 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Fri, 4 Apr 2025 18:57:02 -0700 Subject: [PATCH 1/3] Add line parameter support to create_pull_request_review tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated schema to make path and body the only required fields - Added line parameter as alternative to position for inline comments - Updated handler to accept either position or line based on GitHub API spec - Added new test case that verifies line parameter works properly - Updated error messages for better validation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 2 +- pkg/github/pullrequests.go | 41 +++++++++++++++++++++------------ pkg/github/pullrequests_test.go | 39 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9330723cc..3d96bccb5 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `body`: Review comment text (string, optional) - `event`: Review action ('APPROVE', 'REQUEST_CHANGES', 'COMMENT') (string, required) - `commitId`: SHA of commit to review (string, optional) - - `comments`: Line-specific comments array of objects, each object with path (string), position (number), and body (string) (array, optional) + - `comments`: Line-specific comments array of objects, each object with path (string), either position (number) or line (number), and body (string) (array, optional) - **create_pull_request** - Create a new pull request diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index c02336ca0..3f955a842 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -593,7 +593,7 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe map[string]interface{}{ "type": "object", "additionalProperties": false, - "required": []string{"path", "position", "body"}, + "required": []string{"path", "body"}, "properties": map[string]interface{}{ "path": map[string]interface{}{ "type": "string", @@ -601,7 +601,11 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe }, "position": map[string]interface{}{ "type": "number", - "description": "line number in the file", + "description": "position of the comment in the diff", + }, + "line": map[string]interface{}{ + "type": "number", + "description": "line number in the file to comment on (alternative to position)", }, "body": map[string]interface{}{ "type": "string", @@ -610,7 +614,7 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe }, }, ), - mcp.Description("Line-specific comments array of objects, each object with path (string), position (number), and body (string)"), + mcp.Description("Line-specific comments array of objects, each object with path (string), either position (number) or line (number), and body (string)"), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { @@ -661,7 +665,7 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe for _, c := range commentsObj { commentMap, ok := c.(map[string]interface{}) if !ok { - return mcp.NewToolResultError("each comment must be an object with path, position, and body"), nil + return mcp.NewToolResultError("each comment must be an object with path and body"), nil } path, ok := commentMap["path"].(string) @@ -669,22 +673,29 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe return mcp.NewToolResultError("each comment must have a path"), nil } - positionFloat, ok := commentMap["position"].(float64) - if !ok { - return mcp.NewToolResultError("each comment must have a position"), nil - } - position := int(positionFloat) - body, ok := commentMap["body"].(string) if !ok || body == "" { return mcp.NewToolResultError("each comment must have a body"), nil } - comments = append(comments, &github.DraftReviewComment{ - Path: github.Ptr(path), - Position: github.Ptr(position), - Body: github.Ptr(body), - }) + comment := &github.DraftReviewComment{ + Path: github.Ptr(path), + Body: github.Ptr(body), + } + + if positionFloat, ok := commentMap["position"].(float64); ok { + comment.Position = github.Ptr(int(positionFloat)) + } + + if lineFloat, ok := commentMap["line"].(float64); ok { + comment.Line = github.Ptr(int(lineFloat)) + } + + if comment.Position == nil && comment.Line == nil { + return mcp.NewToolResultError("each comment must have either position or line"), nil + } + + comments = append(comments, comment) } reviewRequest.Comments = comments diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index b666e8a8b..c3cd261ca 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -1167,7 +1167,44 @@ func Test_CreatePullRequestReview(t *testing.T) { }, }, expectError: false, - expectedErrMsg: "each comment must have a position", + expectedErrMsg: "each comment must have either position or line", + }, + { + name: "successful review creation with line parameter", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.PostReposPullsReviewsByOwnerByRepoByPullNumber, + expectRequestBody(t, map[string]interface{}{ + "body": "Code review comments", + "event": "COMMENT", + "comments": []interface{}{ + map[string]interface{}{ + "path": "main.go", + "line": float64(42), + "body": "Consider adding a comment here", + }, + }, + }).andThen( + mockResponse(t, http.StatusOK, mockReview), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "pullNumber": float64(42), + "body": "Code review comments", + "event": "COMMENT", + "comments": []interface{}{ + map[string]interface{}{ + "path": "main.go", + "line": float64(42), + "body": "Consider adding a comment here", + }, + }, + }, + expectError: false, + expectedReview: mockReview, }, { name: "review creation fails", From 0527bc552591df807ee7be0b3a79f428d2f0ed2c Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Sat, 5 Apr 2025 17:22:45 -0700 Subject: [PATCH 2/3] Expand PR review API with multi-line comment support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added new parameters: line, side, start_line, start_side - Added proper validation for multi-line comment parameters - Improved validation logic to handle parameter combinations - Added test cases for regular and multi-line comments - Updated schema documentation for better tool discoverability 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 4 +- pkg/github/pullrequests.go | 46 +++++++++++--- pkg/github/pullrequests_test.go | 108 ++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3d96bccb5..33db66071 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,9 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `body`: Review comment text (string, optional) - `event`: Review action ('APPROVE', 'REQUEST_CHANGES', 'COMMENT') (string, required) - `commitId`: SHA of commit to review (string, optional) - - `comments`: Line-specific comments array of objects, each object with path (string), either position (number) or line (number), and body (string) (array, optional) + - `comments`: Line-specific comments array of objects to place comments on pull request changes (array, optional) + - For inline comments: provide `path`, `position` (or `line`), and `body` + - For multi-line comments: provide `path`, `start_line`, `line`, optional `side`/`start_side`, and `body` - **create_pull_request** - Create a new pull request diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index 3f955a842..a43d5b883 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -605,7 +605,19 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe }, "line": map[string]interface{}{ "type": "number", - "description": "line number in the file to comment on (alternative to position)", + "description": "line number in the file to comment on. For multi-line comments, the end of the line range", + }, + "side": map[string]interface{}{ + "type": "string", + "description": "The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. (LEFT or RIGHT)", + }, + "start_line": map[string]interface{}{ + "type": "number", + "description": "The first line of the range to which the comment refers. Required for multi-line comments.", + }, + "start_side": map[string]interface{}{ + "type": "string", + "description": "The side of the diff on which the start line resides for multi-line comments. (LEFT or RIGHT)", }, "body": map[string]interface{}{ "type": "string", @@ -614,7 +626,7 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe }, }, ), - mcp.Description("Line-specific comments array of objects, each object with path (string), either position (number) or line (number), and body (string)"), + mcp.Description("Line-specific comments array of objects to place comments on pull request changes. Requires path and body. For line comments use line or position. For multi-line comments use start_line and line with optional side parameters."), ), ), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { @@ -678,6 +690,21 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe return mcp.NewToolResultError("each comment must have a body"), nil } + _, hasPosition := commentMap["position"].(float64) + _, hasLine := commentMap["line"].(float64) + _, hasSide := commentMap["side"].(string) + _, hasStartLine := commentMap["start_line"].(float64) + _, hasStartSide := commentMap["start_side"].(string) + + switch { + case !hasPosition && !hasLine: + return mcp.NewToolResultError("each comment must have either position or line"), nil + case hasPosition && (hasLine || hasSide || hasStartLine || hasStartSide): + return mcp.NewToolResultError("position cannot be combined with line, side, start_line, or start_side"), nil + case hasStartSide && !hasSide: + return mcp.NewToolResultError("if start_side is provided, side must also be provided"), nil + } + comment := &github.DraftReviewComment{ Path: github.Ptr(path), Body: github.Ptr(body), @@ -685,14 +712,17 @@ func createPullRequestReview(client *github.Client, t translations.TranslationHe if positionFloat, ok := commentMap["position"].(float64); ok { comment.Position = github.Ptr(int(positionFloat)) - } - - if lineFloat, ok := commentMap["line"].(float64); ok { + } else if lineFloat, ok := commentMap["line"].(float64); ok { comment.Line = github.Ptr(int(lineFloat)) } - - if comment.Position == nil && comment.Line == nil { - return mcp.NewToolResultError("each comment must have either position or line"), nil + if side, ok := commentMap["side"].(string); ok { + comment.Side = github.Ptr(side) + } + if startLineFloat, ok := commentMap["start_line"].(float64); ok { + comment.StartLine = github.Ptr(int(startLineFloat)) + } + if startSide, ok := commentMap["start_side"].(string); ok { + comment.StartSide = github.Ptr(startSide) } comments = append(comments, comment) diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index c3cd261ca..3b9d27923 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -1206,6 +1206,114 @@ func Test_CreatePullRequestReview(t *testing.T) { expectError: false, expectedReview: mockReview, }, + { + name: "successful review creation with multi-line comment", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.PostReposPullsReviewsByOwnerByRepoByPullNumber, + expectRequestBody(t, map[string]interface{}{ + "body": "Multi-line comment review", + "event": "COMMENT", + "comments": []interface{}{ + map[string]interface{}{ + "path": "main.go", + "start_line": float64(10), + "line": float64(15), + "side": "RIGHT", + "body": "This entire block needs refactoring", + }, + }, + }).andThen( + mockResponse(t, http.StatusOK, mockReview), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "pullNumber": float64(42), + "body": "Multi-line comment review", + "event": "COMMENT", + "comments": []interface{}{ + map[string]interface{}{ + "path": "main.go", + "start_line": float64(10), + "line": float64(15), + "side": "RIGHT", + "body": "This entire block needs refactoring", + }, + }, + }, + expectError: false, + expectedReview: mockReview, + }, + { + name: "invalid multi-line comment - missing line parameter", + mockedClient: mock.NewMockedHTTPClient(), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "pullNumber": float64(42), + "event": "COMMENT", + "comments": []interface{}{ + map[string]interface{}{ + "path": "main.go", + "start_line": float64(10), + // missing line parameter + "body": "Invalid multi-line comment", + }, + }, + }, + expectError: false, + expectedErrMsg: "each comment must have either position or line", // Updated error message + }, + { + name: "invalid comment - mixing position with line parameters", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatch( + mock.PostReposPullsReviewsByOwnerByRepoByPullNumber, + mockReview, + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "pullNumber": float64(42), + "event": "COMMENT", + "comments": []interface{}{ + map[string]interface{}{ + "path": "main.go", + "position": float64(5), + "line": float64(42), + "body": "Invalid parameter combination", + }, + }, + }, + expectError: false, + expectedErrMsg: "position cannot be combined with line, side, start_line, or start_side", + }, + { + name: "invalid multi-line comment - missing side parameter", + mockedClient: mock.NewMockedHTTPClient(), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "pullNumber": float64(42), + "event": "COMMENT", + "comments": []interface{}{ + map[string]interface{}{ + "path": "main.go", + "start_line": float64(10), + "line": float64(15), + "start_side": "LEFT", + // missing side parameter + "body": "Invalid multi-line comment", + }, + }, + }, + expectError: false, + expectedErrMsg: "if start_side is provided, side must also be provided", + }, { name: "review creation fails", mockedClient: mock.NewMockedHTTPClient( From 72d49953b0ac0b9440dd58aa05d3d9b2ce4b3ed1 Mon Sep 17 00:00:00 2001 From: Ashwin Bhat Date: Sun, 6 Apr 2025 13:41:05 -0700 Subject: [PATCH 3/3] gofmt --- pkg/github/pullrequests_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index 3b9d27923..9e2e9f478 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -1248,7 +1248,7 @@ func Test_CreatePullRequestReview(t *testing.T) { expectedReview: mockReview, }, { - name: "invalid multi-line comment - missing line parameter", + name: "invalid multi-line comment - missing line parameter", mockedClient: mock.NewMockedHTTPClient(), requestArgs: map[string]interface{}{ "owner": "owner", @@ -1293,7 +1293,7 @@ func Test_CreatePullRequestReview(t *testing.T) { expectedErrMsg: "position cannot be combined with line, side, start_line, or start_side", }, { - name: "invalid multi-line comment - missing side parameter", + name: "invalid multi-line comment - missing side parameter", mockedClient: mock.NewMockedHTTPClient(), requestArgs: map[string]interface{}{ "owner": "owner", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy