11c3d709c2
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
Docker / build (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
GoReleaser Release / release (push) Has been cancelled
License Check / license-check (push) Has been cancelled
Publish to MCP Registry / publish (push) Has been cancelled
Fix environment variable mapping for read-only mode configuration Add SetEnvKeyReplacer to viper configuration to properly map environment variables with underscores to flag names with dashes. This enables the documented GITHUB_READ_ONLY=1 environment variable to work correctly. Without this fix, viper was looking for GITHUB_READ-ONLY (with dash) but the documentation and standard convention use GITHUB_READ_ONLY (with underscore). Fixes issue where read-only mode was not being activated when using GITHUB_READ_ONLY=1 in Docker containers.
123 lines
4.5 KiB
Go
123 lines
4.5 KiB
Go
package main
|
|
|
|
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"
|
|
)
|
|
|
|
// These variables are set by the build process using ldflags.
|
|
var version = "version"
|
|
var commit = "commit"
|
|
var date = "date"
|
|
|
|
var (
|
|
rootCmd = &cobra.Command{
|
|
Use: "server",
|
|
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),
|
|
}
|
|
|
|
stdioCmd = &cobra.Command{
|
|
Use: "stdio",
|
|
Short: "Start stdio server",
|
|
Long: `Start a server that communicates via standard input/output streams using JSON-RPC messages.`,
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
token := viper.GetString("personal_access_token")
|
|
if token == "" {
|
|
return errors.New("GITHUB_PERSONAL_ACCESS_TOKEN not set")
|
|
}
|
|
|
|
// If you're wondering why we're not using viper.GetStringSlice("toolsets"),
|
|
// it's because viper doesn't handle comma-separated values correctly for env
|
|
// vars when using GetStringSlice.
|
|
// https://github.com/spf13/viper/issues/380
|
|
var enabledToolsets []string
|
|
if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil {
|
|
return fmt.Errorf("failed to unmarshal toolsets: %w", err)
|
|
}
|
|
|
|
// No passed toolsets configuration means we enable the default toolset
|
|
if len(enabledToolsets) == 0 {
|
|
enabledToolsets = []string{github.ToolsetMetadataDefault.ID}
|
|
}
|
|
|
|
stdioServerConfig := ghmcp.StdioServerConfig{
|
|
Version: version,
|
|
Host: viper.GetString("host"),
|
|
Token: token,
|
|
EnabledToolsets: enabledToolsets,
|
|
DynamicToolsets: viper.GetBool("dynamic_toolsets"),
|
|
ReadOnly: viper.GetBool("read-only"),
|
|
ExportTranslations: viper.GetBool("export-translations"),
|
|
EnableCommandLogging: viper.GetBool("enable-command-logging"),
|
|
LogFilePath: viper.GetString("log-file"),
|
|
ContentWindowSize: viper.GetInt("content-window-size"),
|
|
}
|
|
return ghmcp.RunStdioServer(stdioServerConfig)
|
|
},
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
cobra.OnInitialize(initConfig)
|
|
rootCmd.SetGlobalNormalizationFunc(wordSepNormalizeFunc)
|
|
|
|
rootCmd.SetVersionTemplate("{{.Short}}\n{{.Version}}\n")
|
|
|
|
// Add global flags that will be shared by all commands
|
|
rootCmd.PersistentFlags().StringSlice("toolsets", nil, github.GenerateToolsetsHelp())
|
|
rootCmd.PersistentFlags().Bool("dynamic-toolsets", false, "Enable dynamic toolsets")
|
|
rootCmd.PersistentFlags().Bool("read-only", false, "Restrict the server to read-only operations")
|
|
rootCmd.PersistentFlags().String("log-file", "", "Path to log file")
|
|
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().Int("content-window-size", 5000, "Specify the content window size")
|
|
|
|
// Bind flag to viper
|
|
_ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets"))
|
|
_ = viper.BindPFlag("dynamic_toolsets", rootCmd.PersistentFlags().Lookup("dynamic-toolsets"))
|
|
_ = viper.BindPFlag("read-only", rootCmd.PersistentFlags().Lookup("read-only"))
|
|
_ = viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file"))
|
|
_ = 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("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size"))
|
|
|
|
// Add subcommands
|
|
rootCmd.AddCommand(stdioCmd)
|
|
}
|
|
|
|
func initConfig() {
|
|
// Initialize Viper configuration
|
|
viper.SetEnvPrefix("github")
|
|
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
|
viper.AutomaticEnv()
|
|
|
|
}
|
|
|
|
func main() {
|
|
if err := rootCmd.Execute(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func wordSepNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName {
|
|
from := []string{"_"}
|
|
to := "-"
|
|
for _, sep := range from {
|
|
name = strings.ReplaceAll(name, sep, to)
|
|
}
|
|
return pflag.NormalizedName(name)
|
|
}
|