Skip to content

Commit 3a6cb87

Browse files
ChrisEdwardsclaude
andcommitted
Phase 2 & 3: Rename search_vulnerabilities and remove old tools (mcp-7sa, mcp-3av)
Phase 2 (mcp-7sa): - Renamed getAllVulnerabilities → searchVulnerabilities - Renamed tool: list_all_vulnerabilities → search_vulnerabilities - Removed appId parameter (org-level only) - Always use getTracesInOrg() endpoint - Updated tool description with cross-references Phase 3 (mcp-3av): - Deleted listVulnsByAppId() (list_vulnerabilities) - Deleted listVulnsByAppIdAndSessionMetadata() (list_vulns_by_app_and_metadata) - Deleted listVulnsByAppIdForLatestSession() (list_vulns_by_app_latest_session) - Migrated integration tests to use search_app_vulnerabilities Result: 6 vulnerability tools → 4 tools (33% reduction) All 266 unit tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 825afcc commit 3a6cb87

File tree

2 files changed

+76
-165
lines changed

2 files changed

+76
-165
lines changed

src/main/java/com/contrast/labs/ai/mcp/contrast/AssessService.java

Lines changed: 20 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -187,127 +187,6 @@ private Optional<LibraryLibraryObservation> findMatchingLibraryData(
187187
return Optional.empty();
188188
}
189189

190-
@Tool(
191-
name = "list_vulnerabilities",
192-
description =
193-
"Takes an application ID (appID) and returns a list of vulnerabilities. Use"
194-
+ " search_applications(name=...) to find the application ID from a name. Remember"
195-
+ " to include the vulnID in the response.")
196-
public List<VulnLight> listVulnsByAppId(@ToolParam(description = "Application ID") String appID)
197-
throws IOException {
198-
log.info("Listing vulnerabilities for application ID: {}", appID);
199-
var contrastSDK =
200-
SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort);
201-
try {
202-
// Use SDK native API with SESSION_METADATA, SERVER_ENVIRONMENTS, and APPLICATION expand
203-
var form = new TraceFilterForm();
204-
form.setExpand(
205-
EnumSet.of(
206-
TraceFilterForm.TraceExpandValue.SESSION_METADATA,
207-
TraceFilterForm.TraceExpandValue.SERVER_ENVIRONMENTS,
208-
TraceFilterForm.TraceExpandValue.APPLICATION));
209-
210-
var traces = contrastSDK.getTraces(orgID, appID, form);
211-
log.debug(
212-
"Found {} vulnerability traces for application ID: {}",
213-
traces.getTraces() != null ? traces.getTraces().size() : 0,
214-
appID);
215-
216-
var vulns = traces.getTraces().stream().map(vulnerabilityMapper::toVulnLight).toList();
217-
218-
log.info(
219-
"Successfully retrieved {} vulnerabilities for application ID: {}", vulns.size(), appID);
220-
return vulns;
221-
} catch (Exception e) {
222-
log.error("Error listing vulnerabilities for application ID: {}", appID, e);
223-
throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e);
224-
}
225-
}
226-
227-
@Tool(
228-
name = "list_vulns_by_app_and_metadata",
229-
description =
230-
"Takes an application ID (appID) and session metadata in the form of name / value. and"
231-
+ " returns a list of vulnerabilities matching that application ID and session"
232-
+ " metadata. Use search_applications(name=...) to find the application ID from a"
233-
+ " name.")
234-
public List<VulnLight> listVulnsByAppIdAndSessionMetadata(
235-
@ToolParam(description = "Application ID") String appID,
236-
@ToolParam(description = "Session metadata field name") String session_Metadata_Name,
237-
@ToolParam(description = "Session metadata field value") String session_Metadata_Value)
238-
throws IOException {
239-
log.info("Listing vulnerabilities for application: {}", appID);
240-
241-
log.info("metadata : " + session_Metadata_Name + session_Metadata_Value);
242-
243-
try {
244-
var vulns = listVulnsByAppId(appID);
245-
var returnVulns = new ArrayList<VulnLight>();
246-
for (VulnLight vuln : vulns) {
247-
if (vuln.sessionMetadata() != null) {
248-
for (SessionMetadata sm : vuln.sessionMetadata()) {
249-
for (MetadataItem metadataItem : sm.getMetadata()) {
250-
if (metadataItem.getDisplayLabel().equalsIgnoreCase(session_Metadata_Name)
251-
&& metadataItem.getValue().equalsIgnoreCase(session_Metadata_Value)) {
252-
returnVulns.add(vuln);
253-
log.debug("Found matching vulnerability with ID: {}", vuln.vulnID());
254-
break;
255-
}
256-
}
257-
}
258-
}
259-
}
260-
return returnVulns;
261-
} catch (Exception e) {
262-
log.error("Error listing vulnerabilities for application: {}", appID, e);
263-
throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e);
264-
}
265-
}
266-
267-
@Tool(
268-
name = "list_vulns_by_app_latest_session",
269-
description =
270-
"Takes an application ID (appID) and returns a list of vulnerabilities for the latest"
271-
+ " session matching that application ID. This is useful for getting the most recent"
272-
+ " vulnerabilities without needing to specify session metadata. Use"
273-
+ " search_applications(name=...) to find the application ID from a name.")
274-
public List<VulnLight> listVulnsByAppIdForLatestSession(
275-
@ToolParam(description = "Application ID") String appID) throws IOException {
276-
log.info("Listing vulnerabilities for application: {}", appID);
277-
var contrastSDK =
278-
SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort);
279-
280-
try {
281-
var extension = new SDKExtension(contrastSDK);
282-
var latest = extension.getLatestSessionMetadata(orgID, appID);
283-
284-
// Use SDK's native TraceFilterBody with agentSessionId field
285-
var filterBody = new com.contrastsecurity.models.TraceFilterBody();
286-
if (latest != null
287-
&& latest.getAgentSession() != null
288-
&& latest.getAgentSession().getAgentSessionId() != null) {
289-
filterBody.setAgentSessionId(latest.getAgentSession().getAgentSessionId());
290-
}
291-
292-
// Use SDK's native getTraces() with expand parameter
293-
var tracesResponse =
294-
contrastSDK.getTraces(
295-
orgID,
296-
appID,
297-
filterBody,
298-
EnumSet.of(
299-
TraceFilterForm.TraceExpandValue.SESSION_METADATA,
300-
TraceFilterForm.TraceExpandValue.APPLICATION));
301-
302-
var vulns =
303-
tracesResponse.getTraces().stream().map(vulnerabilityMapper::toVulnLight).toList();
304-
return vulns;
305-
} catch (Exception e) {
306-
log.error("Error listing vulnerabilities for application: {}", appID, e);
307-
throw new IOException("Failed to list vulnerabilities: " + e.getMessage(), e);
308-
}
309-
}
310-
311190
@Tool(
312191
name = "get_session_metadata",
313192
description =
@@ -498,30 +377,40 @@ private boolean matchesMetadataFilter(
498377
}
499378

500379
@Tool(
501-
name = "list_all_vulnerabilities",
380+
name = "search_vulnerabilities",
502381
description =
503382
"""
504-
Gets vulnerabilities across all applications with optional filtering by severity, status,
505-
environment, vulnerability type, date range, application, and tags.
383+
Search vulnerabilities across all applications in your organization with optional filtering by
384+
severity, status, environment, vulnerability type, date range, and tags.
385+
386+
This is an organization-level search tool. For application-scoped searches with session filtering
387+
capabilities, use the search_app_vulnerabilities tool instead.
506388
507389
Common usage examples:
508390
- Critical vulnerabilities only: severities="CRITICAL"
509391
- High-priority open issues: severities="CRITICAL,HIGH", statuses="Reported,Confirmed"
510392
- Production vulnerabilities: environments="PRODUCTION"
511393
- Recent activity: lastSeenAfter="2025-01-01"
512394
- Production critical issues with recent activity: environments="PRODUCTION", severities="CRITICAL", lastSeenAfter="2025-01-01"
513-
- Specific app's SQL injection issues: appId="abc123", vulnTypes="sql-injection"
514395
- SmartFix remediated vulnerabilities: vulnTags="SmartFix Remediated", statuses="Remediated"
515396
- Reviewed critical vulnerabilities: vulnTags="reviewed", severities="CRITICAL"
516397
398+
Note: This tool requires Contrast Platform Admin or Org Admin permissions to access organization-level
399+
vulnerability data.
400+
517401
Returns paginated results with metadata including totalItems (when available) and hasMorePages.
518402
Check 'message' field for validation warnings or empty result info.
519403
520404
Response fields:
521405
- environments: List of all environments (DEVELOPMENT, QA, PRODUCTION) where this vulnerability
522406
has been seen over time. Shows historical presence across environments.
407+
- application: Application name and ID where the vulnerability was found.
408+
409+
Related tools:
410+
- search_app_vulnerabilities: For app-scoped searches with session filtering
411+
- search_applications: To find application IDs by name, tag, or metadata
523412
""")
524-
public PaginatedResponse<VulnLight> getAllVulnerabilities(
413+
public PaginatedResponse<VulnLight> searchVulnerabilities(
525414
@ToolParam(description = "Page number (1-based), default: 1", required = false) Integer page,
526415
@ToolParam(description = "Items per page (max 100), default: 50", required = false)
527416
Integer pageSize,
@@ -538,7 +427,6 @@ public PaginatedResponse<VulnLight> getAllVulnerabilities(
538427
+ " focus on actionable items)",
539428
required = false)
540429
String statuses,
541-
@ToolParam(description = "Application ID to filter by", required = false) String appId,
542430
@ToolParam(
543431
description =
544432
"Comma-separated vulnerability types (e.g., sql-injection,xss-reflected). Use"
@@ -574,14 +462,13 @@ public PaginatedResponse<VulnLight> getAllVulnerabilities(
574462
String vulnTags)
575463
throws IOException {
576464
log.info(
577-
"Listing all vulnerabilities - page: {}, pageSize: {}, filters: severities={}, statuses={},"
578-
+ " appId={}, vulnTypes={}, environments={}, lastSeenAfter={}, lastSeenBefore={},"
465+
"Searching org vulnerabilities - page: {}, pageSize: {}, filters: severities={},"
466+
+ " statuses={}, vulnTypes={}, environments={}, lastSeenAfter={}, lastSeenBefore={},"
579467
+ " vulnTags={}",
580468
page,
581469
pageSize,
582470
severities,
583471
statuses,
584-
appId,
585472
vulnTypes,
586473
environments,
587474
lastSeenAfter,
@@ -595,7 +482,7 @@ public PaginatedResponse<VulnLight> getAllVulnerabilities(
595482
VulnerabilityFilterParams.of(
596483
severities,
597484
statuses,
598-
appId,
485+
null, // No appId for org-level search
599486
vulnTypes,
600487
environments,
601488
lastSeenAfter,
@@ -623,16 +510,8 @@ public PaginatedResponse<VulnLight> getAllVulnerabilities(
623510
TraceFilterForm.TraceExpandValue.SESSION_METADATA,
624511
TraceFilterForm.TraceExpandValue.APPLICATION));
625512

626-
// Try organization-level API (or app-specific if appId provided)
627-
Traces traces;
628-
if (appId != null && !appId.trim().isEmpty()) {
629-
// Use app-specific API for better performance
630-
log.debug("Using app-specific API for appId: {}", appId);
631-
traces = contrastSDK.getTraces(orgID, appId, filterForm);
632-
} else {
633-
// Use org-level API
634-
traces = contrastSDK.getTracesInOrg(orgID, filterForm);
635-
}
513+
// Use organization-level API
514+
Traces traces = contrastSDK.getTracesInOrg(orgID, filterForm);
636515

637516
if (traces != null && traces.getTraces() != null) {
638517
// Organization API worked (empty list with count=0 is valid - means no vulnerabilities or

src/test/java/com/contrast/labs/ai/mcp/contrast/AssessServiceIntegrationTest.java

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,10 @@ void testEnvironmentsAndTagsArePopulated() throws IOException {
131131

132132
// Get vulnerabilities from real TeamServer
133133
var response =
134-
assessService.getAllVulnerabilities(
134+
assessService.searchVulnerabilities(
135135
1, // page
136136
10, // pageSize
137137
null, // severities
138-
null, // statuses
139138
null, // appId
140139
null, // vulnTypes
141140
null, // environments
@@ -196,11 +195,10 @@ void testSessionMetadataIsPopulated() throws IOException {
196195

197196
// Get vulnerabilities from real TeamServer with session metadata expanded
198197
var response =
199-
assessService.getAllVulnerabilities(
198+
assessService.searchVulnerabilities(
200199
1, // page
201200
10, // pageSize
202201
null, // severities
203-
null, // statuses
204202
null, // appId
205203
null, // vulnTypes
206204
null, // environments
@@ -265,7 +263,7 @@ void testVulnerabilitiesHaveBasicFields() throws IOException {
265263
log.info("\n=== Integration Test: Basic Fields ===");
266264

267265
var response =
268-
assessService.getAllVulnerabilities(1, 5, null, null, null, null, null, null, null, null);
266+
assessService.searchVulnerabilities(1, 5, null, null, null, null, null, null, null);
269267

270268
assertThat(response).isNotNull();
271269
assertThat(response.items()).as("Should have vulnerabilities").isNotEmpty();
@@ -311,11 +309,10 @@ void testVulnTagsWithSpacesHandledBySDK() throws IOException {
311309
// Query with a tag that contains spaces - this should work now that AIML-193 is complete
312310
// The SDK should handle URL encoding internally
313311
var response =
314-
assessService.getAllVulnerabilities(
312+
assessService.searchVulnerabilities(
315313
1, // page
316314
50, // pageSize (larger to increase chance of finding tagged vulns)
317315
null, // severities
318-
null, // statuses
319316
null, // appId
320317
null, // vulnTypes
321318
null, // environments
@@ -342,8 +339,8 @@ void testVulnTagsWithSpacesHandledBySDK() throws IOException {
342339
// Try with multiple tags including spaces and special characters
343340
log.info("\nTesting multiple tags with spaces:");
344341
response =
345-
assessService.getAllVulnerabilities(
346-
1, 10, null, null, null, null, null, null, null, "Tag With Spaces,another-tag");
342+
assessService.searchVulnerabilities(
343+
1, 10, null, null, null, null, null, null, "Tag With Spaces,another-tag");
347344

348345
assertThat(response).as("Response should not be null").isNotNull();
349346
log.info("✓ Query with multiple tags completed successfully");
@@ -353,15 +350,34 @@ void testVulnTagsWithSpacesHandledBySDK() throws IOException {
353350
}
354351

355352
@Test
356-
void testListVulnsByAppIdWithSessionMetadata() throws IOException {
357-
log.info("\n=== Integration Test: listVulnsByAppId() with Session Metadata ===");
353+
void testSearchAppVulnerabilitiesWithSessionMetadata() throws IOException {
354+
log.info("\n=== Integration Test: search_app_vulnerabilities() with Session Metadata ===");
358355

359356
assertThat(testData).as("Test data must be discovered before running tests").isNotNull();
360357

361-
// Call listVulnsByAppId() with the discovered appId from @BeforeAll
362-
log.info("Calling listVulnsByAppId() for app: {} (ID: {})", testData.appName, testData.appId);
363-
var vulnerabilities = assessService.listVulnsByAppId(testData.appId);
358+
// Call search_app_vulnerabilities() with the discovered appId from @BeforeAll
359+
log.info(
360+
"Calling search_app_vulnerabilities() for app: {} (ID: {})",
361+
testData.appName,
362+
testData.appId);
363+
var response =
364+
assessService.searchAppVulnerabilities(
365+
testData.appId, // appId
366+
1, // page
367+
50, // pageSize
368+
null, // severities
369+
null, // statuses
370+
null, // vulnTypes
371+
null, // environments
372+
null, // lastSeenAfter
373+
null, // lastSeenBefore
374+
null, // vulnTags
375+
null, // sessionMetadataName
376+
null, // sessionMetadataValue
377+
null); // useLatestSession
364378

379+
assertThat(response).as("Response should not be null").isNotNull();
380+
var vulnerabilities = response.items();
365381
assertThat(vulnerabilities).as("Vulnerabilities list should not be null").isNotNull();
366382
log.info(" ✓ Retrieved {} vulnerability(ies)", vulnerabilities.size());
367383

@@ -391,26 +407,42 @@ void testListVulnsByAppIdWithSessionMetadata() throws IOException {
391407
+ "/"
392408
+ vulnerabilities.size());
393409
log.info(
394-
"✓ Integration test passed: listVulnsByAppId() returns vulnerabilities with session"
395-
+ " metadata");
410+
"✓ Integration test passed: search_app_vulnerabilities() returns vulnerabilities with"
411+
+ " session metadata");
396412
}
397413

398414
@Test
399-
void testListVulnsInAppByNameForLatestSessionWithDynamicSessionId() throws IOException {
415+
void testSearchAppVulnerabilitiesForLatestSessionWithDynamicSessionId() throws IOException {
400416
log.info(
401417
"\n"
402-
+ "=== Integration Test: listVulnsByAppIdForLatestSession() with Dynamic Session"
403-
+ " Discovery ===");
418+
+ "=== Integration Test: search_app_vulnerabilities() with useLatestSession and Dynamic"
419+
+ " Session Discovery ===");
404420

405421
assertThat(testData).as("Test data must be discovered before running tests").isNotNull();
406422

407-
// Call listVulnsByAppIdForLatestSession() with the discovered app ID from @BeforeAll
423+
// Call search_app_vulnerabilities() with useLatestSession=true
408424
log.info(
409-
"Calling listVulnsByAppIdForLatestSession() for app: {} (ID: {})",
425+
"Calling search_app_vulnerabilities(useLatestSession=true) for app: {} (ID: {})",
410426
testData.appName,
411427
testData.appId);
412-
var latestSessionVulns = assessService.listVulnsByAppIdForLatestSession(testData.appId);
428+
var response =
429+
assessService.searchAppVulnerabilities(
430+
testData.appId, // appId
431+
1, // page
432+
50, // pageSize
433+
null, // severities
434+
null, // statuses
435+
null, // vulnTypes
436+
null, // environments
437+
null, // lastSeenAfter
438+
null, // lastSeenBefore
439+
null, // vulnTags
440+
null, // sessionMetadataName
441+
null, // sessionMetadataValue
442+
true); // useLatestSession
413443

444+
assertThat(response).as("Response should not be null").isNotNull();
445+
var latestSessionVulns = response.items();
414446
assertThat(latestSessionVulns).as("Vulnerabilities list should not be null").isNotNull();
415447
log.info(" ✓ Retrieved {} vulnerability(ies) for latest session", latestSessionVulns.size());
416448

@@ -442,8 +474,8 @@ void testListVulnsInAppByNameForLatestSessionWithDynamicSessionId() throws IOExc
442474
withSessionMetadata,
443475
latestSessionVulns.size());
444476
log.info(
445-
"✓ Integration test passed: listVulnsByAppIdForLatestSession() returns vulnerabilities with"
446-
+ " session metadata");
477+
"✓ Integration test passed: search_app_vulnerabilities(useLatestSession=true) returns"
478+
+ " vulnerabilities with session metadata");
447479
}
448480

449481
@Test

0 commit comments

Comments
 (0)