feat!: add tool input arguments validation (#873)

added tool input arguments validation causes tool execution error. Breaking change, because validation is activated by default

closes #697

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
ashakirin 2026-04-13 16:58:04 +02:00 • committed by GitHub
commit d1823389a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -0,0 +1,98 @@
/*
* Copyright 2026-2026 the original author or authors.
*/
package io.modelcontextprotocol.util;
import java.util.List;
import java.util.Map;
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import io.modelcontextprotocol.json.schema.JsonSchemaValidator.ValidationResponse;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link ToolInputValidator}.
*
* @author Andrei Shakirin
*/
class ToolInputValidatorTests {
private final JsonSchemaValidator validator = mock(JsonSchemaValidator.class);
private final Map<String, Object> inputSchema = Map.of("type", "object", "properties",
Map.of("name", Map.of("type", "string")), "required", List.of("name"));
private final Tool toolWithSchema = Tool.builder()
.name("test-tool")
.description("Test tool")
.inputSchema(inputSchema)
.build();
private final Tool toolWithoutSchema = Tool.builder().name("test-tool").description("Test tool").build();
@Test
void validate_whenDisabled_returnsNull() {
CallToolResult result = ToolInputValidator.validate(toolWithSchema, Map.of("name", "test"), false, validator);
assertThat(result).isNull();
verify(validator, never()).validate(any(), any());
}
@Test
void validate_whenNoSchema_returnsNull() {
CallToolResult result = ToolInputValidator.validate(toolWithoutSchema, Map.of("name", "test"), true, validator);
assertThat(result).isNull();
verify(validator, never()).validate(any(), any());
}
@Test
void validate_whenNoValidator_returnsNull() {
CallToolResult result = ToolInputValidator.validate(toolWithSchema, Map.of("name", "test"), true, null);
assertThat(result).isNull();
}
@Test
void validate_withValidInput_returnsNull() {
when(validator.validate(any(), any())).thenReturn(ValidationResponse.asValid(null));
CallToolResult result = ToolInputValidator.validate(toolWithSchema, Map.of("name", "test"), true, validator);
assertThat(result).isNull();
}
@Test
void validate_withInvalidInput_returnsErrorResult() {
when(validator.validate(any(), any())).thenReturn(ValidationResponse.asInvalid("missing required: 'name'"));
CallToolResult result = ToolInputValidator.validate(toolWithSchema, Map.of(), true, validator);
assertThat(result).isNotNull();
assertThat(result.isError()).isTrue();
assertThat(((TextContent) result.content().get(0)).text()).contains("missing required: 'name'");
verify(validator).validate(any(), any());
}
@Test
void validate_withNullArguments_usesEmptyMap() {
when(validator.validate(any(), any())).thenReturn(ValidationResponse.asValid(null));
CallToolResult result = ToolInputValidator.validate(toolWithSchema, null, true, validator);
assertThat(result).isNull();
verify(validator).validate(any(), any());
}
}