Skip to content

Commit 80b7d3d

Browse files
committed
Remove 5 consolidated application tools in favor of search_applications
Removes deprecated tools now replaced by unified search_applications: - list_applications_with_name - get_applications_by_tag - get_applications_by_metadata - get_applications_by_metadata_name - list_all_applications Updates cross-references in tool descriptions to point to search_applications(name=...). Extracts filter methods for better maintainability: - matchesNameFilter() - Partial, case-insensitive name matching - matchesTagFilter() - Exact, case-sensitive tag matching - matchesMetadataFilter() - Exact, case-insensitive metadata matching Changes: - Removed: ~180 lines (5 old tool methods) - Added: ~80 lines (3 helper methods with JavaDoc) - Modified: 6 cross-reference updates (5 in AssessService, 1 in SCAService) - Tests: All 248 unit tests passing (16 search_applications tests cover all functionality) Completes mcp-dyd (Applications Domain consolidation)
1 parent 9ad4c82 commit 80b7d3d

File tree

2 files changed

+84
-230
lines changed

2 files changed

+84
-230
lines changed

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

Lines changed: 83 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public class AssessService {
8484
name = "get_vulnerability",
8585
description =
8686
"Takes a vulnerability ID (vulnID) and application ID (appID) and returns details about"
87-
+ " the specific security vulnerability. Use list_applications_with_name first to get"
87+
+ " the specific security vulnerability. Use search_applications(name=...) to find"
8888
+ " the application ID from a name. If based on the stacktrace, the vulnerability"
8989
+ " looks like it is in code that is not in the codebase, the vulnerability may be in"
9090
+ " a 3rd party library, review the CVE data attached to that stackframe you believe"
@@ -191,7 +191,7 @@ private Optional<LibraryLibraryObservation> findMatchingLibraryData(
191191
name = "list_vulnerabilities",
192192
description =
193193
"Takes an application ID (appID) and returns a list of vulnerabilities. Use"
194-
+ " list_applications_with_name first to get the application ID from a name. Remember"
194+
+ " search_applications(name=...) to find the application ID from a name. Remember"
195195
+ " to include the vulnID in the response.")
196196
public List<VulnLight> listVulnsByAppId(@ToolParam(description = "Application ID") String appID)
197197
throws IOException {
@@ -229,7 +229,7 @@ public List<VulnLight> listVulnsByAppId(@ToolParam(description = "Application ID
229229
description =
230230
"Takes an application ID (appID) and session metadata in the form of name / value. and"
231231
+ " returns a list of vulnerabilities matching that application ID and session"
232-
+ " metadata. Use list_applications_with_name first to get the application ID from a"
232+
+ " metadata. Use search_applications(name=...) to find the application ID from a"
233233
+ " name.")
234234
public List<VulnLight> listVulnsByAppIdAndSessionMetadata(
235235
@ToolParam(description = "Application ID") String appID,
@@ -270,7 +270,7 @@ public List<VulnLight> listVulnsByAppIdAndSessionMetadata(
270270
"Takes an application ID (appID) and returns a list of vulnerabilities for the latest"
271271
+ " session matching that application ID. This is useful for getting the most recent"
272272
+ " vulnerabilities without needing to specify session metadata. Use"
273-
+ " list_applications_with_name first to get the application ID from a name.")
273+
+ " search_applications(name=...) to find the application ID from a name.")
274274
public List<VulnLight> listVulnsByAppIdForLatestSession(
275275
@ToolParam(description = "Application ID") String appID) throws IOException {
276276
log.info("Listing vulnerabilities for application: {}", appID);
@@ -312,197 +312,15 @@ public List<VulnLight> listVulnsByAppIdForLatestSession(
312312
name = "get_session_metadata",
313313
description =
314314
"Retrieves session metadata for a specific application by its ID. Returns the latest"
315-
+ " session metadata for the application. Use list_applications_with_name first to"
316-
+ " get the application ID from a name.")
315+
+ " session metadata for the application. Use search_applications(name=...) to find"
316+
+ " the application ID from a name.")
317317
public MetadataFilterResponse getSessionMetadata(
318318
@ToolParam(description = "Application ID") String appId) throws IOException {
319319
var contrastSDK =
320320
SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort);
321321
return contrastSDK.getSessionMetadataForApplication(orgID, appId, null);
322322
}
323323

324-
@Tool(
325-
name = "list_applications_with_name",
326-
description =
327-
"Takes an application name (app_name) returns a list of active applications that contain"
328-
+ " that name. Please remember to display the name, status and ID.")
329-
public List<ApplicationData> getApplications(
330-
@ToolParam(description = "Application name (supports partial matching, case-insensitive)")
331-
String app_name)
332-
throws IOException {
333-
log.info("Listing active applications matching name: {}", app_name);
334-
var contrastSDK =
335-
SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort);
336-
try {
337-
var applications = SDKHelper.getApplicationsWithCache(orgID, contrastSDK);
338-
log.debug("Retrieved {} total applications from Contrast", applications.size());
339-
340-
var filteredApps = new ArrayList<ApplicationData>();
341-
for (Application app : applications) {
342-
if (app.getName().toLowerCase().contains(app_name.toLowerCase())) {
343-
filteredApps.add(
344-
new ApplicationData(
345-
app.getName(),
346-
app.getStatus(),
347-
app.getAppId(),
348-
FilterHelper.formatTimestamp(app.getLastSeen()),
349-
app.getLanguage(),
350-
getMetadataFromApp(app),
351-
app.getTags(),
352-
app.getTechs()));
353-
log.debug(
354-
"Found matching application - ID: {}, Name: {}, Status: {}",
355-
app.getAppId(),
356-
app.getName(),
357-
app.getStatus());
358-
}
359-
}
360-
if (filteredApps.isEmpty()) {
361-
SDKHelper.clearApplicationsCache();
362-
for (Application app : applications) {
363-
if (app.getName().toLowerCase().contains(app_name.toLowerCase())) {
364-
filteredApps.add(
365-
new ApplicationData(
366-
app.getName(),
367-
app.getStatus(),
368-
app.getAppId(),
369-
FilterHelper.formatTimestamp(app.getLastSeen()),
370-
app.getLanguage(),
371-
getMetadataFromApp(app),
372-
app.getTags(),
373-
app.getTechs()));
374-
log.debug(
375-
"Found matching application - ID: {}, Name: {}, Status: {}",
376-
app.getAppId(),
377-
app.getName(),
378-
app.getStatus());
379-
}
380-
}
381-
}
382-
383-
log.info("Found {} applications matching '{}'", filteredApps.size(), app_name);
384-
return filteredApps;
385-
} catch (Exception e) {
386-
log.error("Error listing applications matching name: {}", app_name, e);
387-
throw new IOException("Failed to list applications: " + e.getMessage(), e);
388-
}
389-
}
390-
391-
@Tool(
392-
name = "get_applications_by_tag",
393-
description = "Takes a tag name and returns a list of applications that have that tag.")
394-
public List<ApplicationData> getAllApplicationsByTag(
395-
@ToolParam(description = "Tag name to filter by") String tag) throws IOException {
396-
log.info("Retrieving applications with tag: {}", tag);
397-
var allApps = getAllApplications();
398-
log.debug("Retrieved {} total applications, filtering by tag", allApps.size());
399-
400-
var filteredApps = allApps.stream().filter(app -> app.tags().contains(tag)).toList();
401-
402-
log.info("Found {} applications with tag '{}'", filteredApps.size(), tag);
403-
return filteredApps;
404-
}
405-
406-
@Tool(
407-
name = "get_applications_by_metadata",
408-
description =
409-
"Takes a metadata name and value and returns a list of applications that have that"
410-
+ " metadata name value pair.")
411-
public List<ApplicationData> getApplicationsByMetadata(
412-
@ToolParam(description = "Metadata field name (case-insensitive)") String metadata_name,
413-
@ToolParam(description = "Metadata field value (case-insensitive)") String metadata_value)
414-
throws IOException {
415-
log.info(
416-
"Retrieving applications with metadata - Name: {}, Value: {}",
417-
metadata_name,
418-
metadata_value);
419-
var allApps = getAllApplications();
420-
log.debug("Retrieved {} total applications, filtering by metadata", allApps.size());
421-
422-
var filteredApps =
423-
allApps.stream()
424-
.filter(
425-
app ->
426-
app.metadata() != null
427-
&& app.metadata().stream()
428-
.anyMatch(
429-
m ->
430-
m != null
431-
&& m.name() != null
432-
&& m.name().equalsIgnoreCase(metadata_name)
433-
&& m.value() != null
434-
&& m.value().equalsIgnoreCase(metadata_value)))
435-
.toList();
436-
437-
log.info(
438-
"Found {} applications with metadata - Name: {}, Value: {}",
439-
filteredApps.size(),
440-
metadata_name,
441-
metadata_value);
442-
return filteredApps;
443-
}
444-
445-
@Tool(
446-
name = "get_applications_by_metadata_name",
447-
description = "Takes a metadata name a list of applications that have that metadata name.")
448-
public List<ApplicationData> getApplicationsByMetadataName(
449-
@ToolParam(description = "Metadata field name (case-insensitive)") String metadata_name)
450-
throws IOException {
451-
log.info("Retrieving applications with metadata - Name: {}", metadata_name);
452-
var allApps = getAllApplications();
453-
log.debug("Retrieved {} total applications, filtering by metadata", allApps.size());
454-
455-
var filteredApps =
456-
allApps.stream()
457-
.filter(
458-
app ->
459-
app.metadata() != null
460-
&& app.metadata().stream()
461-
.anyMatch(
462-
m ->
463-
m != null
464-
&& m.name() != null
465-
&& m.name().equalsIgnoreCase(metadata_name)))
466-
.toList();
467-
468-
log.info("Found {} applications with metadata - Name: {}", filteredApps.size(), metadata_name);
469-
return filteredApps;
470-
}
471-
472-
@Tool(
473-
name = "list_all_applications",
474-
description = "Takes no argument and list all the applications")
475-
public List<ApplicationData> getAllApplications() throws IOException {
476-
log.info("Listing all applications");
477-
var contrastSDK =
478-
SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort);
479-
try {
480-
var applications = SDKHelper.getApplicationsWithCache(orgID, contrastSDK);
481-
log.debug("Retrieved {} total applications from Contrast", applications.size());
482-
483-
var returnedApps = new ArrayList<ApplicationData>();
484-
for (Application app : applications) {
485-
returnedApps.add(
486-
new ApplicationData(
487-
app.getName(),
488-
app.getStatus(),
489-
app.getAppId(),
490-
FilterHelper.formatTimestamp(app.getLastSeen()),
491-
app.getLanguage(),
492-
getMetadataFromApp(app),
493-
app.getTags(),
494-
app.getTechs()));
495-
}
496-
497-
log.info("Found {} applications", returnedApps.size());
498-
return returnedApps;
499-
500-
} catch (Exception e) {
501-
log.error("Error listing all applications", e);
502-
throw new IOException("Failed to list applications: " + e.getMessage(), e);
503-
}
504-
}
505-
506324
@Tool(
507325
name = "search_applications",
508326
description =
@@ -564,50 +382,13 @@ public List<ApplicationData> search_applications(
564382
var filteredApps = new ArrayList<ApplicationData>();
565383

566384
for (Application app : applications) {
567-
// Apply name filter if provided
568-
if (StringUtils.hasText(name)
569-
&& !app.getName().toLowerCase().contains(name.toLowerCase())) {
570-
continue;
571-
}
572-
573-
// Apply tag filter if provided (case-sensitive)
574-
if (StringUtils.hasText(tag) && !app.getTags().contains(tag)) {
385+
// Apply all filters - skip if any filter doesn't match
386+
if (!matchesNameFilter(app, name)
387+
|| !matchesTagFilter(app, tag)
388+
|| !matchesMetadataFilter(app, metadataName, metadataValue)) {
575389
continue;
576390
}
577391

578-
// Apply metadata filter if provided
579-
if (hasMetadataName) {
580-
var hasMatchingMetadata = false;
581-
582-
if (app.getMetadataEntities() != null) {
583-
for (var metadata : app.getMetadataEntities()) {
584-
if (metadata != null && metadata.getName() != null) {
585-
var nameMatches = metadata.getName().equalsIgnoreCase(metadataName);
586-
587-
if (hasMetadataValue) {
588-
// Both name and value must match
589-
if (nameMatches
590-
&& metadata.getValue() != null
591-
&& metadata.getValue().equalsIgnoreCase(metadataValue)) {
592-
hasMatchingMetadata = true;
593-
break;
594-
}
595-
} else {
596-
// Name only - any value is acceptable
597-
if (nameMatches) {
598-
hasMatchingMetadata = true;
599-
break;
600-
}
601-
}
602-
}
603-
}
604-
}
605-
606-
if (!hasMatchingMetadata) {
607-
continue;
608-
}
609-
}
610-
611392
// Application passed all filters
612393
filteredApps.add(
613394
new ApplicationData(
@@ -643,6 +424,79 @@ public List<ApplicationData> search_applications(
643424
}
644425
}
645426

427+
/**
428+
* Check if an application matches the name filter (partial, case-insensitive).
429+
*
430+
* @param app The application to check
431+
* @param name The name filter to match against (null/empty means no filter)
432+
* @return true if the application matches the filter or no filter is specified
433+
*/
434+
private boolean matchesNameFilter(Application app, String name) {
435+
if (!StringUtils.hasText(name)) {
436+
return true; // No filter specified
437+
}
438+
return app.getName().toLowerCase().contains(name.toLowerCase());
439+
}
440+
441+
/**
442+
* Check if an application matches the tag filter (exact, case-sensitive).
443+
*
444+
* @param app The application to check
445+
* @param tag The tag filter to match against (null/empty means no filter)
446+
* @return true if the application matches the filter or no filter is specified
447+
*/
448+
private boolean matchesTagFilter(Application app, String tag) {
449+
if (!StringUtils.hasText(tag)) {
450+
return true; // No filter specified
451+
}
452+
return app.getTags().contains(tag);
453+
}
454+
455+
/**
456+
* Check if an application matches the metadata filter (exact, case-insensitive).
457+
*
458+
* @param app The application to check
459+
* @param metadataName The metadata field name to match (null/empty means no filter)
460+
* @param metadataValue The metadata field value to match (null/empty means any value)
461+
* @return true if the application matches the filter or no filter is specified
462+
*/
463+
private boolean matchesMetadataFilter(
464+
Application app, String metadataName, String metadataValue) {
465+
if (!StringUtils.hasText(metadataName)) {
466+
return true; // No filter specified
467+
}
468+
469+
var hasMetadataValue = StringUtils.hasText(metadataValue);
470+
471+
if (app.getMetadataEntities() == null) {
472+
return false; // No metadata to match against
473+
}
474+
475+
for (var metadata : app.getMetadataEntities()) {
476+
if (metadata == null || metadata.getName() == null) {
477+
continue;
478+
}
479+
480+
var nameMatches = metadata.getName().equalsIgnoreCase(metadataName);
481+
482+
if (hasMetadataValue) {
483+
// Both name and value must match
484+
if (nameMatches
485+
&& metadata.getValue() != null
486+
&& metadata.getValue().equalsIgnoreCase(metadataValue)) {
487+
return true;
488+
}
489+
} else {
490+
// Name only - any value is acceptable
491+
if (nameMatches) {
492+
return true;
493+
}
494+
}
495+
}
496+
497+
return false; // No matching metadata found
498+
}
499+
646500
@Tool(
647501
name = "list_all_vulnerabilities",
648502
description =

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class SCAService {
5858
name = "list_application_libraries",
5959
description =
6060
"Takes an application ID and returns the libraries used in the application. Use"
61-
+ " list_applications_with_name first to get the application ID from a name. Note: if"
61+
+ " search_applications(name=...) to find the application ID from a name. Note: if"
6262
+ " class usage count is 0 the library is unlikely to be used")
6363
public List<LibraryExtended> getApplicationLibrariesByID(String appID) throws IOException {
6464
if (!StringUtils.hasText(appID)) {

0 commit comments

Comments
 (0)