Skip to content

Commit 99faac6

Browse files
authored
Clarify tool validation error messages (#1023)
- DefaultJsonSchemaValidator now emits a neutral message; - Call sites in ToolInputValidator and the server output handlers prepend their own context prefix, - Integration test assertions strengthened accordingly. Closes #986 Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com> * address review comments Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com> * address review comments Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com> --------- Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
1 parent df75857 commit 99faac6

8 files changed

Lines changed: 30 additions & 25 deletions

File tree

mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
2727
import io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion;
2828
import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
29-
import io.modelcontextprotocol.spec.McpSchema.LoggingLevel;
3029
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
3130
import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
3231
import io.modelcontextprotocol.spec.McpSchema.SetLevelRequest;
@@ -433,9 +432,11 @@ public Mono<CallToolResult> apply(McpAsyncServerExchange exchange, McpSchema.Cal
433432
var validation = this.jsonSchemaValidator.validate(outputSchema, result.structuredContent());
434433

435434
if (!validation.valid()) {
436-
logger.warn("Tool call result validation failed: {}", validation.errorMessage());
435+
String message = "Tool (" + request.name() + ") output validation failed: "
436+
+ validation.errorMessage();
437+
logger.warn(message);
437438
return CallToolResult.builder()
438-
.content(List.of(McpSchema.TextContent.builder(validation.errorMessage()).build()))
439+
.content(List.of(McpSchema.TextContent.builder(message).build()))
439440
.isError(true)
440441
.build();
441442
}

mcp-core/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,19 @@
44

55
package io.modelcontextprotocol.server;
66

7-
import io.modelcontextprotocol.json.TypeRef;
8-
import io.modelcontextprotocol.json.McpJsonMapper;
7+
import java.time.Duration;
8+
import java.util.ArrayList;
9+
import java.util.HashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Optional;
13+
import java.util.concurrent.ConcurrentHashMap;
14+
import java.util.concurrent.CopyOnWriteArrayList;
15+
import java.util.function.BiFunction;
16+
917
import io.modelcontextprotocol.common.McpTransportContext;
18+
import io.modelcontextprotocol.json.McpJsonMapper;
19+
import io.modelcontextprotocol.json.TypeRef;
1020
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
1121
import io.modelcontextprotocol.server.McpStatelessServerFeatures.AsyncResourceTemplateSpecification;
1222
import io.modelcontextprotocol.spec.McpError;
@@ -28,16 +38,6 @@
2838
import reactor.core.publisher.Flux;
2939
import reactor.core.publisher.Mono;
3040

31-
import java.time.Duration;
32-
import java.util.ArrayList;
33-
import java.util.HashMap;
34-
import java.util.List;
35-
import java.util.Map;
36-
import java.util.Optional;
37-
import java.util.concurrent.ConcurrentHashMap;
38-
import java.util.concurrent.CopyOnWriteArrayList;
39-
import java.util.function.BiFunction;
40-
4141
import static io.modelcontextprotocol.spec.McpError.RESOURCE_NOT_FOUND;
4242

4343
/**
@@ -293,9 +293,11 @@ public Mono<CallToolResult> apply(McpTransportContext transportContext, McpSchem
293293
var validation = this.jsonSchemaValidator.validate(outputSchema, result.structuredContent());
294294

295295
if (!validation.valid()) {
296-
logger.warn("Tool call result validation failed: {}", validation.errorMessage());
296+
String message = "Tool (" + request.name() + ") output validation failed: "
297+
+ validation.errorMessage();
298+
logger.warn(message);
297299
return CallToolResult.builder()
298-
.content(List.of(McpSchema.TextContent.builder(validation.errorMessage()).build()))
300+
.content(List.of(McpSchema.TextContent.builder(message).build()))
299301
.isError(true)
300302
.build();
301303
}

mcp-core/src/main/java/io/modelcontextprotocol/util/ToolInputValidator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ public static CallToolResult validate(McpSchema.Tool tool, Map<String, Object> a
4242
Map<String, Object> args = arguments != null ? arguments : Map.of();
4343
var validation = validator.validate(tool.inputSchema(), args);
4444
if (!validation.valid()) {
45-
logger.warn("Tool '{}' input validation failed: {}", tool.name(), validation.errorMessage());
45+
String message = "Tool (" + tool.name() + ") input validation failed: " + validation.errorMessage();
46+
logger.warn(message);
4647
return CallToolResult.builder()
47-
.content(List.of(McpSchema.TextContent.builder(validation.errorMessage()).build()))
48+
.content(List.of(McpSchema.TextContent.builder(message).build()))
4849
.isError(true)
4950
.build();
5051
}

mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson2/DefaultJsonSchemaValidator.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ public ValidationResponse validate(Map<String, Object> schema, Object structured
7676
// Check if validation passed
7777
if (!validationResult.isEmpty()) {
7878
return ValidationResponse
79-
.asInvalid("Validation failed: structuredContent does not match tool outputSchema. "
80-
+ "Validation errors: " + validationResult);
79+
.asInvalid("Validation failed: JSON schema validation errors: " + validationResult);
8180
}
8281

8382
return ValidationResponse.asValid(jsonStructuredOutput.toString());

mcp-json-jackson2/src/test/java/io/modelcontextprotocol/json/jackson2/DefaultJsonSchemaValidatorTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ void testValidateWithInvalidTypeSchema() {
308308
assertFalse(response.valid());
309309
assertNotNull(response.errorMessage());
310310
assertTrue(response.errorMessage().contains("Validation failed"));
311-
assertTrue(response.errorMessage().contains("structuredContent does not match tool outputSchema"));
311+
assertTrue(response.errorMessage().contains("JSON schema validation errors"));
312312
}
313313

314314
@Test

mcp-json-jackson3/src/main/java/io/modelcontextprotocol/json/schema/jackson3/DefaultJsonSchemaValidator.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ public ValidationResponse validate(Map<String, Object> schema, Object structured
7575
// Check if validation passed
7676
if (!validationResult.isEmpty()) {
7777
return ValidationResponse
78-
.asInvalid("Validation failed: structuredContent does not match tool outputSchema. "
79-
+ "Validation errors: " + validationResult);
78+
.asInvalid("Validation failed: JSON schema validation errors: " + validationResult);
8079
}
8180

8281
return ValidationResponse.asValid(jsonStructuredOutput.toString());

mcp-json-jackson3/src/test/java/io/modelcontextprotocol/json/DefaultJsonSchemaValidatorTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ void testValidateWithInvalidTypeSchema() {
308308
assertFalse(response.valid());
309309
assertNotNull(response.errorMessage());
310310
assertTrue(response.errorMessage().contains("Validation failed"));
311-
assertTrue(response.errorMessage().contains("structuredContent does not match tool outputSchema"));
311+
assertTrue(response.errorMessage().contains("JSON schema validation errors"));
312312
}
313313

314314
@Test

mcp-test/src/test/java/io/modelcontextprotocol/server/ToolInputValidationIntegrationTests.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ void invalidInput_withDefaultValidation_shouldReturnToolError(String serverType,
182182

183183
assertThat(result.isError()).isTrue();
184184
String errorMessage = ((TextContent) result.content().get(0)).text();
185+
assertThat(errorMessage).startsWith("Tool (test-tool) input validation failed:");
186+
assertThat(errorMessage).containsIgnoringCase("Validation failed");
187+
assertThat(errorMessage).containsIgnoringCase("JSON schema validation errors");
185188
assertThat(errorMessage).containsIgnoringCase(expectedErrorSubstring);
186189
}
187190
finally {

0 commit comments

Comments
 (0)