diff --git a/Makefile b/Makefile index b77e1eea..dde2eea2 100644 --- a/Makefile +++ b/Makefile @@ -136,8 +136,8 @@ DOCKER_BUILDER ?= docker buildx DOCKER_BUILD_ARGS ?= --pull --load --platform linux/$(LOCALARCH) --builder $(BUILDX_BUILDER_NAME) # tools image build args -TOOLS_ISTIO_VERSION ?= 1.28.5 -TOOLS_ARGO_ROLLOUTS_VERSION ?= 1.8.4 +TOOLS_ISTIO_VERSION ?= 1.29.1 +TOOLS_ARGO_ROLLOUTS_VERSION ?= 1.9.0 TOOLS_KUBECTL_VERSION ?= 1.35.3 TOOLS_HELM_VERSION ?= 4.1.3 TOOLS_CILIUM_VERSION ?= 0.19.2 diff --git a/pkg/cilium/cilium.go b/pkg/cilium/cilium.go index 9479378c..b92a8f1c 100644 --- a/pkg/cilium/cilium.go +++ b/pkg/cilium/cilium.go @@ -3,6 +3,7 @@ package cilium import ( "context" "fmt" + "strings" "github.com/kagent-dev/tools/internal/commands" "github.com/kagent-dev/tools/internal/telemetry" @@ -619,7 +620,8 @@ func runCiliumDbgCommandWithContext(ctx context.Context, command, nodeName strin if err != nil { return "", err } - args := []string{"exec", "-it", podName, "--", "cilium-dbg", command} + args := []string{"exec", "-n", "kube-system", podName, "--", "cilium-dbg"} + args = append(args, strings.Fields(command)...) kubeconfigPath := utils.GetKubeconfig() return commands.NewCommandBuilder("kubectl"). WithArgs(args...). @@ -780,13 +782,13 @@ func handleShowConfigurationOptions(ctx context.Context, request mcp.CallToolReq var cmd string if listAll { - cmd = "endpoint config --all" + cmd = "config --all" } else if listReadOnly { - cmd = "endpoint config -r" + cmd = "config -r" } else if listOptions { - cmd = "endpoint config --list-options" + cmd = "config --list-options" } else { - cmd = "endpoint config" + cmd = "config" } output, err := runCiliumDbgCommand(ctx, cmd, nodeName) @@ -810,7 +812,7 @@ func handleToggleConfigurationOption(ctx context.Context, request mcp.CallToolRe valueStr = "disable" } - cmd := fmt.Sprintf("endpoint config %s=%s", option, valueStr) + cmd := fmt.Sprintf("config %s=%s", option, valueStr) output, err := runCiliumDbgCommand(ctx, cmd, nodeName) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("Failed to toggle configuration option: %v", err)), nil @@ -885,7 +887,7 @@ func handleFQDNCache(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal func handleShowDNSNames(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { nodeName := mcp.ParseString(request, "node_name", "") - output, err := runCiliumDbgCommand(ctx, "dns names", nodeName) + output, err := runCiliumDbgCommand(ctx, "fqdn names", nodeName) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("Failed to show DNS names: %v", err)), nil } @@ -1000,7 +1002,7 @@ func handleListBPFMapEvents(ctx context.Context, request mcp.CallToolRequest) (* return mcp.NewToolResultError("map_name parameter is required"), nil } - cmd := fmt.Sprintf("bpf map events %s", mapName) + cmd := fmt.Sprintf("map events %s", mapName) output, err := runCiliumDbgCommand(ctx, cmd, nodeName) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("Failed to list BPF map events: %v", err)), nil @@ -1016,7 +1018,7 @@ func handleGetBPFMap(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal return mcp.NewToolResultError("map_name parameter is required"), nil } - cmd := fmt.Sprintf("bpf map get %s", mapName) + cmd := fmt.Sprintf("map get %s", mapName) output, err := runCiliumDbgCommand(ctx, cmd, nodeName) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("Failed to get BPF map: %v", err)), nil @@ -1027,7 +1029,7 @@ func handleGetBPFMap(ctx context.Context, request mcp.CallToolRequest) (*mcp.Cal func handleListBPFMaps(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { nodeName := mcp.ParseString(request, "node_name", "") - output, err := runCiliumDbgCommand(ctx, "bpf map list", nodeName) + output, err := runCiliumDbgCommand(ctx, "map list", nodeName) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("Failed to list BPF maps: %v", err)), nil } @@ -1055,7 +1057,7 @@ func handleListMetrics(ctx context.Context, request mcp.CallToolRequest) (*mcp.C func handleListClusterNodes(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { nodeName := mcp.ParseString(request, "node_name", "") - output, err := runCiliumDbgCommand(ctx, "nodes list", nodeName) + output, err := runCiliumDbgCommand(ctx, "node list", nodeName) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("Failed to list cluster nodes: %v", err)), nil } diff --git a/pkg/cilium/cilium_test.go b/pkg/cilium/cilium_test.go index 5e4ec243..50bbed6d 100644 --- a/pkg/cilium/cilium_test.go +++ b/pkg/cilium/cilium_test.go @@ -258,6 +258,358 @@ func TestRunCiliumCliWithContext(t *testing.T) { }) } +// mockCiliumDbgCommand sets up the mock for a cilium-dbg command executed via kubectl exec. +// It mocks: (1) kubectl get pods to resolve the cilium pod name, (2) kubectl exec to run cilium-dbg. +func mockCiliumDbgCommand(mock *cmd.MockShellExecutor, dbgArgs []string, output string, err error) { + // Mock the pod name lookup + mock.AddCommandString("kubectl", []string{ + "get", "pods", "-n", "kube-system", + "--selector=k8s-app=cilium", + "--field-selector=spec.nodeName=test-node", + "-o", "jsonpath={.items[0].metadata.name}", + }, "cilium-abc123", nil) + + // Mock the kubectl exec call + execArgs := []string{"exec", "-n", "kube-system", "cilium-abc123", "--", "cilium-dbg"} + execArgs = append(execArgs, dbgArgs...) + mock.AddCommandString("kubectl", execArgs, output, err) +} + +func newRequestWithArgs(args map[string]any) mcp.CallToolRequest { + return mcp.CallToolRequest{ + Params: mcp.CallToolParams{ + Arguments: args, + }, + } +} + +func TestHandleGetEndpointsList(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"endpoint", "list"}, "ENDPOINT POLICY\n34 Disabled", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleGetEndpointsList(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "ENDPOINT") +} + +func TestHandleGetEndpointDetails(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"endpoint", "get", "34", "-o", "json"}, `{"id": 34}`, nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"endpoint_id": "34", "node_name": "test-node"}) + result, err := handleGetEndpointDetails(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), `"id": 34`) +} + +func TestHandleGetEndpointLogs(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"endpoint", "logs", "34"}, "endpoint log output", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"endpoint_id": "34", "node_name": "test-node"}) + result, err := handleGetEndpointLogs(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "endpoint log output") +} + +func TestHandleGetEndpointHealth(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"endpoint", "health", "34"}, "endpoint health OK", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"endpoint_id": "34", "node_name": "test-node"}) + result, err := handleGetEndpointHealth(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "endpoint health OK") +} + +func TestHandleShowConfigurationOptions(t *testing.T) { + t.Run("default", func(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"config"}, "PolicyEnforcement=default", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleShowConfigurationOptions(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "PolicyEnforcement") + }) + + t.Run("all", func(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"config", "--all"}, "all config options", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node", "list_all": "true"}) + result, err := handleShowConfigurationOptions(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "all config options") + }) + + t.Run("read_only", func(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"config", "-r"}, "read only config", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node", "list_read_only": "true"}) + result, err := handleShowConfigurationOptions(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "read only config") + }) +} + +func TestHandleToggleConfigurationOption(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"config", "PolicyEnforcement=enable"}, "option toggled", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"option": "PolicyEnforcement", "value": "true", "node_name": "test-node"}) + result, err := handleToggleConfigurationOption(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "option toggled") +} + +func TestHandleListIdentities(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"identity", "list"}, "ID LABELS\n1 reserved:host", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListIdentities(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "reserved:host") +} + +func TestHandleGetDaemonStatus(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"status"}, "KVStore: Ok\nKubernetes: Ok", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleGetDaemonStatus(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "KVStore: Ok") +} + +func TestHandleDisplayEncryptionState(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"encrypt", "status"}, "Encryption: Disabled", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleDisplayEncryptionState(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "Encryption: Disabled") +} + +func TestHandleShowDNSNames(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"fqdn", "names"}, "DNS names output", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleShowDNSNames(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "DNS names output") +} + +func TestHandleFQDNCache(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"fqdn", "cache", "list"}, "FQDN cache entries", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleFQDNCache(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "FQDN cache entries") +} + +func TestHandleListClusterNodes(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"node", "list"}, "Name IPv4 Address\nnode1 10.0.0.1", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListClusterNodes(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "node1") +} + +func TestHandleListNodeIds(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"nodeid", "list"}, "ID IP\n1 10.0.0.1", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListNodeIds(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "10.0.0.1") +} + +func TestHandleListBPFMaps(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"map", "list"}, "Name Num entries\ncilium_lb4 22", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListBPFMaps(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "cilium_lb4") +} + +func TestHandleGetBPFMap(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"map", "get", "cilium_lb4"}, "map contents", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"map_name": "cilium_lb4", "node_name": "test-node"}) + result, err := handleGetBPFMap(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "map contents") +} + +func TestHandleListBPFMapEvents(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"map", "events", "cilium_lb4"}, "map events", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"map_name": "cilium_lb4", "node_name": "test-node"}) + result, err := handleListBPFMapEvents(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "map events") +} + +func TestHandleListMetrics(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"metrics", "list"}, "Metric Value\ncilium_endpoint_count 4", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListMetrics(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "cilium_endpoint_count") +} + +func TestHandleListServices(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"service", "list"}, "ID Frontend\n1 10.96.0.1:443", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListServices(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "10.96.0.1") +} + +func TestHandleListIPAddresses(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"ip", "list"}, "IP Identity\n10.0.0.1 1", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListIPAddresses(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "10.0.0.1") +} + +func TestHandleDisplaySelectors(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"policy", "selectors"}, "SELECTOR IDENTITIES", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleDisplaySelectors(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "SELECTOR") +} + +func TestHandleListLocalRedirectPolicies(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"lrp", "list"}, "No local redirect policies", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListLocalRedirectPolicies(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "No local redirect policies") +} + +func TestHandleRequestDebuggingInformation(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"debuginfo"}, "debug info output", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleRequestDebuggingInformation(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "debug info output") +} + +func TestHandleListXDPCIDRFilters(t *testing.T) { + ctx := context.Background() + mock := cmd.NewMockShellExecutor() + mockCiliumDbgCommand(mock, []string{"prefilter", "list"}, "CIDR filters", nil) + ctx = cmd.WithShellExecutor(ctx, mock) + + req := newRequestWithArgs(map[string]any{"node_name": "test-node"}) + result, err := handleListXDPCIDRFilters(ctx, req) + require.NoError(t, err) + assert.False(t, result.IsError) + assert.Contains(t, getResultText(result), "CIDR filters") +} + func getResultText(r *mcp.CallToolResult) string { if r == nil || len(r.Content) == 0 { return "" diff --git a/scripts/cilium/install-cilium.sh b/scripts/cilium/install-cilium.sh new file mode 100755 index 00000000..922a67ff --- /dev/null +++ b/scripts/cilium/install-cilium.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Install Cilium into a Kind cluster via the kagent-tools pod. +# +# Prerequisites: +# - Kind cluster running with kagent-tools deployed +# +# Usage: +# ./scripts/cilium/install-cilium.sh [NAMESPACE] [RELEASE_NAME] [KUBE_CONTEXT] +# +# Defaults: +# NAMESPACE = kagent +# RELEASE_NAME = kagent-tools +# KUBE_CONTEXT = kind-kagent + +set -euo pipefail + +NAMESPACE="${1:-kagent}" +RELEASE_NAME="${2:-kagent-tools}" +KUBE_CONTEXT="${3:-kind-kagent}" + +echo "Installing Cilium via kagent-tools pod in namespace=$NAMESPACE context=$KUBE_CONTEXT" + +# Find the kagent-tools pod +POD=$(kubectl --context "$KUBE_CONTEXT" get pods -n "$NAMESPACE" \ + -l "app.kubernetes.io/instance=$RELEASE_NAME" \ + -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) + +if [ -z "$POD" ]; then + echo "ERROR: No kagent-tools pod found in namespace $NAMESPACE" + exit 1 +fi + +echo "Using pod: $POD" + +# Install Cilium +kubectl --context "$KUBE_CONTEXT" exec -n "$NAMESPACE" "$POD" -- \ + cilium install \ + --set routingMode=native \ + --set ipv4NativeRoutingCIDR=10.244.0.0/16 \ + --set bpf.masquerade=false + +echo "" +echo "Waiting for Cilium pods to be ready..." +kubectl --context "$KUBE_CONTEXT" wait \ + --for=condition=ready pod \ + -l k8s-app=cilium \ + -n kube-system \ + --timeout=120s + +echo "" +echo "Cilium status:" +kubectl --context "$KUBE_CONTEXT" exec -n "$NAMESPACE" "$POD" -- cilium status diff --git a/scripts/cilium/test-mcp-tools.sh b/scripts/cilium/test-mcp-tools.sh new file mode 100755 index 00000000..3ae07d9a --- /dev/null +++ b/scripts/cilium/test-mcp-tools.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# Test Cilium MCP tools against a running kagent-tools instance. +# +# Prerequisites: +# - Kind cluster "kagent" running with Cilium installed +# - kagent-tools deployed and accessible on NodePort 30884 +# +# Usage: +# ./scripts/cilium/test-mcp-tools.sh [MCP_URL] [NODE_NAME] +# +# Defaults: +# MCP_URL = http://127.0.0.1:30884/mcp +# NODE_NAME = kagent-control-plane + +set -euo pipefail + +MCP_URL="${1:-http://127.0.0.1:30884/mcp}" +NODE_NAME="${2:-kagent-control-plane}" + +PASS=0 +FAIL=0 +SKIP=0 + +# Initialize MCP session +SESSION_ID=$(curl -sf -D - -X POST "$MCP_URL" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"cilium-test","version":"1.0"}}}' \ + 2>&1 | grep -i 'mcp-session-id' | tr -d '\r' | awk '{print $2}') + +if [ -z "$SESSION_ID" ]; then + echo "ERROR: Failed to initialize MCP session at $MCP_URL" + exit 1 +fi +echo "MCP session: $SESSION_ID" +echo "" + +ID=2 + +call_tool() { + local name=$1 + local args=$2 + + RESULT=$(curl -sf -X POST "$MCP_URL" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -H "Mcp-Session-Id: $SESSION_ID" \ + -d "{\"jsonrpc\":\"2.0\",\"id\":$ID,\"method\":\"tools/call\",\"params\":{\"name\":\"$name\",\"arguments\":$args}}" 2>&1 || true) + ID=$((ID + 1)) + + if [ -z "$RESULT" ]; then + echo "SKIP: $name (no response)" + SKIP=$((SKIP + 1)) + elif echo "$RESULT" | grep -q '"isError":true'; then + echo "FAIL: $name" + FAIL=$((FAIL + 1)) + else + echo "OK: $name" + PASS=$((PASS + 1)) + fi +} + +NODE_ARGS="{\"node_name\":\"$NODE_NAME\"}" + +echo "=== Cilium CLI tools ===" +call_tool "cilium_status_and_version" "{}" + +echo "" +echo "=== Cilium-dbg read-only tools ===" +call_tool "cilium_get_endpoints_list" "$NODE_ARGS" +call_tool "cilium_get_daemon_status" "$NODE_ARGS" +call_tool "cilium_list_identities" "$NODE_ARGS" +call_tool "cilium_display_encryption_state" "$NODE_ARGS" +call_tool "cilium_list_services" "$NODE_ARGS" +call_tool "cilium_list_metrics" "$NODE_ARGS" +call_tool "cilium_fqdn_cache" "$NODE_ARGS" +call_tool "cilium_show_dns_names" "$NODE_ARGS" +call_tool "cilium_show_configuration_options" "$NODE_ARGS" +call_tool "cilium_list_cluster_nodes" "$NODE_ARGS" +call_tool "cilium_list_node_ids" "$NODE_ARGS" +call_tool "cilium_list_bpf_maps" "$NODE_ARGS" +call_tool "cilium_list_ip_addresses" "$NODE_ARGS" +call_tool "cilium_display_selectors" "$NODE_ARGS" +call_tool "cilium_list_local_redirect_policies" "$NODE_ARGS" +call_tool "cilium_request_debugging_information" "$NODE_ARGS" +call_tool "cilium_show_load_information" "$NODE_ARGS" +call_tool "cilium_display_policy_node_information" "{\"node_name\":\"$NODE_NAME\",\"labels\":\"\"}" +call_tool "cilium_list_xdp_cidr_filters" "$NODE_ARGS" + +echo "" +echo "=== Results ===" +echo "PASS: $PASS FAIL: $FAIL SKIP: $SKIP TOTAL: $((PASS + FAIL + SKIP))" + +if [ "$FAIL" -gt 0 ]; then + exit 1 +fi