Skip to content

feat(kiali): API based filtering for Kiali - #1339

Open
josunect wants to merge 4 commits into
containers:mainfrom
josunect:OSSM-15010-kiali-target-compatibility-filters
Open

feat(kiali): API based filtering for Kiali#1339
josunect wants to merge 4 commits into
containers:mainfrom
josunect:OSSM-15010-kiali-target-compatibility-filters

Conversation

@josunect

@josunect josunect commented Jul 30, 2026

Copy link
Copy Markdown
Contributor
  • URL probe: If [toolset_configs.kiali].url is set, Kiali tools are registered only when GET {url}/api/status succeeds.
  • Auto-discovery: If url is empty, the server discovers the in-cluster Kiali Service URL from the Kiali CR (with well-known DNS fallback), probes /api/status, and uses the working URL for tool calls.
  • Opt-in: Probe and discovery run only when experimental_enable_target_compatibility_tool_filters = true. With the flag off (default), Kiali tools are always registered (unchanged behavior).
  • Tool registration: All Kiali tools share a single HasKiali reachability filter via tools.All(p).

Tests:

  • go test ./pkg/kiali/... ./pkg/toolsets/kiali/...
  • Tools hidden without kialis.kiali.io (with experimental_enable_target_compatibility_tool_filters = true)
  • Tools visible when the Kiali CRD is present

To test the url auto discovery, the mcp must be deployed inside the cluster:

Prerequisites

  • Kiali Operator installed and a Kiali CR (kialis.kiali.io) in Running state.
  • Kiali Service reachable from inside the cluster (GET http://:/api/status returns {"status":...}).
  • A local image built from this branch (the published chart image may not include these changes).

Deploy MCP server in-cluster

make build
docker build -t kubernetes-mcp-server:dev .

load into your cluster if using Kind:

kind load docker-image kubernetes-mcp-server:dev --name <cluster-name>
helm upgrade -i kubernetes-mcp-server charts/kubernetes-mcp-server \
  -n mcp-system --create-namespace \
  -f charts/kubernetes-mcp-server/examples/values-kiali-discovery.yaml \
  --set openshift=false \
  --set ingress.enabled=false \
  --set image.registry=docker.io/library \
  --set image.repository=kubernetes-mcp-server \
  --set image.version=dev \
  --set image.pullPolicy=Never

where values-kiali-discovery.yaml:

ingress:
  enabled: false

serviceAccount:
  create: true
  automountToken: true

rbac:
  create: true
  extraClusterRoles:
    - name: kiali-discovery
      rules:
        - apiGroups: ["kiali.io"]
          resources: ["kialis"]
          verbs: ["get", "list", "watch"]
  extraClusterRoleBindings:
    - name: view
      roleRef:
        name: view
        external: true
    - name: kiali-discovery
      roleRef:
        name: kiali-discovery

config:
  toolsets:
    - core
    - kiali
  experimental_enable_target_compatibility_tool_filters: true
  cluster_auth_mode: kubeconfig

  toolset_configs:
    kiali:
      insecure: true
      # Omit url to enable in-cluster discovery from the Kiali CR.

Key config (already in the example values):

  • experimental_enable_target_compatibility_tool_filters: true
  • toolsets: [core, kiali]
  • No [toolset_configs.kiali].url — discovery uses the Kiali CR
  • RBAC to list/get/watch kialis.kiali.io

Verify

kubectl -n mcp-system rollout status deployment/kubernetes-mcp-server
kubectl -n mcp-system port-forward svc/kubernetes-mcp-server 18080:8080

In MCP Inspector (http://localhost:18080/mcp) or via API:

  • tools/list → all 11 kiali_* tools should appear.
  • kiali_get_mesh_status → should return mesh status from the discovered Kiali URL.

Negative checks

  • No Kiali CR → kiali_* tools should not appear in tools/list.
  • Flag disabled (experimental_enable_target_compatibility_tool_filters: false) → tools always listed, no probe/discovery.

Troubleshooting

  • Confirm the Kiali CR exists: kubectl get kiali -A
  • Confirm MCP can list CRs: kubectl auth can-i list kialis.kiali.io --as=system:serviceaccount:mcp-system: -n
  • Discovery logs are at verbosity 1–2: check pod logs with -v=2 if tools are missing.

@josunect
josunect force-pushed the OSSM-15010-kiali-target-compatibility-filters branch from 17672b7 to 4d6f90c Compare July 30, 2026 16:09
@josunect
josunect marked this pull request as ready for review July 31, 2026 07:32
@github-actions

Copy link
Copy Markdown
Contributor

👋 Heads up — this pull request changes files owned by @aljesusg.

You are listed as an owner of one or more of the changed areas in .github/CODEOWNERS. GitHub cannot auto-request review from owners without write access, so this comment is the notification instead. A review when you have a moment would be appreciated 🙏

@cajieh cajieh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add an MCP ListTools test similar to kubevirt’s TestKubevirtToolsFilteredWithoutCRDs, so we assert Kiali tools are hidden when the experimental flag is on and the Kiali CRD is missing?

Added comments. Otherwise, it looks good. Thank you!

Comment thread pkg/kiali/gvr.go
var KialiGVK = schema.GroupVersionKind{
Group: "kiali.io",
Version: "v1alpha1",
Kind: "Kiali",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is filtering on the Kiali Operator CRD (kiali.io/v1alpha1) intentional for OSSM, or should Helm/standalone installs without that CRD still expose tools when [toolset_configs.kiali].url is set?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cajieh Thanks for the review. Added an MCP ListTools test similar to TestKubevirtToolsFilteredWithoutCRDs, asserting Kiali tools are hidden when the experimental flag is on and the Kiali Operator CRD is missing.

On the CRD question: filtering on kiali.io/v1alpha1 alone is intentional for OSSM/operator installs, but Helm/standalone installs do not register that CRD and talk to Kiali only via [toolset_configs.kiali].url. Because the tools call the Kiali HTTP API (not the Operator CR), HasKiali now treats Kiali as available if either the Operator GVK is present or a non-empty URL is configured. The MCP server wraps the filtering provider with the live config so that URL check sees current toolset_configs without changing the GetTools API.

@josunect
josunect requested a review from cajieh August 6, 2026 07:50
Signed-off-by: Josune Cordoba <jcordoba@redhat.com>
Signed-off-by: Josune Cordoba <jcordoba@redhat.com>
Signed-off-by: Josune Cordoba <jcordoba@redhat.com>
@josunect
josunect force-pushed the OSSM-15010-kiali-target-compatibility-filters branch from 4168160 to ab61eb5 Compare August 12, 2026 10:40
Signed-off-by: Josune Cordoba <jcordoba@redhat.com>
@josunect

Copy link
Copy Markdown
Contributor Author

@manusa @cajieh @Cali0707 this is ready to review. Thanks!

@Cali0707 Cali0707 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for starting on this @josunect !

I left some comments inline, but they may or may not be valid long term as I think there is a larger architectural consideration we need to work through first.

Fundamentally the FilteringProvider was designed to hide cluster-specific details behind a stable abstraction, so that we can swap things to e.g. leverage ACM search instead of iterating over clusters in some setups. However, Kiali is now fundamentally ClusterAware = false after #1224, as it is a mesh control that can span clusters.

This leads to the type assertion from the FilteringProvider to kubernetes.Provider, which IMO we shouldn't be doing. We should either:

  1. explicitly pass that provider in
  2. don't assert and expect it to be there

A key reason for this is that doing this and then looking at the default provider only or hand rolling the multi cluster checks by iterating providers blocks us from making optimizations based on the specifics of the multi cluster setup.

I wonder if maybe the correct fix is we make a kube client that internally checks every target rather than exposing a single method like HasGVKs which iterates over the targets? Then you could have a more expressive API and we could still make changes based on the multi cluster setup?

cc @2uasimojo since you have put a lot of thought into this part of the codebase

Comment thread pkg/kiali/config.go
Comment on lines +30 to +38
// URL is optional: when empty, HasKiali may discover an in-cluster Service URL
// from a Kiali CR and inject it into this config at runtime.
if strings.TrimSpace(c.Url) == "" {
if caValue := strings.TrimSpace(c.CertificateAuthority); caValue != "" {
if _, err := os.Stat(caValue); err != nil {
return fmt.Errorf("certificate_authority must be a valid file path: %w", err)
}
}
return nil

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So HasKiali will only be called if experimental_enable_target_compatibility_tool_filers=true which is not the default. Which means by default this may allow some configs to pass validation that it should not...

Maybe we can at a minimum log a warning in this case?

Comment thread pkg/kiali/gvr.go
func injectDiscoveredURL(cfg *Config, cfgOK bool, url string) {
setDiscoveredURL(url)
if cfgOK && cfg != nil {
cfg.Url = url

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This write technically isn't synchronized wrt to reads as far as I can tell

Comment thread pkg/kiali/gvr.go
var available bool
return func() bool {
once.Do(func() {
available = evaluateKialiAvailability(context.TODO(), p)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to use context.TODO() in places just because the TargetCompatibilityFilters api doesn't pass a ctx parameter in, maybe we should add the ctx paramter to that method?

cc @2uasimojo for input

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should add the ctx paramter to that method?

(FMR, the method def)

I don't object to adding a context param there, but the prototype is super generic for a reason. And we generally expect the impl to be defined in a closure like we've got here. So if we need a context param, it could be plumbed through HasKiali() itself. WDYT?

Comment thread pkg/kiali/status.go
klogutil.FromContext(ctx).V(2).Info("kiali status probe: failed to create HTTP client", "error", err)
return false
}
client.Timeout = statusProbeTimeout

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the timeout explicitly set since there is the context deadline on probeCtx here

Comment thread pkg/kiali/status.go

const (
statusPath = "/api/status"
statusProbeTimeout = 3 * time.Second

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little worried about this timeout slowing things down, as this will ultimately be called whenever the WatchTargets cluster-state changes (which can happen a lot in multi cluster envs).

Since this all runs synchronously each time it fires (and at startup) and we potentially run this against 6 urls, that is a 18 second delay.

Maybe we can:

  1. make these probes in parallel
  2. Tighten this timeout a decent amount?

@2uasimojo 2uasimojo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the part of this that makes it possible to run without a url.

I think it's fine to hide all the kiali tools if discovery/validation of the endpoint fails.

But I don't think TargetCompatibilityToolFilters (the ServerTool field) is the right mechanism for that. If we decide to condition the expensive discovery/validation behind the experimental_enable_target_compatibility_tool_filters option, it should be more like:

func GetTools(p) []api.ServerTool {
	if p.IsTargetCompatibilityToolFiltersEnabled() && !HasKiali() {
		return []
	}

	tools := slices.Concat(
	  ...
	)
	for i := range tools {
		// *No* TargetCompatibilityToolFilters here
		tools[i].ClusterAware = ptr.To(false)
	}
	return tools
}

The ServerTool field is intended for filtering individual tools in a toolset.

Comment thread pkg/kiali/discover.go
if cr == nil {
return nil
}
instanceName := nestedString(cr.Object, "status", "deployment", "instanceName")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll again mention my distaste for Unstructured and generic struct walking like this. I hope there's a really good reason we're not importing the schema and unmarshaling into a real typed struct.

Comment thread pkg/kiali/gvr.go
var available bool
return func() bool {
once.Do(func() {
available = evaluateKialiAvailability(context.TODO(), p)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should add the ctx paramter to that method?

(FMR, the method def)

I don't object to adding a context param there, but the prototype is super generic for a reason. And we generally expect the impl to be defined in a closure like we've got here. So if we need a context param, it could be plumbed through HasKiali() itself. WDYT?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants