feat(mcp): Add granular control over MCP server capabilities
- Add capability configuration to enable/disable tools, resources, prompts, and completions individually - Refactor server configuration to conditionally register capabilities based on configuration - Add support for server instructions configuration - Update documentation with new capability options and completion management - Add tests for new capabilities and configurations Resolves #3207 Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
This commit is contained in:
committed by
Mark Pollack
parent
9392485bd5
commit
327cf40e97
@@ -149,11 +149,11 @@ public class McpServerAutoConfiguration {
|
||||
|
||||
// De-duplicate tools by their name, keeping the first occurrence of each tool
|
||||
// name
|
||||
return tools.stream()
|
||||
.collect(Collectors.toMap(tool -> tool.getToolDefinition().name(), // Key:
|
||||
// tool
|
||||
// name
|
||||
tool -> tool, // Value: the tool itself
|
||||
return tools.stream() // Key: tool name
|
||||
.collect(Collectors.toMap(tool -> tool.getToolDefinition().name(), tool -> tool, // Value:
|
||||
// the
|
||||
// tool
|
||||
// itself
|
||||
(existing, replacement) -> existing)) // On duplicate key, keep the
|
||||
// existing tool
|
||||
.values()
|
||||
@@ -185,47 +185,66 @@ public class McpServerAutoConfiguration {
|
||||
// Create the server with both tool and resource capabilities
|
||||
SyncSpecification serverBuilder = McpServer.sync(transportProvider).serverInfo(serverInfo);
|
||||
|
||||
List<SyncToolSpecification> toolSpecifications = new ArrayList<>(tools.stream().flatMap(List::stream).toList());
|
||||
|
||||
List<ToolCallback> providerToolCallbacks = toolCallbackProvider.stream()
|
||||
.map(pr -> List.of(pr.getToolCallbacks()))
|
||||
.flatMap(List::stream)
|
||||
.filter(fc -> fc instanceof ToolCallback)
|
||||
.map(fc -> (ToolCallback) fc)
|
||||
.toList();
|
||||
|
||||
toolSpecifications.addAll(this.toSyncToolSpecifications(providerToolCallbacks, serverProperties));
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolSpecifications)) {
|
||||
serverBuilder.tools(toolSpecifications);
|
||||
// Tools
|
||||
if (serverProperties.getCapabilities().isTool()) {
|
||||
logger.info("Enable tools capabilities, notification: " + serverProperties.isToolChangeNotification());
|
||||
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
|
||||
logger.info("Registered tools: " + toolSpecifications.size() + ", notification: "
|
||||
+ serverProperties.isToolChangeNotification());
|
||||
|
||||
List<SyncToolSpecification> toolSpecifications = new ArrayList<>(
|
||||
tools.stream().flatMap(List::stream).toList());
|
||||
|
||||
List<ToolCallback> providerToolCallbacks = toolCallbackProvider.stream()
|
||||
.map(pr -> List.of(pr.getToolCallbacks()))
|
||||
.flatMap(List::stream)
|
||||
.filter(fc -> fc instanceof ToolCallback)
|
||||
.map(fc -> (ToolCallback) fc)
|
||||
.toList();
|
||||
|
||||
toolSpecifications.addAll(this.toSyncToolSpecifications(providerToolCallbacks, serverProperties));
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolSpecifications)) {
|
||||
serverBuilder.tools(toolSpecifications);
|
||||
logger.info("Registered tools: " + toolSpecifications.size());
|
||||
}
|
||||
}
|
||||
|
||||
List<SyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
|
||||
if (!CollectionUtils.isEmpty(resourceSpecifications)) {
|
||||
serverBuilder.resources(resourceSpecifications);
|
||||
// Resources
|
||||
if (serverProperties.getCapabilities().isResource()) {
|
||||
logger.info(
|
||||
"Enable resources capabilities, notification: " + serverProperties.isResourceChangeNotification());
|
||||
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
|
||||
logger.info("Registered resources: " + resourceSpecifications.size() + ", notification: "
|
||||
+ serverProperties.isResourceChangeNotification());
|
||||
|
||||
List<SyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
|
||||
if (!CollectionUtils.isEmpty(resourceSpecifications)) {
|
||||
serverBuilder.resources(resourceSpecifications);
|
||||
logger.info("Registered resources: " + resourceSpecifications.size());
|
||||
}
|
||||
}
|
||||
|
||||
List<SyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
|
||||
if (!CollectionUtils.isEmpty(promptSpecifications)) {
|
||||
serverBuilder.prompts(promptSpecifications);
|
||||
// Prompts
|
||||
if (serverProperties.getCapabilities().isPrompt()) {
|
||||
logger.info("Enable prompts capabilities, notification: " + serverProperties.isPromptChangeNotification());
|
||||
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
|
||||
logger.info("Registered prompts: " + promptSpecifications.size() + ", notification: "
|
||||
+ serverProperties.isPromptChangeNotification());
|
||||
|
||||
List<SyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
|
||||
if (!CollectionUtils.isEmpty(promptSpecifications)) {
|
||||
serverBuilder.prompts(promptSpecifications);
|
||||
logger.info("Registered prompts: " + promptSpecifications.size());
|
||||
}
|
||||
}
|
||||
|
||||
List<SyncCompletionSpecification> completionSpecifications = completions.stream()
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
if (!CollectionUtils.isEmpty(completionSpecifications)) {
|
||||
serverBuilder.completions(completionSpecifications);
|
||||
// Completions
|
||||
if (serverProperties.getCapabilities().isCompletion()) {
|
||||
logger.info("Enable completions capabilities");
|
||||
capabilitiesBuilder.completions();
|
||||
logger.info("Registered completions: " + completionSpecifications.size());
|
||||
|
||||
List<SyncCompletionSpecification> completionSpecifications = completions.stream()
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
if (!CollectionUtils.isEmpty(completionSpecifications)) {
|
||||
serverBuilder.completions(completionSpecifications);
|
||||
logger.info("Registered completions: " + completionSpecifications.size());
|
||||
}
|
||||
}
|
||||
|
||||
rootsChangeConsumers.ifAvailable(consumer -> {
|
||||
@@ -257,11 +276,11 @@ public class McpServerAutoConfiguration {
|
||||
McpServerProperties serverProperties) {
|
||||
// De-duplicate tools by their name, keeping the first occurrence of each tool
|
||||
// name
|
||||
return tools.stream()
|
||||
.collect(Collectors.toMap(tool -> tool.getToolDefinition().name(), // Key:
|
||||
// tool
|
||||
// name
|
||||
tool -> tool, // Value: the tool itself
|
||||
return tools.stream() // Key: tool name
|
||||
.collect(Collectors.toMap(tool -> tool.getToolDefinition().name(), tool -> tool, // Value:
|
||||
// the
|
||||
// tool
|
||||
// itself
|
||||
(existing, replacement) -> existing)) // On duplicate key, keep the
|
||||
// existing tool
|
||||
.values()
|
||||
@@ -303,35 +322,51 @@ public class McpServerAutoConfiguration {
|
||||
|
||||
toolSpecifications.addAll(this.toAsyncToolSpecification(providerToolCallbacks, serverProperties));
|
||||
|
||||
// Tools
|
||||
if (serverProperties.getCapabilities().isTool()) {
|
||||
logger.info("Enable tools capabilities, notification: " + serverProperties.isToolChangeNotification());
|
||||
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(toolSpecifications)) {
|
||||
serverBuilder.tools(toolSpecifications);
|
||||
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
|
||||
logger.info("Registered tools: " + toolSpecifications.size() + ", notification: "
|
||||
+ serverProperties.isToolChangeNotification());
|
||||
logger.info("Registered tools: " + toolSpecifications.size());
|
||||
}
|
||||
|
||||
// Resources
|
||||
if (serverProperties.getCapabilities().isResource()) {
|
||||
logger.info(
|
||||
"Enable resources capabilities, notification: " + serverProperties.isResourceChangeNotification());
|
||||
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
|
||||
}
|
||||
|
||||
List<AsyncResourceSpecification> resourceSpecifications = resources.stream().flatMap(List::stream).toList();
|
||||
if (!CollectionUtils.isEmpty(resourceSpecifications)) {
|
||||
serverBuilder.resources(resourceSpecifications);
|
||||
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
|
||||
logger.info("Registered resources: " + resourceSpecifications.size() + ", notification: "
|
||||
+ serverProperties.isResourceChangeNotification());
|
||||
logger.info("Registered resources: " + resourceSpecifications.size());
|
||||
}
|
||||
|
||||
// Prompts
|
||||
if (serverProperties.getCapabilities().isPrompt()) {
|
||||
logger.info("Enable prompts capabilities, notification: " + serverProperties.isPromptChangeNotification());
|
||||
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
|
||||
}
|
||||
List<AsyncPromptSpecification> promptSpecifications = prompts.stream().flatMap(List::stream).toList();
|
||||
if (!CollectionUtils.isEmpty(promptSpecifications)) {
|
||||
serverBuilder.prompts(promptSpecifications);
|
||||
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
|
||||
logger.info("Registered prompts: " + promptSpecifications.size() + ", notification: "
|
||||
+ serverProperties.isPromptChangeNotification());
|
||||
logger.info("Registered prompts: " + promptSpecifications.size());
|
||||
}
|
||||
|
||||
// Completions
|
||||
if (serverProperties.getCapabilities().isCompletion()) {
|
||||
logger.info("Enable completions capabilities");
|
||||
capabilitiesBuilder.completions();
|
||||
}
|
||||
List<AsyncCompletionSpecification> completionSpecifications = completions.stream()
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
if (!CollectionUtils.isEmpty(completionSpecifications)) {
|
||||
serverBuilder.completions(completionSpecifications);
|
||||
capabilitiesBuilder.completions();
|
||||
logger.info("Registered completions: " + completionSpecifications.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,56 @@ public class McpServerProperties {
|
||||
*/
|
||||
private ServerType type = ServerType.SYNC;
|
||||
|
||||
private Capabilities capabilities = new Capabilities();
|
||||
|
||||
public static class Capabilities {
|
||||
|
||||
private boolean resource = true;
|
||||
|
||||
private boolean tool = true;
|
||||
|
||||
private boolean prompt = true;
|
||||
|
||||
private boolean completion = true;
|
||||
|
||||
public boolean isResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(boolean resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public boolean isTool() {
|
||||
return tool;
|
||||
}
|
||||
|
||||
public void setTool(boolean tool) {
|
||||
this.tool = tool;
|
||||
}
|
||||
|
||||
public boolean isPrompt() {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
public void setPrompt(boolean prompt) {
|
||||
this.prompt = prompt;
|
||||
}
|
||||
|
||||
public boolean isCompletion() {
|
||||
return completion;
|
||||
}
|
||||
|
||||
public void setCompletion(boolean completion) {
|
||||
this.completion = completion;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Capabilities getCapabilities() {
|
||||
return capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server types supported by the MCP server.
|
||||
*/
|
||||
|
||||
@@ -18,11 +18,16 @@ package org.springframework.ai.mcp.server.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import io.modelcontextprotocol.client.McpSyncClient;
|
||||
import io.modelcontextprotocol.server.McpAsyncServer;
|
||||
import io.modelcontextprotocol.server.McpAsyncServerExchange;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures.AsyncCompletionSpecification;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures.AsyncToolSpecification;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures.SyncCompletionSpecification;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures.SyncPromptSpecification;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures.SyncResourceSpecification;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification;
|
||||
@@ -38,6 +43,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.ai.mcp.SyncMcpToolCallback;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -66,6 +72,12 @@ public class McpServerAutoConfigurationIT {
|
||||
assertThat(properties.isToolChangeNotification()).isTrue();
|
||||
assertThat(properties.isResourceChangeNotification()).isTrue();
|
||||
assertThat(properties.isPromptChangeNotification()).isTrue();
|
||||
|
||||
// Check capabilities
|
||||
assertThat(properties.getCapabilities().isTool()).isTrue();
|
||||
assertThat(properties.getCapabilities().isResource()).isTrue();
|
||||
assertThat(properties.getCapabilities().isPrompt()).isTrue();
|
||||
assertThat(properties.getCapabilities().isCompletion()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,6 +98,18 @@ public class McpServerAutoConfigurationIT {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void syncServerInstructionsConfiguration() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.mcp.server.instructions=Sync Server Instructions")
|
||||
.run(context -> {
|
||||
McpServerProperties properties = context.getBean(McpServerProperties.class);
|
||||
assertThat(properties.getInstructions()).isEqualTo("Sync Server Instructions");
|
||||
|
||||
McpSyncServer server = context.getBean(McpSyncServer.class);
|
||||
assertThat(server).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void transportConfiguration() {
|
||||
this.contextRunner.withUserConfiguration(CustomTransportConfiguration.class).run(context -> {
|
||||
@@ -207,6 +231,16 @@ public class McpServerAutoConfigurationIT {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void asyncRootsChangeHandlerConfiguration() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.mcp.server.type=ASYNC")
|
||||
.withUserConfiguration(TestAsyncRootsHandlerConfiguration.class)
|
||||
.run(context -> {
|
||||
McpAsyncServer server = context.getBean(McpAsyncServer.class);
|
||||
assertThat(server).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestResourceConfiguration {
|
||||
|
||||
@@ -243,6 +277,63 @@ public class McpServerAutoConfigurationIT {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void capabilitiesConfiguration() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.mcp.server.capabilities.tool=false",
|
||||
"spring.ai.mcp.server.capabilities.resource=false", "spring.ai.mcp.server.capabilities.prompt=false",
|
||||
"spring.ai.mcp.server.capabilities.completion=false")
|
||||
.run(context -> {
|
||||
McpServerProperties properties = context.getBean(McpServerProperties.class);
|
||||
assertThat(properties.getCapabilities().isTool()).isFalse();
|
||||
assertThat(properties.getCapabilities().isResource()).isFalse();
|
||||
assertThat(properties.getCapabilities().isPrompt()).isFalse();
|
||||
assertThat(properties.getCapabilities().isCompletion()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void toolResponseMimeTypeConfiguration() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.mcp.server.tool-response-mime-type.test-tool=application/json")
|
||||
.withUserConfiguration(TestToolConfiguration.class)
|
||||
.run(context -> {
|
||||
McpServerProperties properties = context.getBean(McpServerProperties.class);
|
||||
assertThat(properties.getToolResponseMimeType()).containsEntry("test-tool", "application/json");
|
||||
|
||||
// Verify the MIME type is applied to the tool specifications
|
||||
List<SyncToolSpecification> tools = context.getBean("syncTools", List.class);
|
||||
assertThat(tools).hasSize(1);
|
||||
|
||||
// The server should be properly configured with the tool
|
||||
McpSyncServer server = context.getBean(McpSyncServer.class);
|
||||
assertThat(server).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void completionSpecificationConfiguration() {
|
||||
this.contextRunner.withUserConfiguration(TestCompletionConfiguration.class).run(context -> {
|
||||
List<SyncCompletionSpecification> completions = context.getBean("testCompletions", List.class);
|
||||
assertThat(completions).hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void asyncCompletionSpecificationConfiguration() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.mcp.server.type=ASYNC")
|
||||
.withUserConfiguration(TestAsyncCompletionConfiguration.class)
|
||||
.run(context -> {
|
||||
List<AsyncCompletionSpecification> completions = context.getBean("testAsyncCompletions", List.class);
|
||||
assertThat(completions).hasSize(1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void toolCallbackProviderConfiguration() {
|
||||
this.contextRunner.withUserConfiguration(TestToolCallbackProviderConfiguration.class).run(context -> {
|
||||
assertThat(context).hasSingleBean(ToolCallbackProvider.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestToolConfiguration {
|
||||
|
||||
@@ -262,6 +353,61 @@ public class McpServerAutoConfigurationIT {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestToolCallbackProviderConfiguration {
|
||||
|
||||
@Bean
|
||||
ToolCallbackProvider testToolCallbackProvider() {
|
||||
return () -> {
|
||||
McpSyncClient mockClient = Mockito.mock(McpSyncClient.class);
|
||||
McpSchema.Tool mockTool = Mockito.mock(McpSchema.Tool.class);
|
||||
|
||||
Mockito.when(mockTool.name()).thenReturn("provider-tool");
|
||||
Mockito.when(mockTool.description()).thenReturn("Provider Tool");
|
||||
when(mockClient.getClientInfo()).thenReturn(new McpSchema.Implementation("testClient", "1.0.0"));
|
||||
|
||||
return new ToolCallback[] { new SyncMcpToolCallback(mockClient, mockTool) };
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestCompletionConfiguration {
|
||||
|
||||
@Bean
|
||||
List<SyncCompletionSpecification> testCompletions() {
|
||||
|
||||
BiFunction<McpSyncServerExchange, McpSchema.CompleteRequest, McpSchema.CompleteResult> completionHandler = (
|
||||
exchange, request) -> {
|
||||
// Test implementation
|
||||
return new McpSchema.CompleteResult(
|
||||
new McpSchema.CompleteResult.CompleteCompletion(List.of(), 0, false));
|
||||
};
|
||||
|
||||
return List.of(new McpServerFeatures.SyncCompletionSpecification(
|
||||
new McpSchema.PromptReference("ref/prompt", "code_review"), completionHandler));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestAsyncCompletionConfiguration {
|
||||
|
||||
@Bean
|
||||
List<AsyncCompletionSpecification> testAsyncCompletions() {
|
||||
BiFunction<McpAsyncServerExchange, McpSchema.CompleteRequest, Mono<McpSchema.CompleteResult>> completionHandler = (
|
||||
exchange, request) -> {
|
||||
// Test implementation
|
||||
return Mono.just(new McpSchema.CompleteResult(
|
||||
new McpSchema.CompleteResult.CompleteCompletion(List.of(), 0, false)));
|
||||
};
|
||||
return List.of(new McpServerFeatures.AsyncCompletionSpecification(
|
||||
new McpSchema.PromptReference("ref/prompt", "code_review"), completionHandler));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestRootsHandlerConfiguration {
|
||||
|
||||
@@ -274,6 +420,18 @@ public class McpServerAutoConfigurationIT {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestAsyncRootsHandlerConfiguration {
|
||||
|
||||
@Bean
|
||||
BiConsumer<McpAsyncServerExchange, List<McpSchema.Root>> rootsChangeHandler() {
|
||||
return (exchange, roots) -> {
|
||||
// Test implementation
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class CustomServerTransport implements McpServerTransport {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -91,7 +91,12 @@ All properties are prefixed with `spring.ai.mcp.server`:
|
||||
|`stdio` |Enable/disable stdio transport |`false`
|
||||
|`name` |Server name for identification |`mcp-server`
|
||||
|`version` |Server version |`1.0.0`
|
||||
|`instructions` |Optional instructions to provide guidance to the client on how to interact with this server |`null`
|
||||
|`type` |Server type (SYNC/ASYNC) |`SYNC`
|
||||
|`capabilities.resource` |Enable/disable resource capabilities |`true`
|
||||
|`capabilities.tool` |Enable/disable tool capabilities |`true`
|
||||
|`capabilities.prompt` |Enable/disable prompt capabilities |`true`
|
||||
|`capabilities.completion` |Enable/disable completion capabilities |`true`
|
||||
|`resource-change-notification` |Enable resource change notifications |`true`
|
||||
|`prompt-change-notification` |Enable prompt change notifications |`true`
|
||||
|`tool-change-notification` |Enable tool change notifications |`true`
|
||||
@@ -112,6 +117,17 @@ When activated, it automatically handles the configuration of synchronous tool s
|
||||
To enable this server type, configure your application with `spring.ai.mcp.server.type=ASYNC`.
|
||||
This server type automatically sets up asynchronous tool specifications with built-in Project Reactor support.
|
||||
|
||||
== Server Capabilities
|
||||
|
||||
The MCP Server supports four main capability types that can be individually enabled or disabled:
|
||||
|
||||
* **Tools** - Enable/disable tool capabilities with `spring.ai.mcp.server.capabilities.tool=true|false`
|
||||
* **Resources** - Enable/disable resource capabilities with `spring.ai.mcp.server.capabilities.resource=true|false`
|
||||
* **Prompts** - Enable/disable prompt capabilities with `spring.ai.mcp.server.capabilities.prompt=true|false`
|
||||
* **Completions** - Enable/disable completion capabilities with `spring.ai.mcp.server.capabilities.completion=true|false`
|
||||
|
||||
All capabilities are enabled by default. Disabling a capability will prevent the server from registering and exposing the corresponding features to clients.
|
||||
|
||||
== Transport Options
|
||||
|
||||
The MCP Server supports three transport mechanisms, each with its dedicated starter:
|
||||
@@ -141,6 +157,17 @@ public ToolCallbackProvider myTools(...) {
|
||||
}
|
||||
----
|
||||
|
||||
or directly as individual tool callbacks:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public ToolCallback myTool(...) {
|
||||
// Create and return a single tool callback
|
||||
return new ToolCallback(...);
|
||||
}
|
||||
----
|
||||
|
||||
or using the low-level API:
|
||||
|
||||
[source,java]
|
||||
@@ -152,6 +179,13 @@ public List<McpServerFeatures.SyncToolSpecification> myTools(...) {
|
||||
}
|
||||
----
|
||||
|
||||
The auto-configuration will automatically detect and register all tool callbacks from:
|
||||
* Individual `ToolCallback` beans
|
||||
* Lists of `ToolCallback` beans
|
||||
* `ToolCallbackProvider` beans
|
||||
|
||||
Tools are de-duplicated by name, with the first occurrence of each tool name being used.
|
||||
|
||||
=== link:https://spec.modelcontextprotocol.io/specification/2024-11-05/server/resources/[Resource Management]
|
||||
|
||||
Provides a standardized way for servers to expose resources to clients.
|
||||
@@ -210,6 +244,33 @@ public List<McpServerFeatures.SyncPromptSpecification> myPrompts() {
|
||||
}
|
||||
----
|
||||
|
||||
=== link:https://spec.modelcontextprotocol.io/specification/2024-11-05/server/completions/[Completion Management]
|
||||
|
||||
Provides a standardized way for servers to expose completion capabilities to clients.
|
||||
|
||||
* Support for both sync and async completion specifications
|
||||
* Automatic registration through Spring beans:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Bean
|
||||
public List<McpServerFeatures.SyncCompletionSpecification> myCompletions() {
|
||||
var completion = new McpServerFeatures.SyncCompletionSpecification(
|
||||
"code-completion",
|
||||
"Provides code completion suggestions",
|
||||
(exchange, request) -> {
|
||||
// Implementation that returns completion suggestions
|
||||
return new McpSchema.CompletionResult(List.of(
|
||||
new McpSchema.Completion("suggestion1", "First suggestion"),
|
||||
new McpSchema.Completion("suggestion2", "Second suggestion")
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
return List.of(completion);
|
||||
}
|
||||
----
|
||||
|
||||
=== link:https://spec.modelcontextprotocol.io/specification/2024-11-05/client/roots/#root-list-changes[Root Change Consumers]
|
||||
|
||||
When roots change, clients that support `listChanged` send a Root Change notification.
|
||||
@@ -254,7 +315,13 @@ spring:
|
||||
name: webmvc-mcp-server
|
||||
version: 1.0.0
|
||||
type: SYNC
|
||||
instructions: "This server provides weather information tools and resources"
|
||||
sse-message-endpoint: /mcp/messages
|
||||
capabilities:
|
||||
tool: true
|
||||
resource: true
|
||||
prompt: true
|
||||
completion: true
|
||||
----
|
||||
|
||||
=== WebFlux Server Configuration
|
||||
@@ -268,7 +335,13 @@ spring:
|
||||
name: webflux-mcp-server
|
||||
version: 1.0.0
|
||||
type: ASYNC # Recommended for reactive applications
|
||||
instructions: "This reactive server provides weather information tools and resources"
|
||||
sse-message-endpoint: /mcp/messages
|
||||
capabilities:
|
||||
tool: true
|
||||
resource: true
|
||||
prompt: true
|
||||
completion: true
|
||||
----
|
||||
|
||||
=== Creating a Spring Boot Application with MCP Server
|
||||
|
||||
Reference in New Issue
Block a user