From 6d85ce0700a56caa8e6867c4a502d3b4c42fed7e Mon Sep 17 00:00:00 2001 From: Ievgen Goichuk Date: Thu, 7 Aug 2025 14:01:03 +0100 Subject: [PATCH 1/3] feat(config): kube context name parameter --- pkg/config/config.go | 11 ++++++----- pkg/kubernetes-mcp-server/cmd/root.go | 5 +++++ pkg/kubernetes/configuration.go | 9 ++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 970d8753..879a8a6a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -11,11 +11,12 @@ import ( type StaticConfig struct { DeniedResources []GroupVersionKind `toml:"denied_resources"` - LogLevel int `toml:"log_level,omitempty"` - Port string `toml:"port,omitempty"` - SSEBaseURL string `toml:"sse_base_url,omitempty"` - KubeConfig string `toml:"kubeconfig,omitempty"` - ListOutput string `toml:"list_output,omitempty"` + LogLevel int `toml:"log_level,omitempty"` + Port string `toml:"port,omitempty"` + SSEBaseURL string `toml:"sse_base_url,omitempty"` + KubeConfig string `toml:"kubeconfig,omitempty"` + KubeContext string `toml:"kubecontext,omitempty"` + ListOutput string `toml:"list_output,omitempty"` // When true, expose only tools annotated with readOnlyHint=true ReadOnly bool `toml:"read_only,omitempty"` // When true, disable tools annotated with destructiveHint=true diff --git a/pkg/kubernetes-mcp-server/cmd/root.go b/pkg/kubernetes-mcp-server/cmd/root.go index a96ba763..9d4f3409 100644 --- a/pkg/kubernetes-mcp-server/cmd/root.go +++ b/pkg/kubernetes-mcp-server/cmd/root.go @@ -57,6 +57,7 @@ type MCPServerOptions struct { HttpPort int SSEBaseUrl string Kubeconfig string + KubeContext string Profile string ListOutput string ReadOnly bool @@ -114,6 +115,7 @@ func NewMCPServer(streams genericiooptions.IOStreams) *cobra.Command { cmd.Flags().StringVar(&o.Port, "port", o.Port, "Start a streamable HTTP and SSE HTTP server on the specified port (e.g. 8080)") 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)") cmd.Flags().StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, "Path to the kubeconfig file to use for authentication") + cmd.Flags().StringVar(&o.KubeContext, "kubecontext", o.Kubeconfig, "Context name from kube config") cmd.Flags().StringVar(&o.Profile, "profile", o.Profile, "MCP profile to use (one of: "+strings.Join(mcp.ProfileNames, ", ")+")") cmd.Flags().StringVar(&o.ListOutput, "list-output", o.ListOutput, "Output format for resource list operations (one of: "+strings.Join(output.Names, ", ")+"). Defaults to table.") cmd.Flags().BoolVar(&o.ReadOnly, "read-only", o.ReadOnly, "If true, only tools annotated with readOnlyHint=true are exposed") @@ -170,6 +172,9 @@ func (m *MCPServerOptions) loadFlags(cmd *cobra.Command) { if cmd.Flag("kubeconfig").Changed { m.StaticConfig.KubeConfig = m.Kubeconfig } + if cmd.Flag("kubecontext").Changed { + m.StaticConfig.KubeContext = m.KubeContext + } if cmd.Flag("list-output").Changed || m.StaticConfig.ListOutput == "" { m.StaticConfig.ListOutput = m.ListOutput } diff --git a/pkg/kubernetes/configuration.go b/pkg/kubernetes/configuration.go index df88530f..acdd7fd0 100644 --- a/pkg/kubernetes/configuration.go +++ b/pkg/kubernetes/configuration.go @@ -27,9 +27,13 @@ func resolveKubernetesConfigurations(kubernetes *Manager) error { if kubernetes.staticConfig.KubeConfig != "" { pathOptions.LoadingRules.ExplicitPath = kubernetes.staticConfig.KubeConfig } + overrides := &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}} + if kubernetes.staticConfig.KubeContext != "" { + overrides.CurrentContext = kubernetes.staticConfig.KubeContext + } kubernetes.clientCmdConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( pathOptions.LoadingRules, - &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: ""}}) + overrides) var err error if kubernetes.IsInCluster() { kubernetes.cfg, err = InClusterConfig() @@ -102,6 +106,9 @@ func (m *Manager) ConfigurationView(minify bool) (runtime.Object, error) { return nil, err } if minify { + if m.staticConfig.KubeContext != "" { + cfg.CurrentContext = m.staticConfig.KubeContext + } if err = clientcmdapi.MinifyConfig(&cfg); err != nil { return nil, err } From a261d18d84b05f1169a705958a6bb1df593beca8 Mon Sep 17 00:00:00 2001 From: Ievgen Goichuk Date: Thu, 7 Aug 2025 14:09:41 +0100 Subject: [PATCH 2/3] test(config): kube context name parameter --- pkg/kubernetes/configuration_test.go | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/pkg/kubernetes/configuration_test.go b/pkg/kubernetes/configuration_test.go index 084b99d7..9a26aea5 100644 --- a/pkg/kubernetes/configuration_test.go +++ b/pkg/kubernetes/configuration_test.go @@ -153,3 +153,53 @@ users: } }) } + +func TestKubernetes_ResolveKubernetesConfigurations_KubeContext(t *testing.T) { + tempDir := t.TempDir() + kubeconfigPath := path.Join(tempDir, "config") + kubeconfigContent := ` +apiVersion: v1 +kind: Config +clusters: +- cluster: + server: https://context1.example.com + name: cluster1 +- cluster: + server: https://context2.example.com + name: cluster2 +contexts: +- context: + cluster: cluster1 + user: user1 + name: context1 +- context: + cluster: cluster2 + user: user2 + name: context2 +current-context: context1 +users: +- name: user1 + user: + token: token1 +- name: user2 + user: + token: token2 +` + if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), 0644); err != nil { + t.Fatalf("failed to create kubeconfig file: %v", err) + } + m := Manager{staticConfig: &config.StaticConfig{ + KubeConfig: kubeconfigPath, + KubeContext: "context2", + }} + err := resolveKubernetesConfigurations(&m) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if m.cfg == nil { + t.Errorf("expected non-nil config, got nil") + } + if m.cfg.Host != "https://context2.example.com" { + t.Errorf("expected host https://context2.example.com, got %s", m.cfg.Host) + } +} From 71abe841a642df55732f9926cb2e83b1f9a3e767 Mon Sep 17 00:00:00 2001 From: Ievgen Goichuk Date: Thu, 7 Aug 2025 14:24:47 +0100 Subject: [PATCH 3/3] updated README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 744a4d73..9a282033 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ uvx kubernetes-mcp-server@latest --help | `--port` | Starts the MCP server in Streamable HTTP mode (path /mcp) and Server-Sent Event (SSE) (path /sse) mode and listens on the specified port . | | `--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.). | +| `--kubecontext` | Context name from Kubernetes configuration file. If not provided, it will use current context. | | `--list-output` | Output format for resource list operations (one of: yaml, table) (default "table") | | `--read-only` | If set, the MCP server will run in read-only mode, meaning it will not allow any write operations (create, update, delete) on the Kubernetes cluster. This is useful for debugging or inspecting the cluster without making changes. | | `--disable-destructive` | If set, the MCP server will disable all destructive operations (delete, update, etc.) on the Kubernetes cluster. This is useful for debugging or inspecting the cluster without accidentally making changes. This option has no effect when `--read-only` is used. |