spring-ai mcp server实战

本文主要研究一下如何使用spring-ai-starter-mcp-server进行自定义mcp server

步骤

pom.xml

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
        </dependency>

定义prompts/list方法

    /**
     * 必须是list的形式注入才能识别到
     * @return
     */
    @Bean
    public List<McpServerFeatures.SyncPromptSpecification> syncPromptSpecList() {
        return Arrays.asList(demoPromptSpec());
    }

    public McpServerFeatures.SyncPromptSpecification demoPromptSpec() {
        var syncPromptSpecification = new McpServerFeatures.SyncPromptSpecification(
                new McpSchema.Prompt("greeting", "description", List.of(
                        new McpSchema.PromptArgument("name", "description", true)
                )),
                (exchange, request) -> {
                    // Prompt implementation
                    return new McpSchema.GetPromptResult("description", Collections.emptyList());
                }
        );
        return syncPromptSpecification;
    }

定义resources/list方法

    /**
     * 必须是list的形式注入才能识别到,自动识别然后实现resources/list接口
     * @return
     */
    @Bean
    public List<McpServerFeatures.SyncResourceSpecification> resourceSpecList() {
        return listResource();
    }

定义resources/read方法

    public List<McpServerFeatures.SyncResourceSpecification> resourceSpecList() {
        return Arrays.asList(new McpServerFeatures.SyncResourceSpecification(new McpSchema.Resource("mysql://table1/meta", "table1", "meta data", "text/plain", null), (exchange, request) -> {
            // Resource read implementation
            McpSchema.ResourceContents contents = new McpSchema.TextResourceContents(
                    "meta1", "text/plain", "meta"
            );
            return new McpSchema.ReadResourceResult(Arrays.asList(contents));
        }));
    }

定义tools/list方法

    @Bean
    public ToolCallbackProvider dbTools(DemoService demoService) {
        return MethodToolCallbackProvider.builder().toolObjects(demoService).build();
    }

定义tool/call方法

@Service
public class DemoService {
    
    @Tool(description = "demo tool query")
    public String query(String param1) {
        return "hello" + param1;
    }
}

源码

syncTools

org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java

    @Bean
    @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
            matchIfMissing = true)
    public List<McpServerFeatures.SyncToolSpecification> syncTools(ObjectProvider<List<ToolCallback>> toolCalls,
            List<ToolCallback> toolCallbacksList, McpServerProperties serverProperties) {

        List<ToolCallback> tools = new ArrayList<>(toolCalls.stream().flatMap(List::stream).toList());

        if (!CollectionUtils.isEmpty(toolCallbacksList)) {
            tools.addAll(toolCallbacksList);
        }

        return this.toSyncToolSpecifications(tools, serverProperties);
    }

syncTools这一部分把ToolCallback转为SyncToolSpecifications注册到spring中

mcpSyncServer

org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java

    @Bean
    @ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "type", havingValue = "SYNC",
            matchIfMissing = true)
    public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,
            McpSchema.ServerCapabilities.Builder capabilitiesBuilder, McpServerProperties serverProperties,
            ObjectProvider<List<SyncToolSpecification>> tools,
            ObjectProvider<List<SyncResourceSpecification>> resources,
            ObjectProvider<List<SyncPromptSpecification>> prompts,
            ObjectProvider<BiConsumer<McpSyncServerExchange, List<McpSchema.Root>>> rootsChangeConsumers,
            List<ToolCallbackProvider> toolCallbackProvider) {

        McpSchema.Implementation serverInfo = new Implementation(serverProperties.getName(),
                serverProperties.getVersion());

        // 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);
            capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
            logger.info("Registered tools: " + toolSpecifications.size() + ", notification: "
                    + serverProperties.isToolChangeNotification());
        }

        List<SyncResourceSpecification> 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());
        }

        List<SyncPromptSpecification> 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());
        }

        rootsChangeConsumers.ifAvailable(consumer -> {
            serverBuilder.rootsChangeHandler((exchange, roots) -> {
                consumer.accept(exchange, roots);
            });
            logger.info("Registered roots change consumer");
        });

        serverBuilder.capabilities(capabilitiesBuilder.build());

        return serverBuilder.build();
    }

mcpSyncServer会把注入的List<SyncToolSpecification>、List<SyncResourceSpecification>、List<SyncPromptSpecification>、List<ToolCallbackProvider>设置到serverBuilder的tools、resources、prompts中

小结

spring ai mcp server通过McpServerAutoConfiguration把托管给spring的List<SyncToolSpecification>、List<SyncResourceSpecification>、List<SyncPromptSpecification>、List<ToolCallbackProvider>设置到serverBuilder的tools、resources、prompts中。如果代码要自定义resources、prompts,直接注入List<SyncResourceSpecification>、List<SyncPromptSpecification>即可。tools的话,在托管bean的方法中注解@Tool就可以。

doc

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容