spring-ai mcp server实战

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

步骤

pom.xml

xml 复制代码
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
        </dependency>

定义prompts/list方法

typescript 复制代码
    /**
     * 必须是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方法

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

定义resources/read方法

typescript 复制代码
    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方法

scss 复制代码
    @Bean
    public ToolCallbackProvider dbTools(DemoService demoService) {
        return MethodToolCallbackProvider.builder().toolObjects(demoService).build();
    }

定义tool/call方法

typescript 复制代码
@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

less 复制代码
	@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

less 复制代码
	@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、List、List、List设置到serverBuilder的tools、resources、prompts中

小结

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

doc

相关推荐
你是一个铁憨憨7 小时前
从 GIS 到 Spatial Agent:MCP 如何重新定义 GIS 的 AI 入口
arcgis·ai·agent·mcp·spatial
Java牛马9 小时前
AI Agent 技术栈梳理(Skill / 蒸馏 / MCP / Harness)
人工智能·ai agent·蒸馏·skill·mcp·harness
alwaysmavs11 小时前
实测 OpenConnector:给 Agent 接 SaaS,别再把 Token 塞进环境变量
mcp
ryan_99612 小时前
一次讲清 A2A 协议与 MCP 边界:从 Agent Card 到 Task 生命周期
agent·mcp·a2a·json-rpc·agent通信
xrlfreedom14 小时前
大厂 MCP 面试实录:本地 Server 远程化改造中的技术选型与落地实践
docker·json schema·mcp
五度易链-区域产业数字化管理平台1 天前
WorkBuddy 实战:将带 MD5 签名的第三方 API 封装为 MCP 服务(企业模糊搜索接口案例)
大数据·人工智能·mcp
栩栩云生2 天前
别再硬记 AWS 和 k8s 命令了!一行命令把十几个云平台的 CLI 全接进 AI
kubernetes·agent·mcp
枝枝在Coding2 天前
我把 Codex 接进 SSH 后,才发现 Xterminal MCP 最难的是敢不敢让 Agent 动手
ssh·mcp
可乐ea2 天前
Anthropic 的 CI/CD 值班智能体:Claude Tag 当一线响应者的架构拆解与踩坑复盘
ci/cd·架构·claude·devops·ai智能体·mcp
xrlfreedom2 天前
大厂 MCP 面试实录:内部 REST API 封装为可审计 MCP Tools 的落地方案
docker·mcp·提示注入防护