Files
openshift-mcp-server/pkg/toolsets/config/configuration.go
Calum Murray a2d16e9f41 feat: Multi Cluster Support (#348)
* feat: add cluster provider for kubeconfig

Signed-off-by: Calum Murray <cmurray@redhat.com>

* feat: move server to use ClusterProvider interface

Signed-off-by: Calum Murray <cmurray@redhat.com>

* feat: authentication middleware works with cluster provider

Signed-off-by: Calum Murray <cmurray@redhat.com>

* fix: unit tests work after cluster provider changes

Signed-off-by: Calum Murray <cmurray@redhat.com>

* feat: add tool mutator to add cluster parameter

Signed-off-by: Calum Murray <cmurray@redhat.com>

* test: handle cluster parameter

Signed-off-by: Calum Murray <cmurray@redhat.com>

* fix: handle lazy init correctly

Signed-off-by: Calum Murray <cmurray@redhat.com>

* refactor: move to using multi-strategy ManagerProvider

Signed-off-by: Calum Murray <cmurray@redhat.com>

* feat: add contexts_list tool

Signed-off-by: Calum Murray <cmurray@redhat.com>

* refactor: make tool mutator generic between cluster/context naming

Signed-off-by: Calum Murray <cmurray@redhat.com>

* feat: introduce tool filter

Signed-off-by: Calum Murray <cmurray@redhat.com>

* refactor: use new ManagerProvider/mutator/filter within mcp server

Signed-off-by: Calum Murray <cmurray@redhat.com>

* fix(test): tests expect context parameter in tool defs

Signed-off-by: Calum Murray <cmurray@redhat.com>

* feat: auth handles multi-cluster case correctly

Signed-off-by: Calum Murray <cmurray@redhat.com>

* fix: small changes from local testing

Signed-off-by: Calum Murray <cmurray@redhat.com>

* chore: fix enum test

Signed-off-by: Calum Murray <cmurray@redhat.com>

* review: Multi Cluster support (#1)

* nit: rename contexts_list to configuration_contexts_list

Besides the conventional naming, it helps LLMs understand the context of the tool by providing a certain level of hierarchy.

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* fix(mcp): ToolMutator doesn't rely on magic strings

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* refactor(api): don't expose ManagerProvider to toolsets

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* test(mcp): configuration_contexts_list basic tests

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* test(toolsets): revert edge-case test

This test should not be touched.

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* test(toolsets): add specific metadata tests for multi-cluster

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* fix(mcp): ToolFilter doesn't rely on magic strings (partially)

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* test(api): IsClusterAware and IsTargetListProvider default values

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* test(mcp): revert unneeded changes in mcp_tools_test.go

Signed-off-by: Marc Nuri <marc@marcnuri.com>

---------

Signed-off-by: Marc Nuri <marc@marcnuri.com>

* fix: always include configuration_contexts_list if contexts > 1

Signed-off-by: Calum Murray <cmurray@redhat.com>

* feat: include server urls in configuration_contexts_list

Signed-off-by: Calum Murray <cmurray@redhat.com>

---------

Signed-off-by: Calum Murray <cmurray@redhat.com>
Signed-off-by: Marc Nuri <marc@marcnuri.com>
Co-authored-by: Marc Nuri <marc@marcnuri.com>
2025-10-06 12:01:16 +02:00

116 lines
3.7 KiB
Go

package config
import (
"fmt"
"github.com/google/jsonschema-go/jsonschema"
"k8s.io/utils/ptr"
"github.com/containers/kubernetes-mcp-server/pkg/api"
"github.com/containers/kubernetes-mcp-server/pkg/output"
)
func initConfiguration() []api.ServerTool {
tools := []api.ServerTool{
{
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),
},
},
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),
},
},
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"]
if _, ok := minified.(bool); ok {
minify = minified.(bool)
}
ret, err := params.ConfigurationView(minify)
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get configuration: %v", err)), nil
}
configurationYaml, err := output.MarshalYaml(ret)
if err != nil {
err = fmt.Errorf("failed to get configuration: %v", err)
}
return api.NewToolCallResult(configurationYaml, err), nil
}