Skip to content

Commit bea8311

Browse files
authored
Support only finding closing PRs within one repo
1 parent f21928a commit bea8311

File tree

4 files changed

+55
-268
lines changed

4 files changed

+55
-268
lines changed

e2e/e2e_test.go

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,41 +1636,46 @@ func TestFindClosingPullRequests(t *testing.T) {
16361636
// Test with well-known GitHub repositories and issues
16371637
testCases := []struct {
16381638
name string
1639-
issues []interface{}
1639+
owner string
1640+
repo string
1641+
issueNumbers []int
16401642
limit int
16411643
expectError bool
16421644
expectedResults int
16431645
expectSomeWithClosingPR bool
16441646
}{
16451647
{
16461648
name: "Single issue test - should handle gracefully even if no closing PRs",
1647-
issues: []interface{}{"octocat/Hello-World#1"},
1649+
owner: "octocat",
1650+
repo: "Hello-World",
1651+
issueNumbers: []int{1},
16481652
limit: 5,
16491653
expectError: false,
16501654
expectedResults: 1,
16511655
},
16521656
{
16531657
name: "Multiple issues test",
1654-
issues: []interface{}{"octocat/Hello-World#1", "github/docs#1"},
1658+
owner: "github",
1659+
repo: "docs",
1660+
issueNumbers: []int{1, 2},
16551661
limit: 3,
16561662
expectError: false,
16571663
expectedResults: 2,
16581664
},
16591665
{
1660-
name: "Invalid issue format should return error",
1661-
issues: []interface{}{"invalid-format"},
1662-
expectError: true,
1666+
name: "Empty issue_numbers array should return error",
1667+
owner: "octocat",
1668+
repo: "Hello-World",
1669+
issueNumbers: []int{},
1670+
expectError: true,
16631671
},
16641672
{
1665-
name: "Empty issues array should return error",
1666-
issues: []interface{}{},
1667-
expectError: true,
1668-
},
1669-
{
1670-
name: "Limit too high should return error",
1671-
issues: []interface{}{"octocat/Hello-World#1"},
1672-
limit: 150,
1673-
expectError: true,
1673+
name: "Limit too high should return error",
1674+
owner: "octocat",
1675+
repo: "Hello-World",
1676+
issueNumbers: []int{1},
1677+
limit: 150,
1678+
expectError: true,
16741679
},
16751680
}
16761681

@@ -1682,14 +1687,16 @@ func TestFindClosingPullRequests(t *testing.T) {
16821687

16831688
// Build arguments map
16841689
args := map[string]any{
1685-
"issues": tc.issues,
1690+
"owner": tc.owner,
1691+
"repo": tc.repo,
1692+
"issue_numbers": tc.issueNumbers,
16861693
}
16871694
if tc.limit > 0 {
16881695
args["limit"] = tc.limit
16891696
}
16901697
findClosingPRsRequest.Params.Arguments = args
16911698

1692-
t.Logf("Calling find_closing_pull_requests with issues: %v", tc.issues)
1699+
t.Logf("Calling find_closing_pull_requests with owner: %s, repo: %s, issue_numbers: %v", tc.owner, tc.repo, tc.issueNumbers)
16931700
resp, err := mcpClient.CallTool(ctx, findClosingPRsRequest)
16941701

16951702
if tc.expectError {

pkg/github/find_closing_prs_integration_test.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ func TestFindClosingPullRequestsIntegration(t *testing.T) {
4040
owner string
4141
repo string
4242
issueNumbers []int
43-
issueReferences []string
4443
expectedResults int
4544
expectSomeClosingPRs bool
4645
expectSpecificIssue string
@@ -69,12 +68,6 @@ func TestFindClosingPullRequestsIntegration(t *testing.T) {
6968
issueNumbers: []int{1}, // Very first issue in React repo
7069
expectedResults: 1,
7170
},
72-
{
73-
name: "Cross-repository queries using issue_references",
74-
issueReferences: []string{"octocat/Hello-World#1", "facebook/react#1"},
75-
expectedResults: 2,
76-
expectSomeClosingPRs: false, // These might have closing PRs
77-
},
7871
}
7972

8073
ctx := context.Background()
@@ -83,16 +76,10 @@ func TestFindClosingPullRequestsIntegration(t *testing.T) {
8376
t.Run(tc.name, func(t *testing.T) {
8477
// Create request arguments
8578
args := map[string]interface{}{
86-
"limit": 5,
87-
}
88-
89-
// Add appropriate parameters based on test case
90-
if len(tc.issueNumbers) > 0 {
91-
args["owner"] = tc.owner
92-
args["repo"] = tc.repo
93-
args["issue_numbers"] = tc.issueNumbers
94-
} else if len(tc.issueReferences) > 0 {
95-
args["issue_references"] = tc.issueReferences
79+
"limit": 5,
80+
"owner": tc.owner,
81+
"repo": tc.repo,
82+
"issue_numbers": tc.issueNumbers,
9683
}
9784

9885
// Create mock request

pkg/github/issues.go

Lines changed: 27 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9-
"strconv"
109
"strings"
1110
"time"
1211

@@ -1352,33 +1351,28 @@ type ClosingPullRequest struct {
13521351
// FindClosingPullRequests creates a tool to find pull requests that closed specific issues
13531352
func FindClosingPullRequests(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) {
13541353
return mcp.NewTool("find_closing_pull_requests",
1355-
mcp.WithDescription(t("TOOL_FIND_CLOSING_PULL_REQUESTS_DESCRIPTION", "Find pull requests that closed specific issues using closing references. Supports both single repository queries (owner/repo + issue_numbers array) and cross-repository queries (issue_references array).")),
1354+
mcp.WithDescription(t("TOOL_FIND_CLOSING_PULL_REQUESTS_DESCRIPTION", "Find pull requests that closed specific issues using closing references within a repository.")),
13561355
mcp.WithToolAnnotation(mcp.ToolAnnotation{
13571356
Title: t("TOOL_FIND_CLOSING_PULL_REQUESTS_USER_TITLE", "Find closing pull requests"),
13581357
ReadOnlyHint: ToBoolPtr(true),
13591358
}),
13601359
mcp.WithString("owner",
1361-
mcp.Description("The owner of the repository (required if using issue_numbers)"),
1360+
mcp.Description("The owner of the repository"),
1361+
mcp.Required(),
13621362
),
13631363
mcp.WithString("repo",
1364-
mcp.Description("The name of the repository (required if using issue_numbers)"),
1364+
mcp.Description("The name of the repository"),
1365+
mcp.Required(),
13651366
),
13661367
mcp.WithArray("issue_numbers",
13671368
mcp.Description("Array of issue numbers within the specified repository"),
1369+
mcp.Required(),
13681370
mcp.Items(
13691371
map[string]any{
13701372
"type": "number",
13711373
},
13721374
),
13731375
),
1374-
mcp.WithArray("issue_references",
1375-
mcp.Description("Array of issue references in format 'owner/repo#number' for cross-repository queries"),
1376-
mcp.Items(
1377-
map[string]any{
1378-
"type": "string",
1379-
},
1380-
),
1381-
),
13821376
mcp.WithNumber("limit",
13831377
mcp.Description("Maximum number of closing PRs to return per issue (default: 10, max: 100)"),
13841378
),
@@ -1397,21 +1391,23 @@ func FindClosingPullRequests(getGQLClient GetGQLClientFn, t translations.Transla
13971391
}
13981392
}
13991393

1400-
// Get optional parameters
1401-
owner, _ := OptionalParam[string](request, "owner")
1402-
repo, _ := OptionalParam[string](request, "repo")
1403-
issueNumbers, _ := OptionalIntArrayParam(request, "issue_numbers")
1404-
issueReferences, _ := OptionalStringArrayParam(request, "issue_references")
1405-
1406-
// Validate input combinations
1407-
if len(issueNumbers) == 0 && len(issueReferences) == 0 {
1408-
return mcp.NewToolResultError("either issue_numbers or issue_references must be provided"), nil
1394+
// Get required parameters
1395+
owner, err := RequiredParam[string](request, "owner")
1396+
if err != nil {
1397+
return mcp.NewToolResultError(fmt.Sprintf("owner parameter error: %s", err.Error())), nil
1398+
}
1399+
repo, err := RequiredParam[string](request, "repo")
1400+
if err != nil {
1401+
return mcp.NewToolResultError(fmt.Sprintf("repo parameter error: %s", err.Error())), nil
14091402
}
1410-
if len(issueNumbers) > 0 && len(issueReferences) > 0 {
1411-
return mcp.NewToolResultError("provide either issue_numbers OR issue_references, not both"), nil
1403+
issueNumbers, err := OptionalIntArrayParam(request, "issue_numbers")
1404+
if err != nil {
1405+
return mcp.NewToolResultError(fmt.Sprintf("issue_numbers parameter error: %s", err.Error())), nil
14121406
}
1413-
if len(issueNumbers) > 0 && (owner == "" || repo == "") {
1414-
return mcp.NewToolResultError("owner and repo are required when using issue_numbers"), nil
1407+
1408+
// Validate that issue_numbers is not empty
1409+
if len(issueNumbers) == 0 {
1410+
return mcp.NewToolResultError("issue_numbers parameter is required and cannot be empty"), nil
14151411
}
14161412

14171413
// Get GraphQL client
@@ -1420,39 +1416,16 @@ func FindClosingPullRequests(getGQLClient GetGQLClientFn, t translations.Transla
14201416
return nil, fmt.Errorf("failed to get GraphQL client: %w", err)
14211417
}
14221418

1423-
var queries []IssueQuery
1424-
1425-
// Build queries based on input type
1426-
if len(issueNumbers) > 0 {
1427-
// Single repository, multiple issue numbers
1428-
for _, issueNum := range issueNumbers {
1429-
queries = append(queries, IssueQuery{
1430-
Owner: owner,
1431-
Repo: repo,
1432-
IssueNumber: issueNum,
1433-
})
1434-
}
1435-
} else {
1436-
// Multiple issue references
1437-
for _, ref := range issueReferences {
1438-
parsed, err := parseIssueReference(ref)
1439-
if err != nil {
1440-
return mcp.NewToolResultError(fmt.Sprintf("invalid issue reference '%s': %s", ref, err.Error())), nil
1441-
}
1442-
queries = append(queries, parsed)
1443-
}
1444-
}
1445-
1446-
// Process each issue query
1419+
// Process each issue number
14471420
var results []map[string]interface{}
1448-
for _, query := range queries {
1449-
result, err := queryClosingPRsForIssue(ctx, client, query.Owner, query.Repo, query.IssueNumber, limit)
1421+
for _, issueNum := range issueNumbers {
1422+
result, err := queryClosingPRsForIssue(ctx, client, owner, repo, issueNum, limit)
14501423
if err != nil {
14511424
// Add error result for this issue
14521425
results = append(results, map[string]interface{}{
1453-
"owner": query.Owner,
1454-
"repo": query.Repo,
1455-
"issue_number": query.IssueNumber,
1426+
"owner": owner,
1427+
"repo": repo,
1428+
"issue_number": issueNum,
14561429
"error": err.Error(),
14571430
"total_count": 0,
14581431
"closing_pull_requests": []ClosingPullRequest{},
@@ -1476,66 +1449,6 @@ func FindClosingPullRequests(getGQLClient GetGQLClientFn, t translations.Transla
14761449
}
14771450
}
14781451

1479-
// IssueQuery represents a query for a specific issue
1480-
type IssueQuery struct {
1481-
Owner string
1482-
Repo string
1483-
IssueNumber int
1484-
}
1485-
1486-
// parseIssueReference parses an issue reference in the format "owner/repo#number"
1487-
func parseIssueReference(ref string) (IssueQuery, error) {
1488-
if ref == "" {
1489-
return IssueQuery{}, fmt.Errorf("invalid format, expected 'owner/repo#number'")
1490-
}
1491-
1492-
// Find the '#' separator
1493-
hashIndex := strings.LastIndex(ref, "#")
1494-
if hashIndex == -1 || hashIndex == len(ref)-1 {
1495-
return IssueQuery{}, fmt.Errorf("invalid format, expected 'owner/repo#number'")
1496-
}
1497-
1498-
// Split the repo part and issue number
1499-
repoPart := ref[:hashIndex]
1500-
issueNumPart := ref[hashIndex+1:]
1501-
1502-
// Check for multiple hash symbols (invalid case)
1503-
if strings.Count(ref, "#") > 1 {
1504-
return IssueQuery{}, fmt.Errorf("invalid format, expected 'owner/repo#number'")
1505-
}
1506-
1507-
// Parse issue number
1508-
issueNum, err := strconv.Atoi(issueNumPart)
1509-
if err != nil {
1510-
return IssueQuery{}, fmt.Errorf("invalid issue number: %s", issueNumPart)
1511-
}
1512-
1513-
// Check for negative or zero issue numbers (GitHub issue numbers are positive)
1514-
if issueNum <= 0 {
1515-
return IssueQuery{}, fmt.Errorf("invalid issue number: %s", issueNumPart)
1516-
}
1517-
1518-
// Find the '/' separator in repo part
1519-
slashIndex := strings.Index(repoPart, "/")
1520-
if slashIndex == -1 || slashIndex == 0 || slashIndex == len(repoPart)-1 {
1521-
return IssueQuery{}, fmt.Errorf("invalid format, expected 'owner/repo#number'")
1522-
}
1523-
1524-
// Check for multiple slashes in repo part (invalid case like "owner/repo/extra")
1525-
if strings.Count(repoPart, "/") > 1 {
1526-
return IssueQuery{}, fmt.Errorf("invalid format, expected 'owner/repo#number'")
1527-
}
1528-
1529-
owner := repoPart[:slashIndex]
1530-
repo := repoPart[slashIndex+1:]
1531-
1532-
return IssueQuery{
1533-
Owner: owner,
1534-
Repo: repo,
1535-
IssueNumber: issueNum,
1536-
}, nil
1537-
}
1538-
15391452
// queryClosingPRsForIssue queries closing PRs for a single issue
15401453
func queryClosingPRsForIssue(ctx context.Context, client *githubv4.Client, owner, repo string, issueNumber, limit int) (map[string]interface{}, error) {
15411454
// Define the GraphQL query for this specific issue

0 commit comments

Comments
 (0)