Skip to content

Commit f3e3a40

Browse files
authored
Merge branch 'main' into support-read_only
2 parents f3cb8a6 + 8bd7152 commit f3e3a40

File tree

12 files changed

+840
-156
lines changed

12 files changed

+840
-156
lines changed

pkg/github/__toolsnaps__/get_pull_request_files.snap

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@
1010
"description": "Repository owner",
1111
"type": "string"
1212
},
13+
"page": {
14+
"description": "Page number for pagination (min 1)",
15+
"minimum": 1,
16+
"type": "number"
17+
},
18+
"perPage": {
19+
"description": "Results per page for pagination (min 1, max 100)",
20+
"maximum": 100,
21+
"minimum": 1,
22+
"type": "number"
23+
},
1324
"pullNumber": {
1425
"description": "Pull request number",
1526
"type": "number"

pkg/github/__toolsnaps__/search_issues.snap

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"title": "Search issues",
44
"readOnlyHint": true
55
},
6-
"description": "Search for issues in GitHub repositories.",
6+
"description": "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue",
77
"inputSchema": {
88
"properties": {
99
"order": {
@@ -14,6 +14,10 @@
1414
],
1515
"type": "string"
1616
},
17+
"owner": {
18+
"description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.",
19+
"type": "string"
20+
},
1721
"page": {
1822
"description": "Page number for pagination (min 1)",
1923
"minimum": 1,
@@ -25,10 +29,14 @@
2529
"minimum": 1,
2630
"type": "number"
2731
},
28-
"q": {
32+
"query": {
2933
"description": "Search query using GitHub issues search syntax",
3034
"type": "string"
3135
},
36+
"repo": {
37+
"description": "Optional repository name. If provided with owner, only notifications for this repository are listed.",
38+
"type": "string"
39+
},
3240
"sort": {
3341
"description": "Sort field by number of matches of categories, defaults to best match",
3442
"enum": [
@@ -48,7 +56,7 @@
4856
}
4957
},
5058
"required": [
51-
"q"
59+
"query"
5260
],
5361
"type": "object"
5462
},
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"annotations": {
3+
"title": "Search pull requests",
4+
"readOnlyHint": true
5+
},
6+
"description": "Search for pull requests in GitHub repositories using issues search syntax already scoped to is:pr",
7+
"inputSchema": {
8+
"properties": {
9+
"order": {
10+
"description": "Sort order",
11+
"enum": [
12+
"asc",
13+
"desc"
14+
],
15+
"type": "string"
16+
},
17+
"owner": {
18+
"description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.",
19+
"type": "string"
20+
},
21+
"page": {
22+
"description": "Page number for pagination (min 1)",
23+
"minimum": 1,
24+
"type": "number"
25+
},
26+
"perPage": {
27+
"description": "Results per page for pagination (min 1, max 100)",
28+
"maximum": 100,
29+
"minimum": 1,
30+
"type": "number"
31+
},
32+
"query": {
33+
"description": "Search query using GitHub pull request search syntax",
34+
"type": "string"
35+
},
36+
"repo": {
37+
"description": "Optional repository name. If provided with owner, only notifications for this repository are listed.",
38+
"type": "string"
39+
},
40+
"sort": {
41+
"description": "Sort field by number of matches of categories, defaults to best match",
42+
"enum": [
43+
"comments",
44+
"reactions",
45+
"reactions-+1",
46+
"reactions--1",
47+
"reactions-smile",
48+
"reactions-thinking_face",
49+
"reactions-heart",
50+
"reactions-tada",
51+
"interactions",
52+
"created",
53+
"updated"
54+
],
55+
"type": "string"
56+
}
57+
},
58+
"required": [
59+
"query"
60+
],
61+
"type": "object"
62+
},
63+
"name": "search_pull_requests"
64+
}

pkg/github/__toolsnaps__/search_users.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"title": "Search users",
44
"readOnlyHint": true
55
},
6-
"description": "Search for GitHub users",
6+
"description": "Search for GitHub users exclusively",
77
"inputSchema": {
88
"properties": {
99
"order": {
@@ -25,8 +25,8 @@
2525
"minimum": 1,
2626
"type": "number"
2727
},
28-
"q": {
29-
"description": "Search query using GitHub users search syntax",
28+
"query": {
29+
"description": "Search query using GitHub users search syntax scoped to type:user",
3030
"type": "string"
3131
},
3232
"sort": {
@@ -40,7 +40,7 @@
4040
}
4141
},
4242
"required": [
43-
"q"
43+
"query"
4444
],
4545
"type": "object"
4646
},

pkg/github/issues.go

Lines changed: 10 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -153,18 +153,24 @@ func AddIssueComment(getClient GetClientFn, t translations.TranslationHelperFunc
153153
}
154154
}
155155

156-
// SearchIssues creates a tool to search for issues and pull requests.
156+
// SearchIssues creates a tool to search for issues.
157157
func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
158158
return mcp.NewTool("search_issues",
159-
mcp.WithDescription(t("TOOL_SEARCH_ISSUES_DESCRIPTION", "Search for issues in GitHub repositories.")),
159+
mcp.WithDescription(t("TOOL_SEARCH_ISSUES_DESCRIPTION", "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue")),
160160
mcp.WithToolAnnotation(mcp.ToolAnnotation{
161161
Title: t("TOOL_SEARCH_ISSUES_USER_TITLE", "Search issues"),
162162
ReadOnlyHint: ToBoolPtr(true),
163163
}),
164-
mcp.WithString("q",
164+
mcp.WithString("query",
165165
mcp.Required(),
166166
mcp.Description("Search query using GitHub issues search syntax"),
167167
),
168+
mcp.WithString("owner",
169+
mcp.Description("Optional repository owner. If provided with repo, only notifications for this repository are listed."),
170+
),
171+
mcp.WithString("repo",
172+
mcp.Description("Optional repository name. If provided with owner, only notifications for this repository are listed."),
173+
),
168174
mcp.WithString("sort",
169175
mcp.Description("Sort field by number of matches of categories, defaults to best match"),
170176
mcp.Enum(
@@ -188,56 +194,7 @@ func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (
188194
WithPagination(),
189195
),
190196
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
191-
query, err := RequiredParam[string](request, "q")
192-
if err != nil {
193-
return mcp.NewToolResultError(err.Error()), nil
194-
}
195-
sort, err := OptionalParam[string](request, "sort")
196-
if err != nil {
197-
return mcp.NewToolResultError(err.Error()), nil
198-
}
199-
order, err := OptionalParam[string](request, "order")
200-
if err != nil {
201-
return mcp.NewToolResultError(err.Error()), nil
202-
}
203-
pagination, err := OptionalPaginationParams(request)
204-
if err != nil {
205-
return mcp.NewToolResultError(err.Error()), nil
206-
}
207-
208-
opts := &github.SearchOptions{
209-
Sort: sort,
210-
Order: order,
211-
ListOptions: github.ListOptions{
212-
PerPage: pagination.perPage,
213-
Page: pagination.page,
214-
},
215-
}
216-
217-
client, err := getClient(ctx)
218-
if err != nil {
219-
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
220-
}
221-
result, resp, err := client.Search.Issues(ctx, query, opts)
222-
if err != nil {
223-
return nil, fmt.Errorf("failed to search issues: %w", err)
224-
}
225-
defer func() { _ = resp.Body.Close() }()
226-
227-
if resp.StatusCode != http.StatusOK {
228-
body, err := io.ReadAll(resp.Body)
229-
if err != nil {
230-
return nil, fmt.Errorf("failed to read response body: %w", err)
231-
}
232-
return mcp.NewToolResultError(fmt.Sprintf("failed to search issues: %s", string(body))), nil
233-
}
234-
235-
r, err := json.Marshal(result)
236-
if err != nil {
237-
return nil, fmt.Errorf("failed to marshal response: %w", err)
238-
}
239-
240-
return mcp.NewToolResultText(string(r)), nil
197+
return searchHandler(ctx, getClient, request, "issue", "failed to search issues")
241198
}
242199
}
243200

pkg/github/issues_test.go

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,14 @@ func Test_SearchIssues(t *testing.T) {
237237

238238
assert.Equal(t, "search_issues", tool.Name)
239239
assert.NotEmpty(t, tool.Description)
240-
assert.Contains(t, tool.InputSchema.Properties, "q")
240+
assert.Contains(t, tool.InputSchema.Properties, "query")
241+
assert.Contains(t, tool.InputSchema.Properties, "owner")
242+
assert.Contains(t, tool.InputSchema.Properties, "repo")
241243
assert.Contains(t, tool.InputSchema.Properties, "sort")
242244
assert.Contains(t, tool.InputSchema.Properties, "order")
243245
assert.Contains(t, tool.InputSchema.Properties, "perPage")
244246
assert.Contains(t, tool.InputSchema.Properties, "page")
245-
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"q"})
247+
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"query"})
246248

247249
// Setup mock search results
248250
mockSearchResult := &github.IssuesSearchResult{
@@ -290,7 +292,7 @@ func Test_SearchIssues(t *testing.T) {
290292
expectQueryParams(
291293
t,
292294
map[string]string{
293-
"q": "repo:owner/repo is:issue is:open",
295+
"q": "is:issue repo:owner/repo is:open",
294296
"sort": "created",
295297
"order": "desc",
296298
"page": "1",
@@ -302,7 +304,7 @@ func Test_SearchIssues(t *testing.T) {
302304
),
303305
),
304306
requestArgs: map[string]interface{}{
305-
"q": "repo:owner/repo is:issue is:open",
307+
"query": "repo:owner/repo is:open",
306308
"sort": "created",
307309
"order": "desc",
308310
"page": float64(1),
@@ -311,6 +313,83 @@ func Test_SearchIssues(t *testing.T) {
311313
expectError: false,
312314
expectedResult: mockSearchResult,
313315
},
316+
{
317+
name: "issues search with owner and repo parameters",
318+
mockedClient: mock.NewMockedHTTPClient(
319+
mock.WithRequestMatchHandler(
320+
mock.GetSearchIssues,
321+
expectQueryParams(
322+
t,
323+
map[string]string{
324+
"q": "repo:test-owner/test-repo is:issue is:open",
325+
"sort": "created",
326+
"order": "asc",
327+
"page": "1",
328+
"per_page": "30",
329+
},
330+
).andThen(
331+
mockResponse(t, http.StatusOK, mockSearchResult),
332+
),
333+
),
334+
),
335+
requestArgs: map[string]interface{}{
336+
"query": "is:open",
337+
"owner": "test-owner",
338+
"repo": "test-repo",
339+
"sort": "created",
340+
"order": "asc",
341+
},
342+
expectError: false,
343+
expectedResult: mockSearchResult,
344+
},
345+
{
346+
name: "issues search with only owner parameter (should ignore it)",
347+
mockedClient: mock.NewMockedHTTPClient(
348+
mock.WithRequestMatchHandler(
349+
mock.GetSearchIssues,
350+
expectQueryParams(
351+
t,
352+
map[string]string{
353+
"q": "is:issue bug",
354+
"page": "1",
355+
"per_page": "30",
356+
},
357+
).andThen(
358+
mockResponse(t, http.StatusOK, mockSearchResult),
359+
),
360+
),
361+
),
362+
requestArgs: map[string]interface{}{
363+
"query": "bug",
364+
"owner": "test-owner",
365+
},
366+
expectError: false,
367+
expectedResult: mockSearchResult,
368+
},
369+
{
370+
name: "issues search with only repo parameter (should ignore it)",
371+
mockedClient: mock.NewMockedHTTPClient(
372+
mock.WithRequestMatchHandler(
373+
mock.GetSearchIssues,
374+
expectQueryParams(
375+
t,
376+
map[string]string{
377+
"q": "is:issue feature",
378+
"page": "1",
379+
"per_page": "30",
380+
},
381+
).andThen(
382+
mockResponse(t, http.StatusOK, mockSearchResult),
383+
),
384+
),
385+
),
386+
requestArgs: map[string]interface{}{
387+
"query": "feature",
388+
"repo": "test-repo",
389+
},
390+
expectError: false,
391+
expectedResult: mockSearchResult,
392+
},
314393
{
315394
name: "issues search with minimal parameters",
316395
mockedClient: mock.NewMockedHTTPClient(
@@ -320,7 +399,7 @@ func Test_SearchIssues(t *testing.T) {
320399
),
321400
),
322401
requestArgs: map[string]interface{}{
323-
"q": "repo:owner/repo is:issue is:open",
402+
"query": "is:issue repo:owner/repo is:open",
324403
},
325404
expectError: false,
326405
expectedResult: mockSearchResult,
@@ -337,7 +416,7 @@ func Test_SearchIssues(t *testing.T) {
337416
),
338417
),
339418
requestArgs: map[string]interface{}{
340-
"q": "invalid:query",
419+
"query": "invalid:query",
341420
},
342421
expectError: true,
343422
expectedErrMsg: "failed to search issues",

0 commit comments

Comments
 (0)