@@ -203,7 +203,7 @@ func CreatePullRequest(getClient GetClientFn, t translations.TranslationHelperFu
203
203
}
204
204
205
205
// UpdatePullRequest creates a tool to update an existing pull request.
206
- func UpdatePullRequest (getClient GetClientFn , t translations.TranslationHelperFunc ) (mcp.Tool , server.ToolHandlerFunc ) {
206
+ func UpdatePullRequest (getClient GetClientFn , getGQLClient GetGQLClientFn , t translations.TranslationHelperFunc ) (mcp.Tool , server.ToolHandlerFunc ) {
207
207
return mcp .NewTool ("update_pull_request" ,
208
208
mcp .WithDescription (t ("TOOL_UPDATE_PULL_REQUEST_DESCRIPTION" , "Update an existing pull request in a GitHub repository." )),
209
209
mcp .WithToolAnnotation (mcp.ToolAnnotation {
@@ -232,6 +232,9 @@ func UpdatePullRequest(getClient GetClientFn, t translations.TranslationHelperFu
232
232
mcp .Description ("New state" ),
233
233
mcp .Enum ("open" , "closed" ),
234
234
),
235
+ mcp .WithBoolean ("draft" ,
236
+ mcp .Description ("Mark pull request as draft (true) or ready for review (false)" ),
237
+ ),
235
238
mcp .WithString ("base" ,
236
239
mcp .Description ("New base branch name" ),
237
240
),
@@ -259,43 +262,51 @@ func UpdatePullRequest(getClient GetClientFn, t translations.TranslationHelperFu
259
262
return mcp .NewToolResultError (err .Error ()), nil
260
263
}
261
264
262
- // Build the update struct only with provided fields
265
+ draftProvided := request .GetArguments ()["draft" ] != nil
266
+ var draftValue bool
267
+ if draftProvided {
268
+ draftValue , err = OptionalParam [bool ](request , "draft" )
269
+ if err != nil {
270
+ return nil , err
271
+ }
272
+ }
273
+
263
274
update := & github.PullRequest {}
264
- updateNeeded := false
275
+ restUpdateNeeded := false
265
276
266
277
if title , ok , err := OptionalParamOK [string ](request , "title" ); err != nil {
267
278
return mcp .NewToolResultError (err .Error ()), nil
268
279
} else if ok {
269
280
update .Title = github .Ptr (title )
270
- updateNeeded = true
281
+ restUpdateNeeded = true
271
282
}
272
283
273
284
if body , ok , err := OptionalParamOK [string ](request , "body" ); err != nil {
274
285
return mcp .NewToolResultError (err .Error ()), nil
275
286
} else if ok {
276
287
update .Body = github .Ptr (body )
277
- updateNeeded = true
288
+ restUpdateNeeded = true
278
289
}
279
290
280
291
if state , ok , err := OptionalParamOK [string ](request , "state" ); err != nil {
281
292
return mcp .NewToolResultError (err .Error ()), nil
282
293
} else if ok {
283
294
update .State = github .Ptr (state )
284
- updateNeeded = true
295
+ restUpdateNeeded = true
285
296
}
286
297
287
298
if base , ok , err := OptionalParamOK [string ](request , "base" ); err != nil {
288
299
return mcp .NewToolResultError (err .Error ()), nil
289
300
} else if ok {
290
301
update .Base = & github.PullRequestBranch {Ref : github .Ptr (base )}
291
- updateNeeded = true
302
+ restUpdateNeeded = true
292
303
}
293
304
294
305
if maintainerCanModify , ok , err := OptionalParamOK [bool ](request , "maintainer_can_modify" ); err != nil {
295
306
return mcp .NewToolResultError (err .Error ()), nil
296
307
} else if ok {
297
308
update .MaintainerCanModify = github .Ptr (maintainerCanModify )
298
- updateNeeded = true
309
+ restUpdateNeeded = true
299
310
}
300
311
301
312
// Handle reviewers separately
@@ -305,82 +316,115 @@ func UpdatePullRequest(getClient GetClientFn, t translations.TranslationHelperFu
305
316
}
306
317
307
318
// If no updates and no reviewers, return error early
308
- if ! updateNeeded && len (reviewers ) == 0 {
319
+ if ! restUpdateNeeded && len (reviewers ) == 0 && ! draftProvided {
309
320
return mcp .NewToolResultError ("No update parameters provided" ), nil
310
321
}
311
322
312
- // Create the GitHub client
313
323
client , err := getClient (ctx )
314
324
if err != nil {
315
325
return nil , fmt .Errorf ("failed to get GitHub client: %w" , err )
316
326
}
327
+ pr , resp , err := client .PullRequests .Edit (ctx , owner , repo , pullNumber , update )
328
+ if err != nil {
329
+ return ghErrors .NewGitHubAPIErrorResponse (ctx ,
330
+ "failed to update pull request" ,
331
+ resp ,
332
+ err ,
333
+ ), nil
334
+ }
335
+ defer func () { _ = resp .Body .Close () }()
317
336
318
- var pr * github.PullRequest
319
- var resp * http.Response
320
-
321
- // Update the PR if needed
322
- if updateNeeded {
323
- var ghResp * github.Response
324
- pr , ghResp , err = client .PullRequests .Edit (ctx , owner , repo , pullNumber , update )
337
+ if resp .StatusCode != http .StatusOK {
338
+ body , err := io .ReadAll (resp .Body )
325
339
if err != nil {
326
- return ghErrors .NewGitHubAPIErrorResponse (ctx ,
327
- "failed to update pull request" ,
328
- ghResp ,
329
- err ,
330
- ), nil
340
+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
331
341
}
332
- resp = ghResp .Response
333
- defer func () {
334
- if resp != nil && resp .Body != nil {
335
- _ = resp .Body .Close ()
336
- }
337
- }()
342
+ return mcp .NewToolResultError (fmt .Sprintf ("failed to update pull request: %s" , string (body ))), nil
343
+ }
338
344
339
- if resp .StatusCode != http .StatusOK {
340
- body , err := io .ReadAll (resp .Body )
341
- if err != nil {
342
- return nil , fmt .Errorf ("failed to read response body: %w" , err )
343
- }
344
- return mcp .NewToolResultError (fmt .Sprintf ("failed to update pull request: %s" , string (body ))), nil
345
+ if draftProvided {
346
+ gqlClient , err := getGQLClient (ctx )
347
+ if err != nil {
348
+ return nil , fmt .Errorf ("failed to get GitHub GraphQL client: %w" , err )
345
349
}
346
- }
347
350
348
- // Add reviewers if specified
349
- if len (reviewers ) > 0 {
350
- reviewersRequest := github.ReviewersRequest {
351
- Reviewers : reviewers ,
351
+ var prQuery struct {
352
+ Repository struct {
353
+ PullRequest struct {
354
+ ID githubv4.ID
355
+ IsDraft githubv4.Boolean
356
+ } `graphql:"pullRequest(number: $prNum)"`
357
+ } `graphql:"repository(owner: $owner, name: $repo)"`
352
358
}
353
359
354
- // Use the direct result of RequestReviewers which includes the requested reviewers
355
- updatedPR , resp , err := client .PullRequests .RequestReviewers (ctx , owner , repo , pullNumber , reviewersRequest )
360
+ err = gqlClient .Query (ctx , & prQuery , map [string ]interface {}{
361
+ "owner" : githubv4 .String (owner ),
362
+ "repo" : githubv4 .String (repo ),
363
+ "prNum" : githubv4 .Int (pullNumber ), // #nosec G115 - pull request numbers are always small positive integers
364
+ })
356
365
if err != nil {
357
- return ghErrors .NewGitHubAPIErrorResponse (ctx ,
358
- "failed to request reviewers" ,
359
- resp ,
360
- err ,
361
- ), nil
366
+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to find pull request" , err ), nil
362
367
}
363
- defer func () {
364
- if resp != nil && resp .Body != nil {
365
- _ = resp .Body .Close ()
366
- }
367
- }()
368
368
369
- if resp .StatusCode != http .StatusCreated && resp .StatusCode != http .StatusOK {
370
- body , err := io .ReadAll (resp .Body )
371
- if err != nil {
372
- return nil , fmt .Errorf ("failed to read response body: %w" , err )
369
+ currentIsDraft := bool (prQuery .Repository .PullRequest .IsDraft )
370
+
371
+ if currentIsDraft != draftValue {
372
+ if draftValue {
373
+ // Convert to draft
374
+ var mutation struct {
375
+ ConvertPullRequestToDraft struct {
376
+ PullRequest struct {
377
+ ID githubv4.ID
378
+ IsDraft githubv4.Boolean
379
+ }
380
+ } `graphql:"convertPullRequestToDraft(input: $input)"`
381
+ }
382
+
383
+ err = gqlClient .Mutate (ctx , & mutation , githubv4.ConvertPullRequestToDraftInput {
384
+ PullRequestID : prQuery .Repository .PullRequest .ID ,
385
+ }, nil )
386
+ if err != nil {
387
+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to convert pull request to draft" , err ), nil
388
+ }
389
+ } else {
390
+ // Mark as ready for review
391
+ var mutation struct {
392
+ MarkPullRequestReadyForReview struct {
393
+ PullRequest struct {
394
+ ID githubv4.ID
395
+ IsDraft githubv4.Boolean
396
+ }
397
+ } `graphql:"markPullRequestReadyForReview(input: $input)"`
398
+ }
399
+
400
+ err = gqlClient .Mutate (ctx , & mutation , githubv4.MarkPullRequestReadyForReviewInput {
401
+ PullRequestID : prQuery .Repository .PullRequest .ID ,
402
+ }, nil )
403
+ if err != nil {
404
+ return ghErrors .NewGitHubGraphQLErrorResponse (ctx , "Failed to mark pull request ready for review" , err ), nil
405
+ }
373
406
}
374
- return mcp .NewToolResultError (fmt .Sprintf ("failed to request reviewers: %s" , string (body ))), nil
375
407
}
408
+ }
376
409
377
- // Use the updated PR with reviewers
378
- pr = updatedPR
410
+ client , err := getClient (ctx )
411
+ if err != nil {
412
+ return nil , err
379
413
}
380
414
381
- r , err := json . Marshal ( pr )
415
+ finalPR , resp , err := client . PullRequests . Get ( ctx , owner , repo , pullNumber )
382
416
if err != nil {
383
- return nil , fmt .Errorf ("failed to marshal response: %w" , err )
417
+ return ghErrors .NewGitHubAPIErrorResponse (ctx , "Failed to get pull request" , resp , err ), nil
418
+ }
419
+ defer func () {
420
+ if resp != nil && resp .Body != nil {
421
+ _ = resp .Body .Close ()
422
+ }
423
+ }()
424
+
425
+ r , err := json .Marshal (finalPR )
426
+ if err != nil {
427
+ return mcp .NewToolResultError (fmt .Sprintf ("Failed to marshal response: %v" , err )), nil
384
428
}
385
429
386
430
return mcp .NewToolResultText (string (r )), nil
0 commit comments