mcp: description: | # Kubernetes Read-Only tools Kubernetes Read-Only tools for enabling secure access to Kubernetes cluster information, allowing users to list resources, view pod logs, check cluster contexts, monitor resource usage, and inspect cluster configuration without write permissions. run: shell: bash tools: - name: "kubectl_get" description: | List Kubernetes resources (pods, deployments, services, etc.) Use the `kubectl_get_namespaces` command to list all namespaces. Try to use some filtering for better performance and less noise. requirements: executables: ["kubectl"] params: resource: type: string description: "Resource type to list (pods, deployments, services, configmaps, secrets, etc.)" required: true context: type: string description: "Kubernetes context to use (required)" required: true ns: type: string description: "Kubernetes namespace (optional, uses current context's namespace if not specified)" labels: type: string description: "Label selector to filter resources (e.g. 'app=nginx')" output_format: type: string description: | Output format (wide, json, yaml). Try to use some formatting for better readability. When looking for some specific resource, try to use the `wide` format. filter_grep: type: string description: | Filter logs with grep for the given string (literal text match). filter_grep_regex: type: string description: | Filter logs with grep using a regular expression pattern. The pattern is an extended regular expression. filter_jq: type: string description: | Filter with jq for the given expression. Make sure you use `output_format`` == 'json' to use this. constraints: - "resource.size() > 0" # Resource must not be empty - "resource.size() <= 30" # Reasonable length limit - "!resource.contains(' ')" # No spaces in resource name - "!resource.contains(';')" # Prevent injection - "context.size() > 0 && context.size() <= 253" # Valid context name (required) - "ns == '' || (ns.size() <= 63 && ns.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'))" # Valid k8s namespace - "labels == '' || labels.size() <= 100" # Reasonable label length - "output_format == '' || output_format == 'wide' || output_format == 'json' || output_format == 'yaml'" # Valid formats run: timeout: "60s" command: | NAMESPACE_PARAM="{{ if .ns }}-n {{ .ns }}{{ end }}" LABEL_PARAM="{{ if .labels }}-l {{ .labels }}{{ end }}" FORMAT_PARAM="{{ if .output_format }}-o {{ .output_format }}{{ end }}" {{ if .filter_grep }} kubectl --context={{ .context }} --request-timeout=30s get {{ .resource }} $NAMESPACE_PARAM $LABEL_PARAM $FORMAT_PARAM | grep -F "{{ .filter_grep }}" || true {{ else if .filter_grep_regex }} kubectl --context={{ .context }} --request-timeout=30s get {{ .resource }} $NAMESPACE_PARAM $LABEL_PARAM $FORMAT_PARAM | grep -E "{{ .filter_grep_regex }}" || true {{ else if .filter_jq }} kubectl --context={{ .context }} --request-timeout=30s get {{ .resource }} $NAMESPACE_PARAM $LABEL_PARAM $FORMAT_PARAM | jq "{{ .filter_jq }}" || true {{ else }} kubectl --context={{ .context }} --request-timeout=30s get {{ .resource }} $NAMESPACE_PARAM $LABEL_PARAM $FORMAT_PARAM {{ end }} env: - KUBECONFIG runners: - name: sandbox-exec options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: firejail options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: exec requirements: {} output: prefix: | Kubernetes {{ .resource }} list: - name: "kubectl_describe" description: "Show detailed information about a Kubernetes resource" requirements: executables: ["kubectl"] params: resource: type: string description: "Kubernetes resource type (pods, deployments, services, etc.)" required: true name: type: string description: "Kubernetes resource name (optional, describes all resources of the type if not specified)" ns: type: string description: "Kubernetes namespace (optional, uses current context's namespace if not specified)" context: type: string description: "Kubernetes context to use (optional, uses current context if not specified)" constraints: - "resource.size() > 0" # Resource must not be empty - "resource.size() <= 30" # Reasonable length limit - "!resource.contains(' ')" # No spaces in resource name - "!resource.contains(';')" # Prevent injection - "context.size() > 0 && context.size() <= 253" # Valid context name (required) - "name.size() <= 253" # Maximum DNS label length - "name == '' || !name.contains(';')" # Prevent injection - "ns == '' || (ns.size() <= 63 && ns.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'))" # Valid k8s namespace run: timeout: "60s" command: | NAMESPACE_PARAM="{{ if .ns }}-n {{ .ns }}{{ end }}" NAME_PARAM="{{ .name }}" kubectl --context={{ .context }} --request-timeout=30s describe {{ .resource }} $NAME_PARAM $NAMESPACE_PARAM env: - KUBECONFIG runners: - name: sandbox-exec options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: firejail options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: exec requirements: {} output: prefix: | Details for {{ .resource }}{{ if .name }} "{{ .name }}"{{ end }}: - name: "kubectl_logs" description: | Get logs from a pod. Try to use some filtering for better performance and less noise. requirements: executables: ["kubectl"] params: pod: type: string description: "Pod name" required: true context: type: string description: "Kubernetes context to use (required)" required: true container: type: string description: "Container name (optional, uses the first container if not specified)" ns: type: string description: "Kubernetes namespace (optional, uses current context's namespace if not specified)" tail: type: number description: "Number of lines to show from the end (optional)" previous: type: boolean description: "Show logs from previous container instance if it exists (optional)" since: type: string description: "Show logs since relative time (e.g. '5s', '2m', or '3h')" filter_grep: type: string description: | Filter logs with grep for the given string (literal text match). filter_grep_regex: type: string description: | Filter logs with grep using a regular expression pattern. constraints: - "pod.size() > 0" # Pod name must not be empty - "pod.size() <= 253" # Maximum DNS label length - "!pod.contains(' ')" # No spaces in pod name - "!pod.contains(';')" # Prevent injection - "context.size() > 0 && context.size() <= 253" # Valid context name (required) - "container == '' || (container.size() <= 253 && !container.contains(';'))" # Valid container name - "ns == '' || (ns.size() <= 63 && ns.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'))" # Valid k8s namespace - "tail >= 0.0 && tail <= 10000.0" # Reasonable tail lines limit - "since == '' || since.matches('^[0-9]+(s|m|h|d)$')" # Valid duration format - "filter_grep == '' || (filter_grep.size() <= 100 && !filter_grep.contains(';'))" # Safe grep pattern - "filter_grep_regex == '' || (filter_grep_regex.size() <= 100 && !filter_grep_regex.contains(';'))" # Safe grep regex pattern - "(filter_grep == '' || filter_grep_regex == '')" # Only one of filter_grep or filter_grep_regex can be used run: timeout: "60s" command: | NAMESPACE_PARAM="{{ if .ns }}-n {{ .ns }}{{ end }}" CONTAINER_PARAM="{{ if .container }}-c {{ .container }}{{ end }}" TAIL_PARAM="{{ if .tail }}--tail={{ .tail }}{{ else }}--tail=100{{ end }}" PREVIOUS_PARAM="{{ if .previous }}-p {{ end }}" SINCE_PARAM="{{ if .since }}--since={{ .since }}{{ end }}" # Base kubectl logs command CMD="kubectl --context={{ .context }} --request-timeout=30s logs {{ .pod }} $NAMESPACE_PARAM $CONTAINER_PARAM $TAIL_PARAM $PREVIOUS_PARAM $SINCE_PARAM" # Add grep filtering if specified {{ if .filter_grep }} $CMD | grep -F "{{ .filter_grep }}" || true {{ else if .filter_grep_regex }} $CMD | grep -E "{{ .filter_grep_regex }}" || true {{ else }} $CMD || true {{ end }} env: - KUBECONFIG runners: - name: sandbox-exec options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: firejail options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: exec requirements: {} output: prefix: | Logs for pod {{ .pod }}{{ if .container }} (container: {{ .container }}){{ end }}{{ if .grep }} (filtered for literal: '{{ .grep }}'){{ end }}{{ if .grep_regex }} (filtered for regex: '{{ .grep_regex }}'){{ end }}: - name: "kubectl_get_contexts" description: "List available Kubernetes contexts" requirements: executables: ["kubectl"] params: context: type: string description: "Kubernetes context to use (optional, uses current context if not specified)" run: timeout: "60s" command: | CONTEXT_PARAM="{{ if .context }}--context={{ .context }}{{ end }}" kubectl $CONTEXT_PARAM --request-timeout=30s config get-contexts env: - KUBECONFIG runners: - name: sandbox-exec options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: firejail options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: exec requirements: {} output: prefix: | Available Kubernetes contexts for user {{ env "USER" }}: - name: "kubectl_current_context" description: "Show the current Kubernetes context" requirements: executables: ["kubectl"] params: context: type: string description: "Kubernetes context to use (optional, uses current context if not specified)" run: timeout: "60s" command: | CONTEXT_PARAM="{{ if .context }}--context={{ .context }}{{ end }}" kubectl $CONTEXT_PARAM --request-timeout=30s config current-context env: - KUBECONFIG runners: - name: sandbox-exec options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: firejail options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: exec requirements: {} output: prefix: | Current Kubernetes context: - name: "kubectl_get_namespaces" description: "List all namespaces in the cluster" requirements: executables: ["kubectl"] params: context: type: string description: "Kubernetes context to use (required)" required: true constraints: - "context.size() > 0 && context.size() <= 253" # Valid context name (required) run: timeout: "60s" command: | kubectl --context={{ .context }} --request-timeout=30s get namespaces env: - KUBECONFIG runners: - name: sandbox-exec options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: firejail options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: exec requirements: {} output: prefix: | Available Kubernetes namespaces: - name: "kubectl_portforward_curl" description: "Create a port forward to a Pod:Port and run curl to that target" requirements: executables: - "kubectl" - "nc" - "curl" - "jq" - "grep" params: context: type: string description: "Kubernetes context to use (required)" required: true pod: type: string description: "Pod name" required: true ns: type: string description: "Kubernetes namespace (optional, uses current context's namespace if not specified)" pod_port: type: string description: "Remote port in the Pod to connect to (e.g. 8080). Only one port can be provided." required: true local_port: type: number description: "Local port to bind to (e.g. 54320). Only one port can be provided. A defautl one will be used if not specified" curl_args: type: string description: "Additional arguments to pass to curl (e.g. '-I' for headers only)" path: type: string description: "URL's path (e.g. '/index.html'). Make sure it starts with a '/'." filter_jq: type: string description: "jq expression to apply to the response (optional)" filter_grep: type: string description: "grep expression to apply to the response (optional, literal text match)" filter_grep_regex: type: string description: "grep regex pattern to apply to the response (optional)" constraints: - "context.size() > 0 && context.size() <= 253" # Valid context name (required) - "ns == '' || (ns.size() <= 63 && ns.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'))" # Valid k8s namespace - "pod_port.size() > 0" # Port must not be empty - "pod_port.matches('^[0-9]+$')" # Port must be a number - "path.size() > 0" # Path must not be empty - "path.matches('^/[^ ]+$')" # Path must start with a '/' and contain no spaces - "filter_grep == '' || filter_grep_regex == ''" # Only one of filter_grep or filter_grep_regex can be used - "filter_jq == '' || (filter_grep == '' && filter_grep_regex == '')" # Can't use both jq and grep filters run: timeout: "60s" command: | NAMESPACE_PARAM="{{ if .ns }}-n {{ .ns }}{{ end }}" CURL_ARGS="-s {{ if .curl_args }}{{ .curl_args }}{{ end }}" localport="{{ if .local_port }}{{ .local_port }}{{ else }}54320{{ end }}" remoteport="{{ .pod_port }}" # nmap -sT -p $localport localhost || true # This would show that the port is closed kubectl --context={{ .context }} port-forward $NAMESPACE_PARAM pod/{{ .pod }} $localport:$remoteport >/dev/null & pid=$! trap '{ kill $pid }' EXIT # wait for $localport to become available while ! nc -vz localhost $localport > /dev/null 2>&1 ; do sleep 0.1 done # nmap -sT -p $localport localhost # This would show that the port is open {{ if .filter_jq }} curl $CURL_ARGS "http://localhost:$localport{{ .path }}" | jq '{{ .filter_jq }}' 2>/dev/null || true {{ else if .filter_grep }} curl $CURL_ARGS "http://localhost:$localport{{ .path }}" | grep -F "{{ .filter_grep }}" || true {{ else if .filter_grep_regex }} curl $CURL_ARGS "http://localhost:$localport{{ .path }}" | grep -E "{{ .filter_grep_regex }}" || true {{ else }} curl $CURL_ARGS "http://localhost:$localport{{ .path }}" {{ end }} # the 'trap ... EXIT' above will take care of kill $pid env: - KUBECONFIG runners: - name: sandbox-exec options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "/usr/local/bin" - "/opt/homebrew/bin" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: firejail options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "/usr/local/bin" - "/opt/homebrew/bin" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: exec requirements: {} output: prefix: | Port forwarding to pod {{ .pod }}:{{ .pod_port }}{{ if .filter_grep }} (filtered for literal: '{{ .filter_grep }}'){{ end }}{{ if .filter_grep_regex }} (filtered for regex: '{{ .filter_grep_regex }}'){{ end }}: - name: "kubectl_envoy" description: | Get some configuration information from Envoy by running a port forward to the given Envoy pod and then using curl to access the desired path. Some of the most useful paths are: /certs: List out all loaded TLS certificates, including file name, serial number, subject alternate names and days until expiration in JSON format conforming to the certificate proto definition. /clusters: List out all configured cluster manager clusters. This information includes all discovered upstream hosts in each cluster along with per host statistics. This is useful for debugging service discovery issues. /clusters?format=json: List out all configured cluster manager clusters in JSON format. /config_dump: Dump currently loaded configuration from various Envoy components as JSON-serialized proto messages. See the response definition for more information. /config_dump?resource=RESOURCE: Dump only the currently loaded configuration that matches the specified resource. The resource must be a repeated field in one of the top level config dumps such as static_listeners from ListenersConfigDump or dynamic_active_clusters from ClustersConfigDump. If you need a non-repeated field, use the mask query parameter documented above. If you want only a subset of fields from the repeated resource, use both as documented below. /config_dump?name_regex=REGEX: Dump only the currently loaded configurations whose names match the specified regex. Can be used with both resource and mask query parameters. /listeners: List out all configured listeners. This information includes the names of listeners as well as the addresses that they are listening on. If a listener is configured to listen on port 0, then the output will contain the actual port that was allocated by the OS. /listeners?format=json: List out all configured listeners in JSON format. /server_info: Get the server information for the Envoy instance. /stats: Get the stats for the Envoy instance. /stats?filter=REGEX: Filters the returned stats to those with names matching the regular expression regex. Compatible with usedonly. Performs partial matching by default, so /stats?filter=server will return all stats containing the word server. Full-string matching can be specified with begin- and end-line anchors. (i.e. /stats?filter=^server.concurrency$). /stats?format=json: List out all stats in JSON format. /stats/recentlookups: This endpoint helps Envoy developers debug potential contention issues in the stats system. Initially, only the count of StatName lookups is acumulated, not the specific names that are being looked up. In order to see specific recent requests, you must enable the feature by POSTing to /stats/recentlookups/enable. There may be approximately 40-100 nanoseconds of added overhead per lookup. Information provided by these paths is useful for debugging Envoy configuration and service discovery issues. This information is usually VERY VERBOSE. Try to filter it with grep or jq to get the information you need. Do not try to show or process this information as it is, or you will end up with a lot of noise and a lot of data to process. Read the Envoy documentation located at https://www.envoyproxy.io/docs/envoy/latest/operations/admin for more information about these Envoy paths and the information they provide. requirements: executables: - "kubectl" - "nc" - "curl" - "jq" - "grep" params: context: type: string description: "Kubernetes context to use (required)" required: true pod: type: string description: "Pod name" required: true ns: type: string description: "Kubernetes namespace (optional, uses current context's namespace if not specified)" pod_port: type: string description: "Remote port in the Pod to connect to (e.g. 8080). Only one port can be provided." required: true local_port: type: number description: "Local port to bind to (e.g. 54320). Only one port can be provided. A defautl one will be used if not specified" curl_args: type: string description: "Additional arguments to pass to curl (e.g. '-I' for headers only)" path: type: string description: "URL's path (e.g. '/index.html'). Make sure it starts with a '/'." filter_jq: type: string description: "jq expression to apply to the response (optional)" filter_grep: type: string description: "grep expression to apply to the response (optional, literal text match)" filter_grep_regex: type: string description: "grep regex pattern to apply to the response (optional)" constraints: - "context.size() > 0 && context.size() <= 253" # Valid context name (required) - "ns == '' || (ns.size() <= 63 && ns.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$'))" # Valid k8s namespace - "pod_port.size() > 0" # Port must not be empty - "pod_port.matches('^[0-9]+$')" # Port must be a number - "path.size() > 0" # Path must not be empty - "path.matches('^/[^ ]+$')" # Path must start with a '/' and contain no spaces - "filter_grep == '' || filter_grep_regex == ''" # Only one of filter_grep or filter_grep_regex can be used - "filter_jq == '' || (filter_grep == '' && filter_grep_regex == '')" # Can't use both jq and grep filters run: timeout: "60s" command: | NAMESPACE_PARAM="{{ if .ns }}-n {{ .ns }}{{ end }}" CURL_ARGS="-s {{ if .curl_args }}{{ .curl_args }}{{ end }}" localport="{{ if .local_port }}{{ .local_port }}{{ else }}54320{{ end }}" remoteport="{{ .pod_port }}" # nmap -sT -p $localport localhost || true # This would show that the port is closed kubectl --context={{ .context }} port-forward $NAMESPACE_PARAM pod/{{ .pod }} $localport:$remoteport >/dev/null & pid=$! trap '{ kill $pid }' EXIT # wait for $localport to become available while ! nc -vz localhost $localport > /dev/null 2>&1 ; do sleep 0.1 done # nmap -sT -p $localport localhost # This would show that the port is open {{ if .filter_jq }} curl $CURL_ARGS "http://localhost:$localport{{ .path }}" | jq '{{ .filter_jq }}' || true {{ else if .filter_grep }} curl $CURL_ARGS "http://localhost:$localport{{ .path }}" | grep -F '{{ .filter_grep }}' || true {{ else if .filter_grep_regex }} curl $CURL_ARGS "http://localhost:$localport{{ .path }}" | grep -E '{{ .filter_grep_regex }}' || true {{ else }} curl $CURL_ARGS "http://localhost:$localport{{ .path }}" {{ end }} # the 'trap ... EXIT' above will take care of kill $pid env: - KUBECONFIG runners: - name: sandbox-exec requirements: os: darwin executables: [sandbox-exec] options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "/usr/local/bin" - "/opt/homebrew/bin" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: firejail options: allow_networking: true allow_user_folders: false allow_read_folders: - "/usr/bin" - "/bin" - "/etc" - "/usr/local/bin" - "/opt/homebrew/bin" - "{{ env \"HOME\" }}/.kube" allow_read_files: - "{{ env \"KUBECONFIG\" }}" - name: exec requirements: {} output: prefix: | Envoy configuration from pod {{ .pod }}:{{ .pod_port }}{{ .path }}{{ if .filter_grep }} (filtered for literal: '{{ .filter_grep }}'){{ end }}{{ if .filter_grep_regex }} (filtered for regex: '{{ .filter_grep_regex }}'){{ end }}: