Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import io.modelcontextprotocol.util.Assert;
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
import io.modelcontextprotocol.util.ToolInputValidator;
import io.modelcontextprotocol.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -98,6 +99,8 @@ public class McpAsyncServer {

private final JsonSchemaValidator jsonSchemaValidator;

private final boolean validateToolInputs;

private final McpSchema.ServerCapabilities serverCapabilities;

private final McpSchema.Implementation serverInfo;
Expand Down Expand Up @@ -129,7 +132,8 @@ public class McpAsyncServer {
*/
McpAsyncServer(McpServerTransportProvider mcpTransportProvider, McpJsonMapper jsonMapper,
McpServerFeatures.Async features, Duration requestTimeout,
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator,
boolean validateToolInputs) {
this.mcpTransportProvider = mcpTransportProvider;
this.jsonMapper = jsonMapper;
this.serverInfo = features.serverInfo();
Expand All @@ -142,6 +146,7 @@ public class McpAsyncServer {
this.completions.putAll(features.completions());
this.uriTemplateManagerFactory = uriTemplateManagerFactory;
this.jsonSchemaValidator = jsonSchemaValidator;
this.validateToolInputs = validateToolInputs;

Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
Expand All @@ -157,7 +162,8 @@ public class McpAsyncServer {

McpAsyncServer(McpStreamableServerTransportProvider mcpTransportProvider, McpJsonMapper jsonMapper,
McpServerFeatures.Async features, Duration requestTimeout,
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator,
boolean validateToolInputs) {
this.mcpTransportProvider = mcpTransportProvider;
this.jsonMapper = jsonMapper;
this.serverInfo = features.serverInfo();
Expand All @@ -170,6 +176,7 @@ public class McpAsyncServer {
this.completions.putAll(features.completions());
this.uriTemplateManagerFactory = uriTemplateManagerFactory;
this.jsonSchemaValidator = jsonSchemaValidator;
this.validateToolInputs = validateToolInputs;

Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
Expand Down Expand Up @@ -543,6 +550,13 @@ private McpRequestHandler<CallToolResult> toolsCallRequestHandler() {
.build());
}

McpSchema.Tool tool = toolSpecification.get().tool();
CallToolResult validationError = ToolInputValidator.validate(tool, callToolRequest.arguments(),
this.validateToolInputs, this.jsonSchemaValidator);
if (validationError != null) {
return Mono.just(validationError);
}

return toolSpecification.get().callHandler().apply(exchange, callToolRequest);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public McpAsyncServer build() {
: McpJsonDefaults.getSchemaValidator();

return new McpAsyncServer(transportProvider, jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper,
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator);
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator, validateToolInputs);
}

}
Expand All @@ -269,7 +269,7 @@ public McpAsyncServer build() {
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
: McpJsonDefaults.getSchemaValidator();
return new McpAsyncServer(transportProvider, jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper,
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator);
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator, validateToolInputs);
}

}
Expand All @@ -293,6 +293,8 @@ abstract class AsyncSpecification<S extends AsyncSpecification<S>> {

boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();

boolean validateToolInputs = true;

/**
* The Model Context Protocol (MCP) allows servers to expose tools that can be
* invoked by language models. Tools enable models to interact with external
Expand Down Expand Up @@ -421,6 +423,17 @@ public AsyncSpecification<S> strictToolNameValidation(boolean strict) {
return this;
}

/**
* Sets whether to validate tool inputs against the tool's input schema.
* @param validate true to validate inputs and return error on validation failure,
* false to skip validation. Defaults to true.
* @return This builder instance for method chaining
*/
public AsyncSpecification<S> validateToolInputs(boolean validate) {
this.validateToolInputs = validate;
return this;
}

/**
* Sets the server capabilities that will be advertised to clients during
* connection initialization. Capabilities define what features the server
Expand Down Expand Up @@ -818,7 +831,8 @@ public McpSyncServer build() {
var asyncServer = new McpAsyncServer(transportProvider,
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, asyncFeatures, requestTimeout,
uriTemplateManagerFactory,
jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator());
jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator(),
validateToolInputs);
return new McpSyncServer(asyncServer, this.immediateExecution);
}

Expand Down Expand Up @@ -849,7 +863,7 @@ public McpSyncServer build() {
: McpJsonDefaults.getSchemaValidator();
var asyncServer = new McpAsyncServer(transportProvider,
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, asyncFeatures, this.requestTimeout,
this.uriTemplateManagerFactory, jsonSchemaValidator);
this.uriTemplateManagerFactory, jsonSchemaValidator, validateToolInputs);
return new McpSyncServer(asyncServer, this.immediateExecution);
}

Expand All @@ -872,6 +886,8 @@ abstract class SyncSpecification<S extends SyncSpecification<S>> {

boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();

boolean validateToolInputs = true;

/**
* The Model Context Protocol (MCP) allows servers to expose tools that can be
* invoked by language models. Tools enable models to interact with external
Expand Down Expand Up @@ -1004,6 +1020,17 @@ public SyncSpecification<S> strictToolNameValidation(boolean strict) {
return this;
}

/**
* Sets whether to validate tool inputs against the tool's input schema.
* @param validate true to validate inputs and return error on validation failure,
* false to skip validation. Defaults to true.
* @return This builder instance for method chaining
*/
public SyncSpecification<S> validateToolInputs(boolean validate) {
this.validateToolInputs = validate;
return this;
}

/**
* Sets the server capabilities that will be advertised to clients during
* connection initialization. Capabilities define what features the server
Expand Down Expand Up @@ -1401,6 +1428,8 @@ class StatelessAsyncSpecification {

boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();

boolean validateToolInputs = true;

/**
* The Model Context Protocol (MCP) allows servers to expose tools that can be
* invoked by language models. Tools enable models to interact with external
Expand Down Expand Up @@ -1530,6 +1559,17 @@ public StatelessAsyncSpecification strictToolNameValidation(boolean strict) {
return this;
}

/**
* Sets whether to validate tool inputs against the tool's input schema.
* @param validate true to validate inputs and return error on validation failure,
* false to skip validation. Defaults to true.
* @return This builder instance for method chaining
*/
public StatelessAsyncSpecification validateToolInputs(boolean validate) {
this.validateToolInputs = validate;
return this;
}

/**
* Sets the server capabilities that will be advertised to clients during
* connection initialization. Capabilities define what features the server
Expand Down Expand Up @@ -1859,7 +1899,8 @@ public McpStatelessAsyncServer build() {
this.resources, this.resourceTemplates, this.prompts, this.completions, this.instructions);
return new McpStatelessAsyncServer(transport, jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper,
features, requestTimeout, uriTemplateManagerFactory,
jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator());
jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator(),
validateToolInputs);
}

}
Expand All @@ -1884,6 +1925,8 @@ class StatelessSyncSpecification {

boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();

boolean validateToolInputs = true;

/**
* The Model Context Protocol (MCP) allows servers to expose tools that can be
* invoked by language models. Tools enable models to interact with external
Expand Down Expand Up @@ -2013,6 +2056,17 @@ public StatelessSyncSpecification strictToolNameValidation(boolean strict) {
return this;
}

/**
* Sets whether to validate tool inputs against the tool's input schema.
* @param validate true to validate inputs and return error on validation failure,
* false to skip validation. Defaults to true.
* @return This builder instance for method chaining
*/
public StatelessSyncSpecification validateToolInputs(boolean validate) {
this.validateToolInputs = validate;
return this;
}

/**
* Sets the server capabilities that will be advertised to clients during
* connection initialization. Capabilities define what features the server
Expand Down Expand Up @@ -2360,7 +2414,8 @@ public McpStatelessSyncServer build() {
var asyncServer = new McpStatelessAsyncServer(transport,
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, asyncFeatures, requestTimeout,
uriTemplateManagerFactory,
this.jsonSchemaValidator != null ? this.jsonSchemaValidator : McpJsonDefaults.getSchemaValidator());
this.jsonSchemaValidator != null ? this.jsonSchemaValidator : McpJsonDefaults.getSchemaValidator(),
validateToolInputs);
return new McpStatelessSyncServer(asyncServer, this.immediateExecution);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.modelcontextprotocol.util.Assert;
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
import io.modelcontextprotocol.util.ToolInputValidator;
import io.modelcontextprotocol.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -77,9 +78,12 @@ public class McpStatelessAsyncServer {

private final JsonSchemaValidator jsonSchemaValidator;

private final boolean validateToolInputs;

McpStatelessAsyncServer(McpStatelessServerTransport mcpTransport, McpJsonMapper jsonMapper,
McpStatelessServerFeatures.Async features, Duration requestTimeout,
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator,
boolean validateToolInputs) {
this.mcpTransportProvider = mcpTransport;
this.jsonMapper = jsonMapper;
this.serverInfo = features.serverInfo();
Expand All @@ -92,6 +96,7 @@ public class McpStatelessAsyncServer {
this.completions.putAll(features.completions());
this.uriTemplateManagerFactory = uriTemplateManagerFactory;
this.jsonSchemaValidator = jsonSchemaValidator;
this.validateToolInputs = validateToolInputs;

Map<String, McpStatelessRequestHandler<?>> requestHandlers = new HashMap<>();

Expand Down Expand Up @@ -409,6 +414,13 @@ private McpStatelessRequestHandler<CallToolResult> toolsCallRequestHandler() {
.build());
}

McpSchema.Tool tool = toolSpecification.get().tool();
CallToolResult validationError = ToolInputValidator.validate(tool, callToolRequest.arguments(),
this.validateToolInputs, this.jsonSchemaValidator);
if (validationError != null) {
return Mono.just(validationError);
}

return toolSpecification.get().callHandler().apply(ctx, callToolRequest);
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Validates tool input arguments against JSON schema.
*
* @author Andrei Shakirin
*/
public final class ToolInputValidator {

private static final Logger logger = LoggerFactory.getLogger(ToolInputValidator.class);

private ToolInputValidator() {
}

/**
* Validates tool arguments against the tool's input schema.
* @param tool the tool definition containing the input schema
* @param arguments the arguments to validate
* @param validateToolInputs whether validation is enabled
* @param validator the JSON schema validator (may be null)
* @return CallToolResult with isError=true if validation fails, null if valid or
* validation skipped
*/
public static CallToolResult validate(McpSchema.Tool tool, Map<String, Object> arguments,
boolean validateToolInputs, JsonSchemaValidator validator) {
if (!validateToolInputs || tool.inputSchema() == null || validator == null) {
return null;
}
Map<String, Object> args = arguments != null ? arguments : Map.of();
var validation = validator.validate(tool.inputSchema(), args);
if (!validation.valid()) {
logger.warn("Tool '{}' input validation failed: {}", tool.name(), validation.errorMessage());
return CallToolResult.builder()
.content(List.of(new McpSchema.TextContent(validation.errorMessage())))
.isError(true)
.build();
}
return null;
}

}
Loading
Loading