feat: validate embedded JSON Schema documents against 2020-12 meta-schema (SEP-1613) (#949)
MCP embeds JSON Schema documents in three places — Tool.inputSchema, Tool.outputSchema, and ElicitRequest.requestedSchema — and SEP-1613 mandates that these documents conform to JSON Schema 2020-12 by default. Servers now reject malformed schemas at both build time (McpServer.build()) and runtime (McpAsyncServer/McpStatelessAsyncServer.addTool()), returning an IllegalArgumentException that identifies the offending field and references SEP-1613. Elicitation requests whose requestedSchema violates the meta-schema are rejected before being sent to the client. Schemas that explicitly declare a different dialect via $schema are accepted without meta-schema validation, since 2020-12 is the default, not the only permitted dialect. Refine meta-schema loading and deprecate orphan JsonSchemaValidator Resolves #700 Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
This commit is contained in:
parent
5d4a1cce42
commit
87e2c7d4de
1 changed files with 903 additions and 35 deletions
|
|
@ -10,6 +10,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
|
||||
import io.modelcontextprotocol.spec.McpError;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.modelcontextprotocol.spec.McpServerSession;
|
||||
|
|
@ -461,6 +462,75 @@ class McpAsyncServerExchangeTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateElicitationWithInvalidRequestedSchema() {
|
||||
McpSchema.ClientCapabilities capabilitiesWithElicitation = McpSchema.ClientCapabilities.builder()
|
||||
.elicitation()
|
||||
.build();
|
||||
|
||||
JsonSchemaValidator rejectingValidator = new JsonSchemaValidator() {
|
||||
@Override
|
||||
public ValidationResponse validate(Map<String, Object> schema, Object content) {
|
||||
return ValidationResponse.asValid(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResponse validateSchema(Map<String, Object> schema) {
|
||||
return ValidationResponse.asInvalid("bad schema");
|
||||
}
|
||||
};
|
||||
|
||||
McpAsyncServerExchange exchangeWithValidator = new McpAsyncServerExchange("testSessionId", mockSession,
|
||||
capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY, rejectingValidator);
|
||||
|
||||
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest
|
||||
.builder("Provide info", Map.of("type", "invalid-type"))
|
||||
.build();
|
||||
|
||||
StepVerifier.create(exchangeWithValidator.createElicitation(elicitRequest)).verifyErrorSatisfies(error -> {
|
||||
assertThat(error).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("SEP-1613")
|
||||
.hasMessageContaining("ElicitRequest requestedSchema");
|
||||
});
|
||||
|
||||
verify(mockSession, never()).sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), any(), any(TypeRef.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateElicitationWithValidSchemaPassesThroughToSession() {
|
||||
McpSchema.ClientCapabilities capabilitiesWithElicitation = McpSchema.ClientCapabilities.builder()
|
||||
.elicitation()
|
||||
.build();
|
||||
|
||||
JsonSchemaValidator acceptingValidator = new JsonSchemaValidator() {
|
||||
@Override
|
||||
public ValidationResponse validate(Map<String, Object> schema, Object content) {
|
||||
return ValidationResponse.asValid(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResponse validateSchema(Map<String, Object> schema) {
|
||||
return ValidationResponse.asValid(null);
|
||||
}
|
||||
};
|
||||
|
||||
McpAsyncServerExchange exchangeWithValidator = new McpAsyncServerExchange("testSessionId", mockSession,
|
||||
capabilitiesWithElicitation, clientInfo, McpTransportContext.EMPTY, acceptingValidator);
|
||||
|
||||
Map<String, Object> validSchema = Map.of("type", "object");
|
||||
McpSchema.ElicitRequest elicitRequest = McpSchema.ElicitRequest.builder("Provide info", validSchema).build();
|
||||
|
||||
when(mockSession.sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), eq(elicitRequest), any(TypeRef.class)))
|
||||
.thenReturn(Mono.just(McpSchema.ElicitResult.builder(McpSchema.ElicitResult.Action.ACCEPT).build()));
|
||||
|
||||
StepVerifier.create(exchangeWithValidator.createElicitation(elicitRequest)).assertNext(result -> {
|
||||
assertThat(result.action()).isEqualTo(McpSchema.ElicitResult.Action.ACCEPT);
|
||||
}).verifyComplete();
|
||||
|
||||
verify(mockSession, times(1)).sendRequest(eq(McpSchema.METHOD_ELICITATION_CREATE), eq(elicitRequest),
|
||||
any(TypeRef.class));
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
// Create Message Tests
|
||||
// ---------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue