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

Commitfb384b6

Browse files
ashwin-antclaude
andcommitted
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 parente94a9f3 commitfb384b6

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
@@ -658,6 +658,77 @@ func addPullRequestReviewComment(client *github.Client, t translations.Translati
658658
}
659659
}
660660

661+
// replyToPullRequestReviewComment creates a tool to reply to an existing review comment on a pull request.
662+
funcreplyToPullRequestReviewComment(client*github.Client,t translations.TranslationHelperFunc) (tool mcp.Tool,
663+
handler server.ToolHandlerFunc) {
664+
returnmcp.NewTool("reply_to_pull_request_review_comment",
665+
mcp.WithDescription(t("TOOL_REPLY_TO_PULL_REQUEST_REVIEW_COMMENT_DESCRIPTION","Reply to an existing review comment on a pull request")),
666+
mcp.WithString("owner",
667+
mcp.Required(),
668+
mcp.Description("Repository owner"),
669+
),
670+
mcp.WithString("repo",
671+
mcp.Required(),
672+
mcp.Description("Repository name"),
673+
),
674+
mcp.WithNumber("pull_number",
675+
mcp.Required(),
676+
mcp.Description("Pull request number"),
677+
),
678+
mcp.WithNumber("comment_id",
679+
mcp.Required(),
680+
mcp.Description("The unique identifier of the comment to reply to"),
681+
),
682+
mcp.WithString("body",
683+
mcp.Required(),
684+
mcp.Description("The text of the reply comment"),
685+
),
686+
),
687+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
688+
owner,err:=requiredParam[string](request,"owner")
689+
iferr!=nil {
690+
returnmcp.NewToolResultError(err.Error()),nil
691+
}
692+
repo,err:=requiredParam[string](request,"repo")
693+
iferr!=nil {
694+
returnmcp.NewToolResultError(err.Error()),nil
695+
}
696+
pullNumber,err:=requiredInt(request,"pull_number")
697+
iferr!=nil {
698+
returnmcp.NewToolResultError(err.Error()),nil
699+
}
700+
commentID,err:=requiredInt(request,"comment_id")
701+
iferr!=nil {
702+
returnmcp.NewToolResultError(err.Error()),nil
703+
}
704+
body,err:=requiredParam[string](request,"body")
705+
iferr!=nil {
706+
returnmcp.NewToolResultError(err.Error()),nil
707+
}
708+
709+
createdReply,resp,err:=client.PullRequests.CreateCommentInReplyTo(ctx,owner,repo,pullNumber,body,int64(commentID))
710+
iferr!=nil {
711+
returnnil,fmt.Errorf("failed to reply to pull request comment: %w",err)
712+
}
713+
deferfunc() {_=resp.Body.Close() }()
714+
715+
ifresp.StatusCode!=http.StatusCreated {
716+
body,err:=io.ReadAll(resp.Body)
717+
iferr!=nil {
718+
returnnil,fmt.Errorf("failed to read response body: %w",err)
719+
}
720+
returnmcp.NewToolResultError(fmt.Sprintf("failed to reply to pull request comment: %s",string(body))),nil
721+
}
722+
723+
r,err:=json.Marshal(createdReply)
724+
iferr!=nil {
725+
returnnil,fmt.Errorf("failed to marshal response: %w",err)
726+
}
727+
728+
returnmcp.NewToolResultText(string(r)),nil
729+
}
730+
}
731+
661732
// getPullRequestReviews creates a tool to get the reviews on a pull request.
662733
funcgetPullRequestReviews(client*github.Client,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
663734
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
@@ -1505,3 +1505,106 @@ func Test_AddPullRequestReviewComment(t *testing.T) {
15051505
})
15061506
}
15071507
}
1508+
1509+
funcTest_ReplyToPullRequestReviewComment(t*testing.T) {
1510+
// Verify tool definition once
1511+
mockClient:=github.NewClient(nil)
1512+
tool,_:=replyToPullRequestReviewComment(mockClient,translations.NullTranslationHelper)
1513+
1514+
assert.Equal(t,"reply_to_pull_request_review_comment",tool.Name)
1515+
assert.NotEmpty(t,tool.Description)
1516+
assert.Contains(t,tool.InputSchema.Properties,"owner")
1517+
assert.Contains(t,tool.InputSchema.Properties,"repo")
1518+
assert.Contains(t,tool.InputSchema.Properties,"pull_number")
1519+
assert.Contains(t,tool.InputSchema.Properties,"comment_id")
1520+
assert.Contains(t,tool.InputSchema.Properties,"body")
1521+
assert.ElementsMatch(t,tool.InputSchema.Required, []string{"owner","repo","pull_number","comment_id","body"})
1522+
1523+
// Setup mock PR comment for success case
1524+
mockReply:=&github.PullRequestComment{
1525+
ID:github.Ptr(int64(456)),
1526+
Body:github.Ptr("Good point, will fix!"),
1527+
}
1528+
1529+
tests:= []struct {
1530+
namestring
1531+
mockedClient*http.Client
1532+
requestArgsmap[string]interface{}
1533+
expectErrorbool
1534+
expectedReply*github.PullRequestComment
1535+
expectedErrMsgstring
1536+
}{
1537+
{
1538+
name:"successful reply creation",
1539+
mockedClient:mock.NewMockedHTTPClient(
1540+
mock.WithRequestMatchHandler(
1541+
mock.PostReposPullsCommentsByOwnerByRepoByPullNumber,
1542+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
1543+
w.WriteHeader(http.StatusCreated)
1544+
json.NewEncoder(w).Encode(mockReply)
1545+
}),
1546+
),
1547+
),
1548+
requestArgs:map[string]interface{}{
1549+
"owner":"owner",
1550+
"repo":"repo",
1551+
"pull_number":float64(1),
1552+
"comment_id":float64(123),
1553+
"body":"Good point, will fix!",
1554+
},
1555+
expectError:false,
1556+
expectedReply:mockReply,
1557+
},
1558+
{
1559+
name:"reply creation fails",
1560+
mockedClient:mock.NewMockedHTTPClient(
1561+
mock.WithRequestMatchHandler(
1562+
mock.PostReposPullsCommentsByOwnerByRepoByPullNumber,
1563+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
1564+
w.WriteHeader(http.StatusNotFound)
1565+
w.Header().Set("Content-Type","application/json")
1566+
_,_=w.Write([]byte(`{"message": "Comment not found"}`))
1567+
}),
1568+
),
1569+
),
1570+
requestArgs:map[string]interface{}{
1571+
"owner":"owner",
1572+
"repo":"repo",
1573+
"pull_number":float64(1),
1574+
"comment_id":float64(999),
1575+
"body":"Good point, will fix!",
1576+
},
1577+
expectError:true,
1578+
expectedErrMsg:"failed to reply to pull request comment",
1579+
},
1580+
}
1581+
1582+
for_,tc:=rangetests {
1583+
t.Run(tc.name,func(t*testing.T) {
1584+
mockClient:=github.NewClient(tc.mockedClient)
1585+
1586+
_,handler:=replyToPullRequestReviewComment(mockClient,translations.NullTranslationHelper)
1587+
1588+
request:=createMCPRequest(tc.requestArgs)
1589+
1590+
result,err:=handler(context.Background(),request)
1591+
1592+
iftc.name=="reply creation fails" {
1593+
require.Error(t,err)
1594+
assert.Contains(t,err.Error(),tc.expectedErrMsg)
1595+
return
1596+
}
1597+
1598+
require.NoError(t,err)
1599+
assert.NotNil(t,result)
1600+
require.Len(t,result.Content,1)
1601+
1602+
varreturnedReply github.PullRequestComment
1603+
err=json.Unmarshal([]byte(getTextResult(t,result).Text),&returnedReply)
1604+
require.NoError(t,err)
1605+
1606+
assert.Equal(t,*tc.expectedReply.ID,*returnedReply.ID)
1607+
assert.Equal(t,*tc.expectedReply.Body,*returnedReply.Body)
1608+
})
1609+
}
1610+
}

‎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, readOnly bool, t translations.TranslationH
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