1
1
package main
2
2
3
3
import (
4
- "context "
4
+ "errors "
5
5
"fmt"
6
- "io"
7
- stdlog "log"
8
6
"os"
9
- "os/signal"
10
- "syscall"
11
7
8
+ "github.com/github/github-mcp-server/internal/ghmcp"
12
9
"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"
19
10
"github.com/spf13/cobra"
20
11
"github.com/spf13/viper"
21
12
)
22
13
14
+ // These variables are set by the build process using ldflags.
23
15
var version = "version"
24
16
var commit = "commit"
25
17
var date = "date"
@@ -36,36 +28,35 @@ var (
36
28
Use : "stdio" ,
37
29
Short : "Start stdio server" ,
38
30
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" )
46
35
}
47
36
48
37
// If you're wondering why we're not using viper.GetStringSlice("toolsets"),
49
38
// it's because viper doesn't handle comma-separated values correctly for env
50
39
// vars when using GetStringSlice.
51
40
// https://github.com/spf13/viper/issues/380
52
41
var enabledToolsets []string
53
- err = viper .UnmarshalKey ("toolsets" , & enabledToolsets )
42
+ err : = viper .UnmarshalKey ("toolsets" , & enabledToolsets )
54
43
if err != nil {
55
- stdlog . Fatal ("Failed to unmarshal toolsets:" , err )
44
+ return fmt . Errorf ("Failed to unmarshal toolsets: %w " , err )
56
45
}
57
46
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" ),
68
57
}
58
+
59
+ return ghmcp .RunStdioServer (stdioServerConfig )
69
60
},
70
61
}
71
62
)
@@ -103,143 +94,9 @@ func initConfig() {
103
94
viper .AutomaticEnv ()
104
95
}
105
96
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
-
240
97
func main () {
241
98
if err := rootCmd .Execute (); err != nil {
242
- fmt .Println ( err )
99
+ fmt .Fprintf ( os . Stderr , "%v \n " , err )
243
100
os .Exit (1 )
244
101
}
245
102
}
0 commit comments