Skip to content

Commit 5798e04

Browse files
committed
feat: add fork filter parameter to repository search functionality
1 parent bbba3bb commit 5798e04

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
346346
- `query`: Search query (string, required)
347347
- `sort`: Sort field (string, optional)
348348
- `order`: Sort order (string, optional)
349+
- `fork`: Fork filter: 'true' to include forks, 'only' to show only forks, 'false' to exclude forks (default) (string, optional)
349350
- `page`: Page number (number, optional)
350351
- `perPage`: Results per page (number, optional)
351352

pkg/github/search.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8+
"strings"
89

910
"github.com/github/github-mcp-server/pkg/translations"
1011
"github.com/google/go-github/v69/github"
1112
"github.com/mark3labs/mcp-go/mcp"
1213
"github.com/mark3labs/mcp-go/server"
1314
)
1415

16+
// containsForkFilter checks if a query already contains a fork filter.
17+
// This prevents duplicate or conflicting fork filters in the query.
18+
func containsForkFilter(query string) bool {
19+
query = strings.ToLower(query)
20+
return strings.Contains(query, " fork:") || strings.HasPrefix(query, "fork:")
21+
}
22+
1523
// SearchRepositories creates a tool to search for GitHub repositories.
1624
func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
1725
return mcp.NewTool("search_repositories",
@@ -20,18 +28,37 @@ func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperF
2028
mcp.Required(),
2129
mcp.Description("Search query"),
2230
),
31+
mcp.WithString("fork",
32+
mcp.Description("Fork filter: 'true' to include forks, 'only' to show only forks, 'false' to exclude forks (default)"),
33+
mcp.Enum("true", "only", "false"),
34+
),
2335
WithPagination(),
2436
),
2537
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
2638
query, err := requiredParam[string](request, "query")
2739
if err != nil {
2840
return mcp.NewToolResultError(err.Error()), nil
2941
}
42+
fork, err := OptionalParam[string](request, "fork")
43+
if err != nil {
44+
return mcp.NewToolResultError(err.Error()), nil
45+
}
3046
pagination, err := OptionalPaginationParams(request)
3147
if err != nil {
3248
return mcp.NewToolResultError(err.Error()), nil
3349
}
3450

51+
// Modify query to include fork parameter if specified
52+
if fork != "" {
53+
// GitHub API supports 'fork:true', 'fork:only', and absence for excluding forks
54+
if fork == "true" || fork == "only" {
55+
// Check if the query already contains a fork filter
56+
if !containsForkFilter(query) {
57+
query = query + " fork:" + fork
58+
}
59+
}
60+
}
61+
3562
opts := &github.SearchOptions{
3663
ListOptions: github.ListOptions{
3764
Page: pagination.page,

pkg/github/search_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ func Test_SearchRepositories(t *testing.T) {
8888
"q": "golang test",
8989
"page": "1",
9090
"per_page": "30",
91+
// Note: Not specifying 'fork' parameter here, which verifies that
92+
// when 'fork' is not provided in the request, it's not added to the query parameters
9193
}).andThen(
9294
mockResponse(t, http.StatusOK, mockSearchResult),
9395
),
@@ -99,6 +101,69 @@ func Test_SearchRepositories(t *testing.T) {
99101
expectError: false,
100102
expectedResult: mockSearchResult,
101103
},
104+
{
105+
name: "repository search with fork parameter",
106+
mockedClient: mock.NewMockedHTTPClient(
107+
mock.WithRequestMatchHandler(
108+
mock.GetSearchRepositories,
109+
expectQueryParams(t, map[string]string{
110+
"q": "golang test fork:only",
111+
"page": "1",
112+
"per_page": "30",
113+
}).andThen(
114+
mockResponse(t, http.StatusOK, mockSearchResult),
115+
),
116+
),
117+
),
118+
requestArgs: map[string]interface{}{
119+
"query": "golang test",
120+
"fork": "only",
121+
},
122+
expectError: false,
123+
expectedResult: mockSearchResult,
124+
},
125+
{
126+
name: "repository search with fork parameter (true)",
127+
mockedClient: mock.NewMockedHTTPClient(
128+
mock.WithRequestMatchHandler(
129+
mock.GetSearchRepositories,
130+
expectQueryParams(t, map[string]string{
131+
"q": "golang test fork:true",
132+
"page": "1",
133+
"per_page": "30",
134+
}).andThen(
135+
mockResponse(t, http.StatusOK, mockSearchResult),
136+
),
137+
),
138+
),
139+
requestArgs: map[string]interface{}{
140+
"query": "golang test",
141+
"fork": "true",
142+
},
143+
expectError: false,
144+
expectedResult: mockSearchResult,
145+
},
146+
{
147+
name: "repository search with fork parameter (false)",
148+
mockedClient: mock.NewMockedHTTPClient(
149+
mock.WithRequestMatchHandler(
150+
mock.GetSearchRepositories,
151+
expectQueryParams(t, map[string]string{
152+
"q": "golang test",
153+
"page": "1",
154+
"per_page": "30",
155+
}).andThen(
156+
mockResponse(t, http.StatusOK, mockSearchResult),
157+
),
158+
),
159+
),
160+
requestArgs: map[string]interface{}{
161+
"query": "golang test",
162+
"fork": "false",
163+
},
164+
expectError: false,
165+
expectedResult: mockSearchResult,
166+
},
102167
{
103168
name: "search fails",
104169
mockedClient: mock.NewMockedHTTPClient(

0 commit comments

Comments
 (0)