From ca4989f6771268c332ee3188a71c61e4bfae024b Mon Sep 17 00:00:00 2001 From: Andrey Lebedev Date: Fri, 9 Jan 2026 18:13:10 +0100 Subject: [PATCH] Add toolset for OpenShift router This commit introduces a new toolset for inspecting OpenShift router pods through the Kubernetes MCP Server. Added tools: - router_show_config: View the router's configuration - router_show_info: Get router runtime information and statistics - router_show_sessions: View all active sessions in the router --- docs/ROUTER.md | 206 +++++++++++++++++++++++++++++++++ pkg/mcp/modules.go | 1 + pkg/toolsets/router/show.go | 206 +++++++++++++++++++++++++++++++++ pkg/toolsets/router/toolset.go | 35 ++++++ 4 files changed, 448 insertions(+) create mode 100644 docs/ROUTER.md create mode 100644 pkg/toolsets/router/show.go create mode 100644 pkg/toolsets/router/toolset.go diff --git a/docs/ROUTER.md b/docs/ROUTER.md new file mode 100644 index 000000000..b325cc8c9 --- /dev/null +++ b/docs/ROUTER.md @@ -0,0 +1,206 @@ +# Red Hat OpenShift Router + +This document provides guidance on using the OpenShift router with the Kubernetes MCP Server. + +## Overview + +The router toolset enables AI assistants to inspect and monitor OpenShift router pods directly through the Kubernetes MCP Server. This includes viewing router configuration, checking runtime statistics, and monitoring active connections without requiring manual pod access or HAProxy command-line knowledge. + +## Prerequisites + +- OpenShift Container Platform 4.x cluster + +## Enabling the Toolset + +The `router` toolset is not enabled by default. Enable it using the `--toolsets` flag: + +```bash +kubernetes-mcp-server --toolsets core,config,helm,router +``` + +Or in your MCP client configuration: + +```json +{ + "mcpServers": { + "kubernetes": { + "command": "npx", + "args": [ + "-y", + "kubernetes-mcp-server@latest", + "--toolsets", "core,config,helm,router" + ] + } + } +} +``` + +## Available Tools + +The router toolset provides tools to inspect and monitor OpenShift router pods. + +### Configuration and Runtime Information + +| Tool | Description | Parameters | +|------|-------------|------------| +| `router_show_config` | View the router's configuration | `pod` (optional) - Router pod name | +| `router_show_info` | Get router runtime information and statistics | `pod` (optional) - Router pod name | +| `router_show_sessions` | View all active sessions in the router | `pod` (optional) - Router pod name | + +**Notes**: +- All tools have an optional `pod` parameter. If not provided, the tools will automatically select any available router pod from the default ingress controller. +- Currently, the default ingress controller name (`default`) is hard-coded. Support for custom ingress controller names may be added in future versions. + +## Examples + +### View Router Configuration + +View the router configuration file from any router pod: + +``` +Show me the router configuration + +● I'll get the router configuration for you. + ⎿  # Router configuration (pod: router-default-6fbcf6cb84-vqthh) + ``` + global + … +333 lines (ctrl+o to expand) + +● Here's the router configuration from pod router-default-6fbcf6cb84-vqthh. This is an HAProxy configuration for the OpenShift router with the following key settings: + + Global Settings: + - Max connections: 50,000 + - 4 worker threads + - TLS: Minimum version TLSv1.2 + - Cipher suites: Modern secure ciphers (ECDHE-ECDSA, ECDHE-RSA, etc.) + - DH parameter size: 2048 bits + + Timeout Settings: + - Connect: 5s + - Client/Server: 30s + - HTTP request: 10s + - HTTP keep-alive: 300s + - WebSocket tunnel: 1h + - Console: 5 minutes +``` + +Or specify a particular router pod: + +``` +Show me the router configuration from pod router-default-6fbcf6cb84-vqthh +``` + +### View Router Runtime Information + +Get router runtime statistics and information: + +``` +Show me the router info + ⎿  # Router HAProxy info (pod: router-default-6fbcf6cb84-vqthh) + ``` + Name: HAProxy + … +76 lines (ctrl+o to expand) + +● Here's the HAProxy runtime information for the router (pod: router-default-6fbcf6cb84-vqthh): + + Version Information: + - HAProxy Version: 2.8.10-f28885f + - Release Date: 2024/06/14 + - Uptime: 1h 45m 7s + + Process Information: + - PID: 138 + - Threads: 4 + - Processes: 1 + + Connection Statistics: + - Current Connections: 10 + - Max Connections: 50,000 + - Cumulative Connections: 2,253 + - Cumulative Requests: 4,668 + - Max Connection Rate: 12/sec + + SSL/TLS Statistics: + - Current SSL Connections: 1 + - Cumulative SSL Connections: 862 + - SSL Frontend Max Key Rate: 4/sec + - SSL Backend Max Key Rate: 1/sec +``` + +This displays: +- Router version and uptime +- Process information +- Connection statistics +- Request rates +- Memory usage + +### View Active Sessions + +View all active sessions in the router: + +``` +Show me active router sessions + ⎿  # Router active sessions (pod: router-default-6fbcf6cb84-vqthh) + ``` + 0x7f223405ae50: [12/Jan/2026:22:35:39.393119] id=4697 proto=unix_stream source=unix:1 + … +17 lines (ctrl+o to expand) + +● Here are the active sessions in the router (pod: router-default-6fbcf6cb84-vqthh): + + There is currently 1 active session: + + Session ID 4697 (CLI Connection) + - Protocol: Unix stream socket + - Frontend: GLOBAL (HAProxy admin socket) + - State: ESTABLISHED + - Age: 0s (just created) + - Type: CLI applet connection + + Details: + - Connection flags: 0x8 + - Task state: running with high priority (nice=-64) + - File descriptor: 23 + - Buffers: 32KB request/response buffers allocated + - Request data: 14 bytes total (the command itself) + - Timeouts: 2 minutes for client, no write timeout +``` + +This displays detailed information about each active connection including: +- Session ID and protocol +- Connection state +- Frontend and backend information +- Task and file descriptor details + +## Quick Start + +1. Enable the router toolset when starting the server: + +```bash +kubernetes-mcp-server --toolsets core,config,router +``` + +2. View router configuration: + +``` +Show me the router configuration +``` + +3. Query router information: + +``` +Show me the router info +``` + +## Troubleshooting + +### No Router Pod Found + +If you get an error that no router pod was found, verify that: +- You're connected to an OpenShift cluster (not plain Kubernetes) +- The default ingress controller exists in the `openshift-ingress` namespace +- Router pods are running + +Check router pods: +``` +List pods in namespace openshift-ingress +``` diff --git a/pkg/mcp/modules.go b/pkg/mcp/modules.go index 255f42177..63b86e4e0 100644 --- a/pkg/mcp/modules.go +++ b/pkg/mcp/modules.go @@ -6,4 +6,5 @@ import ( _ "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/containers/kubernetes-mcp-server/pkg/toolsets/router" ) diff --git a/pkg/toolsets/router/show.go b/pkg/toolsets/router/show.go new file mode 100644 index 000000000..68f51514e --- /dev/null +++ b/pkg/toolsets/router/show.go @@ -0,0 +1,206 @@ +package externalsecrets + +import ( + "errors" + "fmt" + "strings" + + "github.com/google/jsonschema-go/jsonschema" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" + + "github.com/containers/kubernetes-mcp-server/pkg/api" + "github.com/containers/kubernetes-mcp-server/pkg/kubernetes" +) + +const ( + ingressNamespace = "openshift-ingress" + defaultIngressControllerName = "default" + routerContainerName = "router" +) + +func initShowTools() []api.ServerTool { + return []api.ServerTool{ + { + Tool: api.Tool{ + Name: "router_show_config", + Description: `Tool to show router's configuration.`, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "pod": { + Type: "string", + Description: "Router pod name (optional, chooses any existing if not provided)", + }, + }, + }, + Annotations: api.ToolAnnotations{ + Title: "Router: show config", + ReadOnlyHint: ptr.To(true), + DestructiveHint: ptr.To(false), + OpenWorldHint: ptr.To(false), + }, + }, + Handler: showConfigHandler, + }, + { + Tool: api.Tool{ + Name: "router_show_info", + Description: `Tool to get HAProxy runtime information from the router.`, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "pod": { + Type: "string", + Description: "Router pod name (optional, chooses any existing if not provided)", + }, + }, + }, + Annotations: api.ToolAnnotations{ + Title: "Router: show info", + ReadOnlyHint: ptr.To(true), + DestructiveHint: ptr.To(false), + OpenWorldHint: ptr.To(false), + }, + }, + Handler: showInfoHandler, + }, + { + Tool: api.Tool{ + Name: "router_show_sessions", + Description: `Tool to view all active sessions in the router.`, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "pod": { + Type: "string", + Description: "Router pod name (optional, chooses any existing if not provided)", + }, + }, + }, + Annotations: api.ToolAnnotations{ + Title: "Router: show sessions", + ReadOnlyHint: ptr.To(true), + DestructiveHint: ptr.To(false), + OpenWorldHint: ptr.To(false), + }, + }, + Handler: showSessionsHandler, + }, + } +} + +func showConfigHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) { + var results []string + + pod, ok := params.GetArguments()["pod"].(string) + if !ok || pod == "" { + p, err := getAnyRouterPod(params, defaultIngressControllerName) + if err != nil { + results = append(results, "# Router configuration") + results = append(results, fmt.Sprintf("Error getting router pod: %v", err)) + return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil + } + pod = p + } + + out, err := kubernetes.NewCore(params).PodsExec(params.Context, ingressNamespace, pod, routerContainerName, []string{"cat", "/var/lib/haproxy/conf/haproxy.config"}) + if err != nil { + results = append(results, fmt.Sprintf("# Router configuration (pod: %s)", pod)) + results = append(results, fmt.Sprintf("Error showing router configuration from pod %q: %v", pod, err)) + } else { + results = append(results, fmt.Sprintf("# Router configuration (pod: %s)", pod)) + results = append(results, "```") + results = append(results, out) + results = append(results, "```") + } + + return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil +} + +func showInfoHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) { + var results []string + + pod, ok := params.GetArguments()["pod"].(string) + if !ok || pod == "" { + p, err := getAnyRouterPod(params, defaultIngressControllerName) + if err != nil { + results = append(results, "# Router HAProxy info") + results = append(results, fmt.Sprintf("Error getting router pod: %v", err)) + return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil + } + pod = p + } + + out, err := kubernetes.NewCore(params).PodsExec(params.Context, ingressNamespace, pod, routerContainerName, []string{"sh", "-c", "echo 'show info' | socat stdio /var/lib/haproxy/run/haproxy.sock"}) + if err != nil { + results = append(results, fmt.Sprintf("# Router HAProxy info (pod: %s)", pod)) + results = append(results, fmt.Sprintf("Error getting HAProxy info from pod %q: %v", pod, err)) + } else { + results = append(results, fmt.Sprintf("# Router HAProxy info (pod: %s)", pod)) + results = append(results, "```") + results = append(results, out) + results = append(results, "```") + } + + return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil +} + +func showSessionsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) { + var results []string + + pod, ok := params.GetArguments()["pod"].(string) + if !ok || pod == "" { + p, err := getAnyRouterPod(params, defaultIngressControllerName) + if err != nil { + results = append(results, "# Router active sessions") + results = append(results, fmt.Sprintf("Error getting router pod: %v", err)) + return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil + } + pod = p + } + + out, err := kubernetes.NewCore(params).PodsExec(params.Context, ingressNamespace, pod, routerContainerName, []string{"sh", "-c", "echo 'show sess all' | socat stdio /var/lib/haproxy/run/haproxy.sock"}) + if err != nil { + results = append(results, fmt.Sprintf("# Router active sessions (pod: %s)", pod)) + results = append(results, fmt.Sprintf("Error getting active sessions from pod %q: %v", pod, err)) + } else { + results = append(results, fmt.Sprintf("# Router active sessions (pod: %s)", pod)) + results = append(results, "```") + results = append(results, out) + results = append(results, "```") + } + + return api.NewToolCallResult(strings.Join(results, "\n"), nil), nil +} + +func getAnyRouterPod(params api.ToolHandlerParams, icName string) (string, error) { + podGVK := &schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Pod", + } + pods, err := kubernetes.NewCore(params).ResourcesList(params, podGVK, ingressNamespace, api.ListOptions{ + ListOptions: metav1.ListOptions{ + LabelSelector: "ingresscontroller.operator.openshift.io/deployment-ingresscontroller=" + icName, + }, + AsTable: false, + }) + if err != nil { + return "", fmt.Errorf("failed to list router pods: %v", err) + } + podsMap := pods.UnstructuredContent() + if items, ok := podsMap["items"].([]interface{}); ok { + for _, item := range items { + if itemMap, ok := item.(map[string]interface{}); ok { + if metadata, ok := itemMap["metadata"].(map[string]interface{}); ok { + if podName, ok := metadata["name"].(string); ok { + return podName, nil + } + } + } + } + } + return "", errors.New("no router pod found") +} diff --git a/pkg/toolsets/router/toolset.go b/pkg/toolsets/router/toolset.go new file mode 100644 index 000000000..312ceefe0 --- /dev/null +++ b/pkg/toolsets/router/toolset.go @@ -0,0 +1,35 @@ +package externalsecrets + +import ( + "slices" + + "github.com/containers/kubernetes-mcp-server/pkg/api" + "github.com/containers/kubernetes-mcp-server/pkg/toolsets" +) + +// Toolset provides tools for managing the Red Hat OpenShift's router. +type Toolset struct{} + +var _ api.Toolset = (*Toolset)(nil) + +func (t *Toolset) GetName() string { + return "router" +} + +func (t *Toolset) GetDescription() string { + return "Tools for managing Red Hat OpenShift's router" +} + +func (t *Toolset) GetTools(_ api.Openshift) []api.ServerTool { + return slices.Concat( + initShowTools(), + ) +} + +func (t *Toolset) GetPrompts() []api.ServerPrompt { + return nil +} + +func init() { + toolsets.Register(&Toolset{}) +}