diff --git a/README.md b/README.md index bd88bb28..0ca13ad2 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,8 @@ uvx kubernetes-mcp-server@latest --help | Option | Description | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--sse-port` | Starts the MCP server in Server-Sent Event (SSE) mode and listens on the specified port. | +| `--http-port` | Starts the MCP server in Streamable HTTP mode and listens on the specified port (path /mcp). | +| `--sse-port` | Starts the MCP server in Server-Sent Event (SSE) mode and listens on the specified port (path /sse). | | `--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). | | `--kubeconfig` | Path to the Kubernetes configuration file. If not provided, it will try to resolve the configuration (in-cluster, default location, etc.). | | `--list-output` | Output format for resource list operations (one of: yaml, table) (default "table") | diff --git a/pkg/kubernetes-mcp-server/cmd/root.go b/pkg/kubernetes-mcp-server/cmd/root.go index cd535cf5..9a3d53af 100644 --- a/pkg/kubernetes-mcp-server/cmd/root.go +++ b/pkg/kubernetes-mcp-server/cmd/root.go @@ -7,7 +7,6 @@ import ( "github.com/manusa/kubernetes-mcp-server/pkg/mcp" "github.com/manusa/kubernetes-mcp-server/pkg/output" "github.com/manusa/kubernetes-mcp-server/pkg/version" - "github.com/mark3labs/mcp-go/server" "github.com/spf13/cobra" "github.com/spf13/viper" "golang.org/x/net/context" @@ -74,16 +73,27 @@ Kubernetes Model Context Protocol (MCP) server } defer mcpServer.Close() - var sseServer *server.SSEServer - if ssePort := viper.GetInt("sse-port"); ssePort > 0 { - sseServer = mcpServer.ServeSse(viper.GetString("sse-base-url")) + ssePort := viper.GetInt("sse-port") + if ssePort > 0 { + sseServer := mcpServer.ServeSse(viper.GetString("sse-base-url")) defer func() { _ = sseServer.Shutdown(cmd.Context()) }() - klog.V(0).Infof("SSE server starting on port %d", ssePort) + klog.V(0).Infof("SSE server starting on port %d and path /sse", ssePort) if err := sseServer.Start(fmt.Sprintf(":%d", ssePort)); err != nil { klog.Errorf("Failed to start SSE server: %s", err) return } } + + httpPort := viper.GetInt("http-port") + if httpPort > 0 { + httpServer := mcpServer.ServeHTTP() + klog.V(0).Infof("Streaming HTTP server starting on port %d and path /mcp", httpPort) + if err := httpServer.Start(fmt.Sprintf(":%d", httpPort)); err != nil { + klog.Errorf("Failed to start streaming HTTP server: %s", err) + return + } + } + if err := mcpServer.ServeStdio(); err != nil && !errors.Is(err, context.Canceled) { panic(err) } @@ -115,6 +125,7 @@ func flagInit() { rootCmd.Flags().BoolP("version", "v", false, "Print version information and quit") rootCmd.Flags().IntP("log-level", "", 0, "Set the log level (from 0 to 9)") rootCmd.Flags().IntP("sse-port", "", 0, "Start a SSE server on the specified port") + rootCmd.Flags().IntP("http-port", "", 0, "Start a streamable HTTP server on the specified port") rootCmd.Flags().StringP("sse-base-url", "", "", "SSE public base URL to use when sending the endpoint message (e.g. https://example.com)") rootCmd.Flags().StringP("kubeconfig", "", "", "Path to the kubeconfig file to use for authentication") rootCmd.Flags().String("profile", "full", "MCP profile to use (one of: "+strings.Join(mcp.ProfileNames, ", ")+")") diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go index 045a2175..8d0f369b 100644 --- a/pkg/mcp/mcp.go +++ b/pkg/mcp/mcp.go @@ -82,6 +82,13 @@ func (s *Server) ServeSse(baseUrl string) *server.SSEServer { return server.NewSSEServer(s.server, options...) } +func (s *Server) ServeHTTP() *server.StreamableHTTPServer { + options := []server.StreamableHTTPOption{ + server.WithHTTPContextFunc(contextFunc), + } + return server.NewStreamableHTTPServer(s.server, options...) +} + func (s *Server) Close() { if s.k != nil { s.k.Close()