11package main
22
33import (
4- "context "
4+ "errors "
55 "fmt"
6- "io"
7- stdlog "log"
86 "os"
9- "os/signal"
10- "syscall"
117
8+ "github.com/github/github-mcp-server/internal/ghmcp"
129 "github.com/github/github-mcp-server/pkg/github"
13- iolog "github.com/github/github-mcp-server/pkg/log"
14- "github.com/github/github-mcp-server/pkg/translations"
15- gogithub "github.com/google/go-github/v69/github"
16- "github.com/mark3labs/mcp-go/mcp"
17- "github.com/mark3labs/mcp-go/server"
18- log "github.com/sirupsen/logrus"
1910 "github.com/spf13/cobra"
2011 "github.com/spf13/viper"
2112)
2213
14+ // These variables are set by the build process using ldflags.
2315var version = "version"
2416var commit = "commit"
2517var date = "date"
@@ -36,36 +28,35 @@ var (
3628 Use : "stdio" ,
3729 Short : "Start stdio server" ,
3830 Long : `Start a server that communicates via standard input/output streams using JSON-RPC messages.` ,
39- Run : func (_ * cobra.Command , _ []string ) {
40- logFile := viper .GetString ("log-file" )
41- readOnly := viper .GetBool ("read-only" )
42- exportTranslations := viper .GetBool ("export-translations" )
43- logger , err := initLogger (logFile )
44- if err != nil {
45- stdlog .Fatal ("Failed to initialize logger:" , err )
31+ RunE : func (_ * cobra.Command , _ []string ) error {
32+ token := viper .GetString ("personal_access_token" )
33+ if token == "" {
34+ return errors .New ("GITHUB_PERSONAL_ACCESS_TOKEN not set" )
4635 }
4736
4837 // If you're wondering why we're not using viper.GetStringSlice("toolsets"),
4938 // it's because viper doesn't handle comma-separated values correctly for env
5039 // vars when using GetStringSlice.
5140 // https://github.com/spf13/viper/issues/380
5241 var enabledToolsets []string
53- err = viper .UnmarshalKey ("toolsets" , & enabledToolsets )
42+ err : = viper .UnmarshalKey ("toolsets" , & enabledToolsets )
5443 if err != nil {
55- stdlog . Fatal ("Failed to unmarshal toolsets:" , err )
44+ return fmt . Errorf ("Failed to unmarshal toolsets: %w " , err )
5645 }
5746
58- logCommands := viper . GetBool ( "enable-command-logging" )
59- cfg := runConfig {
60- readOnly : readOnly ,
61- logger : logger ,
62- logCommands : logCommands ,
63- exportTranslations : exportTranslations ,
64- enabledToolsets : enabledToolsets ,
65- }
66- if err := runStdioServer ( cfg ); err != nil {
67- stdlog . Fatal ( "failed to run stdio server:" , err )
47+ stdioServerConfig := ghmcp. StdioServerConfig {
48+ Version : version ,
49+ Host : viper . GetString ( "host" ) ,
50+ Token : token ,
51+ EnabledToolsets : enabledToolsets ,
52+ DynamicToolsets : viper . GetBool ( "dynamic_toolsets" ) ,
53+ ReadOnly : viper . GetBool ( "read-only" ) ,
54+ ExportTranslations : viper . GetBool ( "export-translations" ),
55+ EnableCommandLogging : viper . GetBool ( "enable-command-logging" ),
56+ LogFilePath : viper . GetString ( "log-file" ),
6857 }
58+
59+ return ghmcp .RunStdioServer (stdioServerConfig )
6960 },
7061 }
7162)
@@ -103,143 +94,9 @@ func initConfig() {
10394 viper .AutomaticEnv ()
10495}
10596
106- func initLogger (outPath string ) (* log.Logger , error ) {
107- if outPath == "" {
108- return log .New (), nil
109- }
110-
111- file , err := os .OpenFile (outPath , os .O_CREATE | os .O_WRONLY | os .O_APPEND , 0666 )
112- if err != nil {
113- return nil , fmt .Errorf ("failed to open log file: %w" , err )
114- }
115-
116- logger := log .New ()
117- logger .SetLevel (log .DebugLevel )
118- logger .SetOutput (file )
119-
120- return logger , nil
121- }
122-
123- type runConfig struct {
124- readOnly bool
125- logger * log.Logger
126- logCommands bool
127- exportTranslations bool
128- enabledToolsets []string
129- }
130-
131- func runStdioServer (cfg runConfig ) error {
132- // Create app context
133- ctx , stop := signal .NotifyContext (context .Background (), os .Interrupt , syscall .SIGTERM )
134- defer stop ()
135-
136- // Create GH client
137- token := viper .GetString ("personal_access_token" )
138- if token == "" {
139- cfg .logger .Fatal ("GITHUB_PERSONAL_ACCESS_TOKEN not set" )
140- }
141- ghClient := gogithub .NewClient (nil ).WithAuthToken (token )
142- ghClient .UserAgent = fmt .Sprintf ("github-mcp-server/%s" , version )
143-
144- host := viper .GetString ("host" )
145-
146- if host != "" {
147- var err error
148- ghClient , err = ghClient .WithEnterpriseURLs (host , host )
149- if err != nil {
150- return fmt .Errorf ("failed to create GitHub client with host: %w" , err )
151- }
152- }
153-
154- t , dumpTranslations := translations .TranslationHelper ()
155-
156- beforeInit := func (_ context.Context , _ any , message * mcp.InitializeRequest ) {
157- ghClient .UserAgent = fmt .Sprintf ("github-mcp-server/%s (%s/%s)" , version , message .Params .ClientInfo .Name , message .Params .ClientInfo .Version )
158- }
159-
160- getClient := func (_ context.Context ) (* gogithub.Client , error ) {
161- return ghClient , nil // closing over client
162- }
163-
164- hooks := & server.Hooks {
165- OnBeforeInitialize : []server.OnBeforeInitializeFunc {beforeInit },
166- }
167- // Create server
168- ghServer := github .NewServer (version , server .WithHooks (hooks ))
169-
170- enabled := cfg .enabledToolsets
171- dynamic := viper .GetBool ("dynamic_toolsets" )
172- if dynamic {
173- // filter "all" from the enabled toolsets
174- enabled = make ([]string , 0 , len (cfg .enabledToolsets ))
175- for _ , toolset := range cfg .enabledToolsets {
176- if toolset != "all" {
177- enabled = append (enabled , toolset )
178- }
179- }
180- }
181-
182- // Create default toolsets
183- toolsets , err := github .InitToolsets (enabled , cfg .readOnly , getClient , t )
184- context := github .InitContextToolset (getClient , t )
185-
186- if err != nil {
187- stdlog .Fatal ("Failed to initialize toolsets:" , err )
188- }
189-
190- // Register resources with the server
191- github .RegisterResources (ghServer , getClient , t )
192- // Register the tools with the server
193- toolsets .RegisterTools (ghServer )
194- context .RegisterTools (ghServer )
195-
196- if dynamic {
197- dynamic := github .InitDynamicToolset (ghServer , toolsets , t )
198- dynamic .RegisterTools (ghServer )
199- }
200-
201- stdioServer := server .NewStdioServer (ghServer )
202-
203- stdLogger := stdlog .New (cfg .logger .Writer (), "stdioserver" , 0 )
204- stdioServer .SetErrorLogger (stdLogger )
205-
206- if cfg .exportTranslations {
207- // Once server is initialized, all translations are loaded
208- dumpTranslations ()
209- }
210-
211- // Start listening for messages
212- errC := make (chan error , 1 )
213- go func () {
214- in , out := io .Reader (os .Stdin ), io .Writer (os .Stdout )
215-
216- if cfg .logCommands {
217- loggedIO := iolog .NewIOLogger (in , out , cfg .logger )
218- in , out = loggedIO , loggedIO
219- }
220-
221- errC <- stdioServer .Listen (ctx , in , out )
222- }()
223-
224- // Output github-mcp-server string
225- _ , _ = fmt .Fprintf (os .Stderr , "GitHub MCP Server running on stdio\n " )
226-
227- // Wait for shutdown signal
228- select {
229- case <- ctx .Done ():
230- cfg .logger .Infof ("shutting down server..." )
231- case err := <- errC :
232- if err != nil {
233- return fmt .Errorf ("error running server: %w" , err )
234- }
235- }
236-
237- return nil
238- }
239-
24097func main () {
24198 if err := rootCmd .Execute (); err != nil {
242- fmt .Println ( err )
99+ fmt .Fprintf ( os . Stderr , "%v \n " , err )
243100 os .Exit (1 )
244101 }
245102}
0 commit comments