Repository navigation
Expand file tree
/
Copy pathhttp_server.go
More file actions
113 lines (93 loc) · 3.02 KB
/
Copy pathhttp_server.go
File metadata and controls
113 lines (93 loc) · 3.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"last9-mcp/internal/constants"
"last9-mcp/internal/models"
last9mcp "github.com/last9/mcp-go-sdk/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// HTTPServer wraps the MCP server for HTTP transport
type HTTPServer struct {
server *last9mcp.Last9MCPServer
config models.Config
}
// NewHTTPServer creates a new HTTP-based MCP server
func NewHTTPServer(server *last9mcp.Last9MCPServer, config models.Config) *HTTPServer {
return &HTTPServer{
server: server,
config: config,
}
}
// Start starts the HTTP server with streamable HTTP support
func (h *HTTPServer) Start() error {
// url is host:port
url := h.config.Host + ":" + h.config.Port
// Create a mux to handle multiple endpoints
mux := http.NewServeMux()
// Stateless: stateful mode keeps sessions per-instance, so behind a load
// balancer a follow-up request routed to another replica 404s. The
// instrumented helper sets the transport, needed for client attribution.
httpHandler := h.server.NewStreamableHTTPHandler(&mcp.StreamableHTTPOptions{Stateless: true})
// Register handlers on both root and /mcp paths for maximum client flexibility
mux.Handle("/", httpHandler) // Root endpoint for standard MCP clients
mux.Handle("/mcp", httpHandler) // /mcp endpoint for explicit MCP usage
mux.HandleFunc("/health", h.handleHealth)
// Create HTTP server with timeouts
httpServer := &http.Server{
Addr: url,
Handler: mux,
ReadTimeout: constants.DefaultHTTPTimeout,
WriteTimeout: constants.DefaultHTTPTimeout,
IdleTimeout: 60 * time.Second,
}
log.Printf("🚀 MCP server listening on %s", url)
// add shutdown hook
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
// Start server in a goroutine
serverErr := make(chan error, 1)
go func() {
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
serverErr <- err
}
}()
// Wait for context cancellation or server error
select {
// add signal chan
case sig := <-signalChan:
log.Printf("🛑 Received signal: %v, initiating graceful shutdown...", sig)
case err := <-serverErr:
log.Printf("❌ Server error: %v", err)
return err
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Attempt graceful shutdown
if err := httpServer.Shutdown(shutdownCtx); err != nil {
log.Printf("❌ Graceful shutdown failed: %v", err)
return err
}
log.Printf("✅ HTTP server shutdown complete")
if err := h.server.Shutdown(shutdownCtx); err != nil {
log.Printf("❌ MCP server shutdown error: %v", err)
return err
}
log.Printf("✅ MCP server shutdown complete")
return nil
}
func (h *HTTPServer) handleHealth(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "healthy",
"server": "last9-mcp",
"version": Version,
})
}