@@ -206,3 +206,199 @@ func TestToolsets(t *testing.T) {
206206 require .True (t , toolsContains ("list_branches" ), "expected to find 'list_branches' tool" )
207207 require .False (t , toolsContains ("get_pull_request" ), "expected not to find 'get_pull_request' tool" )
208208}
209+
210+ func TestPullRequestReview (t * testing.T ) {
211+ t .Parallel ()
212+
213+ mcpClient := setupMCPClient (t )
214+
215+ ctx := context .Background ()
216+
217+ // First, who am I
218+ getMeRequest := mcp.CallToolRequest {}
219+ getMeRequest .Params .Name = "get_me"
220+
221+ t .Log ("Getting current user..." )
222+ resp , err := mcpClient .CallTool (ctx , getMeRequest )
223+ require .NoError (t , err , "expected to call 'get_me' tool successfully" )
224+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
225+
226+ require .False (t , resp .IsError , "expected result not to be an error" )
227+ require .Len (t , resp .Content , 1 , "expected content to have one item" )
228+
229+ textContent , ok := resp .Content [0 ].(mcp.TextContent )
230+ require .True (t , ok , "expected content to be of type TextContent" )
231+
232+ var trimmedGetMeText struct {
233+ Login string `json:"login"`
234+ }
235+ err = json .Unmarshal ([]byte (textContent .Text ), & trimmedGetMeText )
236+ require .NoError (t , err , "expected to unmarshal text content successfully" )
237+
238+ currentOwner := trimmedGetMeText .Login
239+
240+ // Then create a repository with a README (via autoInit)
241+ repoName := fmt .Sprintf ("github-mcp-server-e2e-%s-%d" , t .Name (), time .Now ().UnixMilli ())
242+ createRepoRequest := mcp.CallToolRequest {}
243+ createRepoRequest .Params .Name = "create_repository"
244+ createRepoRequest .Params .Arguments = map [string ]any {
245+ "name" : repoName ,
246+ "private" : true ,
247+ "autoInit" : true ,
248+ }
249+
250+ t .Logf ("Creating repository %s/%s..." , currentOwner , repoName )
251+ _ , err = mcpClient .CallTool (ctx , createRepoRequest )
252+ require .NoError (t , err , "expected to call 'get_me' tool successfully" )
253+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
254+
255+ // Cleanup the repository after the test
256+ t .Cleanup (func () {
257+ // MCP Server doesn't support deletions, but we can use the GitHub Client
258+ ghClient := github .NewClient (nil ).WithAuthToken (getE2EToken (t ))
259+ t .Logf ("Deleting repository %s/%s..." , currentOwner , repoName )
260+ _ , err := ghClient .Repositories .Delete (context .Background (), currentOwner , repoName )
261+ require .NoError (t , err , "expected to delete repository successfully" )
262+ })
263+
264+ // Create a branch on which to create a new commit
265+ createBranchRequest := mcp.CallToolRequest {}
266+ createBranchRequest .Params .Name = "create_branch"
267+ createBranchRequest .Params .Arguments = map [string ]any {
268+ "owner" : currentOwner ,
269+ "repo" : repoName ,
270+ "branch" : "test-branch" ,
271+ "from_branch" : "main" ,
272+ }
273+
274+ t .Logf ("Creating branch in %s/%s..." , currentOwner , repoName )
275+ resp , err = mcpClient .CallTool (ctx , createBranchRequest )
276+ require .NoError (t , err , "expected to call 'create_branch' tool successfully" )
277+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
278+
279+ // Create a commit with a new file
280+ commitRequest := mcp.CallToolRequest {}
281+ commitRequest .Params .Name = "create_or_update_file"
282+ commitRequest .Params .Arguments = map [string ]any {
283+ "owner" : currentOwner ,
284+ "repo" : repoName ,
285+ "path" : "test-file.txt" ,
286+ "content" : fmt .Sprintf ("Created by e2e test %s" , t .Name ()),
287+ "message" : "Add test file" ,
288+ "branch" : "test-branch" ,
289+ }
290+
291+ t .Logf ("Creating commit with new file in %s/%s..." , currentOwner , repoName )
292+ resp , err = mcpClient .CallTool (ctx , commitRequest )
293+ require .NoError (t , err , "expected to call 'create_or_update_file' tool successfully" )
294+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
295+
296+ textContent , ok = resp .Content [0 ].(mcp.TextContent )
297+ require .True (t , ok , "expected content to be of type TextContent" )
298+
299+ var trimmedCommitText struct {
300+ SHA string `json:"sha"`
301+ }
302+ err = json .Unmarshal ([]byte (textContent .Text ), & trimmedCommitText )
303+ require .NoError (t , err , "expected to unmarshal text content successfully" )
304+ commitId := trimmedCommitText .SHA
305+
306+ // Create a pull request
307+ prRequest := mcp.CallToolRequest {}
308+ prRequest .Params .Name = "create_pull_request"
309+ prRequest .Params .Arguments = map [string ]any {
310+ "owner" : currentOwner ,
311+ "repo" : repoName ,
312+ "title" : "Test PR" ,
313+ "body" : "This is a test PR" ,
314+ "head" : "test-branch" ,
315+ "base" : "main" ,
316+ "commitId" : commitId ,
317+ }
318+
319+ t .Logf ("Creating pull request in %s/%s..." , currentOwner , repoName )
320+ resp , err = mcpClient .CallTool (ctx , prRequest )
321+ require .NoError (t , err , "expected to call 'create_pull_request' tool successfully" )
322+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
323+
324+ // Create a review for the pull request, but we can't approve it
325+ // because the current owner also owns the PR.
326+ createPendingPullRequestReviewRequest := mcp.CallToolRequest {}
327+ createPendingPullRequestReviewRequest .Params .Name = "mvp_create_pending_pull_request_review"
328+ createPendingPullRequestReviewRequest .Params .Arguments = map [string ]any {
329+ "owner" : currentOwner ,
330+ "repo" : repoName ,
331+ "pullNumber" : 1 ,
332+ }
333+
334+ t .Logf ("Creating pending review for pull request in %s/%s..." , currentOwner , repoName )
335+ resp , err = mcpClient .CallTool (ctx , createPendingPullRequestReviewRequest )
336+ require .NoError (t , err , "expected to call 'mvp_create_pending_pull_request_review' tool successfully" )
337+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
338+
339+ textContent , ok = resp .Content [0 ].(mcp.TextContent )
340+ require .True (t , ok , "expected content to be of type TextContent" )
341+
342+ var trimmedReviewRequestResponse struct {
343+ PullRequestReviewID string `json:"pullRequestReviewID"`
344+ }
345+ err = json .Unmarshal ([]byte (textContent .Text ), & trimmedReviewRequestResponse )
346+ require .NoError (t , err , "expected to unmarshal text content successfully" )
347+ pullRequestReviewId := trimmedReviewRequestResponse .PullRequestReviewID
348+
349+ // Add a review comment
350+ addReviewCommentRequest := mcp.CallToolRequest {}
351+ addReviewCommentRequest .Params .Name = "mvp_add_pull_request_review_comment"
352+ addReviewCommentRequest .Params .Arguments = map [string ]any {
353+ "path" : "test-file.txt" ,
354+ "body" : "Very nice!" ,
355+ "line" : 1 ,
356+ "pullRequestReviewID" : pullRequestReviewId ,
357+ }
358+
359+ t .Logf ("Adding review comment to pull request in %s/%s..." , currentOwner , repoName )
360+ resp , err = mcpClient .CallTool (ctx , addReviewCommentRequest )
361+ require .NoError (t , err , "expected to call 'add_pull_request_review_comment' tool successfully" )
362+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
363+
364+ // Submit the review
365+ submitReviewRequest := mcp.CallToolRequest {}
366+ submitReviewRequest .Params .Name = "mvp_submit_pull_request_review"
367+ submitReviewRequest .Params .Arguments = map [string ]any {
368+ "event" : "COMMENT" , // the only event we can use as the creator of the PR
369+ "body" : "Needs improvement!" ,
370+ "pullRequestReviewID" : pullRequestReviewId ,
371+ }
372+
373+ t .Logf ("Submitting review for pull request in %s/%s..." , currentOwner , repoName )
374+ resp , err = mcpClient .CallTool (ctx , submitReviewRequest )
375+ require .NoError (t , err , "expected to call 'mvp_submit_pull_request_review' tool successfully" )
376+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
377+
378+ // Finally, get the review and see that it has been created
379+ getPullRequestsReview := mcp.CallToolRequest {}
380+ getPullRequestsReview .Params .Name = "get_pull_request_reviews"
381+ getPullRequestsReview .Params .Arguments = map [string ]any {
382+ "owner" : currentOwner ,
383+ "repo" : repoName ,
384+ "pullNumber" : 1 ,
385+ }
386+
387+ t .Logf ("Getting reviews for pull request in %s/%s..." , currentOwner , repoName )
388+ resp , err = mcpClient .CallTool (ctx , getPullRequestsReview )
389+ require .NoError (t , err , "expected to call 'get_pull_request_reviews' tool successfully" )
390+ require .False (t , resp .IsError , fmt .Sprintf ("expected result not to be an error: %+v" , resp ))
391+
392+ textContent , ok = resp .Content [0 ].(mcp.TextContent )
393+ require .True (t , ok , "expected content to be of type TextContent" )
394+
395+ var reviews []struct {
396+ NodeID string `json:"node_id"`
397+ }
398+ err = json .Unmarshal ([]byte (textContent .Text ), & reviews )
399+ require .NoError (t , err , "expected to unmarshal text content successfully" )
400+
401+ // Check our review is the only one in the list
402+ require .Len (t , reviews , 1 , "expected to find one review" )
403+ require .Equal (t , pullRequestReviewId , reviews [0 ].NodeID , "expected to find our review in the list" )
404+ }
0 commit comments