Spring AI 是 Spring 生态中专门为人工智能应用开发的框架,其核心目标是将 Spring 的设计理念应用于 AI 领域。 目前Spring AI 已经支持了 MCP。
这里我就用它实现一个简单的 MCP,功能就是获取当前的日期。 使用 Spring AI 的 MCP Server 和 MCP Client。
Spring AI 版本:1.0.0-M7
MCP Server 端 pom 依赖
1 2 3 4 <dependency > <groupId > org.springframework.ai</groupId > <artifactId > spring-ai-starter-mcp-server-webmvc</artifactId > </dependency >
application.yaml
1 2 3 4 5 6 7 8 server: port: 8081 spring: ai: mcp: server: name: get-date-mcp-server version: 1.0 .0
工具定义
1 2 3 4 5 6 7 8 9 @Service public class DateService { @Tool(description = "Get today date") public String getTodayDate () { return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd" )); } }
将工具注册
1 2 3 4 @Bean public ToolCallbackProvider tools (DateService dateService) { return MethodToolCallbackProvider.builder().toolObjects(dateService).build(); }
MCP Client 端 这里大模型服务我用的不是 API 云服务,而是自己部署的大模型服务,所以需要自己构造 ChatClient
实例,更适用于企业级大规模应用的场景。 这里大模型用的是支持 function call 的 QwQ 32B(本想用 DeepSeeK R1,但是 spring ai mcp 这种方式还走不通,详见下边遇到的坑)。
QwQ 32B 部署时需要开启 function call 支持: vllm 部署: --enable-auto-tool-choice --tool-call-parser hermes
sglang 部署:--tool-call-parser qwen25
先看一下没有加 MCP 时问 QwQ 32B “今天几号”的结果:
1 2 3 4 5 6 7 8 9 10 好的,用户问“今天几号”,我需要先确定当前的日期。不过,作为AI助手,我其实是不知道实时时间的,但可以假设用户是在提问时的当前日期。首先,我应该检查系统时间或者获取当前的日期信息。不过可能系统会自动提供,或者我需要根据编程时的日期来推断? 假设现在的时间是2023年10月5 日,用户可能想知道今天的日期,可能是为了安排日程、记住某个事件或者确认时间。我需要直接回答今天的日期,同时保持友好和简洁。另外,用户可能没有其他深层需求,但有时候可能需要进一步帮助,比如提醒某个事件或者转换时区,但问题本身比较直接,不需要过多扩展。 需要确保回答准确,如果系统无法获取实时数据,可能需要说明当前日期是基于系统时间,可能存在时差或错误。但通常这类问题会由前端处理,所以作为后端回答,可能只需要给出格式化的回复即可。例如:“今天是2023年10月5 日。”或者加上星期几。另外,检查是否有文化差异,比如不同地区日期格式不同,但中文环境下通常是年月日顺序。 可能用户的问题背后有其他需求,比如需要知道周末安排,或者即将到来的节日,但如果没有更多信息,保持回答简洁最好。确认回复无误后,就可以发送了。 </think> 今天是2023年10月5 日。
实际现在已经2025年了, 这显然是不对的。
下边让我们实现 MCP client 然后再问问它。
application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 server: port: 8080 spring: ai: chat.client.enabled: false openai.api-key: "必须配这个,否则启动报错" mcp: client: toolcallback.enabled: true sse.connections: get-date-mcp-server: url: http://localhost:8081
构建 ChatClient
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OpenAiConfig { @Bean @Qualifier(ModelConstants.QWQ_32B) public ChatClient qwq32b (ToolCallbackProvider tools) { OpenAiChatOptions options = OpenAiChatOptions.builder() .model(ModelConstants.QWQ_32B) .temperature(0.6 ) .topP(0.95 ) .build(); OpenAiApi openAiApi = OpenAiApi.builder() .baseUrl(ApiConstants.BASE_URL2) .apiKey(ApiConstants.API_KEY2) .build(); OpenAiChatModel model = OpenAiChatModel.builder() .openAiApi(openAiApi) .build(); return ChatClient.builder(model) .defaultOptions(options) .defaultTools(tools) .defaultAdvisors( new MessageChatMemoryAdvisor (new InMemoryChatMemory ()), new SimpleLoggerAdvisor () ) .build(); } }
整个 API 接口方便测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(path = "/") public class ChatController { @Resource @Qualifier(ModelConstants.QWQ_32B) private ChatClient qwq32b; @GetMapping("/api/qwq") public String callService3 (@RequestParam String prompt) { return qwq32b.prompt().user(prompt).call().content(); } }
再次问它 http://127.0.0.1:8080/api/qwq?prompt=今天几号
1 2 3 4 好的,用户问今天几号,我需要调用获取当前日期的工具。首先,检查可用的工具,发现有一个叫做spring_ai_mcp_client_get_date_mcp_server_getTodayDate的函数,不需要参数。于是生成对应的tool_call,调用这个函数。然后,系统返回了日期"2025-04-22" ,我需要将这个结果以自然的方式告诉用户。确认日期格式正确,没有错误信息,直接回复今天的日期即可。 </think> 今天是2025年4月22日。
这次显然调用了 MCP Server 并且结果是对了的。
遇到的一些坑 1、mcp server 重启之后,mcp client 并不会重连,这个在生产环境没法直接用,需要自己来构建维护
2、MCP Client 的调用方式是使用 function call 的方式,也就是 DeepSeek R1 用不了,服务器直接500报错