From 950efebe3cf825535681390507c0bd6c94cf11af Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Tue, 25 Nov 2025 12:25:58 +0100 Subject: [PATCH] test(kiali): add toolset schema marshalling verification Signed-off-by: Marc Nuri --- pkg/mcp/testdata/toolsets-core-tools.json | 8 +- ...toolsets-full-tools-multicluster-enum.json | 8 +- .../toolsets-full-tools-multicluster.json | 8 +- .../toolsets-full-tools-openshift.json | 8 +- pkg/mcp/testdata/toolsets-full-tools.json | 8 +- pkg/mcp/testdata/toolsets-kiali-tools.json | 283 ++++++++++++++++++ pkg/mcp/toolsets_test.go | 2 + 7 files changed, 305 insertions(+), 20 deletions(-) create mode 100644 pkg/mcp/testdata/toolsets-kiali-tools.json diff --git a/pkg/mcp/testdata/toolsets-core-tools.json b/pkg/mcp/testdata/toolsets-core-tools.json index b4c5667f..c0a242b1 100644 --- a/pkg/mcp/testdata/toolsets-core-tools.json +++ b/pkg/mcp/testdata/toolsets-core-tools.json @@ -98,14 +98,14 @@ "inputSchema": { "type": "object", "properties": { - "name": { - "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", - "type": "string" - }, "label_selector": { "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes 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 Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" } } }, diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json index 7831c054..a5292c9d 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json @@ -279,14 +279,14 @@ ], "type": "string" }, - "name": { - "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", - "type": "string" - }, "label_selector": { "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes 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 Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" } } }, diff --git a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json index b95f179c..801521d4 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-multicluster.json +++ b/pkg/mcp/testdata/toolsets-full-tools-multicluster.json @@ -247,14 +247,14 @@ "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 Node to get the resource consumption from (Optional, all Nodes if not provided)", - "type": "string" - }, "label_selector": { "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes 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 Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" } } }, diff --git a/pkg/mcp/testdata/toolsets-full-tools-openshift.json b/pkg/mcp/testdata/toolsets-full-tools-openshift.json index e4488b0a..d0c25ac7 100644 --- a/pkg/mcp/testdata/toolsets-full-tools-openshift.json +++ b/pkg/mcp/testdata/toolsets-full-tools-openshift.json @@ -199,14 +199,14 @@ "inputSchema": { "type": "object", "properties": { - "name": { - "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", - "type": "string" - }, "label_selector": { "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes 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 Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" } } }, diff --git a/pkg/mcp/testdata/toolsets-full-tools.json b/pkg/mcp/testdata/toolsets-full-tools.json index ca270027..9e76c9dc 100644 --- a/pkg/mcp/testdata/toolsets-full-tools.json +++ b/pkg/mcp/testdata/toolsets-full-tools.json @@ -199,14 +199,14 @@ "inputSchema": { "type": "object", "properties": { - "name": { - "description": "Name of the Node to get the resource consumption from (Optional, all Nodes if not provided)", - "type": "string" - }, "label_selector": { "description": "Kubernetes label selector (e.g. 'node-role.kubernetes.io/worker=') to filter nodes 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 Node to get the resource consumption from (Optional, all Nodes if not provided)", + "type": "string" } } }, diff --git a/pkg/mcp/testdata/toolsets-kiali-tools.json b/pkg/mcp/testdata/toolsets-kiali-tools.json new file mode 100644 index 00000000..5f5e9f48 --- /dev/null +++ b/pkg/mcp/testdata/toolsets-kiali-tools.json @@ -0,0 +1,283 @@ +[ + { + "annotations": { + "title": "Topology: Mesh, Graph, Health, and Status", + "readOnlyHint": true, + "destructiveHint": false, + "openWorldHint": true + }, + "description": "Returns the topology of a specific namespaces, health, status of the mesh and namespaces. Includes a mesh health summary overview with aggregated counts of healthy, degraded, and failing apps, workloads, and services. Use this for high-level overviews", + "inputSchema": { + "type": "object", + "properties": { + "graphType": { + "description": "Type of graph to return: 'versionedApp', 'app', 'service', 'workload', 'mesh'. Default: 'versionedApp'", + "type": "string" + }, + "namespace": { + "description": "Optional single namespace to include in the graph (alternative to namespaces)", + "type": "string" + }, + "namespaces": { + "description": "Optional comma-separated list of namespaces to include in the graph", + "type": "string" + }, + "rateInterval": { + "description": "Rate interval for fetching (e.g., '10m', '5m', '1h'). Default: '10m'", + "type": "string" + } + } + }, + "name": "kiali_get_mesh_graph" + }, + { + "annotations": { + "title": "Get Metrics for a Resource", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "Gets lists or detailed info for Kubernetes resources (services, workloads) within the mesh", + "inputSchema": { + "type": "object", + "properties": { + "byLabels": { + "description": "Comma-separated list of labels to group metrics by (e.g., 'source_workload,destination_service'). Optional", + "type": "string" + }, + "direction": { + "description": "Traffic direction: 'inbound' or 'outbound'. Optional, defaults to 'outbound'", + "type": "string" + }, + "duration": { + "description": "Time range to get metrics for (optional string - if provided, gets metrics; if empty, get default 1800s).", + "type": "string" + }, + "namespace": { + "description": "Namespace to get resources from", + "type": "string" + }, + "quantiles": { + "description": "Comma-separated list of quantiles for histogram metrics (e.g., '0.5,0.95,0.99'). Optional", + "type": "string" + }, + "rateInterval": { + "description": "Rate interval for metrics (e.g., '1m', '5m'). Optional, defaults to '10m'", + "type": "string" + }, + "reporter": { + "description": "Metrics reporter: 'source', 'destination', or 'both'. Optional, defaults to 'source'", + "type": "string" + }, + "requestProtocol": { + "description": "Filter by request protocol (e.g., 'http', 'grpc', 'tcp'). Optional", + "type": "string" + }, + "resource_name": { + "description": "Name of the resource to get details for (optional string - if provided, gets details; if empty, lists all).", + "type": "string" + }, + "resource_type": { + "description": "Type of resource to get details for (service, workload)", + "enum": [ + "service", + "workload" + ], + "type": "string" + }, + "step": { + "description": "Step between data points in seconds (e.g., '15'). Optional, defaults to 15 seconds", + "type": "string" + } + }, + "required": [ + "resource_type", + "namespace", + "resource_name" + ] + }, + "name": "kiali_get_metrics" + }, + { + "annotations": { + "title": "List or Resource Details", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "Gets lists or detailed info for Kubernetes resources (services, workloads) within the mesh", + "inputSchema": { + "type": "object", + "properties": { + "namespaces": { + "description": "Comma-separated list of namespaces to get services from (e.g. 'bookinfo' or 'bookinfo,default'). If not provided, will list services from all accessible namespaces", + "type": "string" + }, + "resource_name": { + "description": "Name of the resource to get details for (optional string - if provided, gets details; if empty, lists all).", + "type": "string" + }, + "resource_type": { + "description": "Type of resource to get details for (service, workload)", + "enum": [ + "service", + "workload" + ], + "type": "string" + } + } + }, + "name": "kiali_get_resource_details" + }, + { + "annotations": { + "title": "Get Traces for a Resource or Trace Details", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "Gets traces for a specific resource (app, service, workload) in a namespace, or gets detailed information for a specific trace by its ID. If traceId is provided, it returns detailed trace information and other parameters are not required.", + "inputSchema": { + "type": "object", + "properties": { + "clusterName": { + "description": "Cluster name for multi-cluster environments (optional, only used when traceId is not provided)", + "type": "string" + }, + "endMicros": { + "description": "End time for traces in microseconds since epoch (optional, defaults to 10 minutes after startMicros if not provided, only used when traceId is not provided)", + "type": "string" + }, + "limit": { + "description": "Maximum number of traces to return (default: 100, only used when traceId is not provided)", + "minimum": 1, + "type": "integer" + }, + "minDuration": { + "description": "Minimum trace duration in microseconds (optional, only used when traceId is not provided)", + "minimum": 0, + "type": "integer" + }, + "namespace": { + "description": "Namespace to get resources from. Required if traceId is not provided.", + "type": "string" + }, + "resource_name": { + "description": "Name of the resource to get traces for. Required if traceId is not provided.", + "type": "string" + }, + "resource_type": { + "description": "Type of resource to get traces for (app, service, workload). Required if traceId is not provided.", + "enum": [ + "app", + "service", + "workload" + ], + "type": "string" + }, + "startMicros": { + "description": "Start time for traces in microseconds since epoch (optional, defaults to 10 minutes before current time if not provided, only used when traceId is not provided)", + "type": "string" + }, + "tags": { + "description": "JSON string of tags to filter traces (optional, only used when traceId is not provided)", + "type": "string" + }, + "traceId": { + "description": "Unique identifier of the trace to retrieve detailed information for. If provided, this will return detailed trace information and other parameters (resource_type, namespace, resource_name) are not required.", + "type": "string" + } + } + }, + "name": "kiali_get_traces" + }, + { + "annotations": { + "title": "Manage Istio Config: List, Get, Create, Patch, Delete", + "destructiveHint": true, + "idempotentHint": true, + "openWorldHint": true + }, + "description": "Manages Istio configuration objects (Gateways, VirtualServices, etc.). Can list (objects and validations), get, create, patch, or delete objects", + "inputSchema": { + "type": "object", + "properties": { + "action": { + "description": "Action to perform: list, get, create, patch, or delete", + "type": "string" + }, + "group": { + "description": "API group of the Istio object (e.g., 'networking.istio.io', 'gateway.networking.k8s.io')", + "type": "string" + }, + "json_data": { + "description": "JSON data to apply or create the object", + "type": "string" + }, + "kind": { + "description": "Kind of the Istio object (e.g., 'DestinationRule', 'VirtualService', 'HTTPRoute', 'Gateway')", + "type": "string" + }, + "name": { + "description": "Name of the Istio object", + "type": "string" + }, + "namespace": { + "description": "Namespace containing the Istio object", + "type": "string" + }, + "version": { + "description": "API version of the Istio object (e.g., 'v1', 'v1beta1')", + "type": "string" + } + }, + "required": [ + "action" + ] + }, + "name": "kiali_manage_istio_config" + }, + { + "annotations": { + "title": "Workload: Logs", + "readOnlyHint": true, + "destructiveHint": false, + "openWorldHint": true + }, + "description": "Get logs for a specific workload's pods in a namespace. Only requires namespace and workload name - automatically discovers pods and containers. Optionally filter by container name, time range, and other parameters. Container is auto-detected if not specified.", + "inputSchema": { + "type": "object", + "properties": { + "container": { + "description": "Optional container name to filter logs. If not provided, automatically detects and uses the main application container (excludes istio-proxy and istio-init)", + "type": "string" + }, + "namespace": { + "description": "Namespace containing the workload", + "type": "string" + }, + "since": { + "description": "Time duration to fetch logs from (e.g., '5m', '1h', '30s'). If not provided, returns recent logs", + "type": "string" + }, + "tail": { + "description": "Number of lines to retrieve from the end of logs (default: 100)", + "minimum": 1, + "type": "integer" + }, + "workload": { + "description": "Name of the workload to get logs for", + "type": "string" + } + }, + "required": [ + "namespace", + "workload" + ] + }, + "name": "workload_logs" + } +] diff --git a/pkg/mcp/toolsets_test.go b/pkg/mcp/toolsets_test.go index f366a71d..323db2d0 100644 --- a/pkg/mcp/toolsets_test.go +++ b/pkg/mcp/toolsets_test.go @@ -15,6 +15,7 @@ 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/containers/kubernetes-mcp-server/pkg/toolsets/kiali" "github.com/containers/kubernetes-mcp-server/pkg/toolsets/kubevirt" "github.com/mark3labs/mcp-go/mcp" "github.com/stretchr/testify/suite" @@ -156,6 +157,7 @@ func (s *ToolsetsSuite) TestGranularToolsetsTools() { &core.Toolset{}, &config.Toolset{}, &helm.Toolset{}, + &kiali.Toolset{}, &kubevirt.Toolset{}, } for _, testCase := range testCases {