Skip to content

Commit d01f138

Browse files
Added the following.
Ability to retrieve applications based on Metadata Ability to retreive applications based on Tags Ability to retrieve route coverage and vulnerabilities based on Session Metadata. Either requesting a specifc metadata value or the latest session. Fixed an issue which application retrieval and caching. Now if the application cannot be found in the cache, a check is made to the API. Allow users to specify if Teamserver is run on http/https. It defaults to https. But for local teamserver installations http is now an option. Added Technologies as information returned in the Application.
1 parent 8fd792c commit d01f138

25 files changed

+1462
-113
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ Contrast's MCP server allows you as a developer or security professional to quic
4242

4343
* Which libraries in Application X are not being used?
4444

45+
#### Retrieving application based on Tags
46+
* please give me the applications tagged with "backend"
47+
48+
#### Retrieving application based on Metadata
49+
* please give me the applications with metadata "dev-team" "backend-team"
50+
51+
#### Retrieving vulnerabilities based on Session Metadata
52+
* give me the sesssion metadata for application x
53+
* give me the vulnerabilities in the latest session for application X
54+
* give me the vulnerabilities for session metadata "Branch Name" "feature/some-new-fix" for application X
55+
* give me the route coverage for the latest session for application X
56+
* give me the route coverage for session metadata "Branch Name" "feature/some-new-fix" for application X
57+
58+
4559
### For the Security Professional
4660
* Please give me a breakdown of applications and servers vulnerable to CVE-xxxx-xxxx
4761
* Please list the libraries for application named xxx and tell me what version of commons-collections is being used

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.contrast.labs.ai.mcp.contrast.sdkexstension.SDKExtension;
1919
import com.contrast.labs.ai.mcp.contrast.sdkexstension.SDKHelper;
2020
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.ProtectData;
21+
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.application.Application;
2122
import com.contrastsecurity.sdk.ContrastSDK;
2223
import org.slf4j.Logger;
2324
import org.slf4j.LoggerFactory;
@@ -26,6 +27,7 @@
2627
import org.springframework.stereotype.Service;
2728

2829
import java.io.IOException;
30+
import java.util.Optional;
2931

3032
@Service
3133
public class ADRService {
@@ -66,14 +68,14 @@ public ProtectData getProtectData(String applicationName) throws IOException {
6668

6769
// Get application ID from name
6870
logger.debug("Looking up application ID for name: {}", applicationName);
69-
String appID = SDKHelper.getAppIDFromName(applicationName, orgID, contrastSDK);
70-
if (appID == null || appID.isEmpty()) {
71+
Optional<Application> app = SDKHelper.getApplicationByName(applicationName, orgID, contrastSDK);
72+
if (app.isEmpty()) {
7173
logger.warn("No application ID found for application: {}", applicationName);
7274
return null;
7375
}
74-
logger.debug("Found application ID: {} for application: {}", appID, applicationName);
76+
logger.debug("Found application ID: {} for application: {}", app.get().getAppId(), applicationName);
7577

76-
ProtectData result = getProtectDataByAppID(appID);
78+
ProtectData result = getProtectDataByAppID(app.get().getAppId());
7779
long duration = System.currentTimeMillis() - startTime;
7880
logger.info("Completed retrieval of protection rules for application: {} (took {} ms)", applicationName, duration);
7981
return result;

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

Lines changed: 191 additions & 48 deletions
Large diffs are not rendered by default.

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

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import com.contrast.labs.ai.mcp.contrast.sdkexstension.SDKExtension;
44
import com.contrast.labs.ai.mcp.contrast.sdkexstension.SDKHelper;
5+
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.application.Application;
56
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.routecoverage.Route;
7+
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.routecoverage.RouteCoverageBySessionIDAndMetadataRequestExtended;
68
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.routecoverage.RouteCoverageResponse;
79
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.routecoverage.RouteDetailsResponse;
8-
import com.contrastsecurity.models.Application;
10+
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.sessionmetadata.SessionMetadataResponse;
11+
import com.contrastsecurity.models.RouteCoverageBySessionIDAndMetadataRequest;
12+
import com.contrastsecurity.models.RouteCoverageMetadataLabelValues;
913
import com.contrastsecurity.sdk.ContrastSDK;
1014
import org.slf4j.Logger;
1115
import org.slf4j.LoggerFactory;
@@ -52,30 +56,23 @@ public RouteCoverageResponse getRouteCoverage(String app_name) throws IOExceptio
5256
logger.info("Retrieving route coverage for application by name: {}", app_name);
5357
ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort);
5458
SDKExtension sdkExtension = new SDKExtension(contrastSDK);
55-
Optional<String> appID = Optional.empty();
5659
logger.debug("Searching for application ID matching name: {}", app_name);
5760

58-
for(Application app : SDKHelper.getApplicationsWithCache(orgID, contrastSDK)) {
59-
if(app.getName().toLowerCase().contains(app_name.toLowerCase())) {
60-
appID = Optional.of(app.getId());
61-
logger.debug("Found matching application with ID: {}", appID.get());
62-
break;
63-
}
64-
}
61+
Optional<Application> application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK);
6562

66-
if (!appID.isPresent()) {
63+
if (!application.isPresent()) {
6764
logger.error("Application not found: {}", app_name);
6865
throw new IOException("Application not found: " + app_name);
6966
}
7067

71-
logger.debug("Fetching route coverage data for application ID: {}", appID.get());
72-
RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, appID.get(), null);
68+
logger.debug("Fetching route coverage data for application ID: {}", application.get().getAppId());
69+
RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, application.get().getAppId(), null);
7370
logger.debug("Found {} routes for application", response.getRoutes().size());
7471

7572
logger.debug("Retrieving route details for each route");
7673
for(Route route : response.getRoutes()) {
7774
logger.trace("Fetching details for route: {}", route.getSignature());
78-
RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, appID.get(), route.getRouteHash());
75+
RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, application.get().getAppId(), route.getRouteHash());
7976
route.setRouteDetailsResponse(routeDetailsResponse);
8077
}
8178

@@ -105,6 +102,86 @@ public RouteCoverageResponse getRouteCoverageByAppID(String app_id) throws IOExc
105102
return response;
106103
}
107104

105+
@Tool(name = "get_application_route_coverage_by_app_name_and_session_metadata", description = "takes a application name and return the route coverage data for that application for the specified session metadata name and value. " +
106+
"If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had at least one inbound http request to that route/endpoint.")
107+
public RouteCoverageResponse getRouteCoverageByAppNameAndSessionMetadata(String app_name, String session_Metadata_Name, String session_Metadata_Value) throws IOException {
108+
logger.info("Retrieving route coverage for application by Name: {}", app_name);
109+
ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort);
110+
logger.debug("Searching for application ID matching name: {}", app_name);
111+
112+
Optional<Application> application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK);
113+
if (!application.isPresent()) {
114+
logger.error("Application not found: {}", app_name);
115+
throw new IOException("Application not found: " + app_name);
116+
}
117+
return getRouteCoverageByAppIDAndSessionMetadata(application.get().getAppId(), session_Metadata_Name, session_Metadata_Value);
118+
}
119+
120+
@Tool(name = "get_application_route_coverage_by_app_id_and_session_metadata", description = "takes a application id and return the route coverage data for that application for the specified session metadata name and value. " +
121+
"If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had at least one inbound http request to that route/endpoint.")
122+
public RouteCoverageResponse getRouteCoverageByAppIDAndSessionMetadata(String app_id, String session_Metadata_Name, String session_Metadata_Value) throws IOException {
123+
logger.info("Retrieving route coverage for application by ID: {}", app_id);
124+
ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort);
125+
SDKExtension sdkExtension = new SDKExtension(contrastSDK);
126+
RouteCoverageBySessionIDAndMetadataRequestExtended requestExtended = new RouteCoverageBySessionIDAndMetadataRequestExtended();
127+
RouteCoverageMetadataLabelValues metadataLabelValue = new RouteCoverageMetadataLabelValues();
128+
metadataLabelValue.setLabel(session_Metadata_Name);
129+
metadataLabelValue.getValues().add(String.valueOf(session_Metadata_Value));
130+
requestExtended.getValues().add(metadataLabelValue);
131+
logger.debug("Fetching route coverage data for application ID: {}", app_id);
132+
RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, app_id, requestExtended);
133+
logger.debug("Found {} routes for application", response.getRoutes().size());
134+
135+
logger.debug("Retrieving route details for each route");
136+
for(Route route : response.getRoutes()) {
137+
logger.trace("Fetching details for route: {}", route.getSignature());
138+
RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, app_id, route.getRouteHash());
139+
route.setRouteDetailsResponse(routeDetailsResponse);
140+
}
141+
142+
logger.info("Successfully retrieved route coverage for application ID: {}", app_id);
143+
return response;
144+
}
145+
146+
@Tool(name = "get_application_route_coverage_by_app_name_latest_session", description = "takes a application name and return the route coverage data for that application from the latest session. " +
147+
"If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had atleast one inbound http request to that route/endpoint.")
148+
public RouteCoverageResponse getRouteCoverageByAppNameLatestSession(String app_name) throws IOException {
149+
logger.info("Retrieving route coverage for application by Name: {}", app_name);
150+
ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort);
151+
Optional<Application> application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK);
152+
if (application.isEmpty()) {
153+
logger.error("Application not found: {}", app_name);
154+
throw new IOException("Application not found: " + app_name);
155+
}
156+
return getRouteCoverageByAppIDLatestSession(application.get().getAppId());
157+
}
158+
159+
160+
@Tool(name = "get_application_route_coverage_by_app_id_latest_session", description = "takes a application id and return the route coverage data for that application from the latest session. " +
161+
"If a route/endpoint is DISCOVERED, it means it has been found by Assess but that route has had no inbound http requests. If it is EXERCISED, it means it has had atleast one inbound http request to that route/endpoint.")
162+
public RouteCoverageResponse getRouteCoverageByAppIDLatestSession(String app_id) throws IOException {
163+
logger.info("Retrieving route coverage for application by ID: {}", app_id);
164+
ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort);
165+
SDKExtension sdkExtension = new SDKExtension(contrastSDK);
166+
SDKExtension extension = new SDKExtension(contrastSDK);
167+
SessionMetadataResponse latest = extension.getLatestSessionMetadata(orgID,app_id);
168+
RouteCoverageBySessionIDAndMetadataRequestExtended requestExtended = new RouteCoverageBySessionIDAndMetadataRequestExtended();
169+
requestExtended.setSessionId(latest.getAgentSession().getAgentSessionId());
170+
logger.debug("Fetching route coverage data for application ID: {}", app_id);
171+
RouteCoverageResponse response = sdkExtension.getRouteCoverage(orgID, app_id, requestExtended);
172+
logger.debug("Found {} routes for application", response.getRoutes().size());
173+
174+
logger.debug("Retrieving route details for each route");
175+
for(Route route : response.getRoutes()) {
176+
logger.trace("Fetching details for route: {}", route.getSignature());
177+
RouteDetailsResponse routeDetailsResponse = sdkExtension.getRouteDetails(orgID, app_id, route.getRouteHash());
178+
route.setRouteDetailsResponse(routeDetailsResponse);
179+
}
180+
181+
logger.info("Successfully retrieved route coverage for application ID: {}", app_id);
182+
return response;
183+
}
184+
108185

109186

110187

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.CveData;
2222
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.Library;
2323
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.LibraryExtended;
24+
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.application.Application;
2425
import com.contrastsecurity.http.LibraryFilterForm;
25-
import com.contrastsecurity.models.Application;
2626
import com.contrastsecurity.sdk.ContrastSDK;
2727
import org.slf4j.Logger;
2828
import org.slf4j.LoggerFactory;
@@ -62,7 +62,7 @@ public class SCAService {
6262
private String httpProxyPort;
6363

6464

65-
@Tool(name = "list_application_libraries_by_app_id", description = "takes a application ID and returns the libraries used in the application, note if class usage count is 0 the library is unlikely to be used")
65+
@Tool(name = "list_application_libraries_by_app_id", description = "Takes a application ID and returns the libraries used in the application, note if class usage count is 0 the library is unlikely to be used")
6666
public List<LibraryExtended> getApplicationLibrariesByID(String appID) throws IOException {
6767
logger.info("Retrieving libraries for application id: {}", appID);
6868
ContrastSDK contrastSDK = SDKHelper.getSDK(hostName, apiKey, serviceKey, userName,httpProxyHost, httpProxyPort);
@@ -81,18 +81,11 @@ public List<LibraryExtended> getApplicationLibraries(String app_name) throws IOE
8181
logger.debug("ContrastSDK initialized with host: {}", hostName);
8282

8383
SDKExtension extendedSDK = new SDKExtension(contrastSDK);
84-
Optional<String> appID = Optional.empty();
8584
logger.debug("Searching for application ID matching name: {}", app_name);
86-
87-
for(Application app : SDKHelper.getApplicationsWithCache(orgID, contrastSDK)) {
88-
if(app.getName().toLowerCase().contains(app_name.toLowerCase())) {
89-
appID = Optional.of(app.getId());
90-
logger.info("Found matching application - ID: {}, Name: {}", app.getId(), app.getName());
91-
break;
92-
}
93-
}
94-
if(appID.isPresent()) {
95-
return SDKHelper.getLibsForID(appID.get(),orgID, extendedSDK);
85+
86+
Optional<Application> application = SDKHelper.getApplicationByName(app_name, orgID, contrastSDK);
87+
if(application.isPresent()) {
88+
return SDKHelper.getLibsForID(application.get().getAppId(),orgID, extendedSDK);
9689
} else {
9790
logger.error("Application not found: {}", app_name);
9891
throw new IOException("Application not found");

src/main/java/com/contrast/labs/ai/mcp/contrast/data/ApplicationData.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
*/
1616
package com.contrast.labs.ai.mcp.contrast.data;
1717

18-
import java.util.Date;
18+
import java.util.List;
1919

20-
public record ApplicationData(String name, String status, String appID, long lastSeen, String lastSeenDate, String language) {
20+
public record ApplicationData(String name, String status, String appID,
21+
long lastSeen, String lastSeenDate,
22+
String language, List<Metadata> metadata,List<String> tags,
23+
List<String> technologies) {
2124
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.contrast.labs.ai.mcp.contrast.data;
2+
3+
public record Metadata(String name,String value) {
4+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.contrast.labs.ai.mcp.contrast.data;
2+
3+
public class TraceFilterBody extends com.contrastsecurity.models.TraceFilterBody {
4+
5+
private String agentSessionId;
6+
7+
public String getAgentSessionId() {
8+
return agentSessionId;
9+
}
10+
11+
public void setAgentSessionId(String agentSessionId) {
12+
this.agentSessionId = agentSessionId;
13+
}
14+
}

src/main/java/com/contrast/labs/ai/mcp/contrast/data/VulnLight.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@
1515
*/
1616
package com.contrast.labs.ai.mcp.contrast.data;
1717

18-
public record VulnLight(String title, String type, String vulnID,String severity) {
18+
import com.contrast.labs.ai.mcp.contrast.sdkexstension.data.traces.SessionMetadata;
19+
20+
import java.util.List;
21+
22+
public record VulnLight(String title, String type, String vulnID, String severity, List<SessionMetadata> sessionMetadata, String lastSeenDate, long lastSeenTime ) {
1923
}

0 commit comments

Comments
 (0)