Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commite0f735d

Browse files
ashwin-antClaude
and
Claude
committed
Add reply_to_pull_request_review_comment tool
Adds a new tool to reply to existing pull request review comments using the GitHub API's comment reply endpoint. This allows for threaded discussions on pull request reviews.🤖 Generated with [Claude Code](https://claude.ai/code)Co-Authored-By: Claude <noreply@anthropic.com>
1 parent8b5299a commite0f735d

File tree

3 files changed

+175
-0
lines changed

3 files changed

+175
-0
lines changed

‎pkg/github/pullrequests.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,77 @@ func AddPullRequestReviewComment(client *github.Client, t translations.Translati
649649
}
650650
}
651651

652+
// ReplyToPullRequestReviewComment creates a tool to reply to an existing review comment on a pull request.
653+
funcReplyToPullRequestReviewComment(client*github.Client,t translations.TranslationHelperFunc) (tool mcp.Tool,
654+
handler server.ToolHandlerFunc) {
655+
returnmcp.NewTool("reply_to_pull_request_review_comment",
656+
mcp.WithDescription(t("TOOL_REPLY_TO_PULL_REQUEST_REVIEW_COMMENT_DESCRIPTION","Reply to an existing review comment on a pull request")),
657+
mcp.WithString("owner",
658+
mcp.Required(),
659+
mcp.Description("Repository owner"),
660+
),
661+
mcp.WithString("repo",
662+
mcp.Required(),
663+
mcp.Description("Repository name"),
664+
),
665+
mcp.WithNumber("pull_number",
666+
mcp.Required(),
667+
mcp.Description("Pull request number"),
668+
),
669+
mcp.WithNumber("comment_id",
670+
mcp.Required(),
671+
mcp.Description("The unique identifier of the comment to reply to"),
672+
),
673+
mcp.WithString("body",
674+
mcp.Required(),
675+
mcp.Description("The text of the reply comment"),
676+
),
677+
),
678+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
679+
owner,err:=requiredParam[string](request,"owner")
680+
iferr!=nil {
681+
returnmcp.NewToolResultError(err.Error()),nil
682+
}
683+
repo,err:=requiredParam[string](request,"repo")
684+
iferr!=nil {
685+
returnmcp.NewToolResultError(err.Error()),nil
686+
}
687+
pullNumber,err:=requiredInt(request,"pull_number")
688+
iferr!=nil {
689+
returnmcp.NewToolResultError(err.Error()),nil
690+
}
691+
commentID,err:=requiredInt(request,"comment_id")
692+
iferr!=nil {
693+
returnmcp.NewToolResultError(err.Error()),nil
694+
}
695+
body,err:=requiredParam[string](request,"body")
696+
iferr!=nil {
697+
returnmcp.NewToolResultError(err.Error()),nil
698+
}
699+
700+
createdReply,resp,err:=client.PullRequests.CreateCommentInReplyTo(ctx,owner,repo,pullNumber,body,int64(commentID))
701+
iferr!=nil {
702+
returnnil,fmt.Errorf("failed to reply to pull request comment: %w",err)
703+
}
704+
deferfunc() {_=resp.Body.Close() }()
705+
706+
ifresp.StatusCode!=http.StatusCreated {
707+
body,err:=io.ReadAll(resp.Body)
708+
iferr!=nil {
709+
returnnil,fmt.Errorf("failed to read response body: %w",err)
710+
}
711+
returnmcp.NewToolResultError(fmt.Sprintf("failed to reply to pull request comment: %s",string(body))),nil
712+
}
713+
714+
r,err:=json.Marshal(createdReply)
715+
iferr!=nil {
716+
returnnil,fmt.Errorf("failed to marshal response: %w",err)
717+
}
718+
719+
returnmcp.NewToolResultText(string(r)),nil
720+
}
721+
}
722+
652723
// GetPullRequestReviews creates a tool to get the reviews on a pull request.
653724
funcGetPullRequestReviews(client*github.Client,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
654725
returnmcp.NewTool("get_pull_request_reviews",

‎pkg/github/pullrequests_test.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,3 +1650,106 @@ func Test_AddPullRequestReviewComment(t *testing.T) {
16501650
})
16511651
}
16521652
}
1653+
1654+
funcTest_ReplyToPullRequestReviewComment(t*testing.T) {
1655+
// Verify tool definition once
1656+
mockClient:=github.NewClient(nil)
1657+
tool,_:=ReplyToPullRequestReviewComment(mockClient,translations.NullTranslationHelper)
1658+
1659+
assert.Equal(t,"reply_to_pull_request_review_comment",tool.Name)
1660+
assert.NotEmpty(t,tool.Description)
1661+
assert.Contains(t,tool.InputSchema.Properties,"owner")
1662+
assert.Contains(t,tool.InputSchema.Properties,"repo")
1663+
assert.Contains(t,tool.InputSchema.Properties,"pull_number")
1664+
assert.Contains(t,tool.InputSchema.Properties,"comment_id")
1665+
assert.Contains(t,tool.InputSchema.Properties,"body")
1666+
assert.ElementsMatch(t,tool.InputSchema.Required, []string{"owner","repo","pull_number","comment_id","body"})
1667+
1668+
// Setup mock PR comment for success case
1669+
mockReply:=&github.PullRequestComment{
1670+
ID:github.Ptr(int64(456)),
1671+
Body:github.Ptr("Good point, will fix!"),
1672+
}
1673+
1674+
tests:= []struct {
1675+
namestring
1676+
mockedClient*http.Client
1677+
requestArgsmap[string]interface{}
1678+
expectErrorbool
1679+
expectedReply*github.PullRequestComment
1680+
expectedErrMsgstring
1681+
}{
1682+
{
1683+
name:"successful reply creation",
1684+
mockedClient:mock.NewMockedHTTPClient(
1685+
mock.WithRequestMatchHandler(
1686+
mock.PostReposPullsCommentsByOwnerByRepoByPullNumber,
1687+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
1688+
w.WriteHeader(http.StatusCreated)
1689+
json.NewEncoder(w).Encode(mockReply)
1690+
}),
1691+
),
1692+
),
1693+
requestArgs:map[string]interface{}{
1694+
"owner":"owner",
1695+
"repo":"repo",
1696+
"pull_number":float64(1),
1697+
"comment_id":float64(123),
1698+
"body":"Good point, will fix!",
1699+
},
1700+
expectError:false,
1701+
expectedReply:mockReply,
1702+
},
1703+
{
1704+
name:"reply creation fails",
1705+
mockedClient:mock.NewMockedHTTPClient(
1706+
mock.WithRequestMatchHandler(
1707+
mock.PostReposPullsCommentsByOwnerByRepoByPullNumber,
1708+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
1709+
w.WriteHeader(http.StatusNotFound)
1710+
w.Header().Set("Content-Type","application/json")
1711+
_,_=w.Write([]byte(`{"message": "Comment not found"}`))
1712+
}),
1713+
),
1714+
),
1715+
requestArgs:map[string]interface{}{
1716+
"owner":"owner",
1717+
"repo":"repo",
1718+
"pull_number":float64(1),
1719+
"comment_id":float64(999),
1720+
"body":"Good point, will fix!",
1721+
},
1722+
expectError:true,
1723+
expectedErrMsg:"failed to reply to pull request comment",
1724+
},
1725+
}
1726+
1727+
for_,tc:=rangetests {
1728+
t.Run(tc.name,func(t*testing.T) {
1729+
mockClient:=github.NewClient(tc.mockedClient)
1730+
1731+
_,handler:=replyToPullRequestReviewComment(mockClient,translations.NullTranslationHelper)
1732+
1733+
request:=createMCPRequest(tc.requestArgs)
1734+
1735+
result,err:=handler(context.Background(),request)
1736+
1737+
iftc.name=="reply creation fails" {
1738+
require.Error(t,err)
1739+
assert.Contains(t,err.Error(),tc.expectedErrMsg)
1740+
return
1741+
}
1742+
1743+
require.NoError(t,err)
1744+
assert.NotNil(t,result)
1745+
require.Len(t,result.Content,1)
1746+
1747+
varreturnedReply github.PullRequestComment
1748+
err=json.Unmarshal([]byte(getTextResult(t,result).Text),&returnedReply)
1749+
require.NoError(t,err)
1750+
1751+
assert.Equal(t,*tc.expectedReply.ID,*returnedReply.ID)
1752+
assert.Equal(t,*tc.expectedReply.Body,*returnedReply.Body)
1753+
})
1754+
}
1755+
}

‎pkg/github/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func NewServer(client *github.Client, version string, readOnly bool, t translati
5454
s.AddTool(CreatePullRequestReview(client,t))
5555
s.AddTool(CreatePullRequest(client,t))
5656
s.AddTool(AddPullRequestReviewComment(client,t))
57+
s.AddTool(ReplyToPullRequestReviewComment(client,t))
5758
}
5859

5960
// Add GitHub tools - Repositories

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp