55package io .modelcontextprotocol .server ;
66
77import io .modelcontextprotocol .common .McpTransportContext ;
8- import java .time .Duration ;
9- import java .util .ArrayList ;
10- import java .util .Arrays ;
11- import java .util .HashMap ;
12- import java .util .List ;
13- import java .util .Map ;
14- import java .util .function .BiConsumer ;
15- import java .util .function .BiFunction ;
16-
178import io .modelcontextprotocol .json .McpJsonMapper ;
18-
199import io .modelcontextprotocol .json .schema .JsonSchemaValidator ;
2010import io .modelcontextprotocol .spec .McpSchema ;
2111import io .modelcontextprotocol .spec .McpSchema .CallToolResult ;
22- import io .modelcontextprotocol .spec .McpSchema .ResourceTemplate ;
2312import io .modelcontextprotocol .spec .McpServerTransportProvider ;
2413import io .modelcontextprotocol .spec .McpStatelessServerTransport ;
2514import io .modelcontextprotocol .spec .McpStreamableServerTransportProvider ;
2615import io .modelcontextprotocol .util .Assert ;
2716import io .modelcontextprotocol .util .DefaultMcpUriTemplateManagerFactory ;
2817import io .modelcontextprotocol .util .McpUriTemplateManagerFactory ;
18+ import io .modelcontextprotocol .util .ToolNameValidator ;
2919import reactor .core .publisher .Mono ;
3020
21+ import java .time .Duration ;
22+ import java .util .*;
23+ import java .util .function .BiConsumer ;
24+ import java .util .function .BiFunction ;
25+
3126/**
3227 * Factory class for creating Model Context Protocol (MCP) servers. MCP servers expose
3328 * tools, resources, and prompts to AI models through a standardized interface.
@@ -291,6 +286,8 @@ abstract class AsyncSpecification<S extends AsyncSpecification<S>> {
291286
292287 String instructions ;
293288
289+ Boolean skipStrictToolNameValidation ;
290+
294291 /**
295292 * The Model Context Protocol (MCP) allows servers to expose tools that can be
296293 * invoked by language models. Tools enable models to interact with external
@@ -407,6 +404,18 @@ public AsyncSpecification<S> instructions(String instructions) {
407404 return this ;
408405 }
409406
407+ /**
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
412+ * @return This builder instance for method chaining
413+ */
414+ public AsyncSpecification <S > skipStrictToolNameValidation (boolean strict ) {
415+ this .skipStrictToolNameValidation = strict ;
416+ return this ;
417+ }
418+
410419 /**
411420 * Sets the server capabilities that will be advertised to clients during
412421 * connection initialization. Capabilities define what features the server
@@ -459,6 +468,7 @@ public AsyncSpecification<S> tool(McpSchema.Tool tool,
459468 BiFunction <McpAsyncServerExchange , Map <String , Object >, Mono <CallToolResult >> handler ) {
460469 Assert .notNull (tool , "Tool must not be null" );
461470 Assert .notNull (handler , "Handler must not be null" );
471+ validateToolName (tool .name ());
462472 assertNoDuplicateTool (tool .name ());
463473
464474 this .tools .add (new McpServerFeatures .AsyncToolSpecification (tool , handler ));
@@ -484,6 +494,7 @@ public AsyncSpecification<S> toolCall(McpSchema.Tool tool,
484494
485495 Assert .notNull (tool , "Tool must not be null" );
486496 Assert .notNull (callHandler , "Handler must not be null" );
497+ validateToolName (tool .name ());
487498 assertNoDuplicateTool (tool .name ());
488499
489500 this .tools
@@ -506,6 +517,7 @@ public AsyncSpecification<S> tools(List<McpServerFeatures.AsyncToolSpecification
506517 Assert .notNull (toolSpecifications , "Tool handlers list must not be null" );
507518
508519 for (var tool : toolSpecifications ) {
520+ validateToolName (tool .tool ().name ());
509521 assertNoDuplicateTool (tool .tool ().name ());
510522 this .tools .add (tool );
511523 }
@@ -533,12 +545,17 @@ public AsyncSpecification<S> tools(McpServerFeatures.AsyncToolSpecification... t
533545 Assert .notNull (toolSpecifications , "Tool handlers list must not be null" );
534546
535547 for (McpServerFeatures .AsyncToolSpecification tool : toolSpecifications ) {
548+ validateToolName (tool .tool ().name ());
536549 assertNoDuplicateTool (tool .tool ().name ());
537550 this .tools .add (tool );
538551 }
539552 return this ;
540553 }
541554
555+ private void validateToolName (String toolName ) {
556+ ToolNameValidator .validate (toolName , this .skipStrictToolNameValidation );
557+ }
558+
542559 private void assertNoDuplicateTool (String toolName ) {
543560 if (this .tools .stream ().anyMatch (toolSpec -> toolSpec .tool ().name ().equals (toolName ))) {
544561 throw new IllegalArgumentException ("Tool with name '" + toolName + "' is already registered." );
@@ -888,6 +905,8 @@ abstract class SyncSpecification<S extends SyncSpecification<S>> {
888905
889906 String instructions ;
890907
908+ Boolean skipStrictToolNameValidation ;
909+
891910 /**
892911 * The Model Context Protocol (MCP) allows servers to expose tools that can be
893912 * invoked by language models. Tools enable models to interact with external
@@ -1008,6 +1027,18 @@ public SyncSpecification<S> instructions(String instructions) {
10081027 return this ;
10091028 }
10101029
1030+ /**
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
1035+ * @return This builder instance for method chaining
1036+ */
1037+ public SyncSpecification <S > skipStrictToolNameValidation (boolean strict ) {
1038+ this .skipStrictToolNameValidation = strict ;
1039+ return this ;
1040+ }
1041+
10111042 /**
10121043 * Sets the server capabilities that will be advertised to clients during
10131044 * connection initialization. Capabilities define what features the server
@@ -1059,6 +1090,7 @@ public SyncSpecification<S> tool(McpSchema.Tool tool,
10591090 BiFunction <McpSyncServerExchange , Map <String , Object >, McpSchema .CallToolResult > handler ) {
10601091 Assert .notNull (tool , "Tool must not be null" );
10611092 Assert .notNull (handler , "Handler must not be null" );
1093+ validateToolName (tool .name ());
10621094 assertNoDuplicateTool (tool .name ());
10631095
10641096 this .tools .add (new McpServerFeatures .SyncToolSpecification (tool , handler ));
@@ -1083,6 +1115,7 @@ public SyncSpecification<S> toolCall(McpSchema.Tool tool,
10831115 BiFunction <McpSyncServerExchange , McpSchema .CallToolRequest , McpSchema .CallToolResult > handler ) {
10841116 Assert .notNull (tool , "Tool must not be null" );
10851117 Assert .notNull (handler , "Handler must not be null" );
1118+ validateToolName (tool .name ());
10861119 assertNoDuplicateTool (tool .name ());
10871120
10881121 this .tools .add (new McpServerFeatures .SyncToolSpecification (tool , null , handler ));
@@ -1105,7 +1138,8 @@ public SyncSpecification<S> tools(List<McpServerFeatures.SyncToolSpecification>
11051138
11061139 for (var tool : toolSpecifications ) {
11071140 String toolName = tool .tool ().name ();
1108- assertNoDuplicateTool (toolName ); // Check against existing tools
1141+ validateToolName (toolName );
1142+ assertNoDuplicateTool (toolName );
11091143 this .tools .add (tool );
11101144 }
11111145
@@ -1133,12 +1167,17 @@ public SyncSpecification<S> tools(McpServerFeatures.SyncToolSpecification... too
11331167 Assert .notNull (toolSpecifications , "Tool handlers list must not be null" );
11341168
11351169 for (McpServerFeatures .SyncToolSpecification tool : toolSpecifications ) {
1170+ validateToolName (tool .tool ().name ());
11361171 assertNoDuplicateTool (tool .tool ().name ());
11371172 this .tools .add (tool );
11381173 }
11391174 return this ;
11401175 }
11411176
1177+ private void validateToolName (String toolName ) {
1178+ ToolNameValidator .validate (toolName , this .skipStrictToolNameValidation );
1179+ }
1180+
11421181 private void assertNoDuplicateTool (String toolName ) {
11431182 if (this .tools .stream ().anyMatch (toolSpec -> toolSpec .tool ().name ().equals (toolName ))) {
11441183 throw new IllegalArgumentException ("Tool with name '" + toolName + "' is already registered." );
@@ -1434,6 +1473,8 @@ class StatelessAsyncSpecification {
14341473
14351474 String instructions ;
14361475
1476+ Boolean skipStrictToolNameValidation ;
1477+
14371478 /**
14381479 * The Model Context Protocol (MCP) allows servers to expose tools that can be
14391480 * invoked by language models. Tools enable models to interact with external
@@ -1551,6 +1592,18 @@ public StatelessAsyncSpecification instructions(String instructions) {
15511592 return this ;
15521593 }
15531594
1595+ /**
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
1600+ * @return This builder instance for method chaining
1601+ */
1602+ public StatelessAsyncSpecification skipStrictToolNameValidation (boolean strict ) {
1603+ this .skipStrictToolNameValidation = strict ;
1604+ return this ;
1605+ }
1606+
15541607 /**
15551608 * Sets the server capabilities that will be advertised to clients during
15561609 * connection initialization. Capabilities define what features the server
@@ -1589,6 +1642,7 @@ public StatelessAsyncSpecification toolCall(McpSchema.Tool tool,
15891642
15901643 Assert .notNull (tool , "Tool must not be null" );
15911644 Assert .notNull (callHandler , "Handler must not be null" );
1645+ validateToolName (tool .name ());
15921646 assertNoDuplicateTool (tool .name ());
15931647
15941648 this .tools .add (new McpStatelessServerFeatures .AsyncToolSpecification (tool , callHandler ));
@@ -1611,6 +1665,7 @@ public StatelessAsyncSpecification tools(
16111665 Assert .notNull (toolSpecifications , "Tool handlers list must not be null" );
16121666
16131667 for (var tool : toolSpecifications ) {
1668+ validateToolName (tool .tool ().name ());
16141669 assertNoDuplicateTool (tool .tool ().name ());
16151670 this .tools .add (tool );
16161671 }
@@ -1639,12 +1694,17 @@ public StatelessAsyncSpecification tools(
16391694 Assert .notNull (toolSpecifications , "Tool handlers list must not be null" );
16401695
16411696 for (var tool : toolSpecifications ) {
1697+ validateToolName (tool .tool ().name ());
16421698 assertNoDuplicateTool (tool .tool ().name ());
16431699 this .tools .add (tool );
16441700 }
16451701 return this ;
16461702 }
16471703
1704+ private void validateToolName (String toolName ) {
1705+ ToolNameValidator .validate (toolName , this .skipStrictToolNameValidation );
1706+ }
1707+
16481708 private void assertNoDuplicateTool (String toolName ) {
16491709 if (this .tools .stream ().anyMatch (toolSpec -> toolSpec .tool ().name ().equals (toolName ))) {
16501710 throw new IllegalArgumentException ("Tool with name '" + toolName + "' is already registered." );
@@ -1896,6 +1956,8 @@ class StatelessSyncSpecification {
18961956
18971957 String instructions ;
18981958
1959+ Boolean skipStrictToolNameValidation ;
1960+
18991961 /**
19001962 * The Model Context Protocol (MCP) allows servers to expose tools that can be
19011963 * invoked by language models. Tools enable models to interact with external
@@ -2013,6 +2075,18 @@ public StatelessSyncSpecification instructions(String instructions) {
20132075 return this ;
20142076 }
20152077
2078+ /**
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
2083+ * @return This builder instance for method chaining
2084+ */
2085+ public StatelessSyncSpecification skipStrictToolNameValidation (boolean strict ) {
2086+ this .skipStrictToolNameValidation = strict ;
2087+ return this ;
2088+ }
2089+
20162090 /**
20172091 * Sets the server capabilities that will be advertised to clients during
20182092 * connection initialization. Capabilities define what features the server
@@ -2051,6 +2125,7 @@ public StatelessSyncSpecification toolCall(McpSchema.Tool tool,
20512125
20522126 Assert .notNull (tool , "Tool must not be null" );
20532127 Assert .notNull (callHandler , "Handler must not be null" );
2128+ validateToolName (tool .name ());
20542129 assertNoDuplicateTool (tool .name ());
20552130
20562131 this .tools .add (new McpStatelessServerFeatures .SyncToolSpecification (tool , callHandler ));
@@ -2073,6 +2148,7 @@ public StatelessSyncSpecification tools(
20732148 Assert .notNull (toolSpecifications , "Tool handlers list must not be null" );
20742149
20752150 for (var tool : toolSpecifications ) {
2151+ validateToolName (tool .tool ().name ());
20762152 assertNoDuplicateTool (tool .tool ().name ());
20772153 this .tools .add (tool );
20782154 }
@@ -2101,12 +2177,17 @@ public StatelessSyncSpecification tools(
21012177 Assert .notNull (toolSpecifications , "Tool handlers list must not be null" );
21022178
21032179 for (var tool : toolSpecifications ) {
2180+ validateToolName (tool .tool ().name ());
21042181 assertNoDuplicateTool (tool .tool ().name ());
21052182 this .tools .add (tool );
21062183 }
21072184 return this ;
21082185 }
21092186
2187+ private void validateToolName (String toolName ) {
2188+ ToolNameValidator .validate (toolName , this .skipStrictToolNameValidation );
2189+ }
2190+
21102191 private void assertNoDuplicateTool (String toolName ) {
21112192 if (this .tools .stream ().anyMatch (toolSpec -> toolSpec .tool ().name ().equals (toolName ))) {
21122193 throw new IllegalArgumentException ("Tool with name '" + toolName + "' is already registered." );
0 commit comments