Skip to content

Commit edb3567

Browse files
ChrisEdwardsclaude
andcommitted
Fix route coverage parameter validation for sessionMetadataValue
Add symmetric validation to reject sessionMetadataValue when provided without sessionMetadataName. Previously, the tool silently ignored sessionMetadataValue in this scenario, causing users to receive unfiltered results without any indication of the misconfiguration. Changes: - Added validation check rejecting null/empty sessionMetadataName with non-null sessionMetadataValue (mirrors existing reverse check) - Updated @tool description with explicit note about parameter dependency - Updated JavaDoc for both parameters stating "Must be provided with..." - Updated @throws documentation to cover both validation directions Testing: - Added testGetRouteCoverage_SessionMetadataValue_WithoutName() test - Added testGetRouteCoverage_SessionMetadataValue_WithEmptyName() test - All 327 tests passing (26 RouteCoverageService tests) Users now receive immediate, clear error messages instead of silently incorrect results. AI models consuming the tool see explicit documentation about the parameter dependency. Addresses mcp-rlc. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent e9c43e7 commit edb3567

File tree

2 files changed

+46
-5
lines changed

2 files changed

+46
-5
lines changed

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ public class RouteCoverageService {
4545
*
4646
* @param appId Required - The application ID to retrieve route coverage for
4747
* @param sessionMetadataName Optional - Filter by session metadata field name (e.g., "branch").
48-
* Empty strings are treated as null (no filter).
48+
* Must be provided with sessionMetadataValue. Empty strings are treated as null (no filter).
4949
* @param sessionMetadataValue Optional - Filter by session metadata field value (e.g., "main").
50-
* Required if sessionMetadataName is provided. Empty strings are treated as null.
50+
* Must be provided with sessionMetadataName. Empty strings are treated as null.
5151
* @param useLatestSession Optional - If true, only return routes from the latest session
5252
* @return RouteCoverageResponse containing route coverage data with details for each route
5353
* @throws IOException If an error occurs while retrieving data from Contrast
5454
* @throws IllegalArgumentException If sessionMetadataName is provided without
55-
* sessionMetadataValue
55+
* sessionMetadataValue, or if sessionMetadataValue is provided without sessionMetadataName
5656
*/
5757
@Tool(
5858
name = "get_route_coverage",
@@ -61,8 +61,9 @@ public class RouteCoverageService {
6161
+ " not exercised) or EXERCISED (received HTTP traffic). All filter parameters are"
6262
+ " truly optional - if none provided (null or empty strings), returns all routes"
6363
+ " across all sessions. Parameters: appId (required), sessionMetadataName"
64-
+ " (optional), sessionMetadataValue (optional - required if sessionMetadataName"
65-
+ " provided), useLatestSession (optional).")
64+
+ " (optional), sessionMetadataValue (optional). NOTE: sessionMetadataName and"
65+
+ " sessionMetadataValue must be provided together or both omitted - providing"
66+
+ " only one will result in an error. useLatestSession (optional).")
6667
public RouteCoverageResponse getRouteCoverage(
6768
String appId,
6869
String sessionMetadataName,
@@ -79,6 +80,13 @@ public RouteCoverageResponse getRouteCoverage(
7980
throw new IllegalArgumentException(errorMsg);
8081
}
8182

83+
// Validate sessionMetadataValue requires sessionMetadataName
84+
if (StringUtils.hasText(sessionMetadataValue) && !StringUtils.hasText(sessionMetadataName)) {
85+
var errorMsg = "sessionMetadataName is required when sessionMetadataValue is provided";
86+
log.error(errorMsg);
87+
throw new IllegalArgumentException(errorMsg);
88+
}
89+
8290
// Initialize SDK
8391
var contrastSDK =
8492
SDKHelper.getSDK(hostName, apiKey, serviceKey, userName, httpProxyHost, httpProxyPort);

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,39 @@ void testGetRouteCoverage_SessionMetadataFilter_EmptyValue() throws Exception {
311311
verify(mockSDKExtension, never()).getRouteCoverage(anyString(), anyString(), any());
312312
}
313313

314+
@Test
315+
void testGetRouteCoverage_SessionMetadataValue_WithoutName() throws Exception {
316+
// Test validation with sessionMetadataValue provided but sessionMetadataName null (MCP-RLC)
317+
// Act & Assert
318+
assertThatThrownBy(
319+
() -> {
320+
routeCoverageService.getRouteCoverage(TEST_APP_ID, null, TEST_METADATA_VALUE, null);
321+
})
322+
.isInstanceOf(IllegalArgumentException.class)
323+
.hasMessageContaining(
324+
"sessionMetadataName is required when sessionMetadataValue is provided");
325+
326+
// Verify SDK was never called
327+
verify(mockSDKExtension, never()).getRouteCoverage(anyString(), anyString(), any());
328+
}
329+
330+
@Test
331+
void testGetRouteCoverage_SessionMetadataValue_WithEmptyName() throws Exception {
332+
// Test validation with empty string sessionMetadataName and sessionMetadataValue provided
333+
// (MCP-RLC)
334+
// Act & Assert
335+
assertThatThrownBy(
336+
() -> {
337+
routeCoverageService.getRouteCoverage(TEST_APP_ID, "", TEST_METADATA_VALUE, null);
338+
})
339+
.isInstanceOf(IllegalArgumentException.class)
340+
.hasMessageContaining(
341+
"sessionMetadataName is required when sessionMetadataValue is provided");
342+
343+
// Verify SDK was never called
344+
verify(mockSDKExtension, never()).getRouteCoverage(anyString(), anyString(), any());
345+
}
346+
314347
// ========== Test Case 3: Latest Session Filter ==========
315348

316349
@Test

0 commit comments

Comments
 (0)