@@ -9,13 +9,15 @@ import (
99 "net/url"
1010 "os"
1111 "os/signal"
12+ "regexp"
1213 "strings"
1314 "syscall"
1415
1516 "github.com/github/github-mcp-server/pkg/github"
1617 mcplog "github.com/github/github-mcp-server/pkg/log"
1718 "github.com/github/github-mcp-server/pkg/translations"
1819 gogithub "github.com/google/go-github/v69/github"
20+ "github.com/henvic/httpretty"
1921 "github.com/mark3labs/mcp-go/mcp"
2022 "github.com/mark3labs/mcp-go/server"
2123 "github.com/shurcooL/githubv4"
@@ -45,6 +47,12 @@ type MCPServerConfig struct {
4547
4648 // Translator provides translated text for the server tooling
4749 Translator translations.TranslationHelperFunc
50+
51+ // HTTPLogWriter is the writer for HTTP request/response logs, nil means no logging
52+ // TODO: In future, this should probably log back to the MCP server via notifications/message so that
53+ // remote servers are able to deliver debug logs to the client. For the moment, this is a very
54+ // effective solution for debugging stdio.
55+ HTTPLogWriter io.Writer
4856}
4957
5058func NewMCPServer (cfg MCPServerConfig ) (* server.MCPServer , error ) {
@@ -53,8 +61,32 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
5361 return nil , fmt .Errorf ("failed to parse API host: %w" , err )
5462 }
5563
64+ baseTransport := http .DefaultTransport
65+
66+ if cfg .HTTPLogWriter != nil {
67+ httpLogger := & httpretty.Logger {
68+ Time : true ,
69+ TLS : true ,
70+ RequestHeader : true ,
71+ RequestBody : true ,
72+ ResponseHeader : true ,
73+ ResponseBody : true ,
74+ Colors : false , // erase line if you don't like colors
75+ Formatters : []httpretty.Formatter {& httpretty.JSONFormatter {}},
76+ MaxResponseBody : 10000 ,
77+ }
78+ httpLogger .SetOutput (cfg .HTTPLogWriter )
79+ httpLogger .SetBodyFilter (func (h http.Header ) (skip bool , err error ) {
80+ return ! inspectableMIMEType (h .Get ("Content-Type" )), nil
81+ })
82+ baseTransport = httpLogger .RoundTripper (http .DefaultTransport )
83+ }
84+
5685 // Construct our REST client
57- restClient := gogithub .NewClient (nil ).WithAuthToken (cfg .Token )
86+ restHTTPClient := & http.Client {
87+ Transport : baseTransport ,
88+ }
89+ restClient := gogithub .NewClient (restHTTPClient ).WithAuthToken (cfg .Token )
5890 restClient .UserAgent = fmt .Sprintf ("github-mcp-server/%s" , cfg .Version )
5991 restClient .BaseURL = apiHost .baseRESTURL
6092 restClient .UploadURL = apiHost .uploadURL
@@ -64,7 +96,7 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
6496 // did the necessary API host parsing so that github.com will return the correct URL anyway.
6597 gqlHTTPClient := & http.Client {
6698 Transport : & bearerAuthTransport {
67- transport : http . DefaultTransport ,
99+ transport : baseTransport ,
68100 token : cfg .Token ,
69101 },
70102 } // We're going to wrap the Transport later in beforeInit
@@ -169,6 +201,9 @@ type StdioServerConfig struct {
169201
170202 // Path to the log file if not stderr
171203 LogFilePath string
204+
205+ // LogAPIRequests indicates if we should log API requests to stderr
206+ LogAPIRequests bool
172207}
173208
174209// RunStdioServer is not concurrent safe.
@@ -179,6 +214,26 @@ func RunStdioServer(cfg StdioServerConfig) error {
179214
180215 t , dumpTranslations := translations .TranslationHelper ()
181216
217+ logrusLogger := logrus .New ()
218+ logLocation := os .Stderr
219+ if cfg .LogFilePath != "" {
220+ file , err := os .OpenFile (cfg .LogFilePath , os .O_CREATE | os .O_WRONLY | os .O_APPEND , 0600 )
221+ if err != nil {
222+ return fmt .Errorf ("failed to open log file: %w" , err )
223+ }
224+ defer func () { _ = file .Close () }()
225+
226+ logLocation = file
227+ logrusLogger .SetLevel (logrus .DebugLevel )
228+ }
229+ logrusLogger .SetOutput (logLocation )
230+ stdLogger := log .New (logrusLogger .Writer (), "stdioserver" , 0 )
231+
232+ var httpLogWriter io.Writer
233+ if cfg .LogAPIRequests {
234+ httpLogWriter = logLocation
235+ }
236+
182237 ghServer , err := NewMCPServer (MCPServerConfig {
183238 Version : cfg .Version ,
184239 Host : cfg .Host ,
@@ -187,24 +242,13 @@ func RunStdioServer(cfg StdioServerConfig) error {
187242 DynamicToolsets : cfg .DynamicToolsets ,
188243 ReadOnly : cfg .ReadOnly ,
189244 Translator : t ,
245+ HTTPLogWriter : httpLogWriter ,
190246 })
191247 if err != nil {
192248 return fmt .Errorf ("failed to create MCP server: %w" , err )
193249 }
194250
195251 stdioServer := server .NewStdioServer (ghServer )
196-
197- logrusLogger := logrus .New ()
198- if cfg .LogFilePath != "" {
199- file , err := os .OpenFile (cfg .LogFilePath , os .O_CREATE | os .O_WRONLY | os .O_APPEND , 0600 )
200- if err != nil {
201- return fmt .Errorf ("failed to open log file: %w" , err )
202- }
203-
204- logrusLogger .SetLevel (logrus .DebugLevel )
205- logrusLogger .SetOutput (file )
206- }
207- stdLogger := log .New (logrusLogger .Writer (), "stdioserver" , 0 )
208252 stdioServer .SetErrorLogger (stdLogger )
209253
210254 if cfg .ExportTranslations {
@@ -378,3 +422,11 @@ func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, erro
378422 req .Header .Set ("Authorization" , "Bearer " + t .token )
379423 return t .transport .RoundTrip (req )
380424}
425+
426+ var jsonTypeRE = regexp .MustCompile (`[/+]json($|;)` )
427+
428+ func inspectableMIMEType (t string ) bool {
429+ return strings .HasPrefix (t , "text/" ) ||
430+ strings .HasPrefix (t , "application/x-www-form-urlencoded" ) ||
431+ jsonTypeRE .MatchString (t )
432+ }
0 commit comments