@@ -5,7 +5,11 @@ import (
55 "crypto/subtle"
66 "fmt"
77 "net/http"
8+ "os"
9+ "os/signal"
810 "strings"
11+ "syscall"
12+ "time"
913
1014 "seerr-cli/cmd/apiutil"
1115
@@ -14,6 +18,28 @@ import (
1418 "github.com/spf13/viper"
1519)
1620
21+ // Default timeout values for the MCP HTTP server.
22+ const (
23+ httpReadHeaderTimeout = 5 * time .Second
24+ httpReadTimeout = 15 * time .Second
25+ httpWriteTimeout = 30 * time .Second
26+ httpIdleTimeout = 60 * time .Second
27+ httpShutdownTimeout = 30 * time .Second
28+ )
29+
30+ // NewHTTPServer creates an http.Server bound to addr with safe default timeouts.
31+ // It is exported so tests can assert that the server is properly configured.
32+ func NewHTTPServer (addr string , handler http.Handler ) * http.Server {
33+ return & http.Server {
34+ Addr : addr ,
35+ Handler : handler ,
36+ ReadHeaderTimeout : httpReadHeaderTimeout ,
37+ ReadTimeout : httpReadTimeout ,
38+ WriteTimeout : httpWriteTimeout ,
39+ IdleTimeout : httpIdleTimeout ,
40+ }
41+ }
42+
1743var buildVersion = "dev"
1844
1945// SetVersionInfo injects the linker-set build version so the MCP server can
@@ -178,14 +204,36 @@ func runServe(_ *cobra.Command, args []string) error {
178204 if cors {
179205 handler = corsMiddleware (handler )
180206 }
181- srv := & http.Server {
182- Addr : addr ,
183- Handler : handler ,
184- }
207+ srv := NewHTTPServer (addr , handler )
208+
209+ // Catch SIGINT and SIGTERM so the server shuts down gracefully.
210+ sigCh := make (chan os.Signal , 1 )
211+ signal .Notify (sigCh , os .Interrupt , syscall .SIGTERM )
212+
213+ serveErrCh := make (chan error , 1 )
185214 if tlsCert != "" && tlsKey != "" {
186- return srv .ListenAndServeTLS (tlsCert , tlsKey )
215+ go func () { serveErrCh <- srv .ListenAndServeTLS (tlsCert , tlsKey ) }()
216+ } else {
217+ go func () { serveErrCh <- srv .ListenAndServe () }()
218+ }
219+
220+ select {
221+ case err := <- serveErrCh :
222+ // Server exited on its own (e.g. port already in use).
223+ if err != nil && err != http .ErrServerClosed {
224+ return err
225+ }
226+ return nil
227+ case <- sigCh :
228+ mcpLog .Info ("shutting down MCP HTTP server" )
229+ }
230+
231+ shutdownCtx , cancel := context .WithTimeout (context .Background (), httpShutdownTimeout )
232+ defer cancel ()
233+ if err := srv .Shutdown (shutdownCtx ); err != nil {
234+ return fmt .Errorf ("graceful shutdown: %w" , err )
187235 }
188- return srv . ListenAndServe ()
236+ return nil
189237 default :
190238 return fmt .Errorf ("unknown transport %q: must be stdio or http" , transport )
191239 }
0 commit comments