Skip to content

Feature/kube context name #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
11 changes: 6 additions & 5 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions pkg/kubernetes-mcp-server/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type MCPServerOptions struct {
HttpPort int
SSEBaseUrl string
Kubeconfig string
KubeContext string
Profile string
ListOutput string
ReadOnly bool
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/kubernetes/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
Expand Down
50 changes: 50 additions & 0 deletions pkg/kubernetes/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}