Skip to content

Commit 77a12b8

Browse files
committed
feat(kiali): Consolidate tools in get_mesh_graph
Signed-off-by: Alberto Gutierrez <aljesusg@gmail.com>
1 parent ac053ff commit 77a12b8

File tree

9 files changed

+188
-189
lines changed

9 files changed

+188
-189
lines changed

README.md

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -348,11 +348,11 @@ In case multi-cluster support is enabled (default) and you have access to multip
348348

349349
<summary>kiali</summary>
350350

351-
- **kiali_graph** - Check the status of my mesh by querying Kiali graph
351+
- **kiali_get_mesh_graph** - Returns the topology of a specific namespaces, health, status of the mesh and namespaces. Use this for high-level overviews
352+
- `graphType` (`string`) - Type of graph to return: 'versionedApp', 'app', 'service', 'workload', 'mesh'. Default: 'versionedApp'
352353
- `namespace` (`string`) - Optional single namespace to include in the graph (alternative to namespaces)
353354
- `namespaces` (`string`) - Optional comma-separated list of namespaces to include in the graph
354-
355-
- **kiali_mesh_status** - Get the status of mesh components including Istio, Kiali, Grafana, Prometheus and their interactions, versions, and health status
355+
- `rateInterval` (`string`) - Rate interval for fetching (e.g., '10m', '5m', '1h'). Default: '60s'
356356

357357
- **kiali_istio_config** - Get all Istio configuration objects in the mesh including their full YAML resources and details
358358

@@ -389,8 +389,6 @@ In case multi-cluster support is enabled (default) and you have access to multip
389389
- `namespace` (`string`) - Optional single namespace to retrieve validations from (alternative to namespaces)
390390
- `namespaces` (`string`) - Optional comma-separated list of namespaces to retrieve validations from
391391

392-
- **kiali_namespaces** - Get all namespaces in the mesh that the user has access to
393-
394392
- **kiali_services_list** - Get all services in the mesh across specified namespaces with health and Istio resource information
395393
- `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
396394

@@ -429,12 +427,6 @@ In case multi-cluster support is enabled (default) and you have access to multip
429427
- `step` (`string`) - Step between data points in seconds (e.g., '15'). Optional, defaults to 15 seconds
430428
- `workload` (`string`) **(required)** - Name of the workload to get metrics for
431429

432-
- **kiali_health** - Get health status for apps, workloads, and services across specified namespaces in the mesh. Returns health information including error rates and status for the requested resource type
433-
- `namespaces` (`string`) - Comma-separated list of namespaces to get health from (e.g. 'bookinfo' or 'bookinfo,default'). If not provided, returns health for all accessible namespaces
434-
- `queryTime` (`string`) - Unix timestamp (in seconds) for the prometheus query. If not provided, uses current time. Optional
435-
- `rateInterval` (`string`) - Rate interval for fetching error rate (e.g., '10m', '5m', '1h'). Default: '10m'
436-
- `type` (`string`) - Type of health to retrieve: 'app', 'service', or 'workload'. Default: 'app'
437-
438430
- **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.
439431
- `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)
440432
- `namespace` (`string`) **(required)** - Namespace containing the workload
@@ -488,4 +480,4 @@ Compile the project and run the Kubernetes MCP server with [mcp-inspector](https
488480
make build
489481
# Run the Kubernetes MCP server with mcp-inspector
490482
npx @modelcontextprotocol/inspector@latest $(pwd)/kubernetes-mcp-server
491-
```
483+
```

pkg/kiali/get_mesh_graph.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package kiali
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"strings"
7+
"sync"
8+
)
9+
10+
type GetMeshGraphResponse struct {
11+
Graph json.RawMessage `json:"graph,omitempty"`
12+
Health json.RawMessage `json:"health,omitempty"`
13+
MeshStatus json.RawMessage `json:"mesh_status,omitempty"`
14+
Namespaces json.RawMessage `json:"namespaces,omitempty"`
15+
Errors map[string]string `json:"errors,omitempty"`
16+
}
17+
18+
// GetMeshGraph fetches multiple Kiali endpoints in parallel and returns a combined response.
19+
// Each field in the response corresponds to one API call result.
20+
// - graph: /api/namespaces/graph (optionally filtered by namespaces)
21+
// - health: /api/clusters/health (optionally filtered by namespaces and queryParams)
22+
// - status(mesh):/api/mesh/graph
23+
// - namespaces: /api/namespaces
24+
func (k *Kiali) GetMeshGraph(ctx context.Context, namespaces []string, queryParams map[string]string) (string, error) {
25+
cleaned := make([]string, 0, len(namespaces))
26+
for _, ns := range namespaces {
27+
ns = strings.TrimSpace(ns)
28+
if ns != "" {
29+
cleaned = append(cleaned, ns)
30+
}
31+
}
32+
33+
resp := GetMeshGraphResponse{
34+
Errors: make(map[string]string),
35+
}
36+
37+
var wg sync.WaitGroup
38+
wg.Add(4)
39+
40+
// Graph
41+
go func() {
42+
defer wg.Done()
43+
data, err := k.getGraph(ctx, cleaned, queryParams)
44+
if err != nil {
45+
resp.Errors["graph"] = err.Error()
46+
return
47+
}
48+
resp.Graph = data
49+
}()
50+
51+
// Health
52+
go func() {
53+
defer wg.Done()
54+
data, err := k.getHealth(ctx, cleaned, queryParams)
55+
if err != nil {
56+
resp.Errors["health"] = err.Error()
57+
return
58+
}
59+
resp.Health = data
60+
}()
61+
62+
// Mesh status
63+
go func() {
64+
defer wg.Done()
65+
data, err := k.getMeshStatus(ctx)
66+
if err != nil {
67+
resp.Errors["mesh_status"] = err.Error()
68+
return
69+
}
70+
resp.MeshStatus = data
71+
}()
72+
73+
// Namespaces
74+
go func() {
75+
defer wg.Done()
76+
data, err := k.getNamespaces(ctx)
77+
if err != nil {
78+
resp.Errors["namespaces"] = err.Error()
79+
return
80+
}
81+
resp.Namespaces = data
82+
}()
83+
84+
wg.Wait()
85+
86+
// If no errors occurred, omit the errors map in the final JSON
87+
if len(resp.Errors) == 0 {
88+
resp.Errors = nil
89+
}
90+
91+
encoded, err := json.Marshal(resp)
92+
if err != nil {
93+
return "", err
94+
}
95+
return string(encoded), nil
96+
}
97+
98+
// getGraph wraps the Graph call and returns raw JSON.
99+
func (k *Kiali) getGraph(ctx context.Context, namespaces []string, queryParams map[string]string) (json.RawMessage, error) {
100+
out, err := k.Graph(ctx, namespaces, queryParams)
101+
if err != nil {
102+
return nil, err
103+
}
104+
return json.RawMessage([]byte(out)), nil
105+
}
106+
107+
// getHealth wraps the Health call and returns raw JSON.
108+
func (k *Kiali) getHealth(ctx context.Context, namespaces []string, queryParams map[string]string) (json.RawMessage, error) {
109+
nsParam := strings.Join(namespaces, ",")
110+
out, err := k.Health(ctx, nsParam, queryParams)
111+
if err != nil {
112+
return nil, err
113+
}
114+
return json.RawMessage([]byte(out)), nil
115+
}
116+
117+
// getMeshStatus wraps the MeshStatus call and returns raw JSON.
118+
func (k *Kiali) getMeshStatus(ctx context.Context) (json.RawMessage, error) {
119+
out, err := k.MeshStatus(ctx)
120+
if err != nil {
121+
return nil, err
122+
}
123+
return json.RawMessage([]byte(out)), nil
124+
}
125+
126+
// getNamespaces wraps the ListNamespaces call and returns raw JSON.
127+
func (k *Kiali) getNamespaces(ctx context.Context) (json.RawMessage, error) {
128+
out, err := k.ListNamespaces(ctx)
129+
if err != nil {
130+
return nil, err
131+
}
132+
return json.RawMessage([]byte(out)), nil
133+
}

pkg/kiali/graph.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,24 @@ import (
1010
// Graph calls the Kiali graph API using the provided Authorization header value.
1111
// `namespaces` may contain zero, one or many namespaces. If empty, the API may return an empty graph
1212
// or the server default, depending on Kiali configuration.
13-
func (k *Kiali) Graph(ctx context.Context, namespaces []string) (string, error) {
13+
func (k *Kiali) Graph(ctx context.Context, namespaces []string, queryParams map[string]string) (string, error) {
1414
u, err := url.Parse(GraphEndpoint)
1515
if err != nil {
1616
return "", err
1717
}
1818
q := u.Query()
1919
// Static graph parameters per requirements
20-
q.Set("duration", "60s")
21-
q.Set("graphType", "versionedApp")
20+
// Defaults with optional overrides via queryParams
21+
duration := "60s"
22+
graphType := "versionedApp"
23+
if v, ok := queryParams["rateInterval"]; ok && strings.TrimSpace(v) != "" {
24+
duration = strings.TrimSpace(v)
25+
}
26+
if v, ok := queryParams["graphType"]; ok && strings.TrimSpace(v) != "" {
27+
graphType = strings.TrimSpace(v)
28+
}
29+
q.Set("duration", duration)
30+
q.Set("graphType", graphType)
2231
q.Set("includeIdleEdges", "false")
2332
q.Set("injectServiceNodes", "true")
2433
q.Set("boxBy", "cluster,namespace,app")

pkg/kiali/health.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import (
44
"context"
55
"net/http"
66
"net/url"
7+
"strings"
78
)
89

910
// Health returns health status for apps, workloads, and services across namespaces.
1011
// Parameters:
1112
// - namespaces: comma-separated list of namespaces (optional, if empty returns health for all accessible namespaces)
1213
// - queryParams: optional query parameters map for filtering health data (e.g., "type", "rateInterval", "queryTime")
1314
// - type: health type - "app", "service", or "workload" (default: "app")
14-
// - rateInterval: rate interval for fetching error rate (default: "10m")
15+
// - rateInterval: rate interval for fetching error rate (default: "1m")
1516
// - queryTime: Unix timestamp for the prometheus query (optional)
1617
func (k *Kiali) Health(ctx context.Context, namespaces string, queryParams map[string]string) (string, error) {
1718
// Build query parameters
@@ -33,6 +34,18 @@ func (k *Kiali) Health(ctx context.Context, namespaces string, queryParams map[s
3334
}
3435
}
3536

37+
// Ensure health "type" aligns with graphType (versionedApp -> app)
38+
healthType := "app"
39+
if gt, ok := queryParams["graphType"]; ok && strings.TrimSpace(gt) != "" {
40+
v := strings.TrimSpace(gt)
41+
if strings.EqualFold(v, "versionedApp") {
42+
healthType = "app"
43+
} else {
44+
healthType = v
45+
}
46+
}
47+
q.Set("type", healthType)
48+
3649
u.RawQuery = q.Encode()
3750
endpoint := u.String()
3851

pkg/toolsets/kiali/graph.go renamed to pkg/toolsets/kiali/get_mesh_graph.go

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

13-
func initGraph() []api.ServerTool {
13+
func GetMeshGraph() []api.ServerTool {
1414
ret := make([]api.ServerTool, 0)
1515
ret = append(ret, api.ServerTool{
1616
Tool: api.Tool{
17-
Name: "kiali_graph",
18-
Description: "Check the status of my mesh by querying Kiali graph",
17+
Name: "kiali_get_mesh_graph",
18+
Description: "Returns the topology of a specific namespaces, health, status of the mesh and namespaces. Use this for high-level overviews",
1919
InputSchema: &jsonschema.Schema{
2020
Type: "object",
2121
Properties: map[string]*jsonschema.Schema{
@@ -27,22 +27,30 @@ func initGraph() []api.ServerTool {
2727
Type: "string",
2828
Description: "Optional comma-separated list of namespaces to include in the graph",
2929
},
30+
"rateInterval": {
31+
Type: "string",
32+
Description: "Rate interval for fetching (e.g., '10m', '5m', '1h'). Default: '60s'",
33+
},
34+
"graphType": {
35+
Type: "string",
36+
Description: "Type of graph to return: 'versionedApp', 'app', 'service', 'workload', 'mesh'. Default: 'versionedApp'",
37+
},
3038
},
3139
Required: []string{},
3240
},
3341
Annotations: api.ToolAnnotations{
34-
Title: "Graph: Mesh status",
42+
Title: "Topology: Mesh, Graph, Health, and Status",
3543
ReadOnlyHint: ptr.To(true),
3644
DestructiveHint: ptr.To(false),
3745
IdempotentHint: ptr.To(false),
3846
OpenWorldHint: ptr.To(true),
3947
},
40-
}, Handler: graphHandler,
48+
}, Handler: getMeshGraphHandler,
4149
})
4250
return ret
4351
}
4452

45-
func graphHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
53+
func getMeshGraphHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
4654

4755
// Parse arguments: allow either `namespace` or `namespaces` (comma-separated string)
4856
namespaces := make([]string, 0)
@@ -77,8 +85,17 @@ func graphHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
7785
}
7886
namespaces = unique
7987
}
88+
89+
// Extract optional query parameters
90+
queryParams := make(map[string]string)
91+
if rateInterval, ok := params.GetArguments()["rateInterval"].(string); ok && rateInterval != "" {
92+
queryParams["rateInterval"] = rateInterval
93+
}
94+
if graphType, ok := params.GetArguments()["graph_type"].(string); ok && graphType != "" {
95+
queryParams["graphType"] = graphType
96+
}
8097
k := params.NewKiali()
81-
content, err := k.Graph(params.Context, namespaces)
98+
content, err := k.GetMeshGraph(params.Context, namespaces, queryParams)
8299
if err != nil {
83100
return api.NewToolCallResult("", fmt.Errorf("failed to retrieve mesh graph: %v", err)), nil
84101
}

pkg/toolsets/kiali/health.go

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)