|
1 | 1 | package mcp
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bytes" |
4 | 5 | "context"
|
5 | 6 | "errors"
|
6 | 7 | "fmt"
|
7 | 8 | "github.com/manusa/kubernetes-mcp-server/pkg/kubernetes"
|
8 | 9 | "github.com/manusa/kubernetes-mcp-server/pkg/output"
|
| 10 | + "k8s.io/kubectl/pkg/metricsutil" |
9 | 11 |
|
10 | 12 | "github.com/mark3labs/mcp-go/mcp"
|
11 | 13 | "github.com/mark3labs/mcp-go/server"
|
@@ -53,6 +55,19 @@ func (s *Server) initPods() []server.ServerTool {
|
53 | 55 | mcp.WithIdempotentHintAnnotation(true),
|
54 | 56 | mcp.WithOpenWorldHintAnnotation(true),
|
55 | 57 | ), Handler: s.podsDelete},
|
| 58 | + {Tool: mcp.NewTool("pods_top", |
| 59 | + mcp.WithDescription("List the resource consumption (CPU and memory) as recorded by the Kubernetes Metrics Server for the specified Kubernetes Pods in the all namespaces, the provided namespace, or the current namespace"), |
| 60 | + mcp.WithBoolean("all_namespaces", mcp.Description("If true, list the resource consumption for all Pods in all namespaces. If false, list the resource consumption for Pods in the provided namespace or the current namespace"), mcp.DefaultBool(true)), |
| 61 | + mcp.WithString("namespace", mcp.Description("Namespace to get the Pods resource consumption from (Optional, current namespace if not provided and all_namespaces is false)")), |
| 62 | + mcp.WithString("name", mcp.Description("Name of the Pod to get the resource consumption from (Optional, all Pods in the namespace if not provided)")), |
| 63 | + mcp.WithString("label_selector", mcp.Description("Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the pods by label (Optional, only applicable when name is not provided)"), mcp.Pattern("([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]")), |
| 64 | + // Tool annotations |
| 65 | + mcp.WithTitleAnnotation("Pods: Top"), |
| 66 | + mcp.WithReadOnlyHintAnnotation(true), |
| 67 | + mcp.WithDestructiveHintAnnotation(false), |
| 68 | + mcp.WithIdempotentHintAnnotation(true), |
| 69 | + mcp.WithOpenWorldHintAnnotation(true), |
| 70 | + ), Handler: s.podsTop}, |
56 | 71 | {Tool: mcp.NewTool("pods_exec",
|
57 | 72 | mcp.WithDescription("Execute a command in a Kubernetes Pod in the current or provided namespace with the provided name and command"),
|
58 | 73 | mcp.WithString("namespace", mcp.Description("Namespace of the Pod where the command will be executed")),
|
@@ -125,10 +140,10 @@ func (s *Server) podsListInNamespace(ctx context.Context, ctr mcp.CallToolReques
|
125 | 140 | if ns == nil {
|
126 | 141 | return NewTextResult("", errors.New("failed to list pods in namespace, missing argument namespace")), nil
|
127 | 142 | }
|
128 |
| - labelSelector := ctr.GetArguments()["labelSelector"] |
129 | 143 | resourceListOptions := kubernetes.ResourceListOptions{
|
130 | 144 | AsTable: s.configuration.ListOutput.AsTable(),
|
131 | 145 | }
|
| 146 | + labelSelector := ctr.GetArguments()["labelSelector"] |
132 | 147 | if labelSelector != nil {
|
133 | 148 | resourceListOptions.ListOptions.LabelSelector = labelSelector.(string)
|
134 | 149 | }
|
@@ -171,6 +186,33 @@ func (s *Server) podsDelete(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.
|
171 | 186 | return NewTextResult(ret, err), nil
|
172 | 187 | }
|
173 | 188 |
|
| 189 | +func (s *Server) podsTop(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) { |
| 190 | + podsTopOptions := kubernetes.PodsTopOptions{AllNamespaces: true} |
| 191 | + if v, ok := ctr.GetArguments()["namespace"].(string); ok { |
| 192 | + podsTopOptions.Namespace = v |
| 193 | + } |
| 194 | + if v, ok := ctr.GetArguments()["all_namespaces"].(bool); ok { |
| 195 | + podsTopOptions.AllNamespaces = v |
| 196 | + } |
| 197 | + if v, ok := ctr.GetArguments()["name"].(string); ok { |
| 198 | + podsTopOptions.Name = v |
| 199 | + } |
| 200 | + if v, ok := ctr.GetArguments()["label_selector"].(string); ok { |
| 201 | + podsTopOptions.LabelSelector = v |
| 202 | + } |
| 203 | + ret, err := s.k.Derived(ctx).PodsTop(ctx, podsTopOptions) |
| 204 | + if err != nil { |
| 205 | + return NewTextResult("", fmt.Errorf("failed to get pods top: %v", err)), nil |
| 206 | + } |
| 207 | + buf := new(bytes.Buffer) |
| 208 | + printer := metricsutil.NewTopCmdPrinter(buf) |
| 209 | + err = printer.PrintPodMetrics(ret.Items, true, true, false, "", true) |
| 210 | + if err != nil { |
| 211 | + return NewTextResult("", fmt.Errorf("failed to get pods top: %v", err)), nil |
| 212 | + } |
| 213 | + return NewTextResult(buf.String(), nil), nil |
| 214 | +} |
| 215 | + |
174 | 216 | func (s *Server) podsExec(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
175 | 217 | ns := ctr.GetArguments()["namespace"]
|
176 | 218 | if ns == nil {
|
|
0 commit comments