Skip to content

Commit 06001e1

Browse files
ashakirinKehrlann
authored andcommitted
feat: #SEP-986 refactored validation control property to avoid negation in name, used nested tests and minor refactorings done
1 parent c4c03a2 commit 06001e1

File tree

7 files changed

+165
-170
lines changed

7 files changed

+165
-170
lines changed

mcp-core/src/main/java/io/modelcontextprotocol/client/McpAsyncClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ private Mono<McpSchema.ListToolsResult> listToolsInternal(Initialization init, S
659659
.doOnNext(result -> {
660660
// Validate tool names (warn only)
661661
if (result.tools() != null) {
662-
result.tools().forEach(tool -> ToolNameValidator.validate(tool.name(), true));
662+
result.tools().forEach(tool -> ToolNameValidator.validate(tool.name(), false));
663663
}
664664
if (this.enableCallToolSchemaCaching && result.tools() != null) {
665665
// Cache tools output schema

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

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ abstract class AsyncSpecification<S extends AsyncSpecification<S>> {
286286

287287
String instructions;
288288

289-
Boolean skipStrictToolNameValidation;
289+
boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();
290290

291291
/**
292292
* The Model Context Protocol (MCP) allows servers to expose tools that can be
@@ -405,14 +405,14 @@ public AsyncSpecification<S> instructions(String instructions) {
405405
}
406406

407407
/**
408-
* Sets whether to skip strict tool name validation for this server. When set,
409-
* this takes priority over the system property
410-
* {@code io.modelcontextprotocol.skipStrictToolNameValidation}.
411-
* @param skip true to warn only, false to throw exception on invalid names
408+
* Sets whether to use strict tool name validation for this server. When set, this
409+
* takes priority over the system property
410+
* {@code io.modelcontextprotocol.strictToolNameValidation}.
411+
* @param strict true to throw exception on invalid names and false to warn only
412412
* @return This builder instance for method chaining
413413
*/
414-
public AsyncSpecification<S> skipStrictToolNameValidation(boolean strict) {
415-
this.skipStrictToolNameValidation = strict;
414+
public AsyncSpecification<S> strictToolNameValidation(boolean strict) {
415+
this.strictToolNameValidation = strict;
416416
return this;
417417
}
418418

@@ -553,7 +553,7 @@ public AsyncSpecification<S> tools(McpServerFeatures.AsyncToolSpecification... t
553553
}
554554

555555
private void validateToolName(String toolName) {
556-
ToolNameValidator.validate(toolName, this.skipStrictToolNameValidation);
556+
ToolNameValidator.validate(toolName, this.strictToolNameValidation);
557557
}
558558

559559
private void assertNoDuplicateTool(String toolName) {
@@ -905,7 +905,7 @@ abstract class SyncSpecification<S extends SyncSpecification<S>> {
905905

906906
String instructions;
907907

908-
Boolean skipStrictToolNameValidation;
908+
boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();
909909

910910
/**
911911
* The Model Context Protocol (MCP) allows servers to expose tools that can be
@@ -1028,14 +1028,14 @@ public SyncSpecification<S> instructions(String instructions) {
10281028
}
10291029

10301030
/**
1031-
* Sets whether to skip strict tool name validation for this server. When set,
1032-
* this takes priority over the system property
1033-
* {@code io.modelcontextprotocol.skipStrictToolNameValidation}.
1034-
* @param skip true to warn only, false to throw exception on invalid names
1031+
* Sets whether to use strict tool name validation for this server. When set, this
1032+
* takes priority over the system property
1033+
* {@code io.modelcontextprotocol.strictToolNameValidation}.
1034+
* @param strict true to throw exception on invalid names, false to warn only
10351035
* @return This builder instance for method chaining
10361036
*/
1037-
public SyncSpecification<S> skipStrictToolNameValidation(boolean strict) {
1038-
this.skipStrictToolNameValidation = strict;
1037+
public SyncSpecification<S> strictToolNameValidation(boolean strict) {
1038+
this.strictToolNameValidation = strict;
10391039
return this;
10401040
}
10411041

@@ -1175,7 +1175,7 @@ public SyncSpecification<S> tools(McpServerFeatures.SyncToolSpecification... too
11751175
}
11761176

11771177
private void validateToolName(String toolName) {
1178-
ToolNameValidator.validate(toolName, this.skipStrictToolNameValidation);
1178+
ToolNameValidator.validate(toolName, this.strictToolNameValidation);
11791179
}
11801180

11811181
private void assertNoDuplicateTool(String toolName) {
@@ -1473,7 +1473,7 @@ class StatelessAsyncSpecification {
14731473

14741474
String instructions;
14751475

1476-
Boolean skipStrictToolNameValidation;
1476+
boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();
14771477

14781478
/**
14791479
* The Model Context Protocol (MCP) allows servers to expose tools that can be
@@ -1593,14 +1593,14 @@ public StatelessAsyncSpecification instructions(String instructions) {
15931593
}
15941594

15951595
/**
1596-
* Sets whether to skip strict tool name validation for this server. When set,
1597-
* this takes priority over the system property
1598-
* {@code io.modelcontextprotocol.skipStrictToolNameValidation}.
1599-
* @param skip true to warn only, false to throw exception on invalid names
1596+
* Sets whether to use strict tool name validation for this server. When set, this
1597+
* takes priority over the system property
1598+
* {@code io.modelcontextprotocol.strictToolNameValidation}.
1599+
* @param strict true to throw exception on invalid names, false to warn only
16001600
* @return This builder instance for method chaining
16011601
*/
1602-
public StatelessAsyncSpecification skipStrictToolNameValidation(boolean strict) {
1603-
this.skipStrictToolNameValidation = strict;
1602+
public StatelessAsyncSpecification strictToolNameValidation(boolean strict) {
1603+
this.strictToolNameValidation = strict;
16041604
return this;
16051605
}
16061606

@@ -1702,7 +1702,7 @@ public StatelessAsyncSpecification tools(
17021702
}
17031703

17041704
private void validateToolName(String toolName) {
1705-
ToolNameValidator.validate(toolName, this.skipStrictToolNameValidation);
1705+
ToolNameValidator.validate(toolName, this.strictToolNameValidation);
17061706
}
17071707

17081708
private void assertNoDuplicateTool(String toolName) {
@@ -1956,7 +1956,7 @@ class StatelessSyncSpecification {
19561956

19571957
String instructions;
19581958

1959-
Boolean skipStrictToolNameValidation;
1959+
boolean strictToolNameValidation = ToolNameValidator.isStrictByDefault();
19601960

19611961
/**
19621962
* The Model Context Protocol (MCP) allows servers to expose tools that can be
@@ -2076,14 +2076,14 @@ public StatelessSyncSpecification instructions(String instructions) {
20762076
}
20772077

20782078
/**
2079-
* Sets whether to skip strict tool name validation for this server. When set,
2080-
* this takes priority over the system property
2081-
* {@code io.modelcontextprotocol.skipStrictToolNameValidation}.
2082-
* @param skip true to warn only, false to throw exception on invalid names
2079+
* Sets whether to use strict tool name validation for this server. When set, this
2080+
* takes priority over the system property
2081+
* {@code io.modelcontextprotocol.strictToolNameValidation}.
2082+
* @param strict true to throw exception on invalid names, false to warn only
20832083
* @return This builder instance for method chaining
20842084
*/
2085-
public StatelessSyncSpecification skipStrictToolNameValidation(boolean strict) {
2086-
this.skipStrictToolNameValidation = strict;
2085+
public StatelessSyncSpecification strictToolNameValidation(boolean strict) {
2086+
this.strictToolNameValidation = strict;
20872087
return this;
20882088
}
20892089

@@ -2185,7 +2185,7 @@ public StatelessSyncSpecification tools(
21852185
}
21862186

21872187
private void validateToolName(String toolName) {
2188-
ToolNameValidator.validate(toolName, this.skipStrictToolNameValidation);
2188+
ToolNameValidator.validate(toolName, this.strictToolNameValidation);
21892189
}
21902190

21912191
private void assertNoDuplicateTool(String toolName) {

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

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* @see <a href=
2424
* "https://modelcontextprotocol.io/specification/draft/server/tools#tool-names">MCP
2525
* Specification - Tool Names</a>
26+
* @author Andrei Shakirin
2627
*/
2728
public final class ToolNameValidator {
2829

@@ -33,55 +34,49 @@ public final class ToolNameValidator {
3334
private static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_\\-.]+$");
3435

3536
/**
36-
* System property to skip strict tool name validation. Set to "true" to warn only
37-
* instead of throwing exceptions. Default is false (strict).
37+
* System property for strict tool name validation. Set to "false" to warn only
38+
* instead of throwing exceptions. Default is true (strict).
3839
*/
39-
public static final String SKIP_STRICT_VALIDATION_PROPERTY = "io.modelcontextprotocol.skipStrictToolNameValidation";
40+
public static final String STRICT_VALIDATION_PROPERTY = "io.modelcontextprotocol.strictToolNameValidation";
4041

4142
private ToolNameValidator() {
4243
}
4344

4445
/**
45-
* Validates a tool name according to MCP specification. Uses strict validation
46-
* (throws exception) unless system property {@link #SKIP_STRICT_VALIDATION_PROPERTY}
47-
* is set to true.
48-
* @param name the tool name to validate
49-
* @throws IllegalArgumentException if validation fails and skip is not enabled
46+
* Returns the default strict validation setting from system property.
47+
* @return true if strict validation is enabled (default), false if disabled via
48+
* system property
5049
*/
51-
public static void validate(String name) {
52-
validate(name, null);
50+
public static boolean isStrictByDefault() {
51+
return !"false".equalsIgnoreCase(System.getProperty(STRICT_VALIDATION_PROPERTY));
5352
}
5453

5554
/**
5655
* Validates a tool name according to MCP specification.
5756
* @param name the tool name to validate
58-
* @param skip if true, logs warning only; if false, throws exception; if null, uses
59-
* system property {@link #SKIP_STRICT_VALIDATION_PROPERTY} (default: false)
60-
* @throws IllegalArgumentException if validation fails and skip is false
57+
* @param strict if true, throws exception on invalid name; if false, logs warning
58+
* only
59+
* @throws IllegalArgumentException if validation fails and strict is true
6160
*/
62-
public static void validate(String name, Boolean skip) {
63-
boolean warnOnly = skip != null ? skip : Boolean.getBoolean(SKIP_STRICT_VALIDATION_PROPERTY);
64-
61+
public static void validate(String name, boolean strict) {
6562
if (name == null || name.isEmpty()) {
66-
handleError("Tool name must not be null or empty", name, warnOnly);
67-
return;
63+
handleError("Tool name must not be null or empty", name, strict);
6864
}
69-
if (name.length() > MAX_LENGTH) {
70-
handleError("Tool name must not exceed 128 characters", name, warnOnly);
71-
return;
65+
else if (name.length() > MAX_LENGTH) {
66+
handleError("Tool name must not exceed 128 characters", name, strict);
7267
}
73-
if (!VALID_NAME_PATTERN.matcher(name).matches()) {
74-
handleError("Tool name contains invalid characters (allowed: A-Z, a-z, 0-9, _, -, .)", name, warnOnly);
68+
else if (!VALID_NAME_PATTERN.matcher(name).matches()) {
69+
handleError("Tool name contains invalid characters (allowed: A-Z, a-z, 0-9, _, -, .)", name, strict);
7570
}
7671
}
7772

78-
private static void handleError(String message, String name, boolean warnOnly) {
73+
private static void handleError(String message, String name, boolean strict) {
7974
String fullMessage = message + ": '" + name + "'";
80-
if (warnOnly) {
81-
logger.warn("{}. Processing continues, but tool name should be fixed.", fullMessage);
75+
if (strict) {
76+
throw new IllegalArgumentException(fullMessage);
8277
}
8378
else {
84-
throw new IllegalArgumentException(fullMessage);
79+
logger.warn("{}. Processing continues, but tool name should be fixed.", fullMessage);
8580
}
8681
}
8782

mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.modelcontextprotocol.util.ToolNameValidator;
1919
import org.junit.jupiter.api.AfterEach;
2020
import org.junit.jupiter.api.BeforeEach;
21+
import org.junit.jupiter.api.Nested;
2122
import org.junit.jupiter.api.Test;
2223

2324
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
@@ -34,19 +35,6 @@
3435
*/
3536
class AsyncToolSpecificationBuilderTest {
3637

37-
private McpServerTransportProvider transportProvider;
38-
39-
@BeforeEach
40-
void setUp() {
41-
transportProvider = mock(McpServerTransportProvider.class);
42-
System.clearProperty(ToolNameValidator.SKIP_STRICT_VALIDATION_PROPERTY);
43-
}
44-
45-
@AfterEach
46-
void tearDown() {
47-
System.clearProperty(ToolNameValidator.SKIP_STRICT_VALIDATION_PROPERTY);
48-
}
49-
5038
@Test
5139
void builderShouldCreateValidAsyncToolSpecification() {
5240

@@ -283,42 +271,60 @@ void fromSyncShouldReturnNullWhenSyncSpecIsNull() {
283271
assertThat(McpServerFeatures.AsyncToolSpecification.fromSync(null)).isNull();
284272
}
285273

286-
@Test
287-
void toolNameValidation_defaultShouldThrowOnInvalidName() {
288-
Tool invalidTool = Tool.builder().name("invalid tool name").build();
274+
@Nested
275+
class ToolNameValidation {
289276

290-
assertThatThrownBy(() -> McpServer.async(transportProvider).tool(invalidTool, (exchange, args) -> null))
291-
.isInstanceOf(IllegalArgumentException.class)
292-
.hasMessageContaining("invalid characters");
293-
}
277+
private McpServerTransportProvider transportProvider;
294278

295-
@Test
296-
void toolNameValidation_systemPropertySkipShouldWarnOnly() {
297-
System.setProperty(ToolNameValidator.SKIP_STRICT_VALIDATION_PROPERTY, "true");
298-
Tool invalidTool = Tool.builder().name("invalid tool name").build();
279+
@BeforeEach
280+
void setUp() {
281+
transportProvider = mock(McpServerTransportProvider.class);
282+
System.clearProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY);
283+
}
299284

300-
assertThatCode(() -> McpServer.async(transportProvider).tool(invalidTool, (exchange, args) -> null))
301-
.doesNotThrowAnyException();
302-
}
285+
@AfterEach
286+
void tearDown() {
287+
System.clearProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY);
288+
}
303289

304-
@Test
305-
void toolNameValidation_perServerSkipShouldWarnOnly() {
306-
Tool invalidTool = Tool.builder().name("invalid tool name").build();
290+
@Test
291+
void defaultShouldThrowOnInvalidName() {
292+
Tool invalidTool = Tool.builder().name("invalid tool name").build();
307293

308-
assertThatCode(() -> McpServer.async(transportProvider)
309-
.skipStrictToolNameValidation(true)
310-
.tool(invalidTool, (exchange, args) -> null)).doesNotThrowAnyException();
311-
}
294+
assertThatThrownBy(() -> McpServer.async(transportProvider).tool(invalidTool, (exchange, args) -> null))
295+
.isInstanceOf(IllegalArgumentException.class)
296+
.hasMessageContaining("invalid characters");
297+
}
298+
299+
@Test
300+
void systemPropertyFalseShouldWarnOnly() {
301+
System.setProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY, "false");
302+
Tool invalidTool = Tool.builder().name("invalid tool name").build();
303+
304+
assertThatCode(() -> McpServer.async(transportProvider).tool(invalidTool, (exchange, args) -> null))
305+
.doesNotThrowAnyException();
306+
}
307+
308+
@Test
309+
void perServerFalseShouldWarnOnly() {
310+
Tool invalidTool = Tool.builder().name("invalid tool name").build();
311+
312+
assertThatCode(() -> McpServer.async(transportProvider)
313+
.strictToolNameValidation(false)
314+
.tool(invalidTool, (exchange, args) -> null)).doesNotThrowAnyException();
315+
}
316+
317+
@Test
318+
void perServerTrueShouldOverrideSystemProperty() {
319+
System.setProperty(ToolNameValidator.STRICT_VALIDATION_PROPERTY, "false");
320+
Tool invalidTool = Tool.builder().name("invalid tool name").build();
321+
322+
assertThatThrownBy(() -> McpServer.async(transportProvider)
323+
.strictToolNameValidation(true)
324+
.tool(invalidTool, (exchange, args) -> null)).isInstanceOf(IllegalArgumentException.class)
325+
.hasMessageContaining("invalid characters");
326+
}
312327

313-
@Test
314-
void toolNameValidation_perServerSkipShouldOverrideSystemProperty() {
315-
System.setProperty(ToolNameValidator.SKIP_STRICT_VALIDATION_PROPERTY, "true");
316-
Tool invalidTool = Tool.builder().name("invalid tool name").build();
317-
318-
assertThatThrownBy(() -> McpServer.async(transportProvider)
319-
.skipStrictToolNameValidation(false)
320-
.tool(invalidTool, (exchange, args) -> null)).isInstanceOf(IllegalArgumentException.class)
321-
.hasMessageContaining("invalid characters");
322328
}
323329

324330
}

0 commit comments

Comments
 (0)