Skip to content

Commit bc05b10

Browse files
janiszmtodorclaude
authored
chore: improve tools description (#29)
Signed-off-by: Tomasz Janiszewski <tomek@redhat.com> Co-authored-by: Mladen Todorovic <mtodor@gmail.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 3a38536 commit bc05b10

File tree

6 files changed

+57
-31
lines changed

6 files changed

+57
-31
lines changed

internal/toolsets/config/tools.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ func (t *listClustersTool) GetName() string {
6868
// GetTool returns the MCP Tool definition.
6969
func (t *listClustersTool) GetTool() *mcp.Tool {
7070
return &mcp.Tool{
71-
Name: t.name,
72-
Description: "List all clusters managed by StackRox with their IDs, names, and types",
71+
Name: t.name,
72+
Description: "List all clusters managed by StackRox with their IDs, names, and types." +
73+
" Use this tool to get cluster information," +
74+
" or when you need to map a cluster name to its cluster ID for use in other tools.",
7375
InputSchema: listClustersInputSchema(),
7476
}
7577
}
@@ -84,11 +86,13 @@ func listClustersInputSchema() *jsonschema.Schema {
8486

8587
schema.Properties["offset"].Minimum = jsonschema.Ptr(0.0)
8688
schema.Properties["offset"].Default = toolsets.MustJSONMarshal(defaultOffset)
87-
schema.Properties["offset"].Description = "Starting index for pagination (0-based)"
89+
schema.Properties["offset"].Description = "Starting index for pagination (0-based)." +
90+
" When using pagination, always provide both offset and limit together. Default: 0."
8891

8992
schema.Properties["limit"].Minimum = jsonschema.Ptr(0.0)
9093
schema.Properties["limit"].Default = toolsets.MustJSONMarshal(defaultLimit)
91-
schema.Properties["limit"].Description = "Maximum number of clusters to return (default: 0 - unlimited)"
94+
schema.Properties["limit"].Description = "Maximum number of clusters to return." +
95+
" When using pagination, always provide both limit and offset together. Use 0 for unlimited (default)."
9296

9397
return schema
9498
}

internal/toolsets/vulnerability/clusters.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,14 @@ func (t *getClustersForCVETool) GetTool() *mcp.Tool {
7171
Name: t.name,
7272
Description: "Get list of clusters where a specified CVE is detected in Kubernetes orchestrator components" +
7373
" (kube-apiserver, kubelet, etcd, etc.)." +
74-
" Returns clusters where the Kubernetes infrastructure itself has the vulnerability." +
75-
" For comprehensive CVE analysis, also check get_deployments_for_cve (application workloads)" +
76-
" and get_nodes_for_cve (node OS packages).",
74+
" USAGE PATTERNS:" +
75+
" 1) When user asks 'Is CVE-X detected in my clusters?' (plural, general question):" +
76+
" Call ALL THREE CVE tools (get_clusters_with_orchestrator_cve, get_deployments_for_cve, get_nodes_for_cve)" +
77+
" for comprehensive coverage." +
78+
" 2) When user asks specifically about 'orchestrator', 'Kubernetes components'," +
79+
" or 'control plane': Use ONLY this tool." +
80+
" 3) For single cluster queries (e.g., 'in cluster X'): First call list_clusters to get cluster ID," +
81+
" then call ONLY this tool with filterClusterId.",
7782
InputSchema: getClustersForCVEInputSchema(),
7883
}
7984
}
@@ -91,8 +96,12 @@ func getClustersForCVEInputSchema() *jsonschema.Schema {
9196
schema.Required = []string{"cveName"}
9297

9398
schema.Properties["cveName"].Description = "CVE name to filter clusters (e.g., CVE-2021-44228)"
94-
schema.Properties["filterClusterId"].Description = "Optional cluster ID to verify if a specified CVE" +
95-
" is detected on that cluster"
99+
schema.Properties["filterClusterId"].Description =
100+
"Optional cluster ID to verify if CVE is detected in a specific cluster." +
101+
" Only use this parameter when the user's query explicitly mentions a specific cluster name." +
102+
" When checking if a CVE exists at all, call without this parameter to check all clusters at once." +
103+
" To resolve cluster names to IDs, use list_clusters tool first." +
104+
" If the cluster doesn't exist, respond that the CVE is not detected in that cluster (since it doesn't exist)."
96105

97106
return schema
98107
}

internal/toolsets/vulnerability/clusters_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ func TestGetClustersForCVETool_GetTool(t *testing.T) {
3535

3636
require.NotNil(t, mcpTool)
3737
assert.Equal(t, "get_clusters_with_orchestrator_cve", mcpTool.Name)
38-
assert.Contains(t, mcpTool.Description, "clusters where a specified CVE is detected")
38+
assert.Contains(t, mcpTool.Description, "clusters where")
39+
assert.Contains(t, mcpTool.Description, "CVE is detected")
3940
assert.NotNil(t, mcpTool.InputSchema)
4041
}
4142

internal/toolsets/vulnerability/deployments.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type getDeploymentsForCVEInput struct {
3535
FilterClusterID string `json:"filterClusterId,omitempty"`
3636
FilterNamespace string `json:"filterNamespace,omitempty"`
3737
FilterPlatform filterPlatformType `json:"filterPlatform,omitempty"`
38-
IncludeAffectedImages bool `json:"includeAffectedImages,omitempty"`
38+
IncludeDetectedImages bool `json:"includeDetectedImages,omitempty"`
3939
Cursor string `json:"cursor,omitempty"`
4040
}
4141

@@ -55,7 +55,7 @@ type DeploymentResult struct {
5555
Namespace string `json:"namespace"`
5656
ClusterID string `json:"clusterId"`
5757
ClusterName string `json:"clusterName"`
58-
AffectedImages []string `json:"affectedImages,omitempty"`
58+
DetectedImages []string `json:"detectedImages,omitempty"`
5959
ImageFetchError string `json:"imageFetchError,omitempty"`
6060
}
6161

@@ -93,10 +93,16 @@ func (t *getDeploymentsForCVETool) GetName() string {
9393
func (t *getDeploymentsForCVETool) GetTool() *mcp.Tool {
9494
return &mcp.Tool{
9595
Name: t.name,
96-
Description: "Get list of deployments where a specified CVE is detected in application" +
97-
" or platform container images. Checks user workloads for vulnerabilities." +
98-
" For complete CVE analysis, also check get_clusters_with_orchestrator_cve (Kubernetes components)" +
99-
" and get_nodes_for_cve (node OS).",
96+
Description: "Get list of deployments where a specified CVE" +
97+
" is detected in application or platform container images." +
98+
" USAGE PATTERNS:" +
99+
" 1) When user asks 'Is CVE-X detected in my clusters?' (plural, general question):" +
100+
" Call ALL THREE CVE tools (get_clusters_with_orchestrator_cve, get_deployments_for_cve, get_nodes_for_cve)" +
101+
" for comprehensive coverage." +
102+
" 2) When user asks specifically about 'deployments', 'workloads', 'applications'," +
103+
" or 'containers': Use ONLY this tool." +
104+
" 3) For single cluster queries (e.g., 'in cluster X'): First call list_clusters to get cluster ID," +
105+
" then call ONLY this tool with filterClusterId.",
100106
InputSchema: getDeploymentsForCVEInputSchema(),
101107
}
102108
}
@@ -127,10 +133,10 @@ func getDeploymentsForCVEInputSchema() *jsonschema.Schema {
127133
filterPlatformPlatform,
128134
}
129135

130-
schema.Properties["includeAffectedImages"].Description =
131-
"Whether to include affected image names for each deployment.\n" +
136+
schema.Properties["includeDetectedImages"].Description =
137+
"Whether to include detected image names for each deployment.\n" +
132138
"WARNING: This may significantly increase response time."
133-
schema.Properties["includeAffectedImages"].Default = toolsets.MustJSONMarshal(false)
139+
schema.Properties["includeDetectedImages"].Default = toolsets.MustJSONMarshal(false)
134140

135141
schema.Properties["cursor"].Description = "Cursor for next page provided by server"
136142

@@ -224,7 +230,7 @@ func (e *deploymentEnricher) enrich(
224230
return
225231
}
226232

227-
deployment.AffectedImages = images
233+
deployment.DetectedImages = images
228234
})
229235
}
230236

@@ -309,7 +315,7 @@ func (t *getDeploymentsForCVETool) handle(
309315
}
310316
}
311317

312-
if input.IncludeAffectedImages {
318+
if input.IncludeDetectedImages {
313319
imageClient := v1.NewImageServiceClient(conn)
314320
enricher := newDeploymentEnricher(imageClient, input.CVEName, defaultMaxFetchImageConcurrency)
315321

internal/toolsets/vulnerability/deployments_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ func TestHandle_WithIncludeAffectedImages(t *testing.T) {
425425
req := &mcp.CallToolRequest{}
426426
input := getDeploymentsForCVEInput{
427427
CVEName: "CVE-2021-44228",
428-
IncludeAffectedImages: testCase.includeImages,
428+
IncludeDetectedImages: testCase.includeImages,
429429
}
430430

431431
result, output, err := tool.handle(ctx, req, input)
@@ -442,12 +442,12 @@ func TestHandle_WithIncludeAffectedImages(t *testing.T) {
442442

443443
if testCase.includeImages {
444444
assert.Empty(t, dep.ImageFetchError, "unexpected error for %s", dep.Name)
445-
assert.Len(t, dep.AffectedImages, imageCount, "wrong image count for %s", dep.Name)
445+
assert.Len(t, dep.DetectedImages, imageCount, "wrong image count for %s", dep.Name)
446446

447447
continue
448448
}
449449

450-
assert.Empty(t, dep.AffectedImages, "should not have images when disabled")
450+
assert.Empty(t, dep.DetectedImages, "should not have images when disabled")
451451
assert.Empty(t, dep.ImageFetchError, "should not have error when disabled")
452452
}
453453
})
@@ -490,7 +490,7 @@ func TestHandle_ImageFetchPartialFailure(t *testing.T) {
490490
req := &mcp.CallToolRequest{}
491491
input := getDeploymentsForCVEInput{
492492
CVEName: "CVE-2021-44228",
493-
IncludeAffectedImages: true,
493+
IncludeDetectedImages: true,
494494
}
495495

496496
result, output, err := tool.handle(ctx, req, input)
@@ -503,12 +503,12 @@ func TestHandle_ImageFetchPartialFailure(t *testing.T) {
503503
// At least verify structure supports error field.
504504
for _, dep := range output.Deployments {
505505
if dep.Name == "deployment-1" {
506-
assert.Len(t, dep.AffectedImages, 1)
506+
assert.Len(t, dep.DetectedImages, 1)
507507
assert.Empty(t, dep.ImageFetchError)
508508
}
509509
// dep-2 will have empty images since mock returns empty list.
510510
if dep.Name == "deployment-2" {
511-
assert.Empty(t, dep.AffectedImages)
511+
assert.Empty(t, dep.DetectedImages)
512512
assert.Empty(t, dep.ImageFetchError) // Empty list, not error in this mock.
513513
}
514514
}

internal/toolsets/vulnerability/nodes.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,16 @@ func (t *getNodesForCVETool) GetName() string {
7373
func (t *getNodesForCVETool) GetTool() *mcp.Tool {
7474
return &mcp.Tool{
7575
Name: t.name,
76-
Description: "Get aggregated node groups where a specified CVE is detected in node operating system packages" +
77-
", grouped by cluster and OS image. Checks OS-level vulnerabilities on cluster nodes." +
78-
" For comprehensive CVE coverage, also use get_clusters_with_orchestrator_cve (K8s components)" +
79-
" and get_deployments_for_cve (workloads).",
76+
Description: "Get aggregated node groups where a specified CVE is detected" +
77+
" in node operating system packages, grouped by cluster and OS image." +
78+
" USAGE PATTERNS:" +
79+
" 1) When user asks 'Is CVE-X detected in my clusters?' (plural, general question):" +
80+
" Call ALL THREE CVE tools (get_clusters_with_orchestrator_cve, get_deployments_for_cve, get_nodes_for_cve)" +
81+
" for comprehensive coverage." +
82+
" 2) When user asks specifically about 'nodes', 'hosts'," +
83+
" or 'operating systems': Use ONLY this tool." +
84+
" 3) For single cluster queries (e.g., 'in cluster X'): First call list_clusters to get cluster ID," +
85+
" then call ONLY this tool with filterClusterId.",
8086
InputSchema: getNodesForCVEInputSchema(),
8187
}
8288
}

0 commit comments

Comments
 (0)