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

Commit7801e97

Browse files
committed
feat: Add mark_pr_ready_for_review tool
This commit introduces a new tool that allows changing a GitHub pullrequest from draft state to ready for review.The implementation uses GitHub's GraphQL API with the'markPullRequestReadyForReview' mutation, as the REST API doesn'tsupport this state transition for existing draft PRs.Also fixes the GraphQL query by correcting 'Draft' to 'IsDraft' fieldname, ensuring proper detection of a PR's draft status.The changes include:- New tool implementation in the pull_requests toolset- Comprehensive tests- GraphQL query field correction
1 parente9f748f commit7801e97

File tree

3 files changed

+310
-0
lines changed

3 files changed

+310
-0
lines changed

‎pkg/github/pullrequests.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,3 +1606,87 @@ func newGQLIntPtr(i *int32) *githubv4.Int {
16061606
gi:=githubv4.Int(*i)
16071607
return&gi
16081608
}
1609+
1610+
// MarkPullRequestReadyForReview creates a tool to mark a draft pull request as ready for review.
1611+
// This uses the GraphQL API because the REST API does not support changing a PR from draft to ready-for-review.
1612+
funcMarkPullRequestReadyForReview(getGQLClientGetGQLClientFn,t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) {
1613+
returnmcp.NewTool("mark_pr_ready_for_review",
1614+
mcp.WithDescription(t("TOOL_MARK_PR_READY_FOR_REVIEW_DESCRIPTION","Mark a draft pull request as ready for review. Use this to change a pull request from draft state to ready for review.")),
1615+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
1616+
Title:t("TOOL_MARK_PR_READY_FOR_REVIEW_USER_TITLE","Mark pull request ready for review"),
1617+
ReadOnlyHint:toBoolPtr(false),
1618+
}),
1619+
mcp.WithString("owner",
1620+
mcp.Required(),
1621+
mcp.Description("Repository owner"),
1622+
),
1623+
mcp.WithString("repo",
1624+
mcp.Required(),
1625+
mcp.Description("Repository name"),
1626+
),
1627+
mcp.WithNumber("pullNumber",
1628+
mcp.Required(),
1629+
mcp.Description("Pull request number"),
1630+
),
1631+
),
1632+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
1633+
varparamsstruct {
1634+
Ownerstring
1635+
Repostring
1636+
PullNumberint32
1637+
}
1638+
iferr:=mapstructure.Decode(request.Params.Arguments,&params);err!=nil {
1639+
returnmcp.NewToolResultError(err.Error()),nil
1640+
}
1641+
1642+
// Get the GraphQL client
1643+
client,err:=getGQLClient(ctx)
1644+
iferr!=nil {
1645+
returnnil,fmt.Errorf("failed to get GitHub GraphQL client: %w",err)
1646+
}
1647+
1648+
// First, we need to get the GraphQL ID of the pull request
1649+
vargetPullRequestQuerystruct {
1650+
Repositorystruct {
1651+
PullRequeststruct {
1652+
ID githubv4.ID
1653+
IsDraft githubv4.Boolean
1654+
}`graphql:"pullRequest(number: $prNum)"`
1655+
}`graphql:"repository(owner: $owner, name: $repo)"`
1656+
}
1657+
1658+
variables:=map[string]any{
1659+
"owner":githubv4.String(params.Owner),
1660+
"repo":githubv4.String(params.Repo),
1661+
"prNum":githubv4.Int(params.PullNumber),
1662+
}
1663+
1664+
iferr:=client.Query(ctx,&getPullRequestQuery,variables);err!=nil {
1665+
returnmcp.NewToolResultError(fmt.Sprintf("failed to get pull request: %v",err)),nil
1666+
}
1667+
1668+
// Check if the PR is already in non-draft state
1669+
if!getPullRequestQuery.Repository.PullRequest.IsDraft {
1670+
returnmcp.NewToolResultText("Pull request is already marked as ready for review"),nil
1671+
}
1672+
1673+
// Now we can mark the PR as ready for review using the GraphQL mutation
1674+
varmarkReadyForReviewMutationstruct {
1675+
MarkPullRequestReadyForReviewstruct {
1676+
PullRequeststruct {
1677+
ID githubv4.ID// We don't need this, but a selector is required or GQL complains
1678+
}
1679+
}`graphql:"markPullRequestReadyForReview(input: $input)"`
1680+
}
1681+
1682+
input:= githubv4.MarkPullRequestReadyForReviewInput{
1683+
PullRequestID:getPullRequestQuery.Repository.PullRequest.ID,
1684+
}
1685+
1686+
iferr:=client.Mutate(ctx,&markReadyForReviewMutation,input,nil);err!=nil {
1687+
returnmcp.NewToolResultError(fmt.Sprintf("failed to mark pull request as ready for review: %v",err)),nil
1688+
}
1689+
1690+
returnmcp.NewToolResultText("Pull request successfully marked as ready for review"),nil
1691+
}
1692+
}

‎pkg/github/pullrequests_test.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,3 +2291,228 @@ func getLatestPendingReviewQuery(p getLatestPendingReviewQueryParams) githubv4mo
22912291
),
22922292
)
22932293
}
2294+
2295+
funcTestMarkPullRequestReadyForReview(t*testing.T) {
2296+
t.Parallel()
2297+
2298+
// Verify tool definition once
2299+
mockClient:=githubv4.NewClient(nil)
2300+
tool,_:=MarkPullRequestReadyForReview(stubGetGQLClientFn(mockClient),translations.NullTranslationHelper)
2301+
2302+
assert.Equal(t,"mark_pr_ready_for_review",tool.Name)
2303+
assert.NotEmpty(t,tool.Description)
2304+
assert.Contains(t,tool.InputSchema.Properties,"owner")
2305+
assert.Contains(t,tool.InputSchema.Properties,"repo")
2306+
assert.Contains(t,tool.InputSchema.Properties,"pullNumber")
2307+
assert.ElementsMatch(t,tool.InputSchema.Required, []string{"owner","repo","pullNumber"})
2308+
2309+
tests:= []struct {
2310+
namestring
2311+
mockedClient*http.Client
2312+
requestArgsmap[string]any
2313+
expectToolErrorbool
2314+
expectedToolErrMsgstring
2315+
prIsDraftbool
2316+
}{
2317+
{
2318+
name:"successful mark ready for review",
2319+
mockedClient:githubv4mock.NewMockedHTTPClient(
2320+
githubv4mock.NewQueryMatcher(
2321+
struct {
2322+
Repositorystruct {
2323+
PullRequeststruct {
2324+
ID githubv4.ID
2325+
IsDraft githubv4.Boolean
2326+
}`graphql:"pullRequest(number: $prNum)"`
2327+
}`graphql:"repository(owner: $owner, name: $repo)"`
2328+
}{},
2329+
map[string]any{
2330+
"owner":githubv4.String("owner"),
2331+
"repo":githubv4.String("repo"),
2332+
"prNum":githubv4.Int(42),
2333+
},
2334+
githubv4mock.DataResponse(
2335+
map[string]any{
2336+
"repository":map[string]any{
2337+
"pullRequest":map[string]any{
2338+
"id":"PR_kwDODKw3uc6WYN1T",
2339+
"isDraft":true,
2340+
},
2341+
},
2342+
},
2343+
),
2344+
),
2345+
githubv4mock.NewMutationMatcher(
2346+
struct {
2347+
MarkPullRequestReadyForReviewstruct {
2348+
PullRequeststruct {
2349+
ID githubv4.ID
2350+
}
2351+
}`graphql:"markPullRequestReadyForReview(input: $input)"`
2352+
}{},
2353+
githubv4.MarkPullRequestReadyForReviewInput{
2354+
PullRequestID:githubv4.ID("PR_kwDODKw3uc6WYN1T"),
2355+
},
2356+
nil,
2357+
githubv4mock.DataResponse(map[string]any{}),
2358+
),
2359+
),
2360+
requestArgs:map[string]any{
2361+
"owner":"owner",
2362+
"repo":"repo",
2363+
"pullNumber":float64(42),
2364+
},
2365+
expectToolError:false,
2366+
prIsDraft:true,
2367+
},
2368+
{
2369+
name:"PR already ready for review",
2370+
mockedClient:githubv4mock.NewMockedHTTPClient(
2371+
githubv4mock.NewQueryMatcher(
2372+
struct {
2373+
Repositorystruct {
2374+
PullRequeststruct {
2375+
ID githubv4.ID
2376+
IsDraft githubv4.Boolean
2377+
}`graphql:"pullRequest(number: $prNum)"`
2378+
}`graphql:"repository(owner: $owner, name: $repo)"`
2379+
}{},
2380+
map[string]any{
2381+
"owner":githubv4.String("owner"),
2382+
"repo":githubv4.String("repo"),
2383+
"prNum":githubv4.Int(42),
2384+
},
2385+
githubv4mock.DataResponse(
2386+
map[string]any{
2387+
"repository":map[string]any{
2388+
"pullRequest":map[string]any{
2389+
"id":"PR_kwDODKw3uc6WYN1T",
2390+
"isDraft":false,
2391+
},
2392+
},
2393+
},
2394+
),
2395+
),
2396+
),
2397+
requestArgs:map[string]any{
2398+
"owner":"owner",
2399+
"repo":"repo",
2400+
"pullNumber":float64(42),
2401+
},
2402+
expectToolError:false,
2403+
prIsDraft:false,
2404+
},
2405+
{
2406+
name:"failure to get pull request",
2407+
mockedClient:githubv4mock.NewMockedHTTPClient(
2408+
githubv4mock.NewQueryMatcher(
2409+
struct {
2410+
Repositorystruct {
2411+
PullRequeststruct {
2412+
ID githubv4.ID
2413+
IsDraft githubv4.Boolean
2414+
}`graphql:"pullRequest(number: $prNum)"`
2415+
}`graphql:"repository(owner: $owner, name: $repo)"`
2416+
}{},
2417+
map[string]any{
2418+
"owner":githubv4.String("owner"),
2419+
"repo":githubv4.String("repo"),
2420+
"prNum":githubv4.Int(42),
2421+
},
2422+
githubv4mock.ErrorResponse("expected test failure"),
2423+
),
2424+
),
2425+
requestArgs:map[string]any{
2426+
"owner":"owner",
2427+
"repo":"repo",
2428+
"pullNumber":float64(42),
2429+
},
2430+
expectToolError:true,
2431+
expectedToolErrMsg:"failed to get pull request: expected test failure",
2432+
},
2433+
{
2434+
name:"failure to mark ready for review",
2435+
mockedClient:githubv4mock.NewMockedHTTPClient(
2436+
githubv4mock.NewQueryMatcher(
2437+
struct {
2438+
Repositorystruct {
2439+
PullRequeststruct {
2440+
ID githubv4.ID
2441+
IsDraft githubv4.Boolean
2442+
}`graphql:"pullRequest(number: $prNum)"`
2443+
}`graphql:"repository(owner: $owner, name: $repo)"`
2444+
}{},
2445+
map[string]any{
2446+
"owner":githubv4.String("owner"),
2447+
"repo":githubv4.String("repo"),
2448+
"prNum":githubv4.Int(42),
2449+
},
2450+
githubv4mock.DataResponse(
2451+
map[string]any{
2452+
"repository":map[string]any{
2453+
"pullRequest":map[string]any{
2454+
"id":"PR_kwDODKw3uc6WYN1T",
2455+
"isDraft":true,
2456+
},
2457+
},
2458+
},
2459+
),
2460+
),
2461+
githubv4mock.NewMutationMatcher(
2462+
struct {
2463+
MarkPullRequestReadyForReviewstruct {
2464+
PullRequeststruct {
2465+
ID githubv4.ID
2466+
}
2467+
}`graphql:"markPullRequestReadyForReview(input: $input)"`
2468+
}{},
2469+
githubv4.MarkPullRequestReadyForReviewInput{
2470+
PullRequestID:githubv4.ID("PR_kwDODKw3uc6WYN1T"),
2471+
},
2472+
nil,
2473+
githubv4mock.ErrorResponse("expected test failure"),
2474+
),
2475+
),
2476+
requestArgs:map[string]any{
2477+
"owner":"owner",
2478+
"repo":"repo",
2479+
"pullNumber":float64(42),
2480+
},
2481+
expectToolError:true,
2482+
expectedToolErrMsg:"failed to mark pull request as ready for review: expected test failure",
2483+
prIsDraft:true,
2484+
},
2485+
}
2486+
2487+
for_,tc:=rangetests {
2488+
t.Run(tc.name,func(t*testing.T) {
2489+
t.Parallel()
2490+
2491+
// Setup client with mock
2492+
client:=githubv4.NewClient(tc.mockedClient)
2493+
_,handler:=MarkPullRequestReadyForReview(stubGetGQLClientFn(client),translations.NullTranslationHelper)
2494+
2495+
// Create call request
2496+
request:=createMCPRequest(tc.requestArgs)
2497+
2498+
// Call handler
2499+
result,err:=handler(context.Background(),request)
2500+
require.NoError(t,err)
2501+
2502+
textContent:=getTextResult(t,result)
2503+
2504+
iftc.expectToolError {
2505+
require.True(t,result.IsError)
2506+
assert.Contains(t,textContent.Text,tc.expectedToolErrMsg)
2507+
return
2508+
}
2509+
2510+
// Check for the appropriate success message
2511+
iftc.prIsDraft {
2512+
require.Equal(t,"Pull request successfully marked as ready for review",textContent.Text)
2513+
}else {
2514+
require.Equal(t,"Pull request is already marked as ready for review",textContent.Text)
2515+
}
2516+
})
2517+
}
2518+
}

‎pkg/github/tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn,
7272
toolsets.NewServerTool(UpdatePullRequestBranch(getClient,t)),
7373
toolsets.NewServerTool(CreatePullRequest(getClient,t)),
7474
toolsets.NewServerTool(UpdatePullRequest(getClient,t)),
75+
toolsets.NewServerTool(MarkPullRequestReadyForReview(getGQLClient,t)),
7576
toolsets.NewServerTool(RequestCopilotReview(getClient,t)),
7677

7778
// Reviews

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp