From cf064479b20d230f816ec9e80231f167337e4a87 Mon Sep 17 00:00:00 2001 From: Fede Barcelona Date: Fri, 21 Nov 2025 17:11:55 +0100 Subject: [PATCH 1/3] feat(tools): add kubernetes_list_clusters tool This commit introduces a new tool to list Kubernetes clusters using a PromQL query. The tool supports filtering by cluster name and limiting the number of results. It uses the Sysdig client to execute the PromQL query. --- AGENTS.md | 1 + README.md | 5 + cmd/server/main.go | 1 + .../tools/tool_kubernetes_list_clusters.go | 69 +++++++++++ .../tool_kubernetes_list_clusters_test.go | 112 ++++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 internal/infra/mcp/tools/tool_kubernetes_list_clusters.go create mode 100644 internal/infra/mcp/tools/tool_kubernetes_list_clusters_test.go diff --git a/AGENTS.md b/AGENTS.md index 13fca71..210a275 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -49,6 +49,7 @@ The handler filters tools dynamically based on `GetMyPermissions` from Sysdig Se | `get_event_process_tree` | `tool_get_event_process_tree.go` | Retrieve the process tree for an event when available. | `policy-events.read` | “Show the process tree behind event `abc123`.” | | `run_sysql` | `tool_run_sysql.go` | Execute caller-supplied Sysdig SysQL queries safely. | `sage.exec`, `risks.read` | “Run the following SysQL…”. | | `generate_sysql` | `tool_generate_sysql.go` | Convert natural language to SysQL via Sysdig Sage. | `sage.exec` (does not work with Service Accounts) | “Create a SysQL to list S3 buckets.” | +| `kubernetes_list_clusters` | `tool_kubernetes_list_clusters.go` | Lists Kubernetes cluster information. | None | "List all Kubernetes clusters" | Every tool has a companion `_test.go` file that exercises request validation, permission metadata, and Sysdig client calls through mocks. Note that if you add more tools you need to also update this file to reflect that. diff --git a/README.md b/README.md index 34c4b4f..382ca63 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,11 @@ The server dynamically filters the available tools based on the permissions asso - **Required Permission**: `sage.exec`, `risks.read` - **Sample Prompt**: "Run this query: MATCH CloudResource WHERE type = 'aws_s3_bucket' LIMIT 10" +- **`kubernetes_list_clusters`** + - **Description**: Lists the cluster information for all clusters or just the cluster specified. + - **Required Permission**: None + - **Sample Prompt**: "List all kubernetes clusters" or "Show me info for cluster 'production-gke'" + ## Requirements - [Go](https://go.dev/doc/install) 1.25 or higher (if running without Docker). diff --git a/cmd/server/main.go b/cmd/server/main.go index 91581c5..3514338 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -94,6 +94,7 @@ func setupHandler(sysdigClient sysdig.ExtendedClientWithResponsesInterface) *mcp tools.NewToolGetEventProcessTree(sysdigClient), tools.NewToolRunSysql(sysdigClient), tools.NewToolGenerateSysql(sysdigClient), + tools.NewKubernetesListClusters(sysdigClient), ) return handler } diff --git a/internal/infra/mcp/tools/tool_kubernetes_list_clusters.go b/internal/infra/mcp/tools/tool_kubernetes_list_clusters.go new file mode 100644 index 0000000..26771fc --- /dev/null +++ b/internal/infra/mcp/tools/tool_kubernetes_list_clusters.go @@ -0,0 +1,69 @@ +package tools + +import ( + "context" + "encoding/json" + "fmt" + "io" + + "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" + "github.com/sysdiglabs/sysdig-mcp-server/internal/infra/sysdig" +) + +type KubernetesListClusters struct { + SysdigClient sysdig.ExtendedClientWithResponsesInterface +} + +func NewKubernetesListClusters(sysdigClient sysdig.ExtendedClientWithResponsesInterface) *KubernetesListClusters { + return &KubernetesListClusters{ + SysdigClient: sysdigClient, + } +} + +func (t *KubernetesListClusters) RegisterInServer(s *server.MCPServer) { + tool := mcp.NewTool("kubernetes_list_clusters", + mcp.WithDescription("Lists the cluster information for all clusters or just the cluster specified."), + mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), + mcp.WithNumber("limit", + mcp.Description("Maximum number of clusters to return."), + mcp.DefaultNumber(10), + ), + mcp.WithOutputSchema[map[string]any](), + WithRequiredPermissions(), // FIXME(fede): Add the required permissions. It should be `promql.exec` but somehow the token does not have that permission even if you are able to execute queries. + ) + s.AddTool(tool, t.handle) +} + +func (t *KubernetesListClusters) handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + clusterName := mcp.ParseString(request, "cluster_name", "") + limit := mcp.ParseInt(request, "limit", 10) + + query := "kube_cluster_info" + if clusterName != "" { + query = fmt.Sprintf("kube_cluster_info{cluster=\"%s\"}", clusterName) + } + + limitQuery := sysdig.LimitQuery(limit) + params := &sysdig.GetQueryV1Params{ + Query: query, + Limit: &limitQuery, + } + + httpResp, err := t.SysdigClient.GetQueryV1(ctx, params) + if err != nil { + return mcp.NewToolResultErrorFromErr("failed to get cluster list", err), nil + } + + if httpResp.StatusCode != 200 { + bodyBytes, _ := io.ReadAll(httpResp.Body) + return mcp.NewToolResultErrorf("failed to get cluster list: status code %d, body: %s", httpResp.StatusCode, string(bodyBytes)), nil + } + + var queryResponse sysdig.QueryResponseV1 + if err := json.NewDecoder(httpResp.Body).Decode(&queryResponse); err != nil { + return mcp.NewToolResultErrorFromErr("failed to decode response", err), nil + } + + return mcp.NewToolResultJSON(queryResponse) +} diff --git a/internal/infra/mcp/tools/tool_kubernetes_list_clusters_test.go b/internal/infra/mcp/tools/tool_kubernetes_list_clusters_test.go new file mode 100644 index 0000000..85273f2 --- /dev/null +++ b/internal/infra/mcp/tools/tool_kubernetes_list_clusters_test.go @@ -0,0 +1,112 @@ +package tools_test + +import ( + "bytes" + "context" + "io" + "net/http" + + "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/sysdiglabs/sysdig-mcp-server/internal/infra/mcp/tools" + "github.com/sysdiglabs/sysdig-mcp-server/internal/infra/sysdig" + "github.com/sysdiglabs/sysdig-mcp-server/internal/infra/sysdig/mocks" + "go.uber.org/mock/gomock" +) + +var _ = Describe("KubernetesListClusters Tool", func() { + var ( + tool *tools.KubernetesListClusters + mockSysdig *mocks.MockExtendedClientWithResponsesInterface + mcpServer *server.MCPServer + ctrl *gomock.Controller + ) + + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + mockSysdig = mocks.NewMockExtendedClientWithResponsesInterface(ctrl) + tool = tools.NewKubernetesListClusters(mockSysdig) + mcpServer = server.NewMCPServer("test", "test") + tool.RegisterInServer(mcpServer) + }) + + It("should register successfully in the server", func() { + Expect(mcpServer.GetTool("kubernetes_list_clusters")).NotTo(BeNil()) + }) + + When("listing all clusters", func() { + DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) + + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) + + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry(nil, + "kubernetes_list_clusters", + mcp.CallToolRequest{ + Params: mcp.CallToolParams{ + Name: "kubernetes_list_clusters", + Arguments: map[string]any{}, + }, + }, + sysdig.GetQueryV1Params{ + Query: `kube_cluster_info`, + Limit: asPtr(sysdig.LimitQuery(10)), + }, + ), + Entry(nil, + "kubernetes_list_clusters", + mcp.CallToolRequest{ + Params: mcp.CallToolParams{ + Name: "kubernetes_list_clusters", + Arguments: map[string]any{"limit": "20"}, + }, + }, + sysdig.GetQueryV1Params{ + Query: `kube_cluster_info`, + Limit: asPtr(sysdig.LimitQuery(20)), + }, + ), + Entry(nil, + "kubernetes_list_clusters", + mcp.CallToolRequest{ + Params: mcp.CallToolParams{ + Name: "kubernetes_list_clusters", + Arguments: map[string]any{"cluster_name": "my_cluster"}, + }, + }, + sysdig.GetQueryV1Params{ + Query: `kube_cluster_info{cluster="my_cluster"}`, + Limit: asPtr(sysdig.LimitQuery(10)), + }, + ), + Entry(nil, + "kubernetes_list_clusters", + mcp.CallToolRequest{ + Params: mcp.CallToolParams{ + Name: "kubernetes_list_clusters", + Arguments: map[string]any{"cluster_name": "my_cluster", "limit": "20"}, + }, + }, + sysdig.GetQueryV1Params{ + Query: `kube_cluster_info{cluster="my_cluster"}`, + Limit: asPtr(sysdig.LimitQuery(20)), + }, + ), + ) + }) +}) + +func asPtr[T any](arg T) *T { + return &arg +} From f2a47f29d7a571101b6522ba8fa5f35bd7885277 Mon Sep 17 00:00:00 2001 From: Fede Barcelona Date: Mon, 24 Nov 2025 11:53:39 +0100 Subject: [PATCH 2/3] feat!: rename the required env var token to SYSDIG_MCP_API_TOKEN --- .envrc | 2 +- .github/workflows/test.yaml | 2 +- AGENTS.md | 2 +- README.md | 22 +++++++++---------- cmd/server/main.go | 1 + internal/config/config.go | 4 ++-- internal/config/config_test.go | 6 ++--- .../client_generate_sysql_integration_test.go | 2 +- .../client_permissions_integration_test.go | 2 +- .../client_process_tree_integration_test.go | 2 +- 10 files changed, 23 insertions(+), 22 deletions(-) diff --git a/.envrc b/.envrc index 16151f7..6760235 100644 --- a/.envrc +++ b/.envrc @@ -3,4 +3,4 @@ watch_file *.nix dotenv_if_exists .env # You can create a .env file with your env vars for this project. You can also use .secrets if you are using act. See the line below. dotenv_if_exists .secrets # Used by [act](https://nektosact.com/) to load secrets into the pipelines strict_env -env_vars_required SYSDIG_MCP_API_HOST SYSDIG_MCP_API_SECURE_TOKEN +env_vars_required SYSDIG_MCP_API_HOST SYSDIG_MCP_API_TOKEN diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9901ca8..6fe10c7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -30,7 +30,7 @@ jobs: run: just check env: SYSDIG_MCP_API_HOST: ${{ vars.SYSDIG_MCP_API_HOST }} - SYSDIG_MCP_API_SECURE_TOKEN: ${{ secrets.SYSDIG_MCP_API_SECURE_TOKEN }} + SYSDIG_MCP_API_TOKEN: ${{ secrets.SYSDIG_MCP_API_SECURE_TOKEN }} build: name: Build runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 210a275..c1d74a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,7 +71,7 @@ Note that if you add more tools you need to also update this file to reflect tha ## Troubleshooting & Tips -- **Missing config:** `SYSDIG_MCP_API_HOST` and `SYSDIG_MCP_API_SECURE_TOKEN` are mandatory in `stdio`. Validation fails early in `internal/config/config.go`. +- **Missing config:** `SYSDIG_MCP_API_HOST` and `SYSDIG_MCP_API_TOKEN` are mandatory in `stdio`. Validation fails early in `internal/config/config.go`. - **Token scope:** If a tool does not appear, verify the token’s permissions under **Settings > Users & Teams > Roles**. `generate_sysql` currently requires a regular user token, not a Service Account. - **Remote auth:** When using `streamable-http` or `sse`, pass `Authorization: Bearer ` and optionally `X-Sysdig-Host`. These values override env vars via the request context middleware. - **Environment drift:** Always run inside `nix develop`; lint/test expect binaries like `gofumpt`, `golangci-lint`, and `ginkgo` provided by the flake. diff --git a/README.md b/README.md index 382ca63..f174238 100644 --- a/README.md +++ b/README.md @@ -65,12 +65,12 @@ Get up and running with the Sysdig MCP Server quickly using our pre-built Docker "-e", "SYSDIG_MCP_TRANSPORT", "-e", - "SYSDIG_MCP_API_SECURE_TOKEN", + "SYSDIG_MCP_API_TOKEN", "ghcr.io/sysdiglabs/sysdig-mcp-server:latest" ], "env": { "SYSDIG_MCP_API_HOST": "", - "SYSDIG_MCP_API_SECURE_TOKEN": "", + "SYSDIG_MCP_API_TOKEN": "", "SYSDIG_MCP_TRANSPORT": "stdio" } } @@ -122,7 +122,7 @@ The server dynamically filters the available tools based on the permissions asso The following environment variables are **required** for configuring the Sysdig SDK: - `SYSDIG_MCP_API_HOST`: The URL of your Sysdig Secure instance (e.g., `https://us2.app.sysdig.com`). **Required when using `stdio` transport.** -- `SYSDIG_MCP_API_SECURE_TOKEN`: Your Sysdig Secure API token. **Required only when using `stdio` transport.** +- `SYSDIG_MCP_API_TOKEN`: Your Sysdig Secure API token. **Required only when using `stdio` transport.** You can also set the following variables to override the default configuration: @@ -143,7 +143,7 @@ You can find your API token in the Sysdig Secure UI under **Settings > Sysdig Se ```bash # Required SYSDIG_MCP_API_HOST=https://us2.app.sysdig.com -SYSDIG_MCP_API_SECURE_TOKEN=your-api-token-here +SYSDIG_MCP_API_TOKEN=your-api-token-here # Optional SYSDIG_MCP_TRANSPORT=stdio @@ -158,7 +158,7 @@ SYSDIG_MCP_TRANSPORT=streamable-http # Optional (Host and Token can be provided via HTTP headers) # SYSDIG_MCP_API_HOST=https://us2.app.sysdig.com -# SYSDIG_MCP_API_SECURE_TOKEN=your-api-token-here +# SYSDIG_MCP_API_TOKEN=your-api-token-here SYSDIG_MCP_LISTENING_PORT=8080 SYSDIG_MCP_LISTENING_HOST=localhost SYSDIG_MCP_MOUNT_PATH=/sysdig-mcp-server @@ -204,13 +204,13 @@ You can run the MCP server using Docker (recommended for production) or directly The easiest way to run the server is using the pre-built Docker image from GitHub Container Registry (as shown in the [Quickstart Guide](#quickstart-guide)). ```bash -docker run -e SYSDIG_MCP_API_HOST= -e SYSDIG_MCP_API_SECURE_TOKEN= -e SYSDIG_MCP_TRANSPORT=stdio -p 8080:8080 ghcr.io/sysdiglabs/sysdig-mcp-server:latest +docker run -e SYSDIG_MCP_API_HOST= -e SYSDIG_MCP_API_TOKEN= -e SYSDIG_MCP_TRANSPORT=stdio -p 8080:8080 ghcr.io/sysdiglabs/sysdig-mcp-server:latest ``` To use the `streamable-http` or `sse` transports (for remote MCP clients), set the `SYSDIG_MCP_TRANSPORT` environment variable accordingly: ```bash -docker run -e SYSDIG_MCP_TRANSPORT=streamable-http -e SYSDIG_MCP_API_HOST= -e SYSDIG_MCP_API_SECURE_TOKEN= -p 8080:8080 ghcr.io/sysdiglabs/sysdig-mcp-server:latest +docker run -e SYSDIG_MCP_TRANSPORT=streamable-http -e SYSDIG_MCP_API_HOST= -e SYSDIG_MCP_API_TOKEN= -p 8080:8080 ghcr.io/sysdiglabs/sysdig-mcp-server:latest ``` ### Go @@ -294,12 +294,12 @@ For the Claude Desktop app, you can manually configure the MCP server by editing "-e", "SYSDIG_MCP_TRANSPORT", "-e", - "SYSDIG_MCP_API_SECURE_TOKEN", + "SYSDIG_MCP_API_TOKEN", "ghcr.io/sysdiglabs/sysdig-mcp-server:latest" ], "env": { "SYSDIG_MCP_API_HOST": "", - "SYSDIG_MCP_API_SECURE_TOKEN": "", + "SYSDIG_MCP_API_TOKEN": "", "SYSDIG_MCP_TRANSPORT": "stdio" } } @@ -320,7 +320,7 @@ For the Claude Desktop app, you can manually configure the MCP server by editing ], "env": { "SYSDIG_MCP_API_HOST": "", - "SYSDIG_MCP_API_SECURE_TOKEN": "", + "SYSDIG_MCP_API_TOKEN": "", "SYSDIG_MCP_TRANSPORT": "stdio" } } @@ -362,7 +362,7 @@ For the Claude Desktop app, you can manually configure the MCP server by editing env_keys: - SYSDIG_MCP_TRANSPORT - SYSDIG_MCP_API_HOST - - SYSDIG_MCP_API_SECURE_TOKEN + - SYSDIG_MCP_API_TOKEN envs: SYSDIG_MCP_TRANSPORT: stdio name: sysdig-mcp-server diff --git a/cmd/server/main.go b/cmd/server/main.go index 3514338..6ab1b68 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -94,6 +94,7 @@ func setupHandler(sysdigClient sysdig.ExtendedClientWithResponsesInterface) *mcp tools.NewToolGetEventProcessTree(sysdigClient), tools.NewToolRunSysql(sysdigClient), tools.NewToolGenerateSysql(sysdigClient), + tools.NewKubernetesListClusters(sysdigClient), ) return handler diff --git a/internal/config/config.go b/internal/config/config.go index 785f6cb..7a14991 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -20,7 +20,7 @@ func (c *Config) Validate() error { return fmt.Errorf("required configuration missing: SYSDIG_MCP_API_HOST") } if c.Transport == "stdio" && c.APIToken == "" { - return fmt.Errorf("required configuration missing: SYSDIG_MCP_API_SECURE_TOKEN") + return fmt.Errorf("required configuration missing: SYSDIG_MCP_API_TOKEN") } return nil } @@ -28,7 +28,7 @@ func (c *Config) Validate() error { func Load() (*Config, error) { cfg := &Config{ APIHost: getEnv("SYSDIG_MCP_API_HOST", ""), - APIToken: getEnv("SYSDIG_MCP_API_SECURE_TOKEN", ""), + APIToken: getEnv("SYSDIG_MCP_API_TOKEN", ""), Transport: getEnv("SYSDIG_MCP_TRANSPORT", "stdio"), ListeningHost: getEnv("SYSDIG_MCP_LISTENING_HOST", "localhost"), ListeningPort: getEnv("SYSDIG_MCP_LISTENING_PORT", "8080"), diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 1584f31..a6d47ea 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -58,7 +58,7 @@ var _ = Describe("Config", func() { } err := cfg.Validate() Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("SYSDIG_MCP_API_SECURE_TOKEN")) + Expect(err.Error()).To(ContainSubstring("SYSDIG_MCP_API_TOKEN")) }) }) }) @@ -71,7 +71,7 @@ var _ = Describe("Config", func() { Context("with required env vars set for stdio", func() { BeforeEach(func() { _ = os.Setenv("SYSDIG_MCP_API_HOST", "host") - _ = os.Setenv("SYSDIG_MCP_API_SECURE_TOKEN", "token") + _ = os.Setenv("SYSDIG_MCP_API_TOKEN", "token") }) It("should load default values", func() { @@ -105,7 +105,7 @@ var _ = Describe("Config", func() { Context("with all env vars set", func() { BeforeEach(func() { _ = os.Setenv("SYSDIG_MCP_API_HOST", "env-host") - _ = os.Setenv("SYSDIG_MCP_API_SECURE_TOKEN", "env-token") + _ = os.Setenv("SYSDIG_MCP_API_TOKEN", "env-token") _ = os.Setenv("SYSDIG_MCP_TRANSPORT", "http") _ = os.Setenv("SYSDIG_MCP_LISTENING_HOST", "0.0.0.0") _ = os.Setenv("SYSDIG_MCP_LISTENING_PORT", "9090") diff --git a/internal/infra/sysdig/client_generate_sysql_integration_test.go b/internal/infra/sysdig/client_generate_sysql_integration_test.go index 509a0bc..4222c72 100644 --- a/internal/infra/sysdig/client_generate_sysql_integration_test.go +++ b/internal/infra/sysdig/client_generate_sysql_integration_test.go @@ -15,7 +15,7 @@ var _ = Describe("Sysdig Generate Sysql Client", func() { BeforeEach(func() { sysdigURL := os.Getenv("SYSDIG_MCP_API_HOST") - sysdigToken := os.Getenv("SYSDIG_MCP_API_SECURE_TOKEN") + sysdigToken := os.Getenv("SYSDIG_MCP_API_TOKEN") var err error client, err = sysdig.NewSysdigClient(sysdig.WithFixedHostAndToken(sysdigURL, sysdigToken)) diff --git a/internal/infra/sysdig/client_permissions_integration_test.go b/internal/infra/sysdig/client_permissions_integration_test.go index a0f1db6..704cd84 100644 --- a/internal/infra/sysdig/client_permissions_integration_test.go +++ b/internal/infra/sysdig/client_permissions_integration_test.go @@ -17,7 +17,7 @@ var _ = Describe("Sysdig Permissions Client", func() { BeforeEach(func() { sysdigURL = os.Getenv("SYSDIG_MCP_API_HOST") - sysdigToken = os.Getenv("SYSDIG_MCP_API_SECURE_TOKEN") + sysdigToken = os.Getenv("SYSDIG_MCP_API_TOKEN") }) Context("when fetching user permissions", func() { diff --git a/internal/infra/sysdig/client_process_tree_integration_test.go b/internal/infra/sysdig/client_process_tree_integration_test.go index fa1cf52..59809c9 100644 --- a/internal/infra/sysdig/client_process_tree_integration_test.go +++ b/internal/infra/sysdig/client_process_tree_integration_test.go @@ -18,7 +18,7 @@ var _ = Describe("Sysdig Process Tree Client", func() { BeforeEach(func() { sysdigURL := os.Getenv("SYSDIG_MCP_API_HOST") - sysdigToken := os.Getenv("SYSDIG_MCP_API_SECURE_TOKEN") + sysdigToken := os.Getenv("SYSDIG_MCP_API_TOKEN") var err error client, err = sysdig.NewSysdigClient(sysdig.WithFixedHostAndToken(sysdigURL, sysdigToken)) From 3bb5e246db9fc071bd2d7e8c6fda9707af841c7f Mon Sep 17 00:00:00 2001 From: Fede Barcelona Date: Mon, 24 Nov 2025 11:57:10 +0100 Subject: [PATCH 3/3] docs: document required permission even though it's not needed for now --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f174238..0f1584a 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ The server dynamically filters the available tools based on the permissions asso - **`kubernetes_list_clusters`** - **Description**: Lists the cluster information for all clusters or just the cluster specified. - - **Required Permission**: None + - **Required Permission**: `promql.exec` - **Sample Prompt**: "List all kubernetes clusters" or "Show me info for cluster 'production-gke'" ## Requirements