1
1
package main
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
6
+ "encoding/json"
5
7
"fmt"
6
8
"io"
7
9
stdlog"log"
@@ -39,12 +41,20 @@ var (
39
41
logFile := viper .GetString ("log-file" )
40
42
readOnly := viper .GetBool ("read-only" )
41
43
exportTranslations := viper .GetBool ("export-translations" )
44
+ prettyPrintJSON := viper .GetBool ("pretty-print-json" )
42
45
logger ,err := initLogger (logFile )
43
46
if err != nil {
44
47
stdlog .Fatal ("Failed to initialize logger:" ,err )
45
48
}
46
49
logCommands := viper .GetBool ("enable-command-logging" )
47
- if err := runStdioServer (readOnly ,logger ,logCommands ,exportTranslations );err != nil {
50
+ cfg := runConfig {
51
+ readOnly :readOnly ,
52
+ logger :logger ,
53
+ logCommands :logCommands ,
54
+ exportTranslations :exportTranslations ,
55
+ prettyPrintJSON :prettyPrintJSON ,
56
+ }
57
+ if err := runStdioServer (cfg );err != nil {
48
58
stdlog .Fatal ("failed to run stdio server:" ,err )
49
59
}
50
60
},
@@ -60,13 +70,15 @@ func init() {
60
70
rootCmd .PersistentFlags ().Bool ("enable-command-logging" ,false ,"When enabled, the server will log all command requests and responses to the log file" )
61
71
rootCmd .PersistentFlags ().Bool ("export-translations" ,false ,"Save translations to a JSON file" )
62
72
rootCmd .PersistentFlags ().String ("gh-host" ,"" ,"Specify the GitHub hostname (for GitHub Enterprise etc.)" )
73
+ rootCmd .PersistentFlags ().Bool ("pretty-print-json" ,false ,"Pretty print JSON output" )
63
74
64
75
// Bind flag to viper
65
76
_ = viper .BindPFlag ("read-only" ,rootCmd .PersistentFlags ().Lookup ("read-only" ))
66
77
_ = viper .BindPFlag ("log-file" ,rootCmd .PersistentFlags ().Lookup ("log-file" ))
67
78
_ = viper .BindPFlag ("enable-command-logging" ,rootCmd .PersistentFlags ().Lookup ("enable-command-logging" ))
68
79
_ = viper .BindPFlag ("export-translations" ,rootCmd .PersistentFlags ().Lookup ("export-translations" ))
69
80
_ = viper .BindPFlag ("gh-host" ,rootCmd .PersistentFlags ().Lookup ("gh-host" ))
81
+ _ = viper .BindPFlag ("pretty-print-json" ,rootCmd .PersistentFlags ().Lookup ("pretty-print-json" ))
70
82
71
83
// Add subcommands
72
84
rootCmd .AddCommand (stdioCmd )
@@ -95,15 +107,36 @@ func initLogger(outPath string) (*log.Logger, error) {
95
107
return logger ,nil
96
108
}
97
109
98
- func runStdioServer (readOnly bool ,logger * log.Logger ,logCommands bool ,exportTranslations bool )error {
110
+ type runConfig struct {
111
+ readOnly bool
112
+ logger * log.Logger
113
+ logCommands bool
114
+ exportTranslations bool
115
+ prettyPrintJSON bool
116
+ }
117
+
118
+ // JSONPrettyPrintWriter is a Writer that pretty prints input to indented JSON
119
+ type JSONPrettyPrintWriter struct {
120
+ writer io.Writer
121
+ }
122
+
123
+ func (j JSONPrettyPrintWriter )Write (p []byte ) (n int ,err error ) {
124
+ var prettyJSON bytes.Buffer
125
+ if err := json .Indent (& prettyJSON ,p ,"" ,"\t " );err != nil {
126
+ return 0 ,err
127
+ }
128
+ return j .writer .Write (prettyJSON .Bytes ())
129
+ }
130
+
131
+ func runStdioServer (cfg runConfig )error {
99
132
// Create app context
100
133
ctx ,stop := signal .NotifyContext (context .Background (),os .Interrupt ,syscall .SIGTERM )
101
134
defer stop ()
102
135
103
136
// Create GH client
104
137
token := os .Getenv ("GITHUB_PERSONAL_ACCESS_TOKEN" )
105
138
if token == "" {
106
- logger .Fatal ("GITHUB_PERSONAL_ACCESS_TOKEN not set" )
139
+ cfg . logger .Fatal ("GITHUB_PERSONAL_ACCESS_TOKEN not set" )
107
140
}
108
141
ghClient := gogithub .NewClient (nil ).WithAuthToken (token )
109
142
ghClient .UserAgent = fmt .Sprintf ("github-mcp-server/%s" ,version )
@@ -125,13 +158,13 @@ func runStdioServer(readOnly bool, logger *log.Logger, logCommands bool, exportT
125
158
t ,dumpTranslations := translations .TranslationHelper ()
126
159
127
160
// Create
128
- ghServer := github .NewServer (ghClient ,readOnly ,t )
161
+ ghServer := github .NewServer (ghClient ,cfg . readOnly ,t )
129
162
stdioServer := server .NewStdioServer (ghServer )
130
163
131
- stdLogger := stdlog .New (logger .Writer (),"stdioserver" ,0 )
164
+ stdLogger := stdlog .New (cfg . logger .Writer (),"stdioserver" ,0 )
132
165
stdioServer .SetErrorLogger (stdLogger )
133
166
134
- if exportTranslations {
167
+ if cfg . exportTranslations {
135
168
// Once server is initialized, all translations are loaded
136
169
dumpTranslations ()
137
170
}
@@ -141,11 +174,14 @@ func runStdioServer(readOnly bool, logger *log.Logger, logCommands bool, exportT
141
174
go func () {
142
175
in ,out := io .Reader (os .Stdin ),io .Writer (os .Stdout )
143
176
144
- if logCommands {
145
- loggedIO := iolog .NewIOLogger (in ,out ,logger )
177
+ if cfg . logCommands {
178
+ loggedIO := iolog .NewIOLogger (in ,out ,cfg . logger )
146
179
in ,out = loggedIO ,loggedIO
147
180
}
148
181
182
+ if cfg .prettyPrintJSON {
183
+ out = JSONPrettyPrintWriter {writer :out }
184
+ }
149
185
errC <- stdioServer .Listen (ctx ,in ,out )
150
186
}()
151
187
@@ -155,7 +191,7 @@ func runStdioServer(readOnly bool, logger *log.Logger, logCommands bool, exportT
155
191
// Wait for shutdown signal
156
192
select {
157
193
case <- ctx .Done ():
158
- logger .Infof ("shutting down server..." )
194
+ cfg . logger .Infof ("shutting down server..." )
159
195
case err := <- errC :
160
196
if err != nil {
161
197
return fmt .Errorf ("error running server: %w" ,err )