Skip to content

Commit 9c67e10

Browse files
Merge branch 'main' into xodene/feat-gists-tools-start
2 parents da53838 + 4b64121 commit 9c67e10

File tree

12 files changed

+540
-60
lines changed

12 files changed

+540
-60
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ The following sets of tools are available (all are on by default):
467467
- `orderBy`: Order discussions by field. If provided, the 'direction' also needs to be provided. (string, optional)
468468
- `owner`: Repository owner (string, required)
469469
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
470-
- `repo`: Repository name (string, required)
470+
- `repo`: Repository name. If not provided, discussions will be queried at the organisation level. (string, optional)
471471

472472
</details>
473473

@@ -636,7 +636,7 @@ The following sets of tools are available (all are on by default):
636636
- `order`: Sort order (string, optional)
637637
- `page`: Page number for pagination (min 1) (number, optional)
638638
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
639-
- `query`: Search query using GitHub organizations search syntax scoped to type:org (string, required)
639+
- `query`: Organization search query. Examples: 'microsoft', 'location:california', 'created:>=2025-01-01'. Search is automatically scoped to type:org. (string, required)
640640
- `sort`: Sort field by category (string, optional)
641641

642642
</details>
@@ -761,6 +761,7 @@ The following sets of tools are available (all are on by default):
761761
- **update_pull_request** - Edit pull request
762762
- `base`: New base branch name (string, optional)
763763
- `body`: New description (string, optional)
764+
- `draft`: Mark pull request as draft (true) or ready for review (false) (boolean, optional)
764765
- `maintainer_can_modify`: Allow maintainer edits (boolean, optional)
765766
- `owner`: Repository owner (string, required)
766767
- `pullNumber`: Pull request number to update (number, required)
@@ -860,16 +861,16 @@ The following sets of tools are available (all are on by default):
860861
- `repo`: Repository name (string, required)
861862

862863
- **search_code** - Search code
863-
- `order`: Sort order (string, optional)
864+
- `order`: Sort order for results (string, optional)
864865
- `page`: Page number for pagination (min 1) (number, optional)
865866
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
866-
- `query`: Search query using GitHub code search syntax (string, required)
867+
- `query`: Search query using GitHub's powerful code search syntax. Examples: 'content:Skill language:Java org:github', 'NOT is:archived language:Python OR language:go', 'repo:github/github-mcp-server'. Supports exact matching, language filters, path filters, and more. (string, required)
867868
- `sort`: Sort field ('indexed' only) (string, optional)
868869

869870
- **search_repositories** - Search repositories
870871
- `page`: Page number for pagination (min 1) (number, optional)
871872
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
872-
- `query`: Search query (string, required)
873+
- `query`: Repository search query. Examples: 'machine learning in:name stars:>1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering. (string, required)
873874

874875
</details>
875876

@@ -899,8 +900,8 @@ The following sets of tools are available (all are on by default):
899900
- `order`: Sort order (string, optional)
900901
- `page`: Page number for pagination (min 1) (number, optional)
901902
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
902-
- `query`: Search query using GitHub users search syntax scoped to type:user (string, required)
903-
- `sort`: Sort field by category (string, optional)
903+
- `query`: User search query. Examples: 'john smith', 'location:seattle', 'followers:>100'. Search is automatically scoped to type:user. (string, required)
904+
- `sort`: Sort users by number of followers or repositories, or when the person joined GitHub. (string, optional)
904905

905906
</details>
906907
<!-- END AUTOMATED TOOLS -->

pkg/github/__toolsnaps__/search_code.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
"title": "Search code",
44
"readOnlyHint": true
55
},
6-
"description": "Search for code across GitHub repositories",
6+
"description": "Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns.",
77
"inputSchema": {
88
"properties": {
99
"order": {
10-
"description": "Sort order",
10+
"description": "Sort order for results",
1111
"enum": [
1212
"asc",
1313
"desc"
@@ -26,7 +26,7 @@
2626
"type": "number"
2727
},
2828
"query": {
29-
"description": "Search query using GitHub code search syntax",
29+
"description": "Search query using GitHub's powerful code search syntax. Examples: 'content:Skill language:Java org:github', 'NOT is:archived language:Python OR language:go', 'repo:github/github-mcp-server'. Supports exact matching, language filters, path filters, and more.",
3030
"type": "string"
3131
},
3232
"sort": {

pkg/github/__toolsnaps__/search_repositories.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"title": "Search repositories",
44
"readOnlyHint": true
55
},
6-
"description": "Search for GitHub repositories",
6+
"description": "Find GitHub repositories by name, description, readme, topics, or other metadata. Perfect for discovering projects, finding examples, or locating specific repositories across GitHub.",
77
"inputSchema": {
88
"properties": {
99
"page": {
@@ -18,7 +18,7 @@
1818
"type": "number"
1919
},
2020
"query": {
21-
"description": "Search query",
21+
"description": "Repository search query. Examples: 'machine learning in:name stars:\u003e1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering.",
2222
"type": "string"
2323
}
2424
},

pkg/github/__toolsnaps__/search_users.snap

Lines changed: 3 additions & 3 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 exclusively",
6+
"description": "Find GitHub users by username, real name, or other profile information. Useful for locating developers, contributors, or team members.",
77
"inputSchema": {
88
"properties": {
99
"order": {
@@ -26,11 +26,11 @@
2626
"type": "number"
2727
},
2828
"query": {
29-
"description": "Search query using GitHub users search syntax scoped to type:user",
29+
"description": "User search query. Examples: 'john smith', 'location:seattle', 'followers:\u003e100'. Search is automatically scoped to type:user.",
3030
"type": "string"
3131
},
3232
"sort": {
33-
"description": "Sort field by category",
33+
"description": "Sort users by number of followers or repositories, or when the person joined GitHub.",
3434
"enum": [
3535
"followers",
3636
"repositories",

pkg/github/__toolsnaps__/update_pull_request.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
"description": "New description",
1515
"type": "string"
1616
},
17+
"draft": {
18+
"description": "Mark pull request as draft (true) or ready for review (false)",
19+
"type": "boolean"
20+
},
1721
"maintainer_can_modify": {
1822
"description": "Allow maintainer edits",
1923
"type": "boolean"

pkg/github/discussions.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func getQueryType(useOrdering bool, categoryID *githubv4.ID) any {
119119

120120
func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
121121
return mcp.NewTool("list_discussions",
122-
mcp.WithDescription(t("TOOL_LIST_DISCUSSIONS_DESCRIPTION", "List discussions for a repository")),
122+
mcp.WithDescription(t("TOOL_LIST_DISCUSSIONS_DESCRIPTION", "List discussions for a repository or organisation.")),
123123
mcp.WithToolAnnotation(mcp.ToolAnnotation{
124124
Title: t("TOOL_LIST_DISCUSSIONS_USER_TITLE", "List discussions"),
125125
ReadOnlyHint: ToBoolPtr(true),
@@ -129,8 +129,7 @@ func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelp
129129
mcp.Description("Repository owner"),
130130
),
131131
mcp.WithString("repo",
132-
mcp.Required(),
133-
mcp.Description("Repository name"),
132+
mcp.Description("Repository name. If not provided, discussions will be queried at the organisation level."),
134133
),
135134
mcp.WithString("category",
136135
mcp.Description("Optional filter by discussion category ID. If provided, only discussions with this category are listed."),
@@ -150,10 +149,15 @@ func ListDiscussions(getGQLClient GetGQLClientFn, t translations.TranslationHelp
150149
if err != nil {
151150
return mcp.NewToolResultError(err.Error()), nil
152151
}
153-
repo, err := RequiredParam[string](request, "repo")
152+
repo, err := OptionalParam[string](request, "repo")
154153
if err != nil {
155154
return mcp.NewToolResultError(err.Error()), nil
156155
}
156+
// when not provided, default to the .github repository
157+
// this will query discussions at the organisation level
158+
if repo == "" {
159+
repo = ".github"
160+
}
157161

158162
category, err := OptionalParam[string](request, "category")
159163
if err != nil {

pkg/github/discussions_test.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,46 @@ var (
5050
},
5151
}
5252

53+
discussionsOrgLevel = []map[string]any{
54+
{
55+
"number": 1,
56+
"title": "Org Discussion 1 - Community Guidelines",
57+
"createdAt": "2023-01-15T00:00:00Z",
58+
"updatedAt": "2023-01-15T00:00:00Z",
59+
"author": map[string]any{"login": "org-admin"},
60+
"url": "https://github.com/owner/.github/discussions/1",
61+
"category": map[string]any{"name": "Announcements"},
62+
},
63+
{
64+
"number": 2,
65+
"title": "Org Discussion 2 - Roadmap 2023",
66+
"createdAt": "2023-02-20T00:00:00Z",
67+
"updatedAt": "2023-02-20T00:00:00Z",
68+
"author": map[string]any{"login": "org-admin"},
69+
"url": "https://github.com/owner/.github/discussions/2",
70+
"category": map[string]any{"name": "General"},
71+
},
72+
{
73+
"number": 3,
74+
"title": "Org Discussion 3 - Roadmap 2024",
75+
"createdAt": "2023-02-20T00:00:00Z",
76+
"updatedAt": "2023-02-20T00:00:00Z",
77+
"author": map[string]any{"login": "org-admin"},
78+
"url": "https://github.com/owner/.github/discussions/3",
79+
"category": map[string]any{"name": "General"},
80+
},
81+
{
82+
"number": 4,
83+
"title": "Org Discussion 4 - Roadmap 2025",
84+
"createdAt": "2023-02-20T00:00:00Z",
85+
"updatedAt": "2023-02-20T00:00:00Z",
86+
"author": map[string]any{"login": "org-admin"},
87+
"url": "https://github.com/owner/.github/discussions/4",
88+
"category": map[string]any{"name": "General"},
89+
},
90+
91+
}
92+
5393
// Ordered mock responses
5494
discussionsOrderedCreatedAsc = []map[string]any{
5595
discussionsAll[0], // Discussion 1 (created 2023-01-01)
@@ -139,6 +179,22 @@ var (
139179
},
140180
},
141181
})
182+
183+
mockResponseOrgLevel = githubv4mock.DataResponse(map[string]any{
184+
"repository": map[string]any{
185+
"discussions": map[string]any{
186+
"nodes": discussionsOrgLevel,
187+
"pageInfo": map[string]any{
188+
"hasNextPage": false,
189+
"hasPreviousPage": false,
190+
"startCursor": "",
191+
"endCursor": "",
192+
},
193+
"totalCount": 4,
194+
},
195+
},
196+
})
197+
142198
mockErrorRepoNotFound = githubv4mock.ErrorResponse("repository not found")
143199
)
144200

@@ -151,7 +207,7 @@ func Test_ListDiscussions(t *testing.T) {
151207
assert.Contains(t, toolDef.InputSchema.Properties, "repo")
152208
assert.Contains(t, toolDef.InputSchema.Properties, "orderBy")
153209
assert.Contains(t, toolDef.InputSchema.Properties, "direction")
154-
assert.ElementsMatch(t, toolDef.InputSchema.Required, []string{"owner", "repo"})
210+
assert.ElementsMatch(t, toolDef.InputSchema.Required, []string{"owner"})
155211

156212
// Variables matching what GraphQL receives after JSON marshaling/unmarshaling
157213
varsListAll := map[string]interface{}{
@@ -204,6 +260,13 @@ func Test_ListDiscussions(t *testing.T) {
204260
"after": (*string)(nil),
205261
}
206262

263+
varsOrgLevel := map[string]interface{}{
264+
"owner": "owner",
265+
"repo": ".github", // This is what gets set when repo is not provided
266+
"first": float64(30),
267+
"after": (*string)(nil),
268+
}
269+
207270
tests := []struct {
208271
name string
209272
reqParams map[string]interface{}
@@ -314,6 +377,15 @@ func Test_ListDiscussions(t *testing.T) {
314377
expectError: true,
315378
errContains: "repository not found",
316379
},
380+
{
381+
name: "list org-level discussions (no repo provided)",
382+
reqParams: map[string]interface{}{
383+
"owner": "owner",
384+
// repo is not provided, it will default to ".github"
385+
},
386+
expectError: false,
387+
expectedCount: 4,
388+
},
317389
}
318390

319391
// Define the actual query strings that match the implementation
@@ -351,6 +423,9 @@ func Test_ListDiscussions(t *testing.T) {
351423
case "repository not found error":
352424
matcher := githubv4mock.NewQueryMatcher(qBasicNoOrder, varsRepoNotFound, mockErrorRepoNotFound)
353425
httpClient = githubv4mock.NewMockedHTTPClient(matcher)
426+
case "list org-level discussions (no repo provided)":
427+
matcher := githubv4mock.NewQueryMatcher(qBasicNoOrder, varsOrgLevel, mockResponseOrgLevel)
428+
httpClient = githubv4mock.NewMockedHTTPClient(matcher)
354429
}
355430

356431
gqlClient := githubv4.NewClient(httpClient)

0 commit comments

Comments
 (0)