Files
openshift-mcp-server/pkg/mcp/tool_mutator.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

65 lines
1.6 KiB
Go

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
}