Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest;
import io.modelcontextprotocol.spec.McpSchema.GetPromptResult;
import io.modelcontextprotocol.util.ToolNameValidator;
import io.modelcontextprotocol.spec.McpSchema.ListPromptsResult;
import io.modelcontextprotocol.spec.McpSchema.LoggingLevel;
import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
Expand Down Expand Up @@ -656,6 +657,10 @@ private Mono<McpSchema.ListToolsResult> listToolsInternal(Initialization init, S
.sendRequest(McpSchema.METHOD_TOOLS_LIST, new McpSchema.PaginatedRequest(cursor),
LIST_TOOLS_RESULT_TYPE_REF)
.doOnNext(result -> {
// Validate tool names (warn only)
if (result.tools() != null) {
result.tools().forEach(tool -> ToolNameValidator.validate(tool.name(), true));
}
if (this.enableCallToolSchemaCaching && result.tools() != null) {
// Cache tools output schema
result.tools()
Expand Down
105 changes: 93 additions & 12 deletions mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,24 @@
package io.modelcontextprotocol.server;

import io.modelcontextprotocol.common.McpTransportContext;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

import io.modelcontextprotocol.json.McpJsonMapper;

import io.modelcontextprotocol.json.schema.JsonSchemaValidator;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
import io.modelcontextprotocol.spec.McpStatelessServerTransport;
import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider;
import io.modelcontextprotocol.util.Assert;
import io.modelcontextprotocol.util.DefaultMcpUriTemplateManagerFactory;
import io.modelcontextprotocol.util.McpUriTemplateManagerFactory;
import io.modelcontextprotocol.util.ToolNameValidator;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

/**
* Factory class for creating Model Context Protocol (MCP) servers. MCP servers expose
* tools, resources, and prompts to AI models through a standardized interface.
Expand Down Expand Up @@ -291,6 +286,8 @@ abstract class AsyncSpecification<S extends AsyncSpecification<S>> {

String instructions;

Boolean skipStrictToolNameValidation;

/**
* 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 @@ -407,6 +404,18 @@ public AsyncSpecification<S> instructions(String instructions) {
return this;
}

/**
* Sets whether to skip strict tool name validation for this server. When set,
* this takes priority over the system property
* {@code io.modelcontextprotocol.skipStrictToolNameValidation}.
* @param skip true to warn only, false to throw exception on invalid names
* @return This builder instance for method chaining
*/
public AsyncSpecification<S> skipStrictToolNameValidation(boolean strict) {
this.skipStrictToolNameValidation = strict;
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 @@ -459,6 +468,7 @@ public AsyncSpecification<S> tool(McpSchema.Tool tool,
BiFunction<McpAsyncServerExchange, Map<String, Object>, Mono<CallToolResult>> handler) {
Assert.notNull(tool, "Tool must not be null");
Assert.notNull(handler, "Handler must not be null");
validateToolName(tool.name());
assertNoDuplicateTool(tool.name());

this.tools.add(new McpServerFeatures.AsyncToolSpecification(tool, handler));
Expand All @@ -484,6 +494,7 @@ public AsyncSpecification<S> toolCall(McpSchema.Tool tool,

Assert.notNull(tool, "Tool must not be null");
Assert.notNull(callHandler, "Handler must not be null");
validateToolName(tool.name());
assertNoDuplicateTool(tool.name());

this.tools
Expand All @@ -506,6 +517,7 @@ public AsyncSpecification<S> tools(List<McpServerFeatures.AsyncToolSpecification
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");

for (var tool : toolSpecifications) {
validateToolName(tool.tool().name());
assertNoDuplicateTool(tool.tool().name());
this.tools.add(tool);
}
Expand Down Expand Up @@ -533,12 +545,17 @@ public AsyncSpecification<S> tools(McpServerFeatures.AsyncToolSpecification... t
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");

for (McpServerFeatures.AsyncToolSpecification tool : toolSpecifications) {
validateToolName(tool.tool().name());
assertNoDuplicateTool(tool.tool().name());
this.tools.add(tool);
}
return this;
}

private void validateToolName(String toolName) {
ToolNameValidator.validate(toolName, this.skipStrictToolNameValidation);
}

private void assertNoDuplicateTool(String toolName) {
if (this.tools.stream().anyMatch(toolSpec -> toolSpec.tool().name().equals(toolName))) {
throw new IllegalArgumentException("Tool with name '" + toolName + "' is already registered.");
Expand Down Expand Up @@ -888,6 +905,8 @@ abstract class SyncSpecification<S extends SyncSpecification<S>> {

String instructions;

Boolean skipStrictToolNameValidation;

/**
* 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 @@ -1008,6 +1027,18 @@ public SyncSpecification<S> instructions(String instructions) {
return this;
}

/**
* Sets whether to skip strict tool name validation for this server. When set,
* this takes priority over the system property
* {@code io.modelcontextprotocol.skipStrictToolNameValidation}.
* @param skip true to warn only, false to throw exception on invalid names
* @return This builder instance for method chaining
*/
public SyncSpecification<S> skipStrictToolNameValidation(boolean strict) {
this.skipStrictToolNameValidation = strict;
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 @@ -1059,6 +1090,7 @@ public SyncSpecification<S> tool(McpSchema.Tool tool,
BiFunction<McpSyncServerExchange, Map<String, Object>, McpSchema.CallToolResult> handler) {
Assert.notNull(tool, "Tool must not be null");
Assert.notNull(handler, "Handler must not be null");
validateToolName(tool.name());
assertNoDuplicateTool(tool.name());

this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, handler));
Expand All @@ -1083,6 +1115,7 @@ public SyncSpecification<S> toolCall(McpSchema.Tool tool,
BiFunction<McpSyncServerExchange, McpSchema.CallToolRequest, McpSchema.CallToolResult> handler) {
Assert.notNull(tool, "Tool must not be null");
Assert.notNull(handler, "Handler must not be null");
validateToolName(tool.name());
assertNoDuplicateTool(tool.name());

this.tools.add(new McpServerFeatures.SyncToolSpecification(tool, null, handler));
Expand All @@ -1105,7 +1138,8 @@ public SyncSpecification<S> tools(List<McpServerFeatures.SyncToolSpecification>

for (var tool : toolSpecifications) {
String toolName = tool.tool().name();
assertNoDuplicateTool(toolName); // Check against existing tools
validateToolName(toolName);
assertNoDuplicateTool(toolName);
this.tools.add(tool);
}

Expand Down Expand Up @@ -1133,12 +1167,17 @@ public SyncSpecification<S> tools(McpServerFeatures.SyncToolSpecification... too
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");

for (McpServerFeatures.SyncToolSpecification tool : toolSpecifications) {
validateToolName(tool.tool().name());
assertNoDuplicateTool(tool.tool().name());
this.tools.add(tool);
}
return this;
}

private void validateToolName(String toolName) {
ToolNameValidator.validate(toolName, this.skipStrictToolNameValidation);
}

private void assertNoDuplicateTool(String toolName) {
if (this.tools.stream().anyMatch(toolSpec -> toolSpec.tool().name().equals(toolName))) {
throw new IllegalArgumentException("Tool with name '" + toolName + "' is already registered.");
Expand Down Expand Up @@ -1434,6 +1473,8 @@ class StatelessAsyncSpecification {

String instructions;

Boolean skipStrictToolNameValidation;

/**
* 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 @@ -1551,6 +1592,18 @@ public StatelessAsyncSpecification instructions(String instructions) {
return this;
}

/**
* Sets whether to skip strict tool name validation for this server. When set,
* this takes priority over the system property
* {@code io.modelcontextprotocol.skipStrictToolNameValidation}.
* @param skip true to warn only, false to throw exception on invalid names
* @return This builder instance for method chaining
*/
public StatelessAsyncSpecification skipStrictToolNameValidation(boolean strict) {
this.skipStrictToolNameValidation = strict;
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 @@ -1589,6 +1642,7 @@ public StatelessAsyncSpecification toolCall(McpSchema.Tool tool,

Assert.notNull(tool, "Tool must not be null");
Assert.notNull(callHandler, "Handler must not be null");
validateToolName(tool.name());
assertNoDuplicateTool(tool.name());

this.tools.add(new McpStatelessServerFeatures.AsyncToolSpecification(tool, callHandler));
Expand All @@ -1611,6 +1665,7 @@ public StatelessAsyncSpecification tools(
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");

for (var tool : toolSpecifications) {
validateToolName(tool.tool().name());
assertNoDuplicateTool(tool.tool().name());
this.tools.add(tool);
}
Expand Down Expand Up @@ -1639,12 +1694,17 @@ public StatelessAsyncSpecification tools(
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");

for (var tool : toolSpecifications) {
validateToolName(tool.tool().name());
assertNoDuplicateTool(tool.tool().name());
this.tools.add(tool);
}
return this;
}

private void validateToolName(String toolName) {
ToolNameValidator.validate(toolName, this.skipStrictToolNameValidation);
}

private void assertNoDuplicateTool(String toolName) {
if (this.tools.stream().anyMatch(toolSpec -> toolSpec.tool().name().equals(toolName))) {
throw new IllegalArgumentException("Tool with name '" + toolName + "' is already registered.");
Expand Down Expand Up @@ -1896,6 +1956,8 @@ class StatelessSyncSpecification {

String instructions;

Boolean skipStrictToolNameValidation;

/**
* 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 +2075,18 @@ public StatelessSyncSpecification instructions(String instructions) {
return this;
}

/**
* Sets whether to skip strict tool name validation for this server. When set,
* this takes priority over the system property
* {@code io.modelcontextprotocol.skipStrictToolNameValidation}.
* @param skip true to warn only, false to throw exception on invalid names
* @return This builder instance for method chaining
*/
public StatelessSyncSpecification skipStrictToolNameValidation(boolean strict) {
this.skipStrictToolNameValidation = strict;
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 @@ -2051,6 +2125,7 @@ public StatelessSyncSpecification toolCall(McpSchema.Tool tool,

Assert.notNull(tool, "Tool must not be null");
Assert.notNull(callHandler, "Handler must not be null");
validateToolName(tool.name());
assertNoDuplicateTool(tool.name());

this.tools.add(new McpStatelessServerFeatures.SyncToolSpecification(tool, callHandler));
Expand All @@ -2073,6 +2148,7 @@ public StatelessSyncSpecification tools(
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");

for (var tool : toolSpecifications) {
validateToolName(tool.tool().name());
assertNoDuplicateTool(tool.tool().name());
this.tools.add(tool);
}
Expand Down Expand Up @@ -2101,12 +2177,17 @@ public StatelessSyncSpecification tools(
Assert.notNull(toolSpecifications, "Tool handlers list must not be null");

for (var tool : toolSpecifications) {
validateToolName(tool.tool().name());
assertNoDuplicateTool(tool.tool().name());
this.tools.add(tool);
}
return this;
}

private void validateToolName(String toolName) {
ToolNameValidator.validate(toolName, this.skipStrictToolNameValidation);
}

private void assertNoDuplicateTool(String toolName) {
if (this.tools.stream().anyMatch(toolSpec -> toolSpec.tool().name().equals(toolName))) {
throw new IllegalArgumentException("Tool with name '" + toolName + "' is already registered.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1466,7 +1466,6 @@ public Builder meta(Map<String, Object> meta) {
}

public Tool build() {
Assert.hasText(name, "name must not be empty");
return new Tool(name, title, description, inputSchema, outputSchema, annotations, meta);
}

Expand Down
Loading