Java使用spring Ai集成的mcp开发自己的mcp

张开发
2026/4/14 22:52:50 15 分钟阅读

分享文章

Java使用spring Ai集成的mcp开发自己的mcp
什么是模型上下文协议MCPMCP模型上下文协议是一个开源标准用于将人工智能应用与外部系统连接起来。通过MCP像Claude或ChatGPT这样的AI应用可以连接数据源如本地文件、数据库、工具如搜索引擎、计算器和工作流程如专门提示——使它们能够访问关键信息并执行任务。可以把MCP想象成AI应用的USB-C接口。正如USB-C提供了连接电子设备的标准化方式MCP也提供了将AI应用与外部系统连接的标准化方式。MCP的文档What is the Model Context Protocol (MCP)? - Model Context Protocol开始构建自己的MCP选择SDKMCP提供了多种编程语言Q的支持如PythonJavaKotinTypeScrip等。这里以Java SDK为例SDK主要关注Stdio标准输入输出主要是用于本地部署不需要联网大模型也在本地直接访问WebMVC基于Spring MVC同步阻塞一个连接占一个线程主要用于小规模自用默认使用Tomcat连接默认的连接池数量足够使用WebFlux异步非阻塞模型Spring WebFlux基于 Netty少量线程处理大量请求主要用于高并发对外使用其他语言的SDKSDKs - Model Context Protocol多种传输方式MCPModel Control Protocol提供的4 种传输方式本质只有2 大类1.Stdio标准输入输出本地进程内通信 像命令行一样程序之间直接传数据不走网络 最快、最简单、最安全2.SSEServer-Sent Events网络通信 服务器 → 客户端单向实时推消息 走 HTTP 长连接分 3 种实现HTTP、WebMVC、WebFlux4 种传输方式到底是什么超级大白话1Stdio默认不联网两个程序在同一台机器上直接互相读写数据像你在终端输入命令程序直接输出结果最快、最轻量适合本地工具、本地插件2HTTP SSE基础版最原始的 SSE简单 HTTP 长连接不依赖任何框架适合简单场景、轻量服务3WebMVC SSE传统 Spring基于Spring MVC同步阻塞一个连接占一个线程适合传统企业项目、并发不高的场景4WebFlux SSE响应式 Spring基于Spring WebFlux异步非阻塞少量线程可以支持海量连接适合高并发、实时推送、微服务最核心区别传输类型线程模型依赖适合场景Stdio本地进程通信无无本地、最快HTTP SSE网络长连接简单无框架简单服务WebMVC SSE网络长连接同步阻塞Spring MVC传统项目WebFlux SSE网络长连接异步非阻塞Spring WebFlux高并发其他语言比如 Python也是这样区分吗✅答案思想完全一样只是名字不同不管是 Java、Python、Go、JSMCP 传输永远分两大类1本地通信Stdio所有语言都支持Pythonsubprocess stdin/stdoutGoos.Stdin, os.StdoutJSprocess.stdin / process.stdout2网络通信SSE所有语言都能实现Python 里对应的是Flask SSEDjango SSEFastAPI SSE异步对应 WebFlux普通 HTTP SSE对应 WebMVC案例MCP服务器依赖基本环境 jdk17 maven3.9.4 springboot3.3.0parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.0/version relativePath/ !-- lookup parent from repository -- /parent dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version1.1.0-M1-PLATFORM-2/version /dependency注意如果使用其他的版本例如1.1.0.RC1等依赖自己会自动扫描注入工具我们手写的Bean也会注入会导致有一份注入失败但是不影响只是在日志会有告警org.springaicommunity.mcp...AsyncMcpToolProvider → No tool methods found: [] ← 社区版空的 o.springframework.ai.mcp...McpServerAutoConfiguration → Registered tools: 2 ← 官方版有工具application.yml配置文件server: port: 8888 spring: application: name: foss-mcp-server ai: mcp: server: name: foss-mcp-server version: 1.0.0 type: ASYNC sse-message-endpoint: /message sse-endpoint: /sse logging: level: root: INFO org.springframework.ai.mcp: DEBUG org.springframework.web.servlet: DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping: DEBUG org.springframework.web.servlet.mvc.method.annotation.SseEmitter: TRACE com.cctv.mcp: DEBUGconfig配置文件作用就是将工具注入import com.cctv.mcp.service.impl.FossBucketService; import com.cctv.mcp.service.impl.FossMonitoringStorage; import com.cctv.mcp.service.impl.FossObjectService; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class ToolConfig { Bean public ToolCallbackProvider toolCallbackProvider( FossObjectService fossObjectService) { System.out.println( [CONFIG] ToolCallbackProvider 正在初始化...); System.out.println( [CONFIG] FossService 实例 fossObjectService); // 添加这行日志确认服务启动时拿到了对象 System.out.println( [DEBUG] FossService injected: fossObjectService.getClass().getName()); // 手动检查一下类里有没有 Tool 注解的方法 (可选高级调试) java.lang.reflect.Method[] methods fossObjectService.getClass().getDeclaredMethods(); for (java.lang.reflect.Method m : methods) { if (m.isAnnotationPresent(org.springframework.ai.tool.annotation.Tool.class)) { System.out.println( [DEBUG] Found Tool method: m.getName()); } else { // 看看是不是因为导包错导致注解不匹配 for (var ann : m.getAnnotations()) { if (ann.annotationType().getSimpleName().equals(Tool)) { System.out.println( [DEBUG] Found a Tool annotation but class mismatch: ann.annotationType().getName()); } } } } return MethodToolCallbackProvider.builder() .toolObjects( fossObjectService) // 注入你的工具类 .build(); } }具体的工具类import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Component; Component public class FossObjectService { Tool(name hello, description 搜索开源软件信息) // 显式指定 name public String hello(String name) { System.out.println( [TOOL EXEC] 收到工具调用参数 name name); try { Thread.sleep(100); // 模拟一点点耗时 } catch (InterruptedException e) {} String result Hello, name ! 调用成功啦; System.out.println( [TOOL EXEC] 执行完毕返回 result); return result; } }方法的返回类型推荐是string字符串mcp支持 ① Text文本 — 最常用 ② Image图片 — 返回图片数据 ③ Resource资源 — 返回文件/资源引用启动类springboot的启动类import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; SpringBootApplication ServletComponentScan public class McpServerSpringAiApplication { public static void main(String[] args) { SpringApplication.run(McpServerSpringAiApplication.class, args); } }到此为止就可以开启服务测试工具了测试有多种方法使用postman直接测试接口使用mcp客户端测试使用现成的桌面工具如Claude Desktop、Cursor Q等来引入我们的MCP Server。我自己试了下Claude但是Claude需要国外的手机号注册。所以本次使用手动编写MCP客户端代码测试依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-client/artifactId version1.1.2/version /dependency测试类import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; import java.time.Duration; import java.util.List; import java.util.Map; public class McpTestClient { public static void main(String[] args) throws Exception { // ✅ 用 builder 创建 HttpClientSseClientTransport transport HttpClientSseClientTransport .builder(http://localhost:8888) .sseEndpoint(/sse) .build(); // 2. 创建客户端自动完成 initialize 握手 var client McpClient.sync(transport) .requestTimeout(Duration.ofSeconds(30)) .build(); client.initialize(); System.out.println( 连接成功 \n); // 3. 查看所有工具 ListMcpSchema.Tool tools client.listTools().tools(); System.out.println( 可用工具 ); for (McpSchema.Tool tool : tools) { System.out.println(工具名: tool.name()); System.out.println(描述: tool.description()); System.out.println(参数: tool.inputSchema()); System.out.println(---); } // 4. 调用工具不需要大模型直接传参 System.out.println(\n 调用 hello 工具 ); McpSchema.CallToolResult result client.callTool( new McpSchema.CallToolRequest(hello, Map.of(name, 张三)) ); // 5. 打印返回结果 for (McpSchema.Content content : result.content()) { System.out.println(返回内容: content); } // 7. 关闭连接 client.close(); } }然后直接启动服务运行MCP客户端的main方法即可

更多文章