Skip to content

Commit 18c626b

Browse files
committed
feat!: #697 added tool input arguments validation causes tool execution error. Breaking change, because validation is activated by default
1 parent cbb235f commit 18c626b

6 files changed

Lines changed: 557 additions & 40 deletions

File tree

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

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,46 +4,24 @@
44

55
package io.modelcontextprotocol.server;
66

7-
import java.time.Duration;
8-
import java.util.Collections;
9-
import java.util.HashMap;
10-
import java.util.List;
11-
import java.util.Map;
12-
import java.util.Optional;
13-
import java.util.Set;
14-
import java.util.UUID;
15-
import java.util.concurrent.ConcurrentHashMap;
16-
import java.util.concurrent.CopyOnWriteArrayList;
17-
import java.util.function.BiFunction;
18-
197
import io.modelcontextprotocol.json.McpJsonMapper;
208
import io.modelcontextprotocol.json.TypeRef;
219
import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
22-
import io.modelcontextprotocol.spec.DefaultMcpStreamableServerSessionFactory;
23-
import io.modelcontextprotocol.spec.McpClientSession;
24-
import io.modelcontextprotocol.spec.McpError;
25-
import io.modelcontextprotocol.spec.McpSchema;
26-
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
10+
import io.modelcontextprotocol.spec.*;
11+
import io.modelcontextprotocol.spec.McpSchema.*;
2712
import io.modelcontextprotocol.spec.McpSchema.CompleteResult.CompleteCompletion;
28-
import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
29-
import io.modelcontextprotocol.spec.McpSchema.LoggingLevel;
30-
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
31-
import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
32-
import io.modelcontextprotocol.spec.McpSchema.SetLevelRequest;
33-
import io.modelcontextprotocol.spec.McpSchema.Tool;
34-
import io.modelcontextprotocol.spec.McpServerSession;
35-
import io.modelcontextprotocol.spec.McpServerTransportProvider;
36-
import io.modelcontextprotocol.spec.McpServerTransportProviderBase;
37-
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
38-
import io.modelcontextprotocol.util.Assert;
39-
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
40-
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
41-
import io.modelcontextprotocol.util.Utils;
13+
import io.modelcontextprotocol.util.*;
4214
import org.slf4j.Logger;
4315
import org.slf4j.LoggerFactory;
4416
import reactor.core.publisher.Flux;
4517
import reactor.core.publisher.Mono;
4618

19+
import java.time.Duration;
20+
import java.util.*;
21+
import java.util.concurrent.ConcurrentHashMap;
22+
import java.util.concurrent.CopyOnWriteArrayList;
23+
import java.util.function.BiFunction;
24+
4725
import static io.modelcontextprotocol.spec.McpError.RESOURCE_NOT_FOUND;
4826

4927
/**
@@ -98,6 +76,8 @@ public class McpAsyncServer {
9876

9977
private final JsonSchemaValidator jsonSchemaValidator;
10078

79+
private final boolean validateToolInputs;
80+
10181
private final McpSchema.ServerCapabilities serverCapabilities;
10282

10383
private final McpSchema.Implementation serverInfo;
@@ -129,7 +109,8 @@ public class McpAsyncServer {
129109
*/
130110
McpAsyncServer(McpServerTransportProvider mcpTransportProvider, McpJsonMapper jsonMapper,
131111
McpServerFeatures.Async features, Duration requestTimeout,
132-
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
112+
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator,
113+
boolean validateToolInputs) {
133114
this.mcpTransportProvider = mcpTransportProvider;
134115
this.jsonMapper = jsonMapper;
135116
this.serverInfo = features.serverInfo();
@@ -142,6 +123,7 @@ public class McpAsyncServer {
142123
this.completions.putAll(features.completions());
143124
this.uriTemplateManagerFactory = uriTemplateManagerFactory;
144125
this.jsonSchemaValidator = jsonSchemaValidator;
126+
this.validateToolInputs = validateToolInputs;
145127

146128
Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
147129
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
@@ -157,7 +139,8 @@ public class McpAsyncServer {
157139

158140
McpAsyncServer(McpStreamableServerTransportProvider mcpTransportProvider, McpJsonMapper jsonMapper,
159141
McpServerFeatures.Async features, Duration requestTimeout,
160-
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
142+
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator,
143+
boolean validateToolInputs) {
161144
this.mcpTransportProvider = mcpTransportProvider;
162145
this.jsonMapper = jsonMapper;
163146
this.serverInfo = features.serverInfo();
@@ -170,6 +153,7 @@ public class McpAsyncServer {
170153
this.completions.putAll(features.completions());
171154
this.uriTemplateManagerFactory = uriTemplateManagerFactory;
172155
this.jsonSchemaValidator = jsonSchemaValidator;
156+
this.validateToolInputs = validateToolInputs;
173157

174158
Map<String, McpRequestHandler<?>> requestHandlers = prepareRequestHandlers();
175159
Map<String, McpNotificationHandler> notificationHandlers = prepareNotificationHandlers(features);
@@ -543,6 +527,13 @@ private McpRequestHandler<CallToolResult> toolsCallRequestHandler() {
543527
.build());
544528
}
545529

530+
McpSchema.Tool tool = toolSpecification.get().tool();
531+
CallToolResult validationError = ToolInputValidator.validate(tool, callToolRequest.arguments(),
532+
this.validateToolInputs, this.jsonMapper, this.jsonSchemaValidator);
533+
if (validationError != null) {
534+
return Mono.just(validationError);
535+
}
536+
546537
return toolSpecification.get().callHandler().apply(exchange, callToolRequest);
547538
};
548539
}

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

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.modelcontextprotocol.util.Assert;
2626
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
2727
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
28+
import io.modelcontextprotocol.util.ToolInputValidator;
2829
import io.modelcontextprotocol.util.ToolNameValidator;
2930
import reactor.core.publisher.Mono;
3031

@@ -243,7 +244,7 @@ public McpAsyncServer build() {
243244
: McpJsonDefaults.getSchemaValidator();
244245

245246
return new McpAsyncServer(transportProvider, jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper,
246-
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator);
247+
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator, validateToolInputs);
247248
}
248249

249250
}
@@ -269,7 +270,7 @@ public McpAsyncServer build() {
269270
var jsonSchemaValidator = this.jsonSchemaValidator != null ? this.jsonSchemaValidator
270271
: McpJsonDefaults.getSchemaValidator();
271272
return new McpAsyncServer(transportProvider, jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper,
272-
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator);
273+
features, requestTimeout, uriTemplateManagerFactory, jsonSchemaValidator, validateToolInputs);
273274
}
274275

275276
}
@@ -293,6 +294,8 @@ abstract class AsyncSpecification<S extends AsyncSpecification<S>> {
293294

294295
boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();
295296

297+
boolean validateToolInputs = ToolInputValidator.isEnabledByDefault();
298+
296299
/**
297300
* The Model Context Protocol (MCP) allows servers to expose tools that can be
298301
* invoked by language models. Tools enable models to interact with external
@@ -421,6 +424,18 @@ public AsyncSpecification<S> strictToolNameValidation(boolean strict) {
421424
return this;
422425
}
423426

427+
/**
428+
* Sets whether to validate tool inputs against the tool's input schema. When set,
429+
* this takes priority over the system property
430+
* {@code io.modelcontextprotocol.validateToolInputs}.
431+
* @param validate true to validate inputs and return error on validation failure
432+
* @return This builder instance for method chaining
433+
*/
434+
public AsyncSpecification<S> validateToolInputs(boolean validate) {
435+
this.validateToolInputs = validate;
436+
return this;
437+
}
438+
424439
/**
425440
* Sets the server capabilities that will be advertised to clients during
426441
* connection initialization. Capabilities define what features the server
@@ -818,7 +833,8 @@ public McpSyncServer build() {
818833
var asyncServer = new McpAsyncServer(transportProvider,
819834
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, asyncFeatures, requestTimeout,
820835
uriTemplateManagerFactory,
821-
jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator());
836+
jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator(),
837+
validateToolInputs);
822838
return new McpSyncServer(asyncServer, this.immediateExecution);
823839
}
824840

@@ -849,7 +865,7 @@ public McpSyncServer build() {
849865
: McpJsonDefaults.getSchemaValidator();
850866
var asyncServer = new McpAsyncServer(transportProvider,
851867
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, asyncFeatures, this.requestTimeout,
852-
this.uriTemplateManagerFactory, jsonSchemaValidator);
868+
this.uriTemplateManagerFactory, jsonSchemaValidator, validateToolInputs);
853869
return new McpSyncServer(asyncServer, this.immediateExecution);
854870
}
855871

@@ -872,6 +888,8 @@ abstract class SyncSpecification<S extends SyncSpecification<S>> {
872888

873889
boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();
874890

891+
boolean validateToolInputs = ToolInputValidator.isEnabledByDefault();
892+
875893
/**
876894
* The Model Context Protocol (MCP) allows servers to expose tools that can be
877895
* invoked by language models. Tools enable models to interact with external
@@ -1004,6 +1022,18 @@ public SyncSpecification<S> strictToolNameValidation(boolean strict) {
10041022
return this;
10051023
}
10061024

1025+
/**
1026+
* Sets whether to validate tool inputs against the tool's input schema. When set,
1027+
* this takes priority over the system property
1028+
* {@code io.modelcontextprotocol.validateToolInputs}.
1029+
* @param validate true to validate inputs and return error on validation failure
1030+
* @return This builder instance for method chaining
1031+
*/
1032+
public SyncSpecification<S> validateToolInputs(boolean validate) {
1033+
this.validateToolInputs = validate;
1034+
return this;
1035+
}
1036+
10071037
/**
10081038
* Sets the server capabilities that will be advertised to clients during
10091039
* connection initialization. Capabilities define what features the server
@@ -1401,6 +1431,8 @@ class StatelessAsyncSpecification {
14011431

14021432
boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();
14031433

1434+
boolean validateToolInputs = ToolInputValidator.isEnabledByDefault();
1435+
14041436
/**
14051437
* The Model Context Protocol (MCP) allows servers to expose tools that can be
14061438
* invoked by language models. Tools enable models to interact with external
@@ -1530,6 +1562,18 @@ public StatelessAsyncSpecification strictToolNameValidation(boolean strict) {
15301562
return this;
15311563
}
15321564

1565+
/**
1566+
* Sets whether to validate tool inputs against the tool's input schema. When set,
1567+
* this takes priority over the system property
1568+
* {@code io.modelcontextprotocol.validateToolInputs}.
1569+
* @param validate true to validate inputs and return error on validation failure
1570+
* @return This builder instance for method chaining
1571+
*/
1572+
public StatelessAsyncSpecification validateToolInputs(boolean validate) {
1573+
this.validateToolInputs = validate;
1574+
return this;
1575+
}
1576+
15331577
/**
15341578
* Sets the server capabilities that will be advertised to clients during
15351579
* connection initialization. Capabilities define what features the server
@@ -1859,7 +1903,8 @@ public McpStatelessAsyncServer build() {
18591903
this.resources, this.resourceTemplates, this.prompts, this.completions, this.instructions);
18601904
return new McpStatelessAsyncServer(transport, jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper,
18611905
features, requestTimeout, uriTemplateManagerFactory,
1862-
jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator());
1906+
jsonSchemaValidator != null ? jsonSchemaValidator : McpJsonDefaults.getSchemaValidator(),
1907+
validateToolInputs);
18631908
}
18641909

18651910
}
@@ -1884,6 +1929,8 @@ class StatelessSyncSpecification {
18841929

18851930
boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();
18861931

1932+
boolean validateToolInputs = ToolInputValidator.isEnabledByDefault();
1933+
18871934
/**
18881935
* The Model Context Protocol (MCP) allows servers to expose tools that can be
18891936
* invoked by language models. Tools enable models to interact with external
@@ -2013,6 +2060,18 @@ public StatelessSyncSpecification strictToolNameValidation(boolean strict) {
20132060
return this;
20142061
}
20152062

2063+
/**
2064+
* Sets whether to validate tool inputs against the tool's input schema. When set,
2065+
* this takes priority over the system property
2066+
* {@code io.modelcontextprotocol.validateToolInputs}.
2067+
* @param validate true to validate inputs and return error on validation failure
2068+
* @return This builder instance for method chaining
2069+
*/
2070+
public StatelessSyncSpecification validateToolInputs(boolean validate) {
2071+
this.validateToolInputs = validate;
2072+
return this;
2073+
}
2074+
20162075
/**
20172076
* Sets the server capabilities that will be advertised to clients during
20182077
* connection initialization. Capabilities define what features the server
@@ -2360,7 +2419,8 @@ public McpStatelessSyncServer build() {
23602419
var asyncServer = new McpStatelessAsyncServer(transport,
23612420
jsonMapper == null ? McpJsonDefaults.getMapper() : jsonMapper, asyncFeatures, requestTimeout,
23622421
uriTemplateManagerFactory,
2363-
this.jsonSchemaValidator != null ? this.jsonSchemaValidator : McpJsonDefaults.getSchemaValidator());
2422+
this.jsonSchemaValidator != null ? this.jsonSchemaValidator : McpJsonDefaults.getSchemaValidator(),
2423+
validateToolInputs);
23642424
return new McpStatelessSyncServer(asyncServer, this.immediateExecution);
23652425
}
23662426

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.modelcontextprotocol.util.Assert;
2222
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
2323
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
24+
import io.modelcontextprotocol.util.ToolInputValidator;
2425
import io.modelcontextprotocol.util.Utils;
2526
import org.slf4j.Logger;
2627
import org.slf4j.LoggerFactory;
@@ -77,9 +78,12 @@ public class McpStatelessAsyncServer {
7778

7879
private final JsonSchemaValidator jsonSchemaValidator;
7980

81+
private final boolean validateToolInputs;
82+
8083
McpStatelessAsyncServer(McpStatelessServerTransport mcpTransport, McpJsonMapper jsonMapper,
8184
McpStatelessServerFeatures.Async features, Duration requestTimeout,
82-
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator) {
85+
McpUriTemplateManagerFactory uriTemplateManagerFactory, JsonSchemaValidator jsonSchemaValidator,
86+
boolean validateToolInputs) {
8387
this.mcpTransportProvider = mcpTransport;
8488
this.jsonMapper = jsonMapper;
8589
this.serverInfo = features.serverInfo();
@@ -92,6 +96,7 @@ public class McpStatelessAsyncServer {
9296
this.completions.putAll(features.completions());
9397
this.uriTemplateManagerFactory = uriTemplateManagerFactory;
9498
this.jsonSchemaValidator = jsonSchemaValidator;
99+
this.validateToolInputs = validateToolInputs;
95100

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

@@ -409,6 +414,13 @@ private McpStatelessRequestHandler<CallToolResult> toolsCallRequestHandler() {
409414
.build());
410415
}
411416

417+
McpSchema.Tool tool = toolSpecification.get().tool();
418+
CallToolResult validationError = ToolInputValidator.validate(tool, callToolRequest.arguments(),
419+
this.validateToolInputs, this.jsonMapper, this.jsonSchemaValidator);
420+
if (validationError != null) {
421+
return Mono.just(validationError);
422+
}
423+
412424
return toolSpecification.get().callHandler().apply(ctx, callToolRequest);
413425
};
414426
}

0 commit comments

Comments
 (0)