Skip to content

[pull] main from github:main #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jun 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ on:
schedule:
- cron: "27 0 * * *"
push:
branches: ["main"]
branches: ["main", "next"]
# Publish semver tags as releases.
tags: ["v*.*.*"]
pull_request:
branches: ["main"]
branches: ["main", "next"]

env:
# Use docker.io for Docker Hub if empty
Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ These are one time installations required to be able to test your changes locall

## Submitting a pull request

> **Important**: Please open your pull request against the `next` branch, not `main`. The `next` branch is where we integrate new features and changes before they are merged to `main`.

1. [Fork][fork] and clone the repository
1. Make sure the tests pass on your machine: `go test -v ./...`
1. Make sure linter passes on your machine: `golangci-lint run`
1. Create a new branch: `git checkout -b my-branch-name`
1. Make your change, add tests, and make sure the tests and linter still pass
1. Push to your fork and [submit a pull request][pr]
1. Push to your fork and [submit a pull request][pr] targeting the `next` branch
1. Pat yourself on the back and wait for your pull request to be reviewed and merged.

Here are a few things you can do that will increase the likelihood of your pull request being accepted:
Expand Down
14 changes: 13 additions & 1 deletion cmd/github-mcp-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"errors"
"fmt"
"os"
"strings"

"github.com/github/github-mcp-server/internal/ghmcp"
"github.com/github/github-mcp-server/pkg/github"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)

Expand Down Expand Up @@ -54,14 +56,14 @@ var (
EnableCommandLogging: viper.GetBool("enable-command-logging"),
LogFilePath: viper.GetString("log-file"),
}

return ghmcp.RunStdioServer(stdioServerConfig)
},
}
)

func init() {
cobra.OnInitialize(initConfig)
rootCmd.SetGlobalNormalizationFunc(wordSepNormalizeFunc)

rootCmd.SetVersionTemplate("{{.Short}}\n{{.Version}}\n")

Expand Down Expand Up @@ -91,6 +93,7 @@ func initConfig() {
// Initialize Viper configuration
viper.SetEnvPrefix("github")
viper.AutomaticEnv()

}

func main() {
Expand All @@ -99,3 +102,12 @@ func main() {
os.Exit(1)
}
}

func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
from := []string{"_"}
to := "-"
for _, sep := range from {
name = strings.ReplaceAll(name, sep, to)
}
return pflag.NormalizedName(name)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/pflag v1.0.6
github.com/subosito/gotenv v1.6.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
Expand Down
14 changes: 11 additions & 3 deletions pkg/github/__toolsnaps__/search_issues.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"title": "Search issues",
"readOnlyHint": true
},
"description": "Search for issues in GitHub repositories.",
"description": "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue",
"inputSchema": {
"properties": {
"order": {
Expand All @@ -14,6 +14,10 @@
],
"type": "string"
},
"owner": {
"description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
Expand All @@ -25,10 +29,14 @@
"minimum": 1,
"type": "number"
},
"q": {
"query": {
"description": "Search query using GitHub issues search syntax",
"type": "string"
},
"repo": {
"description": "Optional repository name. If provided with owner, only notifications for this repository are listed.",
"type": "string"
},
"sort": {
"description": "Sort field by number of matches of categories, defaults to best match",
"enum": [
Expand All @@ -48,7 +56,7 @@
}
},
"required": [
"q"
"query"
],
"type": "object"
},
Expand Down
64 changes: 64 additions & 0 deletions pkg/github/__toolsnaps__/search_pull_requests.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"annotations": {
"title": "Search pull requests",
"readOnlyHint": true
},
"description": "Search for pull requests in GitHub repositories using issues search syntax already scoped to is:pr",
"inputSchema": {
"properties": {
"order": {
"description": "Sort order",
"enum": [
"asc",
"desc"
],
"type": "string"
},
"owner": {
"description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.",
"type": "string"
},
"page": {
"description": "Page number for pagination (min 1)",
"minimum": 1,
"type": "number"
},
"perPage": {
"description": "Results per page for pagination (min 1, max 100)",
"maximum": 100,
"minimum": 1,
"type": "number"
},
"query": {
"description": "Search query using GitHub pull request search syntax",
"type": "string"
},
"repo": {
"description": "Optional repository name. If provided with owner, only notifications for this repository are listed.",
"type": "string"
},
"sort": {
"description": "Sort field by number of matches of categories, defaults to best match",
"enum": [
"comments",
"reactions",
"reactions-+1",
"reactions--1",
"reactions-smile",
"reactions-thinking_face",
"reactions-heart",
"reactions-tada",
"interactions",
"created",
"updated"
],
"type": "string"
}
},
"required": [
"query"
],
"type": "object"
},
"name": "search_pull_requests"
}
63 changes: 10 additions & 53 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,18 +153,24 @@ func AddIssueComment(getClient GetClientFn, t translations.TranslationHelperFunc
}
}

// SearchIssues creates a tool to search for issues and pull requests.
// SearchIssues creates a tool to search for issues.
func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
return mcp.NewTool("search_issues",
mcp.WithDescription(t("TOOL_SEARCH_ISSUES_DESCRIPTION", "Search for issues in GitHub repositories.")),
mcp.WithDescription(t("TOOL_SEARCH_ISSUES_DESCRIPTION", "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue")),
mcp.WithToolAnnotation(mcp.ToolAnnotation{
Title: t("TOOL_SEARCH_ISSUES_USER_TITLE", "Search issues"),
ReadOnlyHint: ToBoolPtr(true),
}),
mcp.WithString("q",
mcp.WithString("query",
mcp.Required(),
mcp.Description("Search query using GitHub issues search syntax"),
),
mcp.WithString("owner",
mcp.Description("Optional repository owner. If provided with repo, only notifications for this repository are listed."),
),
mcp.WithString("repo",
mcp.Description("Optional repository name. If provided with owner, only notifications for this repository are listed."),
),
mcp.WithString("sort",
mcp.Description("Sort field by number of matches of categories, defaults to best match"),
mcp.Enum(
Expand All @@ -188,56 +194,7 @@ func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (
WithPagination(),
),
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
query, err := RequiredParam[string](request, "q")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
sort, err := OptionalParam[string](request, "sort")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
order, err := OptionalParam[string](request, "order")
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
pagination, err := OptionalPaginationParams(request)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

opts := &github.SearchOptions{
Sort: sort,
Order: order,
ListOptions: github.ListOptions{
PerPage: pagination.perPage,
Page: pagination.page,
},
}

client, err := getClient(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
}
result, resp, err := client.Search.Issues(ctx, query, opts)
if err != nil {
return nil, fmt.Errorf("failed to search issues: %w", err)
}
defer func() { _ = resp.Body.Close() }()

if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
return mcp.NewToolResultError(fmt.Sprintf("failed to search issues: %s", string(body))), nil
}

r, err := json.Marshal(result)
if err != nil {
return nil, fmt.Errorf("failed to marshal response: %w", err)
}

return mcp.NewToolResultText(string(r)), nil
return searchHandler(ctx, getClient, request, "issue", "failed to search issues")
}
}

Expand Down
Loading