不会 MCP?用 Spring AI 一步搞定 Server 实现

发布时间:2026/6/23 23:05:40
不会 MCP?用 Spring AI 一步搞定 Server 实现 在开始之前相信你已经对MCP有了一定了解如果还不熟悉的同学可以先学习下 链接如果你的主要技术栈是Java并且主要框架为SpringBoot/Cloud那SpringAI绝对是AI应用开发框架中的第一选择。SpringAi有如下功能统一模型 API、Prompt 工程、向量数据库与 RAG、函数调用MCP Tools、对话记忆Chat Memory、企业级特性、Spring Boot 集成本文主要内容通过SpringAi框架搭建MCP服务SpringAi集成MCP源码分析搭建MCP服务环境Java17Maven3.6.3Spring BootSpring AI 1.0.0 支持 3.4.x / 3.5.xSpring AI1.0.0MCP Server 依赖org.springframework.ai:spring-ai-starter-mcp-server-webmvcMCP服务主要代码示例首先要有一套业务接口作为支撑然后再开发一个MCP服务的模块我这里已经有了一个日记系统就以这个为例。在原服务的基础上增加新模块。增加SpringAi 和 MCP相关依赖dependencyManagementdependenciesdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-bom/artifactIdversion${spring-ai.version}/version// 1.1.4typepom/typescopeimport/scope/dependency/dependencies/dependencyManagementdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-mcp-server-webmvc/artifactId/dependency增加application-mcp.yml 配置文件 其余的业务配置请自行增加spring:application:name:mirror-mcp-serverai:mcp:server:name:mirror-mcp-server# MCP 服务器的名称version:1.0.0# 服务器版本号instructions:Mirror APP MCP tools for user profile and journal operations.# 服务器的功能描述sse-endpoint:/sse# 客户端通过这个 HTTP 路径与服务器建立 SSE 长连接就是接口路径sse-message-endpoint:/mcp/message# 客户端通过向这个路径发送 HTTP POST 请求来传递消息给服务器就是接口路径stdio:false# 服务器通信方式true使用 stdio 模式进程间通信适合本地命令行工具false不使用 stdio使用网络协议如 HTTP/SSE提供MCP接口篇幅有限 我这里暂且只保留两个接口有同学可能有疑问我们是不是要提供一个controller暴露出来其实不用这就是SpringAI帮我们做的事我们只需要在方法上加上Tool注解、在参数前加ToolParam注解然后把这个包含Tool注解的方法的类注册到MethodToolCallbackProvider中。// 1. 启动类或者配置类中 注册传入包含 Tool 注解方法的对象SpringBootApplication(scanBasePackages{com.mirror.app,com.mirror.mcp})publicclassMirrorMcpServerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(MirrorMcpServerApplication.class,args);}// 注册MirrorMcpToolService(我们自己开发的mcp接口类)对象BeanpublicToolCallbackProvidermirrorMcpTools(MirrorMcpToolServicemirrorMcpToolService){returnMethodToolCallbackProvider.builder().toolObjects(mirrorMcpToolService).build();}}// 2. MCP服务接口ServiceRequiredArgsConstructorpublicclassMirrorMcpToolService{privatefinalUsersServiceusersService;privatefinalJournalEntryServicejournalEntryService;Tool(description检查 Mirror MCP 服务是否可用)publicApiResponseStringhealth(){returnApiResponse.success(mirror-mcp is ready);}// 获取用户信息的MCP接口Tool(description根据用户ID获取用户资料)publicApiResponseUsersProfileDTOgetUserProfile(ToolParam(description用户ID)StringuserId){if(!StringUtils.hasText(userId)){returnApiResponse.error(userId不能为空);}UsersuserusersService.getByUserId(userId);if(usernull){returnApiResponse.error(用户不存在);}returnApiResponse.success(UsersProfileDTO.fromEntity(user));}}PS有想获取完整源码的同学可以到主页~AI或客户端调用MCP接口示例# 1 建立sse连接curl-N-HAccept: text/event-streamhttp://localhost:8081/sse# 然后你会先看到一条 endpoint 事件data 类似data: /mcp/message?sessionIdxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx下面的调用要再新起一个终端因为要维持上面的sse连接# 然后将sessionId作为固定参数传递到下面的调用中# 1) initialize 协商协议版本和能力curl-XPOSThttp://localhost:8081/mcp/message?sessionId3742b51e-0fd5-473a-a3bc-779ffdf205a4\-HContent-Type: application/json\-d{jsonrpc:2.0,id:init-1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{experimental:{},roots:{listChanged:false},sampling:{}},clientInfo:{name:curl-client,version:0.1.0}}}# 2) initialized 通知“初始化完成”之后才进入正常调用阶段curl-XPOSThttp://localhost:8081/mcp/message?sessionId3742b51e-0fd5-473a-a3bc-779ffdf205a4\-HContent-Type: application/json\-d{jsonrpc:2.0,method:notifications/initialized,params:{}}执行完上面的 /sse、initialize、initialized 就可以发起业务调用了# 3) 列出所有工具接口信息curl-XPOSThttp://localhost:8081/mcp/message?sessionId3742b51e-0fd5-473a-a3bc-779ffdf205a4\-HContent-Type: application/json\-d{jsonrpc:2.0,id:tools-1,method:tools/list,params:{}}# 4) 调用 getUserProfile 业务数据curl-XPOSThttp://localhost:8081/mcp/message?sessionId3742b51e-0fd5-473a-a3bc-779ffdf205a4\-HContent-Type: application/json\-d{jsonrpc:2.0,id:call-user-1,method:tools/call,params:{name:getUserProfile,arguments:{userId:8b004b18f9104694ab50fad99ff60d80}}}SpringAi集成MCP的源码执行逻辑主要分为4层结构1.业务工具层MethodToolCallbackTool 方法的执行器负责入参 JSON - Java 参数反射调用方法返回值 - 字符串MethodToolCallbackProvider扫描工具对象里所有 Tool 方法批量生成 MethodToolCallback[]2.协议适配层Spring AI - MCP SDKMcpToolUtils适配器的工具类把 Spring AI 的 ToolCallback 转成 MCP SDK 的 SyncToolSpecification/AsyncToolSpecification然后把 ToolCallback.call(…) 的字符串结果包装成 MCP CallToolResult 标准结构ToolCallbackConverterAutoConfiguration负责调用 McpToolUtils 做上面的转换3.Server 装配层McpServerAutoConfiguration创建 McpSyncServer/McpAsyncServer组装 capabilitiestools/resources/prompts…消费前面转换好的 SyncToolSpecification挂到 serverBuilderMcpSyncServerMCP SDK 的同步 server 门面持有工具/资源/prompt 的最终“可执行规范”4. 传输层HTTP/SSEMcpServerSseWebMvcAutoConfiguration创建 WebMvcSseServerTransportProvider并暴露 RouterFunction把 /sse、/mcp/message 挂到 Spring MVC总结一下大概的执行流程为MethodToolCallbackProvider 扫描 Tool - MethodToolCallbackToolCallbackConverterAutoConfiguration McpToolUtils 转成 MCP ToolSpecificationMcpServerAutoConfiguration 把 specification 装到 McpSyncServerMcpServerSseWebMvcAutoConfiguration 暴露 HTTP 入口运行时 tools/call 到来 - 命中 specification规范 - McpAsyncServer.toolsCallRequestHandler()方法路由到具体工具并处理返回 - 回调 MethodToolCallback#call - 执行你的业务方法简单看几个源码1.Tool注解标记的方法是如何被扫描并注册的// 1. 首先把MirrorMcpToolService类有标记Tool方法的类注册到MethodToolCallbackProvider中BeanpublicToolCallbackProvidermirrorMcpTools(MirrorMcpToolServicemirrorMcpToolService){returnMethodToolCallbackProvider.builder().toolObjects(mirrorMcpToolService).build();}// 2. MethodToolCallbackProvider类中的核心方法// 2.1 作用是遍历toolObjects// 2.2 过滤有Tool注解的方法// 2.3 为每个方法构建MethodToolCallbackMethodToolCallback就是返回MCP标准结构的核心publicToolCallback[]getToolCallbacks(){ToolCallback[]toolCallbacks(ToolCallback[])this.toolObjects.stream().map((toolObject)-(ToolCallback[])Stream.of(ReflectionUtils.getDeclaredMethods(AopUtils.isAopProxy(toolObject)?AopUtils.getTargetClass(toolObject)// 如果是代理获取原始类:toolObject.getClass()// 如果不是代理直接获取类)).filter((toolMethod)-toolMethod.isAnnotationPresent(Tool.class))// 过滤有Tool注解的方法.filter((toolMethod)-!this.isFunctionalType(toolMethod))// 过滤掉函数式接口.map((toolMethod)-MethodToolCallback.builder()// 为每个方法构建ToolCallback.toolDefinition(ToolDefinitions.from(toolMethod))// 这是解析 Tool和 ToolParam注解的核心方法负责生成 MCP 标准格式的工具定义。.toolMetadata(ToolMetadata.from(toolMethod)).toolMethod(toolMethod).toolObject(toolObject).toolCallResultConverter(ToolUtils.getToolCallResultConverter(toolMethod)).build()).toArray((x$0)-newToolCallback[x$0])).flatMap(Stream::of)// 将数组展平.toArray((x$0)-newToolCallback[x$0]);this.validateToolCallbacks(toolCallbacks);// 验证回调returntoolCallbacks;}2.ToolParam注解作用ToolParam 不是 Java 语法必须它是给 Spring AI 用的“参数元数据注解“它主要影响 tools/list 返回的 inputSchema让客户端/AI知道这个参数是干嘛的。// 这个方法会解析出 ToolParam 中的描述ToolDefinitions.from(toolMethod)3.客户端如何通过 /sse、/mcp/message 调到你的接口ps/sse是客户端或者AI和MCP建立连接的路径/mcp/message是客户端或者AI调用MCP工具方法的接口MCP协议的固定值// 核心类就是 WebMvcSseServerTransportProvider类io.modelcontextprotocol.server.transport包中// 1. WebMvcSseServerTransportProvider中handleSseConnection方法和客户端建立sse连接// 2. WebMvcSseServerTransportProvider中handleMessage方法接收客户端数据//上面的源码就不贴了大家自行查阅// 接下来看 WebMvcSseServerTransportProvider 是如何被AI/客户端访问到的// ps:类上的注解我省略了publicclassMcpServerSseWebMvcAutoConfiguration{BeanConditionalOnMissingBeanpublicWebMvcSseServerTransportProviderwebMvcSseServerTransportProvider(Qualifier(mcpServerObjectMapper)ObjectMapperobjectMapper,McpServerSsePropertiesserverProperties){// 1. 将我们配置文件中配置的MCP信息(McpServerSseProperties中)注册给WebMvcSseServerTransportProviderreturnWebMvcSseServerTransportProvider.builder().jsonMapper(newJacksonMcpJsonMapper(objectMapper)).baseUrl(serverProperties.getBaseUrl()).sseEndpoint(serverProperties.getSseEndpoint()).messageEndpoint(serverProperties.getSseMessageEndpoint()).keepAliveInterval(serverProperties.getKeepAliveInterval()).build();}// 2. webMvcSseServerRouterFunction 这个Bean 会把路由对象放进 Spring 容器// 2.1 getRouterFunction方法会获取到路由可以看下面截图// 2.2 RouterFunction Bean 被注册到 ApplicationContext// 2.3 Spring Boot 自动配置会创建 RouterFunctionMappingMVC.fn 里的 HandlerMapping// 2.4 RouterFunctionMapping 会收集所有 RouterFunction? Bean包括这个 MCP 的// 2.5 请求进入 DispatcherServlet 后按 HandlerMapping 链匹配// 2.6 匹配到 RouterFunction 里的规则后交给对应 HandlerFunction 执行// 其实后面就是servlet的执行流程了BeanConditionalOnMissingBean(namewebMvcSseServerRouterFunction)publicRouterFunctionServerResponsewebMvcSseServerRouterFunction(WebMvcSseServerTransportProvidertransportProvider){returntransportProvider.getRouterFunction();}}4.Spring AI 如何将MCP参数解析和响应的ps也是一次完整的执行流程ToolCallbackConverterAutoConfiguration 中的 syncTools()方法会把ToolCallback转换为MCP的SyncToolSpecification格式构建McpSyncServer 将所有注册的工具构建到McpSyncServer中McpServerAutoConfiguration.mcpSyncServer()方法 会 传入第一步syncTools()方法中返回的MCP方法然后注册进server在 server 初始化/build 的时候框架把“协议里支持的每个 method”都注册进了一个MapString, McpRequestHandlerkey 就是 method 字符串运行时收到请求只要按request.method()查表分发WebMvcSseServerTransportProvider类的handleMessage方法 session.handle(message) handleIncomingRequest() handler.handle() McpAsyncServer.toolsCallRequestHandler()方法 最终会回调到MethodToolCallback#call方法进行参数处理// 入参MCP tools/call arguments(Map) → 序列化为 toolInput(JSON string) → MethodToolCallback 解析成 Map → 按“形参名”取值并按类型/泛型反序列化 → 反射调用你的业务方法// 出参业务返回值 → ToolCallResultConverter 转成 JSON 字符串 → MCP 包装成 CallToolResult(TextContent 或 ImageContent) 返回给客户端// 业务返回值被 包装成 MCP 的 CallToolResult 是在McpAsyncServer.toolsCallRequestHandler()这里完成的。privateMcpRequestHandlerMcpSchema.CallToolResulttoolsCallRequestHandler(){return(exchange,params)-{McpSchema.CallToolRequestcallToolRequest(McpSchema.CallToolRequest)this.jsonMapper.convertValue(params,newTypeRefMcpSchema.CallToolRequest(){});OptionalMcpServerFeatures.AsyncToolSpecificationtoolSpecificationthis.tools.stream().filter((tr)-callToolRequest.name().equals(tr.tool().name())).findAny();returntoolSpecification.isEmpty()?Mono.error(McpError.builder(-32602).message(Unknown tool: invalid_tool_name).data(Tool not found: callToolRequest.name()).build()):(Mono)((McpServerFeatures.AsyncToolSpecification)toolSpecification.get()).callHandler().apply(exchange,callToolRequest);};}publicclassMethodToolCallbackimplementsToolCallback{OverridepublicStringcall(StringtoolInput,NullableToolContexttoolContext){Assert.hasText(toolInput,toolInput cannot be null or empty);logger.debug(Starting execution of tool: {},this.toolDefinition.name());// 1. 验证工具上下文支持this.validateToolContextSupport(toolContext);// 2. 提取工具参数JSON字符串 - MapMapString,ObjecttoolArgumentsthis.extractToolArguments(toolInput);// 3. 构建方法参数Object[]methodArgumentsthis.buildMethodArguments(toolArguments,toolContext);// 4. 调用实际方法Objectresultthis.callMethod(methodArguments);logger.debug(Successful execution of tool: {},this.toolDefinition.name());// 5. 获取返回类型TypereturnTypethis.toolMethod.getGenericReturnType();returnthis.toolCallResultConverter.convert(result,returnType);}}最近看到一个很扎心的现象企业越来越关注开发效率而 AI 正在成为新的生产力工具。同样的需求会使用 AI 的工程师往往能够更快完成设计、编码和测试工作。与其担心被 AI 替代不如尽早学会驾驭 AI。最近我不仅在学习 Java 底层还在学习一些人工智能的知识发现了一个不错的 AI 学习网站内容通俗易懂比较适合程序员快速上手感兴趣的话也可以看看人工智能学习网