Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pkg/mcp/testdata/toolsets-core-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@
"description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"kind": {
"description": "kind of the resource (examples of valid kind are: Pod, Service, Deployment, Ingress)",
"type": "string"
Expand Down Expand Up @@ -493,6 +497,10 @@
"description": "apiVersion of the resources (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"fieldSelector": {
"description": "Optional Kubernetes field selector to filter resources by field values (e.g. 'status.phase=Running', 'metadata.name=myresource'). Supported fields vary by resource type. For Pods: metadata.name, metadata.namespace, spec.nodeName, spec.restartPolicy, spec.schedulerName, spec.serviceAccountName, status.phase (Pending/Running/Succeeded/Failed/Unknown), status.podIP, status.nominatedNodeName. See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/",
"pattern": "^[.\\-A-Za-z0-9]+([=!,]{1,2}[.\\-A-Za-z0-9]+)+$",
Expand Down
8 changes: 8 additions & 0 deletions pkg/mcp/testdata/toolsets-full-tools-multicluster-enum.json
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,10 @@
"description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"context": {
"description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
"enum": [
Expand Down Expand Up @@ -768,6 +772,10 @@
"description": "apiVersion of the resources (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"context": {
"description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
"enum": [
Expand Down
8 changes: 8 additions & 0 deletions pkg/mcp/testdata/toolsets-full-tools-multicluster.json
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,10 @@
"description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"context": {
"description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
"type": "string"
Expand Down Expand Up @@ -692,6 +696,10 @@
"description": "apiVersion of the resources (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"context": {
"description": "Optional parameter selecting which context to run the tool in. Defaults to fake-context if not set",
"type": "string"
Expand Down
8 changes: 8 additions & 0 deletions pkg/mcp/testdata/toolsets-full-tools-openshift.json
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,10 @@
"description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"kind": {
"description": "kind of the resource (examples of valid kind are: Pod, Service, Deployment, Ingress)",
"type": "string"
Expand Down Expand Up @@ -614,6 +618,10 @@
"description": "apiVersion of the resources (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"fieldSelector": {
"description": "Optional Kubernetes field selector to filter resources by field values (e.g. 'status.phase=Running', 'metadata.name=myresource'). Supported fields vary by resource type. For Pods: metadata.name, metadata.namespace, spec.nodeName, spec.restartPolicy, spec.schedulerName, spec.serviceAccountName, status.phase (Pending/Running/Succeeded/Failed/Unknown), status.podIP, status.nominatedNodeName. See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/",
"pattern": "^[.\\-A-Za-z0-9]+([=!,]{1,2}[.\\-A-Za-z0-9]+)+$",
Expand Down
8 changes: 8 additions & 0 deletions pkg/mcp/testdata/toolsets-full-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,10 @@
"description": "apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"kind": {
"description": "kind of the resource (examples of valid kind are: Pod, Service, Deployment, Ingress)",
"type": "string"
Expand Down Expand Up @@ -599,6 +603,10 @@
"description": "apiVersion of the resources (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)",
"type": "string"
},
"clean_metadata": {
"description": "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
"type": "boolean"
},
"fieldSelector": {
"description": "Optional Kubernetes field selector to filter resources by field values (e.g. 'status.phase=Running', 'metadata.name=myresource'). Supported fields vary by resource type. For Pods: metadata.name, metadata.namespace, spec.nodeName, spec.restartPolicy, spec.schedulerName, spec.serviceAccountName, status.phase (Pending/Running/Succeeded/Failed/Unknown), status.podIP, status.nominatedNodeName. See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/",
"pattern": "^[.\\-A-Za-z0-9]+([=!,]{1,2}[.\\-A-Za-z0-9]+)+$",
Expand Down
59 changes: 46 additions & 13 deletions pkg/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,24 @@ func (p *table) PrintObj(obj runtime.Unstructured) (string, error) {
return buf.String(), err
}

func MarshalYaml(v any) (string, error) {
func MarshalYaml(v any, opts ...MarshalOption) (string, error) {
var cfg marshalConfig
for _, o := range opts {
o(&cfg)
}
if cfg.clean {
switch t := v.(type) {
case *unstructured.UnstructuredList:
for i := range t.Items {
cleanMetadata(&t.Items[i])
}
case *unstructured.Unstructured:
cleanMetadata(t)
}
}
switch t := v.(type) {
//case unstructured.UnstructuredList:
// for i := range t.Items {
// t.Items[i].SetManagedFields(nil)
// }
// v = t.Items
case *unstructured.UnstructuredList:
for i := range t.Items {
t.Items[i].SetManagedFields(nil)
}
v = t.Items
//case unstructured.Unstructured:
// t.SetManagedFields(nil)
case *unstructured.Unstructured:
t.SetManagedFields(nil)
}
ret, err := yml.Marshal(v)
if err != nil {
Expand All @@ -119,6 +121,37 @@ func MarshalYaml(v any) (string, error) {
return string(ret), nil
}

type marshalConfig struct {
clean bool
}

// MarshalOption configures MarshalYaml behaviour.
type MarshalOption func(*marshalConfig)

// WithCleanMetadata strips verbose metadata (managedFields, resourceVersion, uid, etc.)
// that provides no diagnostic value to the LLM.
func WithCleanMetadata() MarshalOption {
return func(c *marshalConfig) { c.clean = true }
}

// cleanMetadata strips verbose metadata that provides no diagnostic value to the LLM.
func cleanMetadata(obj *unstructured.Unstructured) {
obj.SetManagedFields(nil)
obj.SetResourceVersion("")
obj.SetUID("")
obj.SetGeneration(0)

annotations := obj.GetAnnotations()
if annotations != nil {
delete(annotations, "kubectl.kubernetes.io/last-applied-configuration")
if len(annotations) == 0 {
obj.SetAnnotations(nil)
} else {
obj.SetAnnotations(annotations)
}
}
}

func init() {
Names = make([]string, 0)
for _, output := range Outputs {
Expand Down
23 changes: 21 additions & 2 deletions pkg/toolsets/core/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ func initResources(o api.Openshift) []api.ServerTool {
Type: "string",
Description: "Optional Namespace to retrieve the namespaced resources from (ignored in case of cluster scoped resources). If not provided, will list resources from all namespaces",
},
"clean_metadata": {
Type: "boolean",
Description: "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
},
"labelSelector": {
Type: "string",
Description: "Optional Kubernetes label selector (e.g. 'app=myapp,env=prod' or 'app in (myapp,yourapp)'), use this option when you want to filter the resources by label",
Expand Down Expand Up @@ -77,6 +81,10 @@ func initResources(o api.Openshift) []api.ServerTool {
Type: "string",
Description: "Optional Namespace to retrieve the namespaced resource from (ignored in case of cluster scoped resources). If not provided, will get resource from configured namespace",
},
"clean_metadata": {
Type: "boolean",
Description: "If true, strips verbose metadata (managedFields, resourceVersion, uid, generation, last-applied-configuration) from the output. Useful for diagnostics where only spec/status matter.",
},
"name": {
Type: "string",
Description: "Name of the resource",
Expand Down Expand Up @@ -225,7 +233,14 @@ func resourcesList(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to list resources: %w", err)), nil
}
return api.NewToolCallResult(params.ListOutput.PrintObj(ret)), nil
if params.ListOutput.AsTable() {
return api.NewToolCallResult(params.ListOutput.PrintObj(ret)), nil
}
var marshalOpts []output.MarshalOption
if clean, _ := params.GetArguments()["clean_metadata"].(bool); clean {
marshalOpts = append(marshalOpts, output.WithCleanMetadata())
}
return api.NewToolCallResult(output.MarshalYaml(ret, marshalOpts...)), nil
}

func resourcesGet(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
Expand Down Expand Up @@ -256,7 +271,11 @@ func resourcesGet(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
if err != nil {
return api.NewToolCallResult("", fmt.Errorf("failed to get resource: %w", err)), nil
}
return api.NewToolCallResult(output.MarshalYaml(ret)), nil
var marshalOpts []output.MarshalOption
if clean, _ := params.GetArguments()["clean_metadata"].(bool); clean {
marshalOpts = append(marshalOpts, output.WithCleanMetadata())
}
return api.NewToolCallResult(output.MarshalYaml(ret, marshalOpts...)), nil
}

func resourcesCreateOrUpdate(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
Expand Down