Skip to content

Commit a67e6c7

Browse files
committed
Serve sse and streamable from a single port
1 parent 2a1a3e4 commit a67e6c7

File tree

7 files changed

+46
-36
lines changed

7 files changed

+46
-36
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ RUN make build
88
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
99
WORKDIR /app
1010
COPY --from=builder /app/kubernetes-mcp-server /app/kubernetes-mcp-server
11-
ENTRYPOINT ["/app/kubernetes-mcp-server", "--sse-port", "8080"]
11+
ENTRYPOINT ["/app/kubernetes-mcp-server", "--port", "8080"]
1212

1313
EXPOSE 8080

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ uvx kubernetes-mcp-server@latest --help
158158

159159
| Option | Description |
160160
|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
161-
| `--http-port` | Starts the MCP server in Streamable HTTP mode and listens on the specified port (path /mcp). |
162-
| `--sse-port` | Starts the MCP server in Server-Sent Event (SSE) mode and listens on the specified port (path /sse). |
161+
| `--http-port` | Starts the MCP server in Streamable HTTP mode and listens on the specified port (path /mcp). Deprecated: Please use --port. |
162+
| `--sse-port` | Starts the MCP server in Server-Sent Event (SSE) mode and listens on the specified port (path /sse). Deprecated: Please use --port. |
163163
| `--log-level` | Sets the logging level (values [from 0-9](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md)). Similar to [kubectl logging levels](https://kubernetes.io/docs/reference/kubectl/quick-reference/#kubectl-output-verbosity-and-debugging). |
164164
| `--kubeconfig` | Path to the Kubernetes configuration file. If not provided, it will try to resolve the configuration (in-cluster, default location, etc.). |
165165
| `--list-output` | Output format for resource list operations (one of: yaml, table) (default "table") |

pkg/config/config.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ type StaticConfig struct {
1010
DeniedResources []GroupVersionKind `toml:"denied_resources"`
1111

1212
LogLevel int `toml:"log_level,omitempty"`
13-
SSEPort int `toml:"sse_port,omitempty"`
14-
HTTPPort int `toml:"http_port,omitempty"`
13+
Port string `toml:"port,omitempty"`
1514
SSEBaseURL string `toml:"sse_base_url,omitempty"`
1615
KubeConfig string `toml:"kubeconfig,omitempty"`
1716
ListOutput string `toml:"list_output,omitempty"`

pkg/config/config_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ kind = "Role
5151
func TestReadConfigValid(t *testing.T) {
5252
validConfigPath := writeConfig(t, `
5353
log_level = 1
54-
sse_port = 9999
54+
port = "9999"
5555
kubeconfig = "test"
5656
list_output = "yaml"
5757
read_only = true
@@ -88,15 +88,12 @@ version = "v1"
8888
if config.LogLevel != 1 {
8989
t.Fatalf("Unexpected log level: %v", config.LogLevel)
9090
}
91-
if config.SSEPort != 9999 {
92-
t.Fatalf("Unexpected sse_port value: %v", config.SSEPort)
91+
if config.Port != "9999" {
92+
t.Fatalf("Unexpected port value: %v", config.Port)
9393
}
9494
if config.SSEBaseURL != "" {
9595
t.Fatalf("Unexpected sse_base_url value: %v", config.SSEBaseURL)
9696
}
97-
if config.HTTPPort != 0 {
98-
t.Fatalf("Unexpected http_port value: %v", config.HTTPPort)
99-
}
10097
if config.KubeConfig != "test" {
10198
t.Fatalf("Unexpected kubeconfig value: %v", config.KubeConfig)
10299
}

pkg/kubernetes-mcp-server/cmd/root.go

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"flag"
77
"fmt"
8+
"net/http"
89
"strconv"
910
"strings"
1011

@@ -35,16 +36,17 @@ kubernetes-mcp-server --version
3536
kubernetes-mcp-server
3637
3738
# start a SSE server on port 8080
38-
kubernetes-mcp-server --sse-port 8080
39+
kubernetes-mcp-server --port 8080
3940
4041
# start a SSE server on port 8443 with a public HTTPS host of example.com
41-
kubernetes-mcp-server --sse-port 8443 --sse-base-url https://example.com:8443
42+
kubernetes-mcp-server --port 8443 --sse-base-url https://example.com:8443
4243
`))
4344
)
4445

4546
type MCPServerOptions struct {
4647
Version bool
4748
LogLevel int
49+
Port string
4850
SSEPort int
4951
HttpPort int
5052
SSEBaseUrl string
@@ -95,7 +97,10 @@ func NewMCPServer(streams genericiooptions.IOStreams) *cobra.Command {
9597
cmd.Flags().IntVar(&o.LogLevel, "log-level", o.LogLevel, "Set the log level (from 0 to 9)")
9698
cmd.Flags().StringVar(&o.ConfigPath, "config", o.ConfigPath, "Path of the config file. Each profile has its set of defaults.")
9799
cmd.Flags().IntVar(&o.SSEPort, "sse-port", o.SSEPort, "Start a SSE server on the specified port")
100+
cmd.Flag("sse-port").Deprecated = "Use --port instead"
98101
cmd.Flags().IntVar(&o.HttpPort, "http-port", o.HttpPort, "Start a streamable HTTP server on the specified port")
102+
cmd.Flag("http-port").Deprecated = "Use --port instead"
103+
cmd.Flags().StringVar(&o.Port, "port", o.Port, "Start a streamable HTTP and SSE HTTP server on the specified port (e.g. 8080)")
99104
cmd.Flags().StringVar(&o.SSEBaseUrl, "sse-base-url", o.SSEBaseUrl, "SSE public base URL to use when sending the endpoint message (e.g. https://example.com)")
100105
cmd.Flags().StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, "Path to the kubeconfig file to use for authentication")
101106
cmd.Flags().StringVar(&o.Profile, "profile", o.Profile, "MCP profile to use (one of: "+strings.Join(mcp.ProfileNames, ", ")+")")
@@ -126,11 +131,12 @@ func (m *MCPServerOptions) loadFlags(cmd *cobra.Command) {
126131
if cmd.Flag("log-level").Changed {
127132
m.StaticConfig.LogLevel = m.LogLevel
128133
}
129-
if cmd.Flag("sse-port").Changed {
130-
m.StaticConfig.SSEPort = m.SSEPort
131-
}
132-
if cmd.Flag("http-port").Changed {
133-
m.StaticConfig.HTTPPort = m.HttpPort
134+
if cmd.Flag("port").Changed {
135+
m.StaticConfig.Port = m.Port
136+
} else if cmd.Flag("sse-port").Changed {
137+
m.StaticConfig.Port = strconv.Itoa(m.SSEPort)
138+
} else if cmd.Flag("http-port").Changed {
139+
m.StaticConfig.Port = strconv.Itoa(m.HttpPort)
134140
}
135141
if cmd.Flag("sse-base-url").Changed {
136142
m.StaticConfig.SSEBaseURL = m.SSEBaseUrl
@@ -162,6 +168,9 @@ func (m *MCPServerOptions) initializeLogging() {
162168
}
163169

164170
func (m *MCPServerOptions) Validate() error {
171+
if m.Port != "" && (m.SSEPort > 0 || m.HttpPort > 0) {
172+
return fmt.Errorf("--port is mutually exclusive with deprecated --http-port and --sse-port flags")
173+
}
165174
return nil
166175
}
167176

@@ -198,23 +207,27 @@ func (m *MCPServerOptions) Run() error {
198207
}
199208
defer mcpServer.Close()
200209

201-
ctx := context.Background()
202-
203-
if m.StaticConfig.SSEPort > 0 {
204-
sseServer := mcpServer.ServeSse(m.StaticConfig.SSEBaseURL)
205-
defer func() { _ = sseServer.Shutdown(ctx) }()
206-
klog.V(0).Infof("SSE server starting on port %d and path /sse", m.StaticConfig.SSEPort)
207-
if err := sseServer.Start(fmt.Sprintf(":%d", m.StaticConfig.SSEPort)); err != nil {
208-
return fmt.Errorf("failed to start SSE server: %w\n", err)
210+
if m.StaticConfig.Port != "" {
211+
mux := http.NewServeMux()
212+
httpServer := &http.Server{
213+
Addr: ":" + m.StaticConfig.Port,
214+
Handler: mux,
209215
}
210-
}
211216

212-
if m.StaticConfig.HTTPPort > 0 {
213-
httpServer := mcpServer.ServeHTTP()
214-
klog.V(0).Infof("Streaming HTTP server starting on port %d and path /mcp", m.StaticConfig.HTTPPort)
215-
if err := httpServer.Start(fmt.Sprintf(":%d", m.StaticConfig.HTTPPort)); err != nil {
216-
return fmt.Errorf("failed to start streaming HTTP server: %w\n", err)
217+
sseServer := mcpServer.ServeSse(m.SSEBaseUrl, httpServer)
218+
streamableHttpServer := mcpServer.ServeHTTP(httpServer)
219+
mux.Handle("/sse", sseServer)
220+
mux.Handle("/message", sseServer)
221+
mux.Handle("/mcp", streamableHttpServer)
222+
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
223+
w.WriteHeader(http.StatusOK)
224+
})
225+
226+
klog.V(0).Infof("Streaming and SSE HTTP servers starting on port %s and paths /mcp, /sse, /message", m.StaticConfig.Port)
227+
if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
228+
return err
217229
}
230+
return nil
218231
}
219232

220233
if err := mcpServer.ServeStdio(); err != nil && !errors.Is(err, context.Canceled) {

pkg/kubernetes-mcp-server/cmd/testdata/valid-config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
log_level = 1
2-
sse_port = 9999
2+
port = "9999"
33
kubeconfig = "test"
44
list_output = "yaml"
55
read_only = true

pkg/mcp/mcp.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,19 @@ func (s *Server) ServeStdio() error {
7676
return server.ServeStdio(s.server)
7777
}
7878

79-
func (s *Server) ServeSse(baseUrl string) *server.SSEServer {
79+
func (s *Server) ServeSse(baseUrl string, httpServer *http.Server) *server.SSEServer {
8080
options := make([]server.SSEOption, 0)
81-
options = append(options, server.WithSSEContextFunc(contextFunc))
81+
options = append(options, server.WithSSEContextFunc(contextFunc), server.WithHTTPServer(httpServer))
8282
if baseUrl != "" {
8383
options = append(options, server.WithBaseURL(baseUrl))
8484
}
8585
return server.NewSSEServer(s.server, options...)
8686
}
8787

88-
func (s *Server) ServeHTTP() *server.StreamableHTTPServer {
88+
func (s *Server) ServeHTTP(httpServer *http.Server) *server.StreamableHTTPServer {
8989
options := []server.StreamableHTTPOption{
9090
server.WithHTTPContextFunc(contextFunc),
91+
server.WithStreamableHTTPServer(httpServer),
9192
}
9293
return server.NewStreamableHTTPServer(s.server, options...)
9394
}

0 commit comments

Comments
 (0)