Skip to content

Commit f696ba8

Browse files
committed
Consolidate kiali tools metrics and traces
Signed-off-by: Alberto Gutierrez <[email protected]>
1 parent aa5a9d2 commit f696ba8

File tree

9 files changed

+457
-759
lines changed

9 files changed

+457
-759
lines changed

README.md

Lines changed: 12 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -363,43 +363,23 @@ In case multi-cluster support is enabled (default) and you have access to multip
363363
- `namespace` (`string`) - Namespace containing the Istio object
364364
- `version` (`string`) - API version of the Istio object (e.g., 'v1', 'v1beta1')
365365

366-
- **kiali_services_list** - Get all services in the mesh across specified namespaces with health and Istio resource information
366+
- **kiali_get_resource_details** - Gets lists or detailed info for Kubernetes resources (services, workloads) within the mesh
367367
- `namespaces` (`string`) - 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
368+
- `resource_name` (`string`) - Name of the resource to get details for (optional string - if provided, gets details; if empty, lists all).
369+
- `resource_type` (`string`) - Type of resource to get details for (service, workload)
368370

369-
- **kiali_service_details** - Get detailed information for a specific service in a namespace, including validation, health status, and configuration
370-
- `namespace` (`string`) **(required)** - Namespace containing the service
371-
- `service` (`string`) **(required)** - Name of the service to get details for
372-
373-
- **kiali_service_metrics** - Get metrics for a specific service in a namespace. Supports filtering by time range, direction (inbound/outbound), reporter, and other query parameters
374-
- `byLabels` (`string`) - Comma-separated list of labels to group metrics by (e.g., 'source_workload,destination_service'). Optional
375-
- `direction` (`string`) - Traffic direction: 'inbound' or 'outbound'. Optional, defaults to 'outbound'
376-
- `duration` (`string`) - Duration of the query period in seconds (e.g., '1800' for 30 minutes). Optional, defaults to 1800 seconds
377-
- `namespace` (`string`) **(required)** - Namespace containing the service
378-
- `quantiles` (`string`) - Comma-separated list of quantiles for histogram metrics (e.g., '0.5,0.95,0.99'). Optional
379-
- `rateInterval` (`string`) - Rate interval for metrics (e.g., '1m', '5m'). Optional, defaults to '1m'
380-
- `reporter` (`string`) - Metrics reporter: 'source', 'destination', or 'both'. Optional, defaults to 'source'
381-
- `requestProtocol` (`string`) - Filter by request protocol (e.g., 'http', 'grpc', 'tcp'). Optional
382-
- `service` (`string`) **(required)** - Name of the service to get metrics for
383-
- `step` (`string`) - Step between data points in seconds (e.g., '15'). Optional, defaults to 15 seconds
384-
385-
- **kiali_workloads_list** - Get all workloads in the mesh across specified namespaces with health and Istio resource information
386-
- `namespaces` (`string`) - Comma-separated list of namespaces to get workloads from (e.g. 'bookinfo' or 'bookinfo,default'). If not provided, will list workloads from all accessible namespaces
387-
388-
- **kiali_workload_details** - Get detailed information for a specific workload in a namespace, including validation, health status, and configuration
389-
- `namespace` (`string`) **(required)** - Namespace containing the workload
390-
- `workload` (`string`) **(required)** - Name of the workload to get details for
391-
392-
- **kiali_workload_metrics** - Get metrics for a specific workload in a namespace. Supports filtering by time range, direction (inbound/outbound), reporter, and other query parameters
371+
- **kiali_get_metrics** - Gets lists or detailed info for Kubernetes resources (services, workloads) within the mesh
393372
- `byLabels` (`string`) - Comma-separated list of labels to group metrics by (e.g., 'source_workload,destination_service'). Optional
394373
- `direction` (`string`) - Traffic direction: 'inbound' or 'outbound'. Optional, defaults to 'outbound'
395-
- `duration` (`string`) - Duration of the query period in seconds (e.g., '1800' for 30 minutes). Optional, defaults to 1800 seconds
396-
- `namespace` (`string`) **(required)** - Namespace containing the workload
374+
- `duration` (`string`) - Time range to get metrics for (optional string - if provided, gets metrics; if empty, get default 1800s).
375+
- `namespace` (`string`) **(required)** - Namespace to get resources from
397376
- `quantiles` (`string`) - Comma-separated list of quantiles for histogram metrics (e.g., '0.5,0.95,0.99'). Optional
398377
- `rateInterval` (`string`) - Rate interval for metrics (e.g., '1m', '5m'). Optional, defaults to '1m'
399378
- `reporter` (`string`) - Metrics reporter: 'source', 'destination', or 'both'. Optional, defaults to 'source'
400379
- `requestProtocol` (`string`) - Filter by request protocol (e.g., 'http', 'grpc', 'tcp'). Optional
380+
- `resource_name` (`string`) **(required)** - Name of the resource to get details for (optional string - if provided, gets details; if empty, lists all).
381+
- `resource_type` (`string`) **(required)** - Type of resource to get details for (service, workload)
401382
- `step` (`string`) - Step between data points in seconds (e.g., '15'). Optional, defaults to 15 seconds
402-
- `workload` (`string`) **(required)** - Name of the workload to get metrics for
403383

404384
- **workload_logs** - 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.
405385
- `container` (`string`) - Optional container name to filter logs. If not provided, automatically detects and uses the main application container (excludes istio-proxy and istio-init)
@@ -408,35 +388,16 @@ In case multi-cluster support is enabled (default) and you have access to multip
408388
- `tail` (`integer`) - Number of lines to retrieve from the end of logs (default: 100)
409389
- `workload` (`string`) **(required)** - Name of the workload to get logs for
410390

411-
- **kiali_app_traces** - Get distributed tracing data for a specific app in a namespace. Returns trace information including spans, duration, and error details for troubleshooting and performance analysis.
412-
- `app` (`string`) **(required)** - Name of the app to get traces for
413-
- `clusterName` (`string`) - Cluster name for multi-cluster environments (optional)
414-
- `endMicros` (`string`) - End time for traces in microseconds since epoch (optional)
415-
- `limit` (`integer`) - Maximum number of traces to return (default: 100)
416-
- `minDuration` (`integer`) - Minimum trace duration in microseconds (optional)
417-
- `namespace` (`string`) **(required)** - Namespace containing the app
418-
- `startMicros` (`string`) - Start time for traces in microseconds since epoch (optional)
419-
- `tags` (`string`) - JSON string of tags to filter traces (optional)
420-
421-
- **kiali_service_traces** - Get distributed tracing data for a specific service in a namespace. Returns trace information including spans, duration, and error details for troubleshooting and performance analysis.
422-
- `clusterName` (`string`) - Cluster name for multi-cluster environments (optional)
423-
- `endMicros` (`string`) - End time for traces in microseconds since epoch (optional)
424-
- `limit` (`integer`) - Maximum number of traces to return (default: 100)
425-
- `minDuration` (`integer`) - Minimum trace duration in microseconds (optional)
426-
- `namespace` (`string`) **(required)** - Namespace containing the service
427-
- `service` (`string`) **(required)** - Name of the service to get traces for
428-
- `startMicros` (`string`) - Start time for traces in microseconds since epoch (optional)
429-
- `tags` (`string`) - JSON string of tags to filter traces (optional)
430-
431-
- **kiali_workload_traces** - Get distributed tracing data for a specific workload in a namespace. Returns trace information including spans, duration, and error details for troubleshooting and performance analysis.
391+
- **kiali_get_traces** - Gets traces for a specific resource (app, service, workload) in a namespace
432392
- `clusterName` (`string`) - Cluster name for multi-cluster environments (optional)
433393
- `endMicros` (`string`) - End time for traces in microseconds since epoch (optional)
434394
- `limit` (`integer`) - Maximum number of traces to return (default: 100)
435395
- `minDuration` (`integer`) - Minimum trace duration in microseconds (optional)
436-
- `namespace` (`string`) **(required)** - Namespace containing the workload
396+
- `namespace` (`string`) **(required)** - Namespace to get resources from
397+
- `resource_name` (`string`) **(required)** - Name of the resource to get details for (optional string - if provided, gets details; if empty, lists all).
398+
- `resource_type` (`string`) **(required)** - Type of resource to get metrics for (app, service, workload)
437399
- `startMicros` (`string`) - Start time for traces in microseconds since epoch (optional)
438400
- `tags` (`string`) - JSON string of tags to filter traces (optional)
439-
- `workload` (`string`) **(required)** - Name of the workload to get traces for
440401

441402
</details>
442403

pkg/toolsets/kiali/get_mesh_graph.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
"github.com/containers/kubernetes-mcp-server/pkg/api"
1111
)
1212

13-
func GetMeshGraph() []api.ServerTool {
13+
func initGetMeshGraph() []api.ServerTool {
1414
ret := make([]api.ServerTool, 0)
1515
ret = append(ret, api.ServerTool{
1616
Tool: api.Tool{

pkg/toolsets/kiali/get_metrics.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package kiali
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/google/jsonschema-go/jsonschema"
9+
"k8s.io/utils/ptr"
10+
11+
"github.com/containers/kubernetes-mcp-server/pkg/api"
12+
kialiclient "github.com/containers/kubernetes-mcp-server/pkg/kiali"
13+
)
14+
15+
type resourceOperations struct {
16+
singularName string
17+
metricsFunc func(ctx context.Context, k *kialiclient.Kiali, namespace, name string, queryParams map[string]string) (string, error)
18+
}
19+
20+
var opsMap = map[string]resourceOperations{
21+
"service": {
22+
singularName: "service",
23+
metricsFunc: func(ctx context.Context, k *kialiclient.Kiali, ns, name string, queryParams map[string]string) (string, error) {
24+
return k.ServiceMetrics(ctx, ns, name, queryParams)
25+
},
26+
},
27+
"workload": {
28+
singularName: "workload",
29+
metricsFunc: func(ctx context.Context, k *kialiclient.Kiali, ns, name string, queryParams map[string]string) (string, error) {
30+
return k.WorkloadMetrics(ctx, ns, name, queryParams)
31+
},
32+
},
33+
}
34+
35+
func initGetMetrics() []api.ServerTool {
36+
ret := make([]api.ServerTool, 0)
37+
38+
ret = append(ret, api.ServerTool{
39+
Tool: api.Tool{
40+
Name: "kiali_get_metrics",
41+
Description: "Gets lists or detailed info for Kubernetes resources (services, workloads) within the mesh",
42+
InputSchema: &jsonschema.Schema{
43+
Type: "object",
44+
Properties: map[string]*jsonschema.Schema{
45+
"resource_type": {
46+
Type: "string",
47+
Description: "Type of resource to get details for (service, workload)",
48+
Enum: []any{"service", "workload"},
49+
},
50+
"namespace": {
51+
Type: "string",
52+
Description: "Namespace to get resources from",
53+
},
54+
"resource_name": {
55+
Type: "string",
56+
Description: "Name of the resource to get details for (optional string - if provided, gets details; if empty, lists all).",
57+
},
58+
"duration": {
59+
Type: "string",
60+
Description: "Time range to get metrics for (optional string - if provided, gets metrics; if empty, get default 1800s).",
61+
},
62+
"step": {
63+
Type: "string",
64+
Description: "Step between data points in seconds (e.g., '15'). Optional, defaults to 15 seconds",
65+
},
66+
"rateInterval": {
67+
Type: "string",
68+
Description: "Rate interval for metrics (e.g., '1m', '5m'). Optional, defaults to '1m'",
69+
},
70+
"direction": {
71+
Type: "string",
72+
Description: "Traffic direction: 'inbound' or 'outbound'. Optional, defaults to 'outbound'",
73+
},
74+
"reporter": {
75+
Type: "string",
76+
Description: "Metrics reporter: 'source', 'destination', or 'both'. Optional, defaults to 'source'",
77+
},
78+
"requestProtocol": {
79+
Type: "string",
80+
Description: "Filter by request protocol (e.g., 'http', 'grpc', 'tcp'). Optional",
81+
},
82+
"quantiles": {
83+
Type: "string",
84+
Description: "Comma-separated list of quantiles for histogram metrics (e.g., '0.5,0.95,0.99'). Optional",
85+
},
86+
"byLabels": {
87+
Type: "string",
88+
Description: "Comma-separated list of labels to group metrics by (e.g., 'source_workload,destination_service'). Optional",
89+
},
90+
},
91+
Required: []string{"resource_type", "namespace", "resource_name"},
92+
},
93+
Annotations: api.ToolAnnotations{
94+
Title: "Get Metrics for a Resource",
95+
ReadOnlyHint: ptr.To(true),
96+
DestructiveHint: ptr.To(false),
97+
IdempotentHint: ptr.To(true),
98+
OpenWorldHint: ptr.To(true),
99+
},
100+
}, Handler: resourceMetricsHandler,
101+
})
102+
103+
return ret
104+
}
105+
106+
func resourceMetricsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
107+
// Extract parameters
108+
resourceType, _ := params.GetArguments()["resource_type"].(string)
109+
namespace, _ := params.GetArguments()["namespace"].(string)
110+
resourceName, _ := params.GetArguments()["resource_name"].(string)
111+
112+
resourceType = strings.ToLower(strings.TrimSpace(resourceType))
113+
namespace = strings.TrimSpace(namespace)
114+
resourceName = strings.TrimSpace(resourceName)
115+
116+
if resourceType == "" {
117+
return api.NewToolCallResult("", fmt.Errorf("resource_type is required")), nil
118+
}
119+
if namespace == "" || len(strings.Split(namespace, ",")) != 1 {
120+
return api.NewToolCallResult("", fmt.Errorf("namespace is required")), nil
121+
}
122+
if resourceName == "" {
123+
return api.NewToolCallResult("", fmt.Errorf("resource_name is required")), nil
124+
}
125+
126+
ops, ok := opsMap[resourceType]
127+
if !ok {
128+
return api.NewToolCallResult("", fmt.Errorf("invalid resource type: %s", resourceType)), nil
129+
}
130+
131+
queryParams := make(map[string]string)
132+
if duration, ok := params.GetArguments()["duration"].(string); ok && duration != "" {
133+
queryParams["duration"] = duration
134+
}
135+
if step, ok := params.GetArguments()["step"].(string); ok && step != "" {
136+
queryParams["step"] = step
137+
}
138+
if rateInterval, ok := params.GetArguments()["rateInterval"].(string); ok && rateInterval != "" {
139+
queryParams["rateInterval"] = rateInterval
140+
}
141+
if direction, ok := params.GetArguments()["direction"].(string); ok && direction != "" {
142+
queryParams["direction"] = direction
143+
}
144+
if reporter, ok := params.GetArguments()["reporter"].(string); ok && reporter != "" {
145+
queryParams["reporter"] = reporter
146+
}
147+
if requestProtocol, ok := params.GetArguments()["requestProtocol"].(string); ok && requestProtocol != "" {
148+
queryParams["requestProtocol"] = requestProtocol
149+
}
150+
if quantiles, ok := params.GetArguments()["quantiles"].(string); ok && quantiles != "" {
151+
queryParams["quantiles"] = quantiles
152+
}
153+
if byLabels, ok := params.GetArguments()["byLabels"].(string); ok && byLabels != "" {
154+
queryParams["byLabels"] = byLabels
155+
}
156+
157+
k := params.NewKiali()
158+
159+
content, err := ops.metricsFunc(params.Context, k, namespace, resourceName, queryParams)
160+
if err != nil {
161+
return api.NewToolCallResult("", fmt.Errorf("failed to get %s metrics: %v", ops.singularName, err)), nil
162+
}
163+
return api.NewToolCallResult(content, nil), nil
164+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package kiali
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/google/jsonschema-go/jsonschema"
9+
"k8s.io/utils/ptr"
10+
11+
"github.com/containers/kubernetes-mcp-server/pkg/api"
12+
kialiclient "github.com/containers/kubernetes-mcp-server/pkg/kiali"
13+
)
14+
15+
type listDetailsOperations struct {
16+
singularName string
17+
listFunc func(ctx context.Context, k *kialiclient.Kiali, namespaces string) (string, error)
18+
detailsFunc func(ctx context.Context, k *kialiclient.Kiali, namespace, name string) (string, error)
19+
}
20+
21+
var listDetailsOpsMap = map[string]listDetailsOperations{
22+
"service": {
23+
singularName: "service",
24+
listFunc: func(ctx context.Context, k *kialiclient.Kiali, nss string) (string, error) {
25+
return k.ServicesList(ctx, nss)
26+
},
27+
detailsFunc: func(ctx context.Context, k *kialiclient.Kiali, ns, name string) (string, error) {
28+
return k.ServiceDetails(ctx, ns, name)
29+
},
30+
},
31+
"workload": {
32+
singularName: "workload",
33+
listFunc: func(ctx context.Context, k *kialiclient.Kiali, nss string) (string, error) {
34+
return k.WorkloadsList(ctx, nss)
35+
},
36+
detailsFunc: func(ctx context.Context, k *kialiclient.Kiali, ns, name string) (string, error) {
37+
return k.WorkloadDetails(ctx, ns, name)
38+
},
39+
},
40+
}
41+
42+
func initGetResourceDetails() []api.ServerTool {
43+
ret := make([]api.ServerTool, 0)
44+
45+
ret = append(ret, api.ServerTool{
46+
Tool: api.Tool{
47+
Name: "kiali_get_resource_details",
48+
Description: "Gets lists or detailed info for Kubernetes resources (services, workloads) within the mesh",
49+
InputSchema: &jsonschema.Schema{
50+
Type: "object",
51+
Properties: map[string]*jsonschema.Schema{
52+
"resource_type": {
53+
Type: "string",
54+
Description: "Type of resource to get details for (service, workload)",
55+
Enum: []any{"service", "workload"},
56+
},
57+
"namespaces": {
58+
Type: "string",
59+
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",
60+
},
61+
"resource_name": {
62+
Type: "string",
63+
Description: "Name of the resource to get details for (optional string - if provided, gets details; if empty, lists all).",
64+
},
65+
},
66+
},
67+
Annotations: api.ToolAnnotations{
68+
Title: "List or Resource Details",
69+
ReadOnlyHint: ptr.To(true),
70+
DestructiveHint: ptr.To(false),
71+
IdempotentHint: ptr.To(true),
72+
OpenWorldHint: ptr.To(true),
73+
},
74+
}, Handler: resourceDetailsHandler,
75+
})
76+
77+
return ret
78+
}
79+
80+
func resourceDetailsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
81+
// Extract parameters
82+
resourceType, _ := params.GetArguments()["resource_type"].(string)
83+
namespaces, _ := params.GetArguments()["namespaces"].(string)
84+
resourceName, _ := params.GetArguments()["resource_name"].(string)
85+
86+
resourceType = strings.ToLower(strings.TrimSpace(resourceType))
87+
namespaces = strings.TrimSpace(namespaces)
88+
resourceName = strings.TrimSpace(resourceName)
89+
90+
if resourceType == "" {
91+
return api.NewToolCallResult("", fmt.Errorf("resource_type is required")), nil
92+
}
93+
94+
k := params.NewKiali()
95+
96+
ops, ok := listDetailsOpsMap[resourceType]
97+
if !ok {
98+
return api.NewToolCallResult("", fmt.Errorf("invalid resource type: %s", resourceType)), nil
99+
}
100+
101+
// If a resource name is provided, fetch details. Requires exactly one namespace.
102+
if resourceName != "" {
103+
if count := len(strings.Split(namespaces, ",")); count != 1 {
104+
return api.NewToolCallResult("", fmt.Errorf("exactly one namespace must be provided for %s details", ops.singularName)), nil
105+
}
106+
content, err := ops.detailsFunc(params.Context, k, namespaces, resourceName)
107+
if err != nil {
108+
return api.NewToolCallResult("", fmt.Errorf("failed to get %s details: %v", ops.singularName, err)), nil
109+
}
110+
return api.NewToolCallResult(content, nil), nil
111+
}
112+
113+
// Otherwise, list resources (supports multiple namespaces)
114+
content, err := ops.listFunc(params.Context, k, namespaces)
115+
if err != nil {
116+
return api.NewToolCallResult("", fmt.Errorf("failed to list %ss: %v", ops.singularName, err)), nil
117+
}
118+
return api.NewToolCallResult(content, nil), nil
119+
}

0 commit comments

Comments
 (0)