Repository: containers/kubernetes-mcp-server

Issue:
Support for multiple contexts and switching between them
Currently there doesn't seem to be a way to handle multiple contexts. This is a pretty tough limitation.

In my agent flows, I typically need to access several clusters in a group. There is a k8s mcp that has implemented this quite well:

https://github.com/razvanmacovei/k8s-multicluster-mcp

Might be worth a look and implementing the same.

Discussion:
@manusa: We can try to add an **optional** `context` parameter for each function/tool.

- The parameter is only added in case the MCP Server is a local deployment based on a kubeconfig file that has more than one context (to avoid confusing smaller LLMs)
- The parameter description will include a list of contexts that the LLM can select (this might be problematic in case the kubeconfig file has hundreds of contexts)

/cc @alexeykazakov@alexeykazakov: > The parameter description will include a list of contexts that the LLM can select (this might be problematic in case the kubeconfig file has hundreds of contexts)

Right. Or, as you said, it could be also a new tool: list-contexts. But in this case it would always require to do this extra tool call to list all available contexts before calling the actual tool.@ardaguclu: In multiple context environment, it is clear that we can't use MCP Server's kubeconfig which is tied to a single cluster. Another point is that user has to pass `bearer` to authenticate the MCP Server. 

Under these two facts I think: Enriching the `audience` field of bearer token with the cluster API Server url that is tried to be interacted with can resolve this. As complementary, MCP Server can ask the cluster url via Elicitation.

EDITED: Delegating the context management to bearer token or elicitation will remove the unexpected context usages triggered by LLM.@ardaguclu: > We can try to add an optional context parameter for each function/tool.

This brings about the requirement to pass context parameter in each request. Also what will happen if user modifies context parameter in each tool call within the same session?.@ardaguclu: As far as I understand, https://github.com/razvanmacovei/k8s-multicluster-mcp supports defining multiple kubeconfigs in that MCP Server. If I am unwanted actor who can reach to this MCP Server, there are plenty of kubeconfigs that are usable for any unwanted operation.@alexeykazakov: Imaging a situation when an agent needs to get access to multiple clusters. In different sessions of with the same session. For example it has a task to do something in a multi-cluster system.
IMO, the MCP Server should not be in the business of policing what the cluster the client can access and what it can't. It's up to the kubeconfig to dictate it.
MCP Server just needs to provide the client a clear way of telling what cluster it wants to execute the tool for.
Providing a tool for switching the context for the entire session would limit the usage to the cases when there is no need to access multiple clusters for the same session. So I think adding an optional context param to every command is probably a better way.
And moving this to the bearer token would make it very tricky for the client (or agent, or LLM) to manage contexts. Would it have to get a new special token generated every time it needs to use a specific context?
@ardaguclu: > And moving this to the bearer token would make it very tricky for the client (or agent, or LLM) to manage contexts. Would it have to get a new special token generated every time it needs to use a specific context?

You are right. It makes sense to send same bearer token everytime and MCP Server exchanges it based on which cluster it will talk to.

> It's up to the kubeconfig to dictate it.

Which kubeconfig do you mean?. Client must not pass kubeconfig  to MCP Server (as this is prohibited by the protocol). Or the kubeconfig residing in MCP Server?@alexeykazakov: > Which kubeconfig do you mean?. Client must not pass kubeconfig to MCP Server (as this is prohibited by the protocol). Or the kubeconfig residing in MCP Server?

the kubeconfig residing in MCP Server@ardaguclu: > > Which kubeconfig do you mean?. Client must not pass kubeconfig to MCP Server (as this is prohibited by the protocol). Or the kubeconfig residing in MCP Server?
> 
> the kubeconfig residing in MCP Server

Using kubeconfig of MCP Server results in leveraged access violation (i.e. user does not have access to list the pods but MCP server has). It is difficult to fix this without using impersonation.  So that I think, we try to move forward with bearer token + token exchange without relying on MCP Server's kubeconfig. @alexeykazakov: > Using kubeconfig of MCP Server results in leveraged access violation (i.e. user does not have access to list the pods but MCP server has). It is difficult to fix this without using impersonation. So that I think, we try to move forward with bearer token + token exchange without relying on MCP Server's kubeconfig.

Can you elaborate on the use case you are trying to cover? First at all, the problem with kubeconfig permissions is not related to multi-cluster/context kubeconfigs, is it? You will have the same problem with a single cluster kubeconfig.
Are you actually talking about a multi-tenant MCP server? When different users/agents accessing the same MCP Server instance.
The current kubernetes MCP Server implementation is single tenant. And this issue is about supporting multiple clusters/contexts (in the single tenant scenario). 

Multi-tenancy IMO is a different problem.
Unless I'm missing something?@ardaguclu: > the problem with kubeconfig permissions is not related to multi-cluster/context kubeconfigs, is it? You will have the same problem with a single cluster kubeconfig.

That is correct. So that we have been experimenting the scenarios that do not need the kubeconfig of MCP Server at all.

> The current kubernetes MCP Server implementation is single tenant. And this issue is about supporting multiple clusters/contexts (in the single tenant scenario).

Yes, I'm also talking about single tenant, multiple clusters/contexts scenarios.

Solution patch:
diff --git a/go.mod b/go.mod
index efcc6dd..2ab6b79 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,7 @@ require (
 	github.com/fsnotify/fsnotify v1.9.0
 	github.com/go-jose/go-jose/v4 v4.1.2
 	github.com/google/jsonschema-go v0.3.0
-	github.com/mark3labs/mcp-go v0.40.0
+	github.com/mark3labs/mcp-go v0.41.1
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/afero v1.15.0
 	github.com/spf13/cobra v1.10.1
diff --git a/go.sum b/go.sum
index 192cea5..78e94df 100644
--- a/go.sum
+++ b/go.sum
@@ -187,8 +187,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn
 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
 github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
 github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
-github.com/mark3labs/mcp-go v0.40.0 h1:M0oqK412OHBKut9JwXSsj4KanSmEKpzoW8TcxoPOkAU=
-github.com/mark3labs/mcp-go v0.40.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
+github.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA=
+github.com/mark3labs/mcp-go v0.41.1/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
 github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
 github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
diff --git a/pkg/api/toolsets.go b/pkg/api/toolsets.go
index 3ccb888..9a99048 100644
--- a/pkg/api/toolsets.go
+++ b/pkg/api/toolsets.go
@@ -10,8 +10,29 @@ import (
 )
 
 type ServerTool struct {
-	Tool    Tool
-	Handler ToolHandlerFunc
+	Tool               Tool
+	Handler            ToolHandlerFunc
+	ClusterAware       *bool
+	TargetListProvider *bool
+}
+
+// IsClusterAware indicates whether the tool can accept a "cluster" or "context" parameter
+// to operate on a specific Kubernetes cluster context.
+// Defaults to true if not explicitly set
+func (s *ServerTool) IsClusterAware() bool {
+	if s.ClusterAware != nil {
+		return *s.ClusterAware
+	}
+	return true
+}
+
+// IsTargetListProvider indicates whether the tool is used to provide a list of targets (clusters/contexts)
+// Defaults to false if not explicitly set
+func (s *ServerTool) IsTargetListProvider() bool {
+	if s.TargetListProvider != nil {
+		return *s.TargetListProvider
+	}
+	return false
 }
 
 type Toolset interface {
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 177055f..543a4d3 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -6,6 +6,11 @@ import (
 	"github.com/BurntSushi/toml"
 )
 
+const (
+	ClusterProviderKubeConfig = "kubeconfig"
+	ClusterProviderInCluster  = "in-cluster"
+)
+
 // StaticConfig is the configuration for the server.
 // It allows to configure server specific settings and tools to be enabled or disabled.
 type StaticConfig struct {
@@ -49,6 +54,12 @@ type StaticConfig struct {
 	StsScopes            []string `toml:"sts_scopes,omitempty"`
 	CertificateAuthority string   `toml:"certificate_authority,omitempty"`
 	ServerURL            string   `toml:"server_url,omitempty"`
+	// ClusterProviderStrategy is how the server finds clusters.
+	// If set to "kubeconfig", the clusters will be loaded from those in the kubeconfig.
+	// If set to "in-cluster", the server will use the in cluster config
+	ClusterProviderStrategy string `toml:"cluster_provider_strategy,omitempty"`
+	// ClusterContexts is which context should be used for each cluster
+	ClusterContexts map[string]string `toml:"cluster_contexts"`
 }
 
 func Default() *StaticConfig {
diff --git a/pkg/http/authorization.go b/pkg/http/authorization.go
index 39259d5..261fdb9 100644
--- a/pkg/http/authorization.go
+++ b/pkg/http/authorization.go
@@ -1,8 +1,11 @@
 package http
 
 import (
+	"bytes"
 	"context"
+	"encoding/json"
 	"fmt"
+	"io"
 	"net/http"
 	"strings"
 
@@ -20,7 +23,50 @@ import (
 
 type KubernetesApiTokenVerifier interface {
 	// KubernetesApiVerifyToken TODO: clarify proper implementation
-	KubernetesApiVerifyToken(ctx context.Context, token, audience string) (*authenticationapiv1.UserInfo, []string, error)
+	KubernetesApiVerifyToken(ctx context.Context, token, audience, cluster string) (*authenticationapiv1.UserInfo, []string, error)
+	// GetTargetParameterName returns the parameter name used for target identification in MCP requests
+	GetTargetParameterName() string
+}
+
+// extractTargetFromRequest extracts cluster parameter from MCP request body
+func extractTargetFromRequest(r *http.Request, targetName string) (string, error) {
+	if r.Body == nil {
+		return "", nil
+	}
+
+	// Read the body
+	body, err := io.ReadAll(r.Body)
+	if err != nil {
+		return "", err
+	}
+
+	// Restore the body for downstream handlers
+	r.Body = io.NopCloser(bytes.NewBuffer(body))
+
+	// Parse the MCP request
+	var mcpRequest struct {
+		Params struct {
+			Arguments map[string]interface{} `json:"arguments"`
+		} `json:"params"`
+	}
+
+	if err := json.Unmarshal(body, &mcpRequest); err != nil {
+		// If we can't parse the request, just return empty cluster (will use default)
+		return "", nil
+	}
+
+	// Extract target parameter
+	if cluster, ok := mcpRequest.Params.Arguments[targetName].(string); ok {
+		return cluster, nil
+	}
+
+	return "", nil
+}
+
+// write401 sends a 401/Unauthorized response with WWW-Authenticate header.
+func write401(w http.ResponseWriter, wwwAuthenticateHeader, errorType, message string) {
+	w.Header().Set("WWW-Authenticate", wwwAuthenticateHeader+fmt.Sprintf(`, error="%s"`, errorType))
+	http.Error(w, message, http.StatusUnauthorized)
 }
 
 // AuthorizationMiddleware validates the OAuth flow for protected resources.
@@ -82,9 +128,7 @@ func AuthorizationMiddleware(staticConfig *config.StaticConfig, oidcProvider *oi
 			authHeader := r.Header.Get("Authorization")
 			if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
 				klog.V(1).Infof("Authentication failed - missing or invalid bearer token: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr)
-
-				w.Header().Set("WWW-Authenticate", wwwAuthenticateHeader+", error=\"missing_token\"")
-				http.Error(w, "Unauthorized: Bearer token required", http.StatusUnauthorized)
+				write401(w, wwwAuthenticateHeader, "missing_token", "Unauthorized: Bearer token required")
 				return
 			}
 
@@ -128,13 +172,16 @@ func AuthorizationMiddleware(staticConfig *config.StaticConfig, oidcProvider *oi
 			}
 			// Kubernetes API Server TokenReview validation
 			if err == nil && staticConfig.ValidateToken {
-				err = claims.ValidateWithKubernetesApi(r.Context(), staticConfig.OAuthAudience, verifier)
+				targetParameterName := verifier.GetTargetParameterName()
+				cluster, clusterErr := extractTargetFromRequest(r, targetParameterName)
+				if clusterErr != nil {
+					klog.V(2).Infof("Failed to extract cluster from request, using default: %v", clusterErr)
+				}
+				err = claims.ValidateWithKubernetesApi(r.Context(), staticConfig.OAuthAudience, cluster, verifier)
 			}
 			if err != nil {
 				klog.V(1).Infof("Authentication failed - JWT validation error: %s %s from %s, error: %v", r.Method, r.URL.Path, r.RemoteAddr, err)
-
-				w.Header().Set("WWW-Authenticate", wwwAuthenticateHeader+", error=\"invalid_token\"")
-				http.Error(w, "Unauthorized: Invalid token", http.StatusUnauthorized)
+				write401(w, wwwAuthenticateHeader, "invalid_token", "Unauthorized: Invalid token")
 				return
 			}
 
@@ -198,9 +245,9 @@ func (c *JWTClaims) ValidateWithProvider(ctx context.Context, audience string, p
 	return nil
 }
 
-func (c *JWTClaims) ValidateWithKubernetesApi(ctx context.Context, audience string, verifier KubernetesApiTokenVerifier) error {
+func (c *JWTClaims) ValidateWithKubernetesApi(ctx context.Context, audience, cluster string, verifier KubernetesApiTokenVerifier) error {
 	if verifier != nil {
-		_, _, err := verifier.KubernetesApiVerifyToken(ctx, c.Token, audience)
+		_, _, err := verifier.KubernetesApiVerifyToken(ctx, c.Token, audience, cluster)
 		if err != nil {
 			return fmt.Errorf("kubernetes API token validation error: %v", err)
 		}
diff --git a/pkg/kubernetes/configuration.go b/pkg/kubernetes/configuration.go
index 75a4a7e..ff521a2 100644
--- a/pkg/kubernetes/configuration.go
+++ b/pkg/kubernetes/configuration.go
@@ -8,6 +8,8 @@ import (
 	"k8s.io/client-go/tools/clientcmd/api/latest"
 )
 
+const inClusterKubeConfigDefaultContext = "in-cluster"
+
 // InClusterConfig is a variable that holds the function to get the in-cluster config
 // Exposed for testing
 var InClusterConfig = func() (*rest.Config, error) {
@@ -81,6 +83,45 @@ func (m *Manager) ToRawKubeConfigLoader() clientcmd.ClientConfig {
 	return m.clientCmdConfig
 }
 
+// ConfigurationContextsDefault returns the current context name
+// TODO: Should be moved to the Provider level ?
+func (k *Kubernetes) ConfigurationContextsDefault() (string, error) {
+	if k.manager.IsInCluster() {
+		return inClusterKubeConfigDefaultContext, nil
+	}
+	cfg, err := k.manager.clientCmdConfig.RawConfig()
+	if err != nil {
+		return "", err
+	}
+	return cfg.CurrentContext, nil
+}
+
+// ConfigurationContextsList returns the list of available context names
+// TODO: Should be moved to the Provider level ?
+func (k *Kubernetes) ConfigurationContextsList() (map[string]string, error) {
+	if k.manager.IsInCluster() {
+		return map[string]string{inClusterKubeConfigDefaultContext: ""}, nil
+	}
+	cfg, err := k.manager.clientCmdConfig.RawConfig()
+	if err != nil {
+		return nil, err
+	}
+	contexts := make(map[string]string, len(cfg.Contexts))
+	for name, context := range cfg.Contexts {
+		cluster, ok := cfg.Clusters[context.Cluster]
+		if !ok || cluster.Server == "" {
+			contexts[name] = "unknown"
+		} else {
+			contexts[name] = cluster.Server
+		}
+	}
+	return contexts, nil
+}
+
+// ConfigurationView returns the current kubeconfig content as a kubeconfig YAML
+// If minify is true, keeps only the current-context and the relevant pieces of the configuration for that context.
+// If minify is false, all contexts, clusters, auth-infos, and users are returned in the configuration.
+// TODO: Should be moved to the Provider level ?
 func (k *Kubernetes) ConfigurationView(minify bool) (runtime.Object, error) {
 	var cfg clientcmdapi.Config
 	var err error
@@ -93,11 +134,11 @@ func (k *Kubernetes) ConfigurationView(minify bool) (runtime.Object, error) {
 		cfg.AuthInfos["user"] = &clientcmdapi.AuthInfo{
 			Token: k.manager.cfg.BearerToken,
 		}
-		cfg.Contexts["context"] = &clientcmdapi.Context{
+		cfg.Contexts[inClusterKubeConfigDefaultContext] = &clientcmdapi.Context{
 			Cluster:  "cluster",
 			AuthInfo: "user",
 		}
-		cfg.CurrentContext = "context"
+		cfg.CurrentContext = inClusterKubeConfigDefaultContext
 	} else if cfg, err = k.manager.clientCmdConfig.RawConfig(); err != nil {
 		return nil, err
 	}
diff --git a/pkg/kubernetes/provider.go b/pkg/kubernetes/provider.go
new file mode 100644
index 0000000..57162d6
--- /dev/null
+++ b/pkg/kubernetes/provider.go
@@ -0,0 +1,231 @@
+package kubernetes
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/containers/kubernetes-mcp-server/pkg/config"
+	"k8s.io/client-go/discovery/cached/memory"
+	"k8s.io/client-go/dynamic"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/restmapper"
+	"k8s.io/client-go/tools/clientcmd"
+)
+
+const (
+	KubeConfigTargetParameterName = "context"
+)
+
+type ManagerProvider interface {
+	GetTargets(ctx context.Context) ([]string, error)
+	GetManagerFor(ctx context.Context, target string) (*Manager, error)
+	GetDefaultTarget() string
+	GetTargetParameterName() string
+	WatchTargets(func() error)
+	Close()
+}
+
+type kubeConfigClusterProvider struct {
+	defaultContext string
+	managers       map[string]*Manager
+}
+
+var _ ManagerProvider = &kubeConfigClusterProvider{}
+
+type inClusterProvider struct {
+	manager *Manager
+}
+
+var _ ManagerProvider = &inClusterProvider{}
+
+func NewManagerProvider(cfg *config.StaticConfig) (ManagerProvider, error) {
+	m, err := NewManager(cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	switch resolveStrategy(cfg, m) {
+	case config.ClusterProviderKubeConfig:
+		return newKubeConfigClusterProvider(m)
+	case config.ClusterProviderInCluster:
+		return newInClusterProvider(m)
+	default:
+		return nil, fmt.Errorf("invalid ClusterProviderStrategy '%s', must be 'kubeconfig' or 'in-cluster'", cfg.ClusterProviderStrategy)
+	}
+}
+
+func newKubeConfigClusterProvider(m *Manager) (*kubeConfigClusterProvider, error) {
+	// Handle in-cluster mode
+	if m.IsInCluster() {
+		return nil, fmt.Errorf("kubeconfig ClusterProviderStrategy is invalid for in-cluster deployments")
+	}
+
+	rawConfig, err := m.clientCmdConfig.RawConfig()
+	if err != nil {
+		return nil, err
+	}
+
+	allClusterManagers := map[string]*Manager{
+		rawConfig.CurrentContext: m, // we already initialized a manager for the default context, let's use it
+	}
+
+	for name := range rawConfig.Contexts {
+		if name == rawConfig.CurrentContext {
+			continue // already initialized this, don't want to set it to nil
+		}
+
+		allClusterManagers[name] = nil
+	}
+
+	return &kubeConfigClusterProvider{
+		defaultContext: rawConfig.CurrentContext,
+		managers:       allClusterManagers,
+	}, nil
+}
+
+func newInClusterProvider(m *Manager) (*inClusterProvider, error) {
+	return &inClusterProvider{
+		manager: m,
+	}, nil
+}
+
+func (k *kubeConfigClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
+	contextNames := make([]string, 0, len(k.managers))
+	for cluster := range k.managers {
+		contextNames = append(contextNames, cluster)
+	}
+
+	return contextNames, nil
+}
+
+func (k *kubeConfigClusterProvider) GetTargetParameterName() string {
+	return KubeConfigTargetParameterName
+}
+
+func (k *kubeConfigClusterProvider) GetManagerFor(ctx context.Context, context string) (*Manager, error) {
+	m, ok := k.managers[context]
+	if ok && m != nil {
+		return m, nil
+	}
+
+	baseManager := k.managers[k.defaultContext]
+
+	if baseManager.IsInCluster() {
+		// In cluster mode, so context switching is not applicable
+		return baseManager, nil
+	}
+
+	m, err := baseManager.newForContext(context)
+	if err != nil {
+		return nil, err
+	}
+
+	k.managers[context] = m
+
+	return m, nil
+}
+
+func (k *kubeConfigClusterProvider) GetDefaultTarget() string {
+	return k.defaultContext
+}
+
+func (k *kubeConfigClusterProvider) WatchTargets(onKubeConfigChanged func() error) {
+	m := k.managers[k.defaultContext]
+
+	m.WatchKubeConfig(onKubeConfigChanged)
+}
+
+func (k *kubeConfigClusterProvider) Close() {
+	m := k.managers[k.defaultContext]
+
+	m.Close()
+}
+
+func (i *inClusterProvider) GetTargets(ctx context.Context) ([]string, error) {
+	return []string{""}, nil
+}
+
+func (i *inClusterProvider) GetManagerFor(ctx context.Context, target string) (*Manager, error) {
+	if target != "" {
+		return nil, fmt.Errorf("unable to get manager for other context/cluster with in-cluster strategy")
+	}
+
+	return i.manager, nil
+}
+
+func (i *inClusterProvider) GetDefaultTarget() string {
+	return ""
+}
+
+func (i *inClusterProvider) GetTargetParameterName() string {
+	return ""
+}
+
+func (i *inClusterProvider) WatchTargets(watch func() error) {
+	i.manager.WatchKubeConfig(watch)
+}
+
+func (i *inClusterProvider) Close() {
+	i.manager.Close()
+}
+
+func (m *Manager) newForContext(context string) (*Manager, error) {
+	pathOptions := clientcmd.NewDefaultPathOptions()
+	if m.staticConfig.KubeConfig != "" {
+		pathOptions.LoadingRules.ExplicitPath = m.staticConfig.KubeConfig
+	}
+
+	clientCmdConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
+		pathOptions.LoadingRules,
+		&clientcmd.ConfigOverrides{
+			CurrentContext: context,
+		},
+	)
+
+	cfg, err := clientCmdConfig.ClientConfig()
+	if err != nil {
+		return nil, err
+	}
+
+	if cfg.UserAgent == "" {
+		cfg.UserAgent = rest.DefaultKubernetesUserAgent()
+	}
+
+	manager := &Manager{
+		cfg:             cfg,
+		clientCmdConfig: clientCmdConfig,
+		staticConfig:    m.staticConfig,
+	}
+
+	// Initialize clients for new manager
+	manager.accessControlClientSet, err = NewAccessControlClientset(manager.cfg, manager.staticConfig)
+	if err != nil {
+		return nil, err
+	}
+
+	manager.discoveryClient = memory.NewMemCacheClient(manager.accessControlClientSet.DiscoveryClient())
+
+	manager.accessControlRESTMapper = NewAccessControlRESTMapper(
+		restmapper.NewDeferredDiscoveryRESTMapper(manager.discoveryClient),
+		manager.staticConfig,
+	)
+
+	manager.dynamicClient, err = dynamic.NewForConfig(manager.cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	return manager, nil
+}
+
+func resolveStrategy(cfg *config.StaticConfig, m *Manager) string {
+	if cfg.ClusterProviderStrategy != "" {
+		return cfg.ClusterProviderStrategy
+	}
+
+	if m.IsInCluster() {
+		return config.ClusterProviderInCluster
+	}
+
+	return config.ClusterProviderKubeConfig
+}
diff --git a/pkg/mcp/m3labs.go b/pkg/mcp/m3labs.go
index 4531c54..bae6aeb 100644
--- a/pkg/mcp/m3labs.go
+++ b/pkg/mcp/m3labs.go
@@ -39,10 +39,19 @@ func ServerToolToM3LabsServerTool(s *Server, tools []api.ServerTool) ([]server.S
 			m3labTool.RawInputSchema = schema
 		}
 		m3labHandler := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
-			k, err := s.k.Derived(ctx)
+			// get the correct internalk8s.Manager for the target specified in the request
+			cluster := request.GetString(s.p.GetTargetParameterName(), s.p.GetDefaultTarget())
+			m, err := s.p.GetManagerFor(ctx, cluster)
 			if err != nil {
 				return nil, err
 			}
+
+			// derive the manager based on auth on top of the settings for the cluster
+			k, err := m.Derived(ctx)
+			if err != nil {
+				return nil, err
+			}
+
 			result, err := tool.Handler(api.ToolHandlerParams{
 				Context:         ctx,
 				Kubernetes:      k,
diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go
index 95e16ec..d8e9177 100644
--- a/pkg/mcp/mcp.go
+++ b/pkg/mcp/mcp.go
@@ -67,7 +67,7 @@ type Server struct {
 	configuration *Configuration
 	server        *server.MCPServer
 	enabledTools  []string
-	k             *internalk8s.Manager
+	p             internalk8s.ManagerProvider
 }
 
 func NewServer(configuration Configuration) (*Server, error) {
@@ -91,26 +91,57 @@ func NewServer(configuration Configuration) (*Server, error) {
 			serverOptions...,
 		),
 	}
-	if err := s.reloadKubernetesClient(); err != nil {
+	if err := s.reloadKubernetesClusterProvider(); err != nil {
 		return nil, err
 	}
-	s.k.WatchKubeConfig(s.reloadKubernetesClient)
+	s.p.WatchTargets(s.reloadKubernetesClusterProvider)
 
 	return s, nil
 }
 
-func (s *Server) reloadKubernetesClient() error {
-	k, err := internalk8s.NewManager(s.configuration.StaticConfig)
+func (s *Server) reloadKubernetesClusterProvider() error {
+	ctx := context.Background()
+	p, err := internalk8s.NewManagerProvider(s.configuration.StaticConfig)
 	if err != nil {
 		return err
 	}
-	s.k = k
+
+	// close the old provider
+	if s.p != nil {
+		s.p.Close()
+	}
+
+	s.p = p
+
+	k, err := s.p.GetManagerFor(ctx, s.p.GetDefaultTarget())
+	if err != nil {
+		return err
+	}
+
+	targets, err := p.GetTargets(ctx)
+	if err != nil {
+		return err
+	}
+
+	filter := CompositeFilter(
+		s.configuration.isToolApplicable,
+		ShouldIncludeTargetListTool(p.GetTargetParameterName(), targets),
+	)
+
+	mutator := WithTargetParameter(
+		p.GetDefaultTarget(),
+		p.GetTargetParameterName(),
+		targets,
+	)
+
 	applicableTools := make([]api.ServerTool, 0)
 	for _, toolset := range s.configuration.Toolsets() {
-		for _, tool := range toolset.GetTools(s.k) {
-			if !s.configuration.isToolApplicable(tool) {
+		for _, tool := range toolset.GetTools(k) {
+			tool := mutator(tool)
+			if !filter(tool) {
 				continue
 			}
+
 			applicableTools = append(applicableTools, tool)
 			s.enabledTools = append(s.enabledTools, tool.Tool.Name)
 		}
@@ -119,7 +150,11 @@ func (s *Server) reloadKubernetesClient() error {
 	if err != nil {
 		return fmt.Errorf("failed to convert tools: %v", err)
 	}
+
 	s.server.SetTools(m3labsServerTools...)
+
+	// start new watch
+	s.p.WatchTargets(s.reloadKubernetesClusterProvider)
 	return nil
 }
 
@@ -146,20 +181,32 @@ func (s *Server) ServeHTTP(httpServer *http.Server) *server.StreamableHTTPServer
 }
 
 // KubernetesApiVerifyToken verifies the given token with the audience by
-// sending an TokenReview request to API Server.
-func (s *Server) KubernetesApiVerifyToken(ctx context.Context, token string, audience string) (*authenticationapiv1.UserInfo, []string, error) {
-	if s.k == nil {
-		return nil, nil, fmt.Errorf("kubernetes manager is not initialized")
+// sending an TokenReview request to API Server for the specified cluster.
+func (s *Server) KubernetesApiVerifyToken(ctx context.Context, token string, audience string, cluster string) (*authenticationapiv1.UserInfo, []string, error) {
+	if s.p == nil {
+		return nil, nil, fmt.Errorf("kubernetes cluster provider is not initialized")
 	}
-	return s.k.VerifyToken(ctx, token, audience)
+
+	// Use provided cluster or default
+	if cluster == "" {
+		cluster = s.p.GetDefaultTarget()
+	}
+
+	// Get the cluster manager for the specified cluster
+	m, err := s.p.GetManagerFor(ctx, cluster)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return m.VerifyToken(ctx, token, audience)
 }
 
-// GetKubernetesAPIServerHost returns the Kubernetes API server host from the configuration.
-func (s *Server) GetKubernetesAPIServerHost() string {
-	if s.k == nil {
-		return ""
+// GetTargetParameterName returns the parameter name used for target identification in MCP requests
+func (s *Server) GetTargetParameterName() string {
+	if s.p == nil {
+		return "" // fallback for uninitialized provider
 	}
-	return s.k.GetAPIServerHost()
+	return s.p.GetTargetParameterName()
 }
 
 func (s *Server) GetEnabledTools() []string {
@@ -167,8 +214,8 @@ func (s *Server) GetEnabledTools() []string {
 }
 
 func (s *Server) Close() {
-	if s.k != nil {
-		s.k.Close()
+	if s.p != nil {
+		s.p.Close()
 	}
 }
 
diff --git a/pkg/mcp/tool_filter.go b/pkg/mcp/tool_filter.go
new file mode 100644
index 0000000..c097132
--- /dev/null
+++ b/pkg/mcp/tool_filter.go
@@ -0,0 +1,41 @@
+package mcp
+
+import (
+	"github.com/containers/kubernetes-mcp-server/pkg/api"
+	"github.com/containers/kubernetes-mcp-server/pkg/kubernetes"
+)
+
+// ToolFilter is a function that takes a ServerTool and returns a boolean indicating whether to include the tool
+type ToolFilter func(tool api.ServerTool) bool
+
+func CompositeFilter(filters ...ToolFilter) ToolFilter {
+	return func(tool api.ServerTool) bool {
+		for _, f := range filters {
+			if !f(tool) {
+				return false
+			}
+		}
+
+		return true
+	}
+}
+
+func ShouldIncludeTargetListTool(targetName string, targets []string) ToolFilter {
+	return func(tool api.ServerTool) bool {
+		if !tool.IsTargetListProvider() {
+			return true
+		}
+		if len(targets) <= 1 {
+			// there is no need to provide a tool to list the single available target
+			return false
+		}
+
+		// TODO: this check should be removed or make more generic when we have other
+		if tool.Tool.Name == "configuration_contexts_list" && targetName != kubernetes.KubeConfigTargetParameterName {
+			// let's not include configuration_contexts_list if we aren't targeting contexts in our ManagerProvider
+			return false
+		}
+
+		return true
+	}
+}
diff --git a/pkg/mcp/tool_mutator.go b/pkg/mcp/tool_mutator.go
new file mode 100644
index 0000000..f41d43d
--- /dev/null
+++ b/pkg/mcp/tool_mutator.go
@@ -0,0 +1,64 @@
+package mcp
+
+import (
+	"fmt"
+	"sort"
+
+	"github.com/containers/kubernetes-mcp-server/pkg/api"
+	"github.com/google/jsonschema-go/jsonschema"
+)
+
+type ToolMutator func(tool api.ServerTool) api.ServerTool
+
+const maxTargetsInEnum = 5 // TODO: test and validate that this is a reasonable cutoff
+
+// WithTargetParameter adds a target selection parameter to the tool's input schema if the tool is cluster-aware
+func WithTargetParameter(defaultCluster, targetParameterName string, targets []string) ToolMutator {
+	return func(tool api.ServerTool) api.ServerTool {
+		if !tool.IsClusterAware() {
+			return tool
+		}
+
+		if tool.Tool.InputSchema == nil {
+			tool.Tool.InputSchema = &jsonschema.Schema{Type: "object"}
+		}
+
+		if tool.Tool.InputSchema.Properties == nil {
+			tool.Tool.InputSchema.Properties = make(map[string]*jsonschema.Schema)
+		}
+
+		if len(targets) > 1 {
+			tool.Tool.InputSchema.Properties[targetParameterName] = createTargetProperty(
+				defaultCluster,
+				targetParameterName,
+				targets,
+			)
+		}
+
+		return tool
+	}
+}
+
+func createTargetProperty(defaultCluster, targetName string, targets []string) *jsonschema.Schema {
+	baseSchema := &jsonschema.Schema{
+		Type: "string",
+		Description: fmt.Sprintf(
+			"Optional parameter selecting which %s to run the tool in. Defaults to %s if not set",
+			targetName,
+			defaultCluster,
+		),
+	}
+
+	if len(targets) <= maxTargetsInEnum {
+		// Sort clusters to ensure consistent enum ordering
+		sort.Strings(targets)
+
+		enumValues := make([]any, 0, len(targets))
+		for _, c := range targets {
+			enumValues = append(enumValues, c)
+		}
+		baseSchema.Enum = enumValues
+	}
+
+	return baseSchema
+}
diff --git a/pkg/toolsets/config/configuration.go b/pkg/toolsets/config/configuration.go
index 4bc3ead..6b6b45d 100644
--- a/pkg/toolsets/config/configuration.go
+++ b/pkg/toolsets/config/configuration.go
@@ -12,33 +12,91 @@ import (
 
 func initConfiguration() []api.ServerTool {
 	tools := []api.ServerTool{
-		{Tool: api.Tool{
-			Name:        "configuration_view",
-			Description: "Get the current Kubernetes configuration content as a kubeconfig YAML",
-			InputSchema: &jsonschema.Schema{
-				Type: "object",
-				Properties: map[string]*jsonschema.Schema{
-					"minified": {
-						Type: "boolean",
-						Description: "Return a minified version of the configuration. " +
-							"If set to true, keeps only the current-context and the relevant pieces of the configuration for that context. " +
-							"If set to false, all contexts, clusters, auth-infos, and users are returned in the configuration. " +
-							"(Optional, default true)",
-					},
+		{
+			Tool: api.Tool{
+				Name:        "configuration_contexts_list",
+				Description: "List all available context names and associated server urls from the kubeconfig file",
+				InputSchema: &jsonschema.Schema{
+					Type: "object",
+				},
+				Annotations: api.ToolAnnotations{
+					Title:           "Configuration: Contexts List",
+					ReadOnlyHint:    ptr.To(true),
+					DestructiveHint: ptr.To(false),
+					IdempotentHint:  ptr.To(true),
+					OpenWorldHint:   ptr.To(false),
 				},
 			},
-			Annotations: api.ToolAnnotations{
-				Title:           "Configuration: View",
-				ReadOnlyHint:    ptr.To(true),
-				DestructiveHint: ptr.To(false),
-				IdempotentHint:  ptr.To(false),
-				OpenWorldHint:   ptr.To(true),
+			ClusterAware:       ptr.To(false),
+			TargetListProvider: ptr.To(true),
+			Handler:            contextsList,
+		},
+		{
+			Tool: api.Tool{
+				Name:        "configuration_view",
+				Description: "Get the current Kubernetes configuration content as a kubeconfig YAML",
+				InputSchema: &jsonschema.Schema{
+					Type: "object",
+					Properties: map[string]*jsonschema.Schema{
+						"minified": {
+							Type: "boolean",
+							Description: "Return a minified version of the configuration. " +
+								"If set to true, keeps only the current-context and the relevant pieces of the configuration for that context. " +
+								"If set to false, all contexts, clusters, auth-infos, and users are returned in the configuration. " +
+								"(Optional, default true)",
+						},
+					},
+				},
+				Annotations: api.ToolAnnotations{
+					Title:           "Configuration: View",
+					ReadOnlyHint:    ptr.To(true),
+					DestructiveHint: ptr.To(false),
+					IdempotentHint:  ptr.To(false),
+					OpenWorldHint:   ptr.To(true),
+				},
 			},
-		}, Handler: configurationView},
+			ClusterAware: ptr.To(false),
+			Handler:      configurationView,
+		},
 	}
 	return tools
 }
 
+func contextsList(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
+	contexts, err := params.ConfigurationContextsList()
+	if err != nil {
+		return api.NewToolCallResult("", fmt.Errorf("failed to list contexts: %v", err)), nil
+	}
+
+	if len(contexts) == 0 {
+		return api.NewToolCallResult("No contexts found in kubeconfig", nil), nil
+	}
+
+	defaultContext, err := params.ConfigurationContextsDefault()
+	if err != nil {
+		return api.NewToolCallResult("", fmt.Errorf("failed to get default context: %v", err)), nil
+	}
+
+	result := fmt.Sprintf("Available Kubernetes contexts (%d total, default: %s):\n\n", len(contexts), defaultContext)
+	result += "Format: [*] CONTEXT_NAME -> SERVER_URL\n"
+	result += " (* indicates the default context used in tools if context is not set)\n\n"
+	result += "Contexts:\n---------\n"
+	for context, server := range contexts {
+		marker := " "
+		if context == defaultContext {
+			marker = "*"
+		}
+
+		result += fmt.Sprintf("%s%s -> %s\n", marker, context, server)
+	}
+	result += "---------\n\n"
+
+	result += "To use a specific context with any tool, set the 'context' parameter in the tool call arguments"
+
+	// TODO: Review output format, current is not parseable and might not be ideal for LLM consumption
+	return api.NewToolCallResult(result, nil), nil
+}
+
 func configurationView(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
 	minify := true
 	minified := params.GetArguments()["minified"]



Test patch:
diff --git a/internal/test/kubernetes.go b/internal/test/kubernetes.go
index 7e33f8a..eee059f 100644
--- a/internal/test/kubernetes.go
+++ b/internal/test/kubernetes.go
@@ -8,15 +8,10 @@ func KubeConfigFake() *clientcmdapi.Config {
 	fakeConfig := clientcmdapi.NewConfig()
 	fakeConfig.Clusters["fake"] = clientcmdapi.NewCluster()
 	fakeConfig.Clusters["fake"].Server = "https://127.0.0.1:6443"
-	fakeConfig.Clusters["additional-cluster"] = clientcmdapi.NewCluster()
 	fakeConfig.AuthInfos["fake"] = clientcmdapi.NewAuthInfo()
-	fakeConfig.AuthInfos["additional-auth"] = clientcmdapi.NewAuthInfo()
 	fakeConfig.Contexts["fake-context"] = clientcmdapi.NewContext()
 	fakeConfig.Contexts["fake-context"].Cluster = "fake"
 	fakeConfig.Contexts["fake-context"].AuthInfo = "fake"
-	fakeConfig.Contexts["additional-context"] = clientcmdapi.NewContext()
-	fakeConfig.Contexts["additional-context"].Cluster = "additional-cluster"
-	fakeConfig.Contexts["additional-context"].AuthInfo = "additional-auth"
 	fakeConfig.CurrentContext = "fake-context"
 	return fakeConfig
 }
diff --git a/internal/test/mock_server.go b/internal/test/mock_server.go
index c3c20d0..58740ad 100644
--- a/internal/test/mock_server.go
+++ b/internal/test/mock_server.go
@@ -73,10 +73,14 @@ func (m *MockServer) Kubeconfig() *api.Config {
 }
 
 func (m *MockServer) KubeconfigFile(t *testing.T) string {
-	kubeconfig := filepath.Join(t.TempDir(), "config")
-	err := clientcmd.WriteToFile(*m.Kubeconfig(), kubeconfig)
+	return KubeconfigFile(t, m.Kubeconfig())
+}
+
+func KubeconfigFile(t *testing.T, kubeconfig *api.Config) string {
+	kubeconfigFile := filepath.Join(t.TempDir(), "config")
+	err := clientcmd.WriteToFile(*kubeconfig, kubeconfigFile)
 	require.NoError(t, err, "Expected no error writing kubeconfig file")
-	return kubeconfig
+	return kubeconfigFile
 }
 
 func WriteObject(w http.ResponseWriter, obj runtime.Object) {
diff --git a/pkg/api/toolsets_test.go b/pkg/api/toolsets_test.go
new file mode 100644
index 0000000..0482d09
--- /dev/null
+++ b/pkg/api/toolsets_test.go
@@ -0,0 +1,47 @@
+package api
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+	"k8s.io/utils/ptr"
+)
+
+type ToolsetsSuite struct {
+	suite.Suite
+}
+
+func (s *ToolsetsSuite) TestServerTool() {
+	s.Run("IsClusterAware", func() {
+		s.Run("defaults to true", func() {
+			tool := &ServerTool{}
+			s.True(tool.IsClusterAware(), "Expected IsClusterAware to be true by default")
+		})
+		s.Run("can be set to false", func() {
+			tool := &ServerTool{ClusterAware: ptr.To(false)}
+			s.False(tool.IsClusterAware(), "Expected IsClusterAware to be false when set to false")
+		})
+		s.Run("can be set to true", func() {
+			tool := &ServerTool{ClusterAware: ptr.To(true)}
+			s.True(tool.IsClusterAware(), "Expected IsClusterAware to be true when set to true")
+		})
+	})
+	s.Run("IsTargetListProvider", func() {
+		s.Run("defaults to false", func() {
+			tool := &ServerTool{}
+			s.False(tool.IsTargetListProvider(), "Expected IsTargetListProvider to be false by default")
+		})
+		s.Run("can be set to false", func() {
+			tool := &ServerTool{TargetListProvider: ptr.To(false)}
+			s.False(tool.IsTargetListProvider(), "Expected IsTargetListProvider to be false when set to false")
+		})
+		s.Run("can be set to true", func() {
+			tool := &ServerTool{TargetListProvider: ptr.To(true)}
+			s.True(tool.IsTargetListProvider(), "Expected IsTargetListProvider to be true when set to true")
+		})
+	})
+}
+
+func TestToolsets(t *testing.T) {
+	suite.Run(t, new(ToolsetsSuite))
+}
diff --git a/pkg/http/http_test.go b/pkg/http/http_test.go
index c5f0231..36e7f88 100644
--- a/pkg/http/http_test.go
+++ b/pkg/http/http_test.go
@@ -292,7 +292,7 @@ func TestHealthCheck(t *testing.T) {
 		})
 	})
 	// Health exposed even when require Authorization
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true}}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 		resp, err := http.Get(fmt.Sprintf("http://%s/healthz", ctx.HttpAddress))
 		if err != nil {
 			t.Fatalf("Failed to get health check endpoint with OAuth: %v", err)
@@ -313,7 +313,7 @@ func TestWellKnownReverseProxy(t *testing.T) {
 		".well-known/openid-configuration",
 	}
 	// With No Authorization URL configured
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true}}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 		for _, path := range cases {
 			resp, err := http.Get(fmt.Sprintf("http://%s/%s", ctx.HttpAddress, path))
 			t.Cleanup(func() { _ = resp.Body.Close() })
@@ -333,7 +333,12 @@ func TestWellKnownReverseProxy(t *testing.T) {
 		_, _ = w.Write([]byte(`NOT A JSON PAYLOAD`))
 	}))
 	t.Cleanup(invalidPayloadServer.Close)
-	invalidPayloadConfig := &config.StaticConfig{AuthorizationURL: invalidPayloadServer.URL, RequireOAuth: true, ValidateToken: true}
+	invalidPayloadConfig := &config.StaticConfig{
+		AuthorizationURL:        invalidPayloadServer.URL,
+		RequireOAuth:            true,
+		ValidateToken:           true,
+		ClusterProviderStrategy: config.ClusterProviderKubeConfig,
+	}
 	testCaseWithContext(t, &httpContext{StaticConfig: invalidPayloadConfig}, func(ctx *httpContext) {
 		for _, path := range cases {
 			resp, err := http.Get(fmt.Sprintf("http://%s/%s", ctx.HttpAddress, path))
@@ -358,7 +363,12 @@ func TestWellKnownReverseProxy(t *testing.T) {
 		_, _ = w.Write([]byte(`{"issuer": "https://example.com","scopes_supported":["mcp-server"]}`))
 	}))
 	t.Cleanup(testServer.Close)
-	staticConfig := &config.StaticConfig{AuthorizationURL: testServer.URL, RequireOAuth: true, ValidateToken: true}
+	staticConfig := &config.StaticConfig{
+		AuthorizationURL:        testServer.URL,
+		RequireOAuth:            true,
+		ValidateToken:           true,
+		ClusterProviderStrategy: config.ClusterProviderKubeConfig,
+	}
 	testCaseWithContext(t, &httpContext{StaticConfig: staticConfig}, func(ctx *httpContext) {
 		for _, path := range cases {
 			resp, err := http.Get(fmt.Sprintf("http://%s/%s", ctx.HttpAddress, path))
@@ -401,7 +411,12 @@ func TestWellKnownOverrides(t *testing.T) {
 			}`))
 	}))
 	t.Cleanup(testServer.Close)
-	baseConfig := config.StaticConfig{AuthorizationURL: testServer.URL, RequireOAuth: true, ValidateToken: true}
+	baseConfig := config.StaticConfig{
+		AuthorizationURL:        testServer.URL,
+		RequireOAuth:            true,
+		ValidateToken:           true,
+		ClusterProviderStrategy: config.ClusterProviderKubeConfig,
+	}
 	// With Dynamic Client Registration disabled
 	disableDynamicRegistrationConfig := baseConfig
 	disableDynamicRegistrationConfig.DisableDynamicClientRegistration = true
@@ -488,7 +503,7 @@ func TestMiddlewareLogging(t *testing.T) {
 
 func TestAuthorizationUnauthorized(t *testing.T) {
 	// Missing Authorization header
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true}}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 		resp, err := http.Get(fmt.Sprintf("http://%s/mcp", ctx.HttpAddress))
 		if err != nil {
 			t.Fatalf("Failed to get protected endpoint: %v", err)
@@ -513,7 +528,7 @@ func TestAuthorizationUnauthorized(t *testing.T) {
 		})
 	})
 	// Authorization header without Bearer prefix
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true}}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil)
 		if err != nil {
 			t.Fatalf("Failed to create request: %v", err)
@@ -538,7 +553,7 @@ func TestAuthorizationUnauthorized(t *testing.T) {
 		})
 	})
 	// Invalid Authorization header
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true}}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil)
 		if err != nil {
 			t.Fatalf("Failed to create request: %v", err)
@@ -569,7 +584,7 @@ func TestAuthorizationUnauthorized(t *testing.T) {
 		})
 	})
 	// Expired Authorization Bearer token
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true}}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil)
 		if err != nil {
 			t.Fatalf("Failed to create request: %v", err)
@@ -600,7 +615,7 @@ func TestAuthorizationUnauthorized(t *testing.T) {
 		})
 	})
 	// Invalid audience claim Bearer token
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "expected-audience", ValidateToken: true}}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "expected-audience", ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil)
 		if err != nil {
 			t.Fatalf("Failed to create request: %v", err)
@@ -633,7 +648,7 @@ func TestAuthorizationUnauthorized(t *testing.T) {
 	// Failed OIDC validation
 	oidcTestServer := NewOidcTestServer(t)
 	t.Cleanup(oidcTestServer.Close)
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: true}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) {
 		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil)
 		if err != nil {
 			t.Fatalf("Failed to create request: %v", err)
@@ -670,7 +685,7 @@ func TestAuthorizationUnauthorized(t *testing.T) {
 		"aud": "mcp-server"
 	}`
 	validOidcToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, rawClaims)
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: true}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: true, ClusterProviderStrategy: config.ClusterProviderKubeConfig}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) {
 		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/mcp", ctx.HttpAddress), nil)
 		if err != nil {
 			t.Fatalf("Failed to create request: %v", err)
@@ -703,7 +718,7 @@ func TestAuthorizationUnauthorized(t *testing.T) {
 }
 
 func TestAuthorizationRequireOAuthFalse(t *testing.T) {
-	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: false}}, func(ctx *httpContext) {
+	testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: false, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 		resp, err := http.Get(fmt.Sprintf("http://%s/mcp", ctx.HttpAddress))
 		if err != nil {
 			t.Fatalf("Failed to get protected endpoint: %v", err)
@@ -728,7 +743,7 @@ func TestAuthorizationRawToken(t *testing.T) {
 		{"mcp-server", true},  // Audience set, validation enabled
 	}
 	for _, c := range cases {
-		testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: c.audience, ValidateToken: c.validateToken}}, func(ctx *httpContext) {
+		testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: c.audience, ValidateToken: c.validateToken, ClusterProviderStrategy: config.ClusterProviderKubeConfig}}, func(ctx *httpContext) {
 			tokenReviewed := false
 			ctx.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 				if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" {
@@ -777,7 +792,7 @@ func TestAuthorizationOidcToken(t *testing.T) {
 	validOidcToken := oidctest.SignIDToken(oidcTestServer.PrivateKey, "test-oidc-key-id", oidc.RS256, rawClaims)
 	cases := []bool{false, true}
 	for _, validateToken := range cases {
-		testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: validateToken}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) {
+		testCaseWithContext(t, &httpContext{StaticConfig: &config.StaticConfig{RequireOAuth: true, OAuthAudience: "mcp-server", ValidateToken: validateToken, ClusterProviderStrategy: config.ClusterProviderKubeConfig}, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) {
 			tokenReviewed := false
 			ctx.mockServer.Handle(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 				if req.URL.EscapedPath() == "/apis/authentication.k8s.io/v1/tokenreviews" {
@@ -833,13 +848,14 @@ func TestAuthorizationOidcTokenExchange(t *testing.T) {
 	cases := []bool{false, true}
 	for _, validateToken := range cases {
 		staticConfig := &config.StaticConfig{
-			RequireOAuth:    true,
-			OAuthAudience:   "mcp-server",
-			ValidateToken:   validateToken,
-			StsClientId:     "test-sts-client-id",
-			StsClientSecret: "test-sts-client-secret",
-			StsAudience:     "backend-audience",
-			StsScopes:       []string{"backend-scope"},
+			RequireOAuth:            true,
+			OAuthAudience:           "mcp-server",
+			ValidateToken:           validateToken,
+			StsClientId:             "test-sts-client-id",
+			StsClientSecret:         "test-sts-client-secret",
+			StsAudience:             "backend-audience",
+			StsScopes:               []string{"backend-scope"},
+			ClusterProviderStrategy: config.ClusterProviderKubeConfig,
 		}
 		testCaseWithContext(t, &httpContext{StaticConfig: staticConfig, OidcProvider: oidcTestServer.Provider}, func(ctx *httpContext) {
 			tokenReviewed := false
diff --git a/pkg/mcp/common_test.go b/pkg/mcp/common_test.go
index 15f2b56..e9c4975 100644
--- a/pkg/mcp/common_test.go
+++ b/pkg/mcp/common_test.go
@@ -219,7 +219,7 @@ func (c *mcpContext) withKubeConfig(rc *rest.Config) *clientcmdapi.Config {
 	_ = clientcmd.WriteToFile(*fakeConfig, kubeConfig)
 	_ = os.Setenv("KUBECONFIG", kubeConfig)
 	if c.mcpServer != nil {
-		if err := c.mcpServer.reloadKubernetesClient(); err != nil {
+		if err := c.mcpServer.reloadKubernetesClusterProvider(); err != nil {
 			panic(err)
 		}
 	}
diff --git a/pkg/mcp/configuration_test.go b/pkg/mcp/configuration_test.go
index 61e9593..f39a818 100644
--- a/pkg/mcp/configuration_test.go
+++ b/pkg/mcp/configuration_test.go
@@ -1,11 +1,13 @@
 package mcp
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/mark3labs/mcp-go/mcp"
 	"github.com/stretchr/testify/suite"
 	"k8s.io/client-go/rest"
+	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
 	v1 "k8s.io/client-go/tools/clientcmd/api/v1"
 	"sigs.k8s.io/yaml"
 
@@ -22,7 +24,37 @@ func (s *ConfigurationSuite) SetupTest() {
 	// Use mock server for predictable kubeconfig content
 	mockServer := test.NewMockServer()
 	s.T().Cleanup(mockServer.Close)
-	s.Cfg.KubeConfig = mockServer.KubeconfigFile(s.T())
+	kubeconfig := mockServer.Kubeconfig()
+	for i := 0; i < 10; i++ {
+		// Add multiple fake contexts to force configuration_contexts_list tool to appear
+		// and test minification in configuration_view tool
+		name := fmt.Sprintf("cluster-%d", i)
+		kubeconfig.Contexts[name] = clientcmdapi.NewContext()
+		kubeconfig.Clusters[name+"-cluster"] = clientcmdapi.NewCluster()
+		kubeconfig.AuthInfos[name+"-auth"] = clientcmdapi.NewAuthInfo()
+		kubeconfig.Contexts[name].Cluster = name + "-cluster"
+		kubeconfig.Contexts[name].AuthInfo = name + "-auth"
+	}
+	s.Cfg.KubeConfig = test.KubeconfigFile(s.T(), kubeconfig)
+}
+
+func (s *ConfigurationSuite) TestContextsList() {
+	s.InitMcpClient()
+	s.Run("configuration_contexts_list", func() {
+		toolResult, err := s.CallTool("configuration_contexts_list", map[string]interface{}{})
+		s.Run("returns contexts", func() {
+			s.Nilf(err, "call tool failed %v", err)
+		})
+		s.Require().NotNil(toolResult, "Expected tool result from call")
+		s.Lenf(toolResult.Content, 1, "invalid tool result content length %v", len(toolResult.Content))
+		s.Run("contains context count", func() {
+			s.Regexpf(`^Available Kubernetes contexts \(11 total`, toolResult.Content[0].(mcp.TextContent).Text, "invalid tool count result content %v", toolResult.Content[0].(mcp.TextContent).Text)
+		})
+		s.Run("contains default context name", func() {
+			s.Regexpf(`^Available Kubernetes contexts \(\d+ total, default: fake-context\)`, toolResult.Content[0].(mcp.TextContent).Text, "invalid tool context default result content %v", toolResult.Content[0].(mcp.TextContent).Text)
+			s.Regexpf(`(?m)^\*fake-context -> http:\/\/127\.0\.0\.1:\d*$`, toolResult.Content[0].(mcp.TextContent).Text, "invalid tool context default result content %v", toolResult.Content[0].(mcp.TextContent).Text)
+		})
+	})
 }
 
 func (s *ConfigurationSuite) TestConfigurationView() {
@@ -70,19 +102,23 @@ func (s *ConfigurationSuite) TestConfigurationView() {
 			s.Nilf(err, "invalid tool result content %v", err)
 		})
 		s.Run("returns additional context info", func() {
-			s.Lenf(decoded.Contexts, 2, "invalid context count, expected 2, got %v", len(decoded.Contexts))
-			s.Equalf("additional-context", decoded.Contexts[0].Name, "additional-context not found: %v", decoded.Contexts)
-			s.Equalf("additional-cluster", decoded.Contexts[0].Context.Cluster, "additional-cluster not found: %v", decoded.Contexts)
-			s.Equalf("additional-auth", decoded.Contexts[0].Context.AuthInfo, "additional-auth not found: %v", decoded.Contexts)
-			s.Equalf("fake-context", decoded.Contexts[1].Name, "fake-context not found: %v", decoded.Contexts)
+			s.Lenf(decoded.Contexts, 11, "invalid context count, expected 12, got %v", len(decoded.Contexts))
+			s.Equalf("cluster-0", decoded.Contexts[0].Name, "cluster-0 not found: %v", decoded.Contexts)
+			s.Equalf("cluster-0-cluster", decoded.Contexts[0].Context.Cluster, "cluster-0-cluster not found: %v", decoded.Contexts)
+			s.Equalf("cluster-0-auth", decoded.Contexts[0].Context.AuthInfo, "cluster-0-auth not found: %v", decoded.Contexts)
+			s.Equalf("fake", decoded.Contexts[10].Context.Cluster, "fake not found: %v", decoded.Contexts)
+			s.Equalf("fake", decoded.Contexts[10].Context.AuthInfo, "fake not found: %v", decoded.Contexts)
+			s.Equalf("fake-context", decoded.Contexts[10].Name, "fake-context not found: %v", decoded.Contexts)
 		})
 		s.Run("returns cluster info", func() {
-			s.Lenf(decoded.Clusters, 2, "invalid cluster count, expected 2, got %v", len(decoded.Clusters))
-			s.Equalf("additional-cluster", decoded.Clusters[0].Name, "additional-cluster not found: %v", decoded.Clusters)
+			s.Lenf(decoded.Clusters, 11, "invalid cluster count, expected 2, got %v", len(decoded.Clusters))
+			s.Equalf("cluster-0-cluster", decoded.Clusters[0].Name, "cluster-0-cluster not found: %v", decoded.Clusters)
+			s.Equalf("fake", decoded.Clusters[10].Name, "fake not found: %v", decoded.Clusters)
 		})
 		s.Run("configuration_view with minified=false returns auth info", func() {
-			s.Lenf(decoded.AuthInfos, 2, "invalid auth info count, expected 2, got %v", len(decoded.AuthInfos))
-			s.Equalf("additional-auth", decoded.AuthInfos[0].Name, "additional-auth not found: %v", decoded.AuthInfos)
+			s.Lenf(decoded.AuthInfos, 11, "invalid auth info count, expected 2, got %v", len(decoded.AuthInfos))
+			s.Equalf("cluster-0-auth", decoded.AuthInfos[0].Name, "cluster-0-auth not found: %v", decoded.AuthInfos)
+			s.Equalf("fake", decoded.AuthInfos[10].Name, "fake not found: %v", decoded.AuthInfos)
 		})
 	})
 }
@@ -109,11 +145,11 @@ func (s *ConfigurationSuite) TestConfigurationViewInCluster() {
 			s.Nilf(err, "invalid tool result content %v", err)
 		})
 		s.Run("returns current-context", func() {
-			s.Equalf("context", decoded.CurrentContext, "context not found: %v", decoded.CurrentContext)
+			s.Equalf("in-cluster", decoded.CurrentContext, "context not found: %v", decoded.CurrentContext)
 		})
 		s.Run("returns context info", func() {
 			s.Lenf(decoded.Contexts, 1, "invalid context count, expected 1, got %v", len(decoded.Contexts))
-			s.Equalf("context", decoded.Contexts[0].Name, "context not found: %v", decoded.Contexts)
+			s.Equalf("in-cluster", decoded.Contexts[0].Name, "context not found: %v", decoded.Contexts)
 			s.Equalf("cluster", decoded.Contexts[0].Context.Cluster, "cluster not found: %v", decoded.Contexts)
 			s.Equalf("user", decoded.Contexts[0].Context.AuthInfo, "user not found: %v", decoded.Contexts)
 		})
diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json
new file mode 100644
index 0000000..97af6fb
--- /dev/null
+++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json
@@ -0,0 +1,680 @@
+[
+  {
+    "annotations": {
+      "title": "Configuration: Contexts List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": true,
+      "openWorldHint": false
+    },
+    "description": "List all available context names and associated server urls from the kubeconfig file",
+    "inputSchema": {
+      "type": "object"
+    },
+    "name": "configuration_contexts_list"
+  },
+  {
+    "annotations": {
+      "title": "Configuration: View",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Get the current Kubernetes configuration content as a kubeconfig YAML",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "minified": {
+          "description": "Return a minified version of the configuration. If set to true, keeps only the current-context and the relevant pieces of the configuration for that context. If set to false, all contexts, clusters, auth-infos, and users are returned in the configuration. (Optional, default true)",
+          "type": "boolean"
+        }
+      }
+    },
+    "name": "configuration_view"
+  },
+  {
+    "annotations": {
+      "title": "Events: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Kubernetes events in the current cluster from all namespaces",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Optional Namespace to retrieve the events from. If not provided, will list events from all namespaces",
+          "type": "string"
+        }
+      }
+    },
+    "name": "events_list"
+  },
+  {
+    "annotations": {
+      "title": "Helm: Install",
+      "readOnlyHint": false,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Install a Helm chart in the current or provided namespace",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "chart": {
+          "description": "Chart reference to install (for example: stable/grafana, oci://ghcr.io/nginxinc/charts/nginx-ingress)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Helm release (Optional, random name if not provided)",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to install the Helm chart in (Optional, current namespace if not provided)",
+          "type": "string"
+        },
+        "values": {
+          "description": "Values to pass to the Helm chart (Optional)",
+          "type": "object"
+        }
+      },
+      "required": [
+        "chart"
+      ]
+    },
+    "name": "helm_install"
+  },
+  {
+    "annotations": {
+      "title": "Helm: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Helm releases in the current or provided namespace (or in all namespaces if specified)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "all_namespaces": {
+          "description": "If true, lists all Helm releases in all namespaces ignoring the namespace argument (Optional)",
+          "type": "boolean"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to list Helm releases from (Optional, all namespaces if not provided)",
+          "type": "string"
+        }
+      }
+    },
+    "name": "helm_list"
+  },
+  {
+    "annotations": {
+      "title": "Helm: Uninstall",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "Uninstall a Helm release in the current or provided namespace",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Helm release to uninstall",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to uninstall the Helm release from (Optional, current namespace if not provided)",
+          "type": "string"
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "name": "helm_uninstall"
+  },
+  {
+    "annotations": {
+      "title": "Namespaces: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Kubernetes namespaces in the current cluster",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        }
+      }
+    },
+    "name": "namespaces_list"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Delete",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "Delete a Kubernetes Pod in the current or provided namespace with the provided name",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod to delete",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to delete the Pod from",
+          "type": "string"
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "name": "pods_delete"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Exec",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Execute a command in a Kubernetes Pod in the current or provided namespace with the provided name and command",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "command": {
+          "description": "Command to execute in the Pod container. The first item is the command to be run, and the rest are the arguments to that command. Example: [\"ls\", \"-l\", \"/tmp\"]",
+          "items": {
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "container": {
+          "description": "Name of the Pod container where the command will be executed (Optional)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod where the command will be executed",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace of the Pod where the command will be executed",
+          "type": "string"
+        }
+      },
+      "required": [
+        "name",
+        "command"
+      ]
+    },
+    "name": "pods_exec"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Get",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Get a Kubernetes Pod in the current or provided namespace with the provided name",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to get the Pod from",
+          "type": "string"
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "name": "pods_get"
+  },
+  {
+    "annotations": {
+      "title": "Pods: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Kubernetes pods in the current cluster from all namespaces",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "labelSelector": {
+          "description": "Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label",
+          "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]",
+          "type": "string"
+        }
+      }
+    },
+    "name": "pods_list"
+  },
+  {
+    "annotations": {
+      "title": "Pods: List in Namespace",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Kubernetes pods in the specified namespace in the current cluster",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "labelSelector": {
+          "description": "Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label",
+          "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to list pods from",
+          "type": "string"
+        }
+      },
+      "required": [
+        "namespace"
+      ]
+    },
+    "name": "pods_list_in_namespace"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Log",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Get the logs of a Kubernetes Pod in the current or provided namespace with the provided name",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "container": {
+          "description": "Name of the Pod container to get the logs from (Optional)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod to get the logs from",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to get the Pod logs from",
+          "type": "string"
+        },
+        "previous": {
+          "description": "Return previous terminated container logs (Optional)",
+          "type": "boolean"
+        },
+        "tail": {
+          "default": 100,
+          "description": "Number of lines to retrieve from the end of the logs (Optional, default: 100)",
+          "minimum": 0,
+          "type": "integer"
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "name": "pods_log"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Run",
+      "readOnlyHint": false,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Run a Kubernetes Pod in the current or provided namespace with the provided container image and optional name",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "image": {
+          "description": "Container Image to run in the Pod",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod (Optional, random name if not provided)",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to run the Pod in",
+          "type": "string"
+        },
+        "port": {
+          "description": "TCP/IP port to expose from the Pod container (Optional, no port exposed if not provided)",
+          "type": "number"
+        }
+      },
+      "required": [
+        "image"
+      ]
+    },
+    "name": "pods_run"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Top",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Pods in the all namespaces, the provided namespace, or the current namespace",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "all_namespaces": {
+          "default": true,
+          "description": "If true, list the resource consumption for all Pods in all namespaces. If false, list the resource consumption for Pods in the provided namespace or the current namespace",
+          "type": "boolean"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "label_selector": {
+          "description": "Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label (Optional, only applicable when name is not provided)",
+          "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod to get the resource consumption from (Optional, all Pods in the namespace if not provided)",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to get the Pods resource consumption from (Optional, current namespace if not provided and all_namespaces is false)",
+          "type": "string"
+        }
+      }
+    },
+    "name": "pods_top"
+  },
+  {
+    "annotations": {
+      "title": "Resources: Create or Update",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "Create or update a Kubernetes resource in the current cluster by providing a YAML or JSON representation of the resource\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "resource": {
+          "description": "A JSON or YAML containing a representation of the Kubernetes resource. Should include top-level fields such as apiVersion,kind,metadata, and spec",
+          "type": "string"
+        }
+      },
+      "required": [
+        "resource"
+      ]
+    },
+    "name": "resources_create_or_update"
+  },
+  {
+    "annotations": {
+      "title": "Resources: Delete",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "Delete a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "apiVersion": {
+          "description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "kind": {
+          "description": "kind of the resource (examples of valid kind are: Pod, Service, Deployment, Ingress)",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the resource",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Optional Namespace to delete the namespaced resource from (ignored in case of cluster scoped resources). If not provided, will delete resource from configured namespace",
+          "type": "string"
+        }
+      },
+      "required": [
+        "apiVersion",
+        "kind",
+        "name"
+      ]
+    },
+    "name": "resources_delete"
+  },
+  {
+    "annotations": {
+      "title": "Resources: Get",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "apiVersion": {
+          "description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "kind": {
+          "description": "kind of the resource (examples of valid kind are: Pod, Service, Deployment, Ingress)",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the resource",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Optional Namespace to retrieve the namespaced resource from (ignored in case of cluster scoped resources). If not provided, will get resource from configured namespace",
+          "type": "string"
+        }
+      },
+      "required": [
+        "apiVersion",
+        "kind",
+        "name"
+      ]
+    },
+    "name": "resources_get"
+  },
+  {
+    "annotations": {
+      "title": "Resources: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List Kubernetes resources and objects in the current cluster by providing their apiVersion and kind and optionally the namespace and label selector\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "apiVersion": {
+          "description": "apiVersion of the resources (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "enum": [
+            "extra-cluster",
+            "fake-context"
+          ],
+          "type": "string"
+        },
+        "kind": {
+          "description": "kind of the resources (examples of valid kind are: Pod, Service, Deployment, Ingress)",
+          "type": "string"
+        },
+        "labelSelector": {
+          "description": "Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label",
+          "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Optional Namespace to retrieve the namespaced resources from (ignored in case of cluster scoped resources). If not provided, will list resources from all namespaces",
+          "type": "string"
+        }
+      },
+      "required": [
+        "apiVersion",
+        "kind"
+      ]
+    },
+    "name": "resources_list"
+  }
+]
diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json
new file mode 100644
index 0000000..861a1b5
--- /dev/null
+++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json
@@ -0,0 +1,612 @@
+[
+  {
+    "annotations": {
+      "title": "Configuration: Contexts List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": true,
+      "openWorldHint": false
+    },
+    "description": "List all available context names and associated server urls from the kubeconfig file",
+    "inputSchema": {
+      "type": "object"
+    },
+    "name": "configuration_contexts_list"
+  },
+  {
+    "annotations": {
+      "title": "Configuration: View",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Get the current Kubernetes configuration content as a kubeconfig YAML",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "minified": {
+          "description": "Return a minified version of the configuration. If set to true, keeps only the current-context and the relevant pieces of the configuration for that context. If set to false, all contexts, clusters, auth-infos, and users are returned in the configuration. (Optional, default true)",
+          "type": "boolean"
+        }
+      }
+    },
+    "name": "configuration_view"
+  },
+  {
+    "annotations": {
+      "title": "Events: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Kubernetes events in the current cluster from all namespaces",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Optional Namespace to retrieve the events from. If not provided, will list events from all namespaces",
+          "type": "string"
+        }
+      }
+    },
+    "name": "events_list"
+  },
+  {
+    "annotations": {
+      "title": "Helm: Install",
+      "readOnlyHint": false,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Install a Helm chart in the current or provided namespace",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "chart": {
+          "description": "Chart reference to install (for example: stable/grafana, oci://ghcr.io/nginxinc/charts/nginx-ingress)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Helm release (Optional, random name if not provided)",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to install the Helm chart in (Optional, current namespace if not provided)",
+          "type": "string"
+        },
+        "values": {
+          "description": "Values to pass to the Helm chart (Optional)",
+          "type": "object"
+        }
+      },
+      "required": [
+        "chart"
+      ]
+    },
+    "name": "helm_install"
+  },
+  {
+    "annotations": {
+      "title": "Helm: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Helm releases in the current or provided namespace (or in all namespaces if specified)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "all_namespaces": {
+          "description": "If true, lists all Helm releases in all namespaces ignoring the namespace argument (Optional)",
+          "type": "boolean"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to list Helm releases from (Optional, all namespaces if not provided)",
+          "type": "string"
+        }
+      }
+    },
+    "name": "helm_list"
+  },
+  {
+    "annotations": {
+      "title": "Helm: Uninstall",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "Uninstall a Helm release in the current or provided namespace",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Helm release to uninstall",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to uninstall the Helm release from (Optional, current namespace if not provided)",
+          "type": "string"
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "name": "helm_uninstall"
+  },
+  {
+    "annotations": {
+      "title": "Namespaces: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Kubernetes namespaces in the current cluster",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        }
+      }
+    },
+    "name": "namespaces_list"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Delete",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "Delete a Kubernetes Pod in the current or provided namespace with the provided name",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod to delete",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to delete the Pod from",
+          "type": "string"
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "name": "pods_delete"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Exec",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Execute a command in a Kubernetes Pod in the current or provided namespace with the provided name and command",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "command": {
+          "description": "Command to execute in the Pod container. The first item is the command to be run, and the rest are the arguments to that command. Example: [\"ls\", \"-l\", \"/tmp\"]",
+          "items": {
+            "type": "string"
+          },
+          "type": "array"
+        },
+        "container": {
+          "description": "Name of the Pod container where the command will be executed (Optional)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod where the command will be executed",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace of the Pod where the command will be executed",
+          "type": "string"
+        }
+      },
+      "required": [
+        "name",
+        "command"
+      ]
+    },
+    "name": "pods_exec"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Get",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Get a Kubernetes Pod in the current or provided namespace with the provided name",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to get the Pod from",
+          "type": "string"
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "name": "pods_get"
+  },
+  {
+    "annotations": {
+      "title": "Pods: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Kubernetes pods in the current cluster from all namespaces",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "labelSelector": {
+          "description": "Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label",
+          "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]",
+          "type": "string"
+        }
+      }
+    },
+    "name": "pods_list"
+  },
+  {
+    "annotations": {
+      "title": "Pods: List in Namespace",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List all the Kubernetes pods in the specified namespace in the current cluster",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "labelSelector": {
+          "description": "Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label",
+          "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to list pods from",
+          "type": "string"
+        }
+      },
+      "required": [
+        "namespace"
+      ]
+    },
+    "name": "pods_list_in_namespace"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Log",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Get the logs of a Kubernetes Pod in the current or provided namespace with the provided name",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "container": {
+          "description": "Name of the Pod container to get the logs from (Optional)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod to get the logs from",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to get the Pod logs from",
+          "type": "string"
+        },
+        "previous": {
+          "description": "Return previous terminated container logs (Optional)",
+          "type": "boolean"
+        },
+        "tail": {
+          "default": 100,
+          "description": "Number of lines to retrieve from the end of the logs (Optional, default: 100)",
+          "minimum": 0,
+          "type": "integer"
+        }
+      },
+      "required": [
+        "name"
+      ]
+    },
+    "name": "pods_log"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Run",
+      "readOnlyHint": false,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Run a Kubernetes Pod in the current or provided namespace with the provided container image and optional name",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "image": {
+          "description": "Container Image to run in the Pod",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod (Optional, random name if not provided)",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to run the Pod in",
+          "type": "string"
+        },
+        "port": {
+          "description": "TCP/IP port to expose from the Pod container (Optional, no port exposed if not provided)",
+          "type": "number"
+        }
+      },
+      "required": [
+        "image"
+      ]
+    },
+    "name": "pods_run"
+  },
+  {
+    "annotations": {
+      "title": "Pods: Top",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Pods in the all namespaces, the provided namespace, or the current namespace",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "all_namespaces": {
+          "default": true,
+          "description": "If true, list the resource consumption for all Pods in all namespaces. If false, list the resource consumption for Pods in the provided namespace or the current namespace",
+          "type": "boolean"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "label_selector": {
+          "description": "Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label (Optional, only applicable when name is not provided)",
+          "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the Pod to get the resource consumption from (Optional, all Pods in the namespace if not provided)",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Namespace to get the Pods resource consumption from (Optional, current namespace if not provided and all_namespaces is false)",
+          "type": "string"
+        }
+      }
+    },
+    "name": "pods_top"
+  },
+  {
+    "annotations": {
+      "title": "Resources: Create or Update",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "Create or update a Kubernetes resource in the current cluster by providing a YAML or JSON representation of the resource\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "resource": {
+          "description": "A JSON or YAML containing a representation of the Kubernetes resource. Should include top-level fields such as apiVersion,kind,metadata, and spec",
+          "type": "string"
+        }
+      },
+      "required": [
+        "resource"
+      ]
+    },
+    "name": "resources_create_or_update"
+  },
+  {
+    "annotations": {
+      "title": "Resources: Delete",
+      "readOnlyHint": false,
+      "destructiveHint": true,
+      "idempotentHint": true,
+      "openWorldHint": true
+    },
+    "description": "Delete a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "apiVersion": {
+          "description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "kind": {
+          "description": "kind of the resource (examples of valid kind are: Pod, Service, Deployment, Ingress)",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the resource",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Optional Namespace to delete the namespaced resource from (ignored in case of cluster scoped resources). If not provided, will delete resource from configured namespace",
+          "type": "string"
+        }
+      },
+      "required": [
+        "apiVersion",
+        "kind",
+        "name"
+      ]
+    },
+    "name": "resources_delete"
+  },
+  {
+    "annotations": {
+      "title": "Resources: Get",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "apiVersion": {
+          "description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "kind": {
+          "description": "kind of the resource (examples of valid kind are: Pod, Service, Deployment, Ingress)",
+          "type": "string"
+        },
+        "name": {
+          "description": "Name of the resource",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Optional Namespace to retrieve the namespaced resource from (ignored in case of cluster scoped resources). If not provided, will get resource from configured namespace",
+          "type": "string"
+        }
+      },
+      "required": [
+        "apiVersion",
+        "kind",
+        "name"
+      ]
+    },
+    "name": "resources_get"
+  },
+  {
+    "annotations": {
+      "title": "Resources: List",
+      "readOnlyHint": true,
+      "destructiveHint": false,
+      "idempotentHint": false,
+      "openWorldHint": true
+    },
+    "description": "List Kubernetes resources and objects in the current cluster by providing their apiVersion and kind and optionally the namespace and label selector\n(common apiVersion and kind include: v1 Pod, v1 Service, v1 Node, apps/v1 Deployment, networking.k8s.io/v1 Ingress)",
+    "inputSchema": {
+      "type": "object",
+      "properties": {
+        "apiVersion": {
+          "description": "apiVersion of the resources (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
+          "type": "string"
+        },
+        "context": {
+          "description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
+          "type": "string"
+        },
+        "kind": {
+          "description": "kind of the resources (examples of valid kind are: Pod, Service, Deployment, Ingress)",
+          "type": "string"
+        },
+        "labelSelector": {
+          "description": "Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label",
+          "pattern": "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]",
+          "type": "string"
+        },
+        "namespace": {
+          "description": "Optional Namespace to retrieve the namespaced resources from (ignored in case of cluster scoped resources). If not provided, will list resources from all namespaces",
+          "type": "string"
+        }
+      },
+      "required": [
+        "apiVersion",
+        "kind"
+      ]
+    },
+    "name": "resources_list"
+  }
+]
diff --git a/pkg/mcp/tool_filter_test.go b/pkg/mcp/tool_filter_test.go
new file mode 100644
index 0000000..e875815
--- /dev/null
+++ b/pkg/mcp/tool_filter_test.go
@@ -0,0 +1,84 @@
+package mcp
+
+import (
+	"testing"
+
+	"github.com/containers/kubernetes-mcp-server/pkg/api"
+	"github.com/stretchr/testify/suite"
+	"k8s.io/utils/ptr"
+)
+
+type ToolFilterSuite struct {
+	suite.Suite
+}
+
+func (s *ToolFilterSuite) TestToolFilterType() {
+	s.Run("ToolFilter type can be used as function", func() {
+		var mutator ToolFilter = func(tool api.ServerTool) bool {
+			return tool.Tool.Name == "included"
+		}
+		s.Run("returns true for included tool", func() {
+			tool := api.ServerTool{Tool: api.Tool{Name: "included"}}
+			s.True(mutator(tool))
+		})
+		s.Run("returns false for excluded tool", func() {
+			tool := api.ServerTool{Tool: api.Tool{Name: "excluded"}}
+			s.False(mutator(tool))
+		})
+	})
+}
+
+func (s *ToolFilterSuite) TestCompositeFilter() {
+	s.Run("returns true if all filters return true", func() {
+		filter := CompositeFilter(
+			func(tool api.ServerTool) bool { return true },
+			func(tool api.ServerTool) bool { return true },
+		)
+		tool := api.ServerTool{Tool: api.Tool{Name: "test"}}
+		s.True(filter(tool))
+	})
+	s.Run("returns false if any filter returns false", func() {
+		filter := CompositeFilter(
+			func(tool api.ServerTool) bool { return true },
+			func(tool api.ServerTool) bool { return false },
+		)
+		tool := api.ServerTool{Tool: api.Tool{Name: "test"}}
+		s.False(filter(tool))
+	})
+}
+
+func (s *ToolFilterSuite) TestShouldIncludeTargetListTool() {
+	s.Run("non-target-list-provider tools: returns true ", func() {
+		filter := ShouldIncludeTargetListTool("any", []string{"a", "b", "c", "d", "e", "f"})
+		tool := api.ServerTool{Tool: api.Tool{Name: "test"}, TargetListProvider: ptr.To(false)}
+		s.True(filter(tool))
+	})
+	s.Run("target-list-provider tools", func() {
+		s.Run("with targets == 1: returns false", func() {
+			filter := ShouldIncludeTargetListTool("any", []string{"1"})
+			tool := api.ServerTool{Tool: api.Tool{Name: "test"}, TargetListProvider: ptr.To(true)}
+			s.False(filter(tool))
+		})
+		s.Run("with targets == 1", func() {
+			s.Run("and tool is configuration_contexts_list and targetName is not context: returns false", func() {
+				filter := ShouldIncludeTargetListTool("not_context", []string{"1"})
+				tool := api.ServerTool{Tool: api.Tool{Name: "configuration_contexts_list"}, TargetListProvider: ptr.To(true)}
+				s.False(filter(tool))
+			})
+			s.Run("and tool is configuration_contexts_list and targetName is context: returns false", func() {
+				filter := ShouldIncludeTargetListTool("context", []string{"1"})
+				tool := api.ServerTool{Tool: api.Tool{Name: "configuration_contexts_list"}, TargetListProvider: ptr.To(true)}
+				s.False(filter(tool))
+			})
+			s.Run("and tool is not configuration_contexts_list: returns false", func() {
+				filter := ShouldIncludeTargetListTool("any", []string{"1"})
+				tool := api.ServerTool{Tool: api.Tool{Name: "other_tool"}, TargetListProvider: ptr.To(true)}
+				s.False(filter(tool))
+			})
+		})
+	})
+}
+
+func TestToolFilter(t *testing.T) {
+	suite.Run(t, new(ToolFilterSuite))
+}
diff --git a/pkg/mcp/tool_mutator_test.go b/pkg/mcp/tool_mutator_test.go
new file mode 100644
index 0000000..e915dd7
--- /dev/null
+++ b/pkg/mcp/tool_mutator_test.go
@@ -0,0 +1,347 @@
+package mcp
+
+import (
+	"testing"
+
+	"github.com/containers/kubernetes-mcp-server/pkg/api"
+	"github.com/google/jsonschema-go/jsonschema"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/stretchr/testify/suite"
+	"k8s.io/utils/ptr"
+)
+
+// createTestTool creates a basic ServerTool for testing
+func createTestTool(name string) api.ServerTool {
+	return api.ServerTool{
+		Tool: api.Tool{
+			Name:        name,
+			Description: "A test tool",
+			InputSchema: &jsonschema.Schema{
+				Type:       "object",
+				Properties: make(map[string]*jsonschema.Schema),
+			},
+		},
+	}
+}
+
+// createTestToolWithNilSchema creates a ServerTool with nil InputSchema for testing
+func createTestToolWithNilSchema(name string) api.ServerTool {
+	return api.ServerTool{
+		Tool: api.Tool{
+			Name:        name,
+			Description: "A test tool",
+			InputSchema: nil,
+		},
+	}
+}
+
+// createTestToolWithNilProperties creates a ServerTool with nil Properties for testing
+func createTestToolWithNilProperties(name string) api.ServerTool {
+	return api.ServerTool{
+		Tool: api.Tool{
+			Name:        name,
+			Description: "A test tool",
+			InputSchema: &jsonschema.Schema{
+				Type:       "object",
+				Properties: nil,
+			},
+		},
+	}
+}
+
+// createTestToolWithExistingProperties creates a ServerTool with existing properties for testing
+func createTestToolWithExistingProperties(name string) api.ServerTool {
+	return api.ServerTool{
+		Tool: api.Tool{
+			Name:        name,
+			Description: "A test tool",
+			InputSchema: &jsonschema.Schema{
+				Type: "object",
+				Properties: map[string]*jsonschema.Schema{
+					"existing-prop": {Type: "string"},
+				},
+			},
+		},
+	}
+}
+
+func TestWithClusterParameter(t *testing.T) {
+	tests := []struct {
+		name                string
+		defaultCluster      string
+		targetParameterName string
+		clusters            []string
+		toolName            string
+		toolFactory         func(string) api.ServerTool
+		expectCluster       bool
+		expectEnum          bool
+		enumCount           int
+	}{
+		{
+			name:           "adds cluster parameter when multiple clusters provided",
+			defaultCluster: "default-cluster",
+			clusters:       []string{"cluster1", "cluster2", "cluster3"},
+			toolName:       "test-tool",
+			toolFactory:    createTestTool,
+			expectCluster:  true,
+			expectEnum:     true,
+			enumCount:      3,
+		},
+		{
+			name:           "does not add cluster parameter when single cluster provided",
+			defaultCluster: "default-cluster",
+			clusters:       []string{"single-cluster"},
+			toolName:       "test-tool",
+			toolFactory:    createTestTool,
+			expectCluster:  false,
+			expectEnum:     false,
+			enumCount:      0,
+		},
+		{
+			name:           "creates InputSchema when nil",
+			defaultCluster: "default-cluster",
+			clusters:       []string{"cluster1", "cluster2"},
+			toolName:       "test-tool",
+			toolFactory:    createTestToolWithNilSchema,
+			expectCluster:  true,
+			expectEnum:     true,
+			enumCount:      2,
+		},
+		{
+			name:           "creates Properties map when nil",
+			defaultCluster: "default-cluster",
+			clusters:       []string{"cluster1", "cluster2"},
+			toolName:       "test-tool",
+			toolFactory:    createTestToolWithNilProperties,
+			expectCluster:  true,
+			expectEnum:     true,
+			enumCount:      2,
+		},
+		{
+			name:           "preserves existing properties",
+			defaultCluster: "default-cluster",
+			clusters:       []string{"cluster1", "cluster2"},
+			toolName:       "test-tool",
+			toolFactory:    createTestToolWithExistingProperties,
+			expectCluster:  true,
+			expectEnum:     true,
+			enumCount:      2,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.targetParameterName == "" {
+				tt.targetParameterName = "cluster"
+			}
+			mutator := WithTargetParameter(tt.defaultCluster, tt.targetParameterName, tt.clusters)
+			tool := tt.toolFactory(tt.toolName)
+			originalTool := tool // Keep reference to check if tool was unchanged
+
+			result := mutator(tool)
+
+			if !tt.expectCluster {
+				if tt.toolName == "skip-this-tool" {
+					// For skipped tools, the entire tool should be unchanged
+					assert.Equal(t, originalTool, result)
+				} else {
+					// For single cluster, schema should exist but no cluster property
+					require.NotNil(t, result.Tool.InputSchema)
+					require.NotNil(t, result.Tool.InputSchema.Properties)
+					_, exists := result.Tool.InputSchema.Properties["cluster"]
+					assert.False(t, exists, "cluster property should not exist")
+				}
+				return
+			}
+
+			// Common assertions for cases where cluster parameter should be added
+			require.NotNil(t, result.Tool.InputSchema)
+			assert.Equal(t, "object", result.Tool.InputSchema.Type)
+			require.NotNil(t, result.Tool.InputSchema.Properties)
+
+			clusterProperty, exists := result.Tool.InputSchema.Properties["cluster"]
+			assert.True(t, exists, "cluster property should exist")
+			assert.NotNil(t, clusterProperty)
+			assert.Equal(t, "string", clusterProperty.Type)
+			assert.Contains(t, clusterProperty.Description, tt.defaultCluster)
+
+			if tt.expectEnum {
+				assert.NotNil(t, clusterProperty.Enum)
+				assert.Equal(t, tt.enumCount, len(clusterProperty.Enum))
+				for _, cluster := range tt.clusters {
+					assert.Contains(t, clusterProperty.Enum, cluster)
+				}
+			}
+		})
+	}
+}
+
+func TestCreateClusterProperty(t *testing.T) {
+	tests := []struct {
+		name           string
+		defaultCluster string
+		targetName     string
+		clusters       []string
+		expectEnum     bool
+		expectedCount  int
+	}{
+		{
+			name:           "creates property with enum when clusters <= maxClustersInEnum",
+			defaultCluster: "default",
+			targetName:     "cluster",
+			clusters:       []string{"cluster1", "cluster2", "cluster3"},
+			expectEnum:     true,
+			expectedCount:  3,
+		},
+		{
+			name:           "creates property without enum when clusters > maxClustersInEnum",
+			defaultCluster: "default",
+			targetName:     "cluster",
+			clusters:       make([]string, maxTargetsInEnum+5), // 20 clusters
+			expectEnum:     false,
+			expectedCount:  0,
+		},
+		{
+			name:           "creates property with exact maxClustersInEnum clusters",
+			defaultCluster: "default",
+			targetName:     "cluster",
+			clusters:       make([]string, maxTargetsInEnum),
+			expectEnum:     true,
+			expectedCount:  maxTargetsInEnum,
+		},
+		{
+			name:           "handles single cluster",
+			defaultCluster: "default",
+			targetName:     "cluster",
+			clusters:       []string{"single-cluster"},
+			expectEnum:     true,
+			expectedCount:  1,
+		},
+		{
+			name:           "handles empty clusters list",
+			defaultCluster: "default",
+			targetName:     "cluster",
+			clusters:       []string{},
+			expectEnum:     true,
+			expectedCount:  0,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Initialize clusters with names if they were created with make()
+			if len(tt.clusters) > 3 && tt.clusters[0] == "" {
+				for i := range tt.clusters {
+					tt.clusters[i] = "cluster" + string(rune('A'+i))
+				}
+			}
+
+			property := createTargetProperty(tt.defaultCluster, tt.targetName, tt.clusters)
+
+			assert.Equal(t, "string", property.Type)
+			assert.Contains(t, property.Description, tt.defaultCluster)
+			assert.Contains(t, property.Description, "Defaults to "+tt.defaultCluster+" if not set")
+
+			if tt.expectEnum {
+				assert.NotNil(t, property.Enum, "enum should be created")
+				assert.Equal(t, tt.expectedCount, len(property.Enum))
+				if tt.expectedCount > 0 && tt.expectedCount <= 3 {
+					// Only check specific values for small, predefined lists
+					for _, cluster := range tt.clusters {
+						assert.Contains(t, property.Enum, cluster)
+					}
+				}
+			} else {
+				assert.Nil(t, property.Enum, "enum should not be created for too many clusters")
+			}
+		})
+	}
+}
+
+func TestToolMutatorType(t *testing.T) {
+	t.Run("ToolMutator type can be used as function", func(t *testing.T) {
+		var mutator ToolMutator = func(tool api.ServerTool) api.ServerTool {
+			tool.Tool.Name = "modified-" + tool.Tool.Name
+			return tool
+		}
+
+		originalTool := createTestTool("original")
+		result := mutator(originalTool)
+		assert.Equal(t, "modified-original", result.Tool.Name)
+	})
+}
+
+func TestMaxClustersInEnumConstant(t *testing.T) {
+	t.Run("maxClustersInEnum has expected value", func(t *testing.T) {
+		assert.Equal(t, 5, maxTargetsInEnum, "maxClustersInEnum should be 5")
+	})
+}
+
+type TargetParameterToolMutatorSuite struct {
+	suite.Suite
+}
+
+func (s *TargetParameterToolMutatorSuite) TestClusterAwareTool() {
+	tm := WithTargetParameter("default-cluster", "cluster", []string{"cluster-1", "cluster-2", "cluster-3"})
+	tool := createTestTool("cluster-aware-tool")
+	// Tools are cluster-aware by default
+	tm(tool)
+	s.Require().NotNil(tool.Tool.InputSchema.Properties)
+	s.Run("adds cluster parameter", func() {
+		s.NotNil(tool.Tool.InputSchema.Properties["cluster"], "Expected cluster property to be added")
+	})
+	s.Run("adds correct description", func() {
+		desc := tool.Tool.InputSchema.Properties["cluster"].Description
+		s.Contains(desc, "Optional parameter selecting which cluster to run the tool in", "Expected description to mention cluster selection")
+		s.Contains(desc, "Defaults to default-cluster if not set", "Expected description to mention default cluster")
+	})
+	s.Run("adds enum with clusters", func() {
+		s.Require().NotNil(tool.Tool.InputSchema.Properties["cluster"])
+		enum := tool.Tool.InputSchema.Properties["cluster"].Enum
+		s.NotNilf(enum, "Expected enum to be set")
+		s.Equal(3, len(enum), "Expected enum to have 3 entries")
+		s.Contains(enum, "cluster-1", "Expected enum to contain cluster-1")
+		s.Contains(enum, "cluster-2", "Expected enum to contain cluster-2")
+		s.Contains(enum, "cluster-3", "Expected enum to contain cluster-3")
+	})
+}
+
+func (s *TargetParameterToolMutatorSuite) TestClusterAwareToolSingleCluster() {
+	tm := WithTargetParameter("default", "cluster", []string{"only-cluster"})
+	tool := createTestTool("cluster-aware-tool-single-cluster")
+	// Tools are cluster-aware by default
+	tm(tool)
+	s.Run("does not add cluster parameter for single cluster", func() {
+		s.Nilf(tool.Tool.InputSchema.Properties["cluster"], "Expected cluster property to not be added for single cluster")
+	})
+}
+
+func (s *TargetParameterToolMutatorSuite) TestClusterAwareToolMultipleClusters() {
+	tm := WithTargetParameter("default", "cluster", []string{"cluster-1", "cluster-2", "cluster-3", "cluster-4", "cluster-5", "cluster-6"})
+	tool := createTestTool("cluster-aware-tool-multiple-clusters")
+	// Tools are cluster-aware by default
+	tm(tool)
+	s.Run("adds cluster parameter", func() {
+		s.NotNilf(tool.Tool.InputSchema.Properties["cluster"], "Expected cluster property to be added")
+	})
+	s.Run("does not add enum when list of clusters is > 5", func() {
+		s.Require().NotNil(tool.Tool.InputSchema.Properties["cluster"])
+		enum := tool.Tool.InputSchema.Properties["cluster"].Enum
+		s.Nilf(enum, "Expected enum to not be set for too many clusters")
+	})
+}
+
+func (s *TargetParameterToolMutatorSuite) TestNonClusterAwareTool() {
+	tm := WithTargetParameter("default", "cluster", []string{"cluster-1", "cluster-2"})
+	tool := createTestTool("non-cluster-aware-tool")
+	tool.ClusterAware = ptr.To(false)
+	tm(tool)
+	s.Run("does not add cluster parameter", func() {
+		s.Nilf(tool.Tool.InputSchema.Properties["cluster"], "Expected cluster property to not be added")
+	})
+}
+
+func TestTargetParameterToolMutator(t *testing.T) {
+	suite.Run(t, new(TargetParameterToolMutatorSuite))
+}
diff --git a/pkg/mcp/toolsets_test.go b/pkg/mcp/toolsets_test.go
index fa8a95f..527b1e2 100644
--- a/pkg/mcp/toolsets_test.go
+++ b/pkg/mcp/toolsets_test.go
@@ -2,11 +2,9 @@ package mcp
 
 import (
 	"encoding/json"
+	"strconv"
 	"testing"
 
-	"github.com/mark3labs/mcp-go/mcp"
-	"github.com/stretchr/testify/suite"
-
 	"github.com/containers/kubernetes-mcp-server/internal/test"
 	"github.com/containers/kubernetes-mcp-server/pkg/api"
 	configuration "github.com/containers/kubernetes-mcp-server/pkg/config"
@@ -14,6 +12,9 @@ import (
 	"github.com/containers/kubernetes-mcp-server/pkg/toolsets/config"
 	"github.com/containers/kubernetes-mcp-server/pkg/toolsets/core"
 	"github.com/containers/kubernetes-mcp-server/pkg/toolsets/helm"
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/stretchr/testify/suite"
+	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
 )
 
 type ToolsetsSuite struct {
@@ -98,6 +99,50 @@ func (s *ToolsetsSuite) TestDefaultToolsetsToolsInOpenShift() {
 	})
 }
 
+func (s *ToolsetsSuite) TestDefaultToolsetsToolsInMultiCluster() {
+	s.Run("Default configuration toolsets in multi-cluster (with 11 clusters)", func() {
+		kubeconfig := s.Kubeconfig()
+		for i := 0; i < 10; i++ {
+			// Add multiple fake contexts to force multi-cluster behavior
+			kubeconfig.Contexts[strconv.Itoa(i)] = clientcmdapi.NewContext()
+		}
+		s.Cfg.KubeConfig = test.KubeconfigFile(s.T(), kubeconfig)
+		s.InitMcpClient()
+		tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{})
+		s.Run("ListTools returns tools", func() {
+			s.NotNil(tools, "Expected tools from ListTools")
+			s.NoError(err, "Expected no error from ListTools")
+		})
+		s.Run("ListTools returns correct Tool metadata", func() {
+			expectedMetadata := test.ReadFile("testdata", "toolsets-full-tools-multicluster.json")
+			metadata, err := json.MarshalIndent(tools.Tools, "", "  ")
+			s.Require().NoErrorf(err, "failed to marshal tools metadata: %v", err)
+			s.JSONEq(expectedMetadata, string(metadata), "tools metadata does not match expected")
+		})
+	})
+}
+
+func (s *ToolsetsSuite) TestDefaultToolsetsToolsInMultiClusterEnum() {
+	s.Run("Default configuration toolsets in multi-cluster (with 2 clusters)", func() {
+		kubeconfig := s.Kubeconfig()
+		// Add additional cluster to force multi-cluster behavior with enum parameter
+		kubeconfig.Contexts["extra-cluster"] = clientcmdapi.NewContext()
+		s.Cfg.KubeConfig = test.KubeconfigFile(s.T(), kubeconfig)
+		s.InitMcpClient()
+		tools, err := s.ListTools(s.T().Context(), mcp.ListToolsRequest{})
+		s.Run("ListTools returns tools", func() {
+			s.NotNil(tools, "Expected tools from ListTools")
+			s.NoError(err, "Expected no error from ListTools")
+		})
+		s.Run("ListTools returns correct Tool metadata", func() {
+			expectedMetadata := test.ReadFile("testdata", "toolsets-full-tools-multicluster-enum.json")
+			metadata, err := json.MarshalIndent(tools.Tools, "", "  ")
+			s.Require().NoErrorf(err, "failed to marshal tools metadata: %v", err)
+			s.JSONEq(expectedMetadata, string(metadata), "tools metadata does not match expected")
+		})
+	})
+}
+
 func (s *ToolsetsSuite) TestGranularToolsetsTools() {
 	testCases := []api.Toolset{
 		&core.Toolset{},