diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index 5ca0e21..5c689f0 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -9,6 +9,7 @@ import ( "os/signal" "syscall" + "github.com/cli/go-gh/pkg/auth" "github.com/github/github-mcp-server/pkg/github" iolog "github.com/github/github-mcp-server/pkg/log" "github.com/github/github-mcp-server/pkg/translations" @@ -24,9 +25,12 @@ var version = "version" var commit = "commit" var date = "date" +var rootCommandName = "github-mcp-server" +var defaultTokenSource = "env" + var ( rootCmd = &cobra.Command{ - Use: "server", + Use: rootCommandName, Short: "GitHub MCP Server", Long: `A GitHub MCP server that handles various tools and resources.`, Version: fmt.Sprintf("Version: %s\nCommit: %s\nBuild Date: %s", version, commit, date), @@ -75,6 +79,7 @@ func init() { rootCmd.PersistentFlags().Bool("enable-command-logging", false, "When enabled, the server will log all command requests and responses to the log file") rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file") rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)") + rootCmd.PersistentFlags().String("token-source", defaultTokenSource, "Authentication token source (e.g. env, gh)") // Bind flag to viper _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) @@ -84,6 +89,7 @@ func init() { _ = viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging")) _ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations")) _ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host")) + _ = viper.BindPFlag("token-source", rootCmd.PersistentFlags().Lookup("token-source")) // Add subcommands rootCmd.AddCommand(stdioCmd) @@ -126,21 +132,9 @@ func runStdioServer(cfg runConfig) error { defer stop() // Create GH client - token := viper.GetString("personal_access_token") - if token == "" { - cfg.logger.Fatal("GITHUB_PERSONAL_ACCESS_TOKEN not set") - } - ghClient := gogithub.NewClient(nil).WithAuthToken(token) - ghClient.UserAgent = fmt.Sprintf("github-mcp-server/%s", version) - - host := viper.GetString("host") - - if host != "" { - var err error - ghClient, err = ghClient.WithEnterpriseURLs(host, host) - if err != nil { - return fmt.Errorf("failed to create GitHub client with host: %w", err) - } + ghClient, err := newGitHubClient() + if err != nil { + cfg.logger.Fatalf("failed to create GitHub client: %v", err) } t, dumpTranslations := translations.TranslationHelper() @@ -229,6 +223,50 @@ func runStdioServer(cfg runConfig) error { return nil } +func getToken(host string) (string, error) { + tokenSource := viper.GetString("token-source") + switch tokenSource { + case "env": + token := os.Getenv("GITHUB_PERSONAL_ACCESS_TOKEN") + if token == "" { + return "", fmt.Errorf("GITHUB_PERSONAL_ACCESS_TOKEN not set") + } + return token, nil + case "gh": + token, source := auth.TokenForHost(host) + if source == "default" { + return "", fmt.Errorf("no token found for host: %s", host) + } + return token, nil + } + return "", fmt.Errorf("unknown token source: %s", tokenSource) +} + +func getHost() string { + host := os.Getenv("GH_HOST") + if host == "" { + host = viper.GetString("gh-host") + } + return host +} + +func newGitHubClient() (*gogithub.Client, error) { + host := getHost() + token, err := getToken(host) + if err != nil { + return nil, fmt.Errorf("failed to get token: %w", err) + } + ghClient := gogithub.NewClient(nil).WithAuthToken(token) + if host != "" { + ghClient, err = ghClient.WithEnterpriseURLs(host, host) + if err != nil { + return nil, fmt.Errorf("failed to create GitHub client with host: %w", err) + } + } + ghClient.UserAgent = fmt.Sprintf("github-mcp-server/%s", version) + return ghClient, nil +} + func main() { if err := rootCmd.Execute(); err != nil { fmt.Println(err) diff --git a/go.mod b/go.mod index 7c09fba..e44deb1 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,11 @@ require ( github.com/stretchr/testify v1.10.0 ) +require github.com/cli/safeexec v1.0.0 // indirect + require ( github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cli/go-gh v1.2.1 github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect diff --git a/go.sum b/go.sum index 3378b4f..9006fca 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cli/go-gh v1.2.1 h1:xFrjejSsgPiwXFP6VYynKWwxLQcNJy3Twbu82ZDlR/o= +github.com/cli/go-gh v1.2.1/go.mod h1:Jxk8X+TCO4Ui/GarwY9tByWm/8zp4jJktzVZNlTW5VM= +github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= +github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=