一个简单的java基于spring-ai构建mcp server以及mcp client案例
简介
- 本文主要基于spring-ai 框架构建,目前mcp官方的示例中对于mcp相关的java实现,也是用的spring-ai举例,由此可见java这边使用spring实现应该是标准了。
- 当然官方也有单独的sdk提供,感兴趣的可以用这个sdk自己实现
- 官方MCP简介https://mcp-docs.cn/introduction 可以找到里面的快速开始-面向服务器开发者/面向客户端开发者,查看详细代码示例,本文主要偏向于应用,要了解概念等可以参考该网站
- 一般来说mcp server分为stdio和sse,本文主要用sse作为案例,结尾也有个简单的stdio案例
- stdio一般和客户端在同一个机器上启动,用来提供一些本地功能例如根据需求生成文件等,当然也可以作为远程调用的agent,client和server通过标准的输入输出流进行交互
- sse一般用来提供一些远程服务,比如实时查询天气状况,股票情况,应用数据等实时数据或者提供公共服务,client和server通过http的sse进行交互
准备
-
springboot3
-
java17 (springboot3依赖)
-
deepchat (测试mcp使用 https://deepchat.thinkinai.xyz/)
-
模型 base_url,api_key (例如deepseek https://api-docs.deepseek.com/zh-cn/)
-
本项目包含一个父项目和两个子项目mcp-server和mcp-client
创建父项目(整个项目结构结尾有)
父项目pom.xml如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.4.4</version> <relativePath/> </parent> <groupId>org.example</groupId> <artifactId>zm-mcp</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>mcp-server</module> <module>mcp-client</module> </modules> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0-M7</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <repositories> <repository> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> </repository> </repositories> </project>
创建子项目MCP-SERVER
该项目作为mcp的server端,对外提供mcp功能
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.example</groupId> <artifactId>zm-mcp</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>mcp-server</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webflux</artifactId> </dependency> </dependencies> </project>
2.定义application.yml
server: port: 9999 spring: ai: mcp: server: type: async
3.定义工具
package com.zm.ai.server.compnent; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; import java.sql.SQLException; import java.util.Map; @Component public class ToolComponent { @Tool(description = "可以获取到某个城市的天气信息") public Map<String, String> getWeather(@ToolParam(required = true,description = "入参城市,请传入城市拼音") String city) throws SQLException { System.out.println("获取到了城市信息:" + city); return Map.of("datas",city + "的天气为大雪,温度为零下5度到零下10度"); } }
4.注册工具
package com.zm.ai.server.compnent; 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 ToolRegister { @Bean public ToolCallbackProvider mcpTools(ToolComponent component){ return MethodToolCallbackProvider.builder().toolObjects(component).build(); } }
5.springboot启动类
package com.zm.ai.server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ServerApplication { public static void main(String[] args) { SpringApplication.run(ServerApplication.class,args); } }
6.启动并关注日志(Registered tools: 1, notification: true)
2025-06-06T11:07:20.661+08:00 INFO 19876 --- [ main] com.zm.ai.server.ServerApplication : No active profile set, falling back to 1 default profile: "default" 2025-06-06T11:07:21.448+08:00 INFO 19876 --- [ main] o.s.a.m.s.a.McpServerAutoConfiguration : Registered tools: 1, notification: true 2025-06-06T11:07:21.587+08:00 INFO 19876 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 9999 (http) 2025-06-06T11:07:21.592+08:00 INFO 19876 --- [ main] com.zm.ai.server.ServerApplication : Started ServerApplication in 1.219 seconds (process running for 1.587)
7.使用mcp-sdk测试改mcp是否发布成功
sdk的包
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.10.0</version>
</dependency>
测试代码
public class MCPClientStarter { public static void main(String[] args) { HttpClientSseClientTransport transport = HttpClientSseClientTransport.builder("http://10.181.20.3:9999").build(); McpSyncClient client = McpClient.sync(transport).build(); client.initialize(); System.out.println("是否已经初始化:" + client.isInitialized()); McpSchema.ListToolsResult listToolsResult = client.listTools(); List<McpSchema.Tool> tools = listToolsResult.tools(); System.out.println("获取到的tools:" + tools.stream().map(McpSchema.Tool::name).collect(Collectors.joining(","))); for (McpSchema.Tool tool : tools) { McpSchema.JsonSchema jsonSchema = tool.inputSchema(); System.out.println(jsonSchema); McpSchema.CallToolResult callToolResult = client.callTool(new McpSchema.CallToolRequest(tool.name(), Map.of("city", "北京"))); System.out.println("获取到的结果为:=========="); callToolResult.content().forEach(System.out::println); } } }
输出结果为
是否已经初始化:true 获取到的tools:getWeather JsonSchema[type=object, properties={city={type=string, description=入参城市,请传入城市拼音}}, required=[city], additionalProperties=false, defs=null, definitions=null] 获取到的结果为:========== TextContent[audience=null, priority=null, text={"datas":"北京的天气为大雪,温度为零下5度到零下10度"}]
8.使用deepchat测试该mcp是否正常,并配合大模型使用
假设已经安装好deepchat,并已经配置了base_url和api_key,我们先来问deepchat一个天气问题,案例此处使用deepseek-r1模型


可以看到deepseek无法回答出天气,因为模型中并不提供实时天气信息查询的功能
此时我们添加mcp,点击会话下面的锤子,或者右上角的设置->MCP设置,均可添加mcp工具,我们添加刚启动的工具


注意服务器类型选sse,url即上面的服务器的url,此处注意如果工具不通,可以尝试把localhost更换为本机局域网ip。
保存成功后我们在mcp工具中进行测试

启动该mcpserver并重新询问天气,模型已经成功调用mcp server处理了


创建子项目MCP-CLIENT
某些时候我们可能需要自己开发一个ai-agent,而不是用deepchat这种东西,那我们怎么使用mcp呢
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.example</groupId> <artifactId>zm-mcp</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>mcp-client</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-autoconfigure-model-tool</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-client-chat</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> </dependencies> </project>
2. application.yml 本案例使用deepseek,api-key和base-url记得配置自己的
server:
port: 8888
spring:
ai:
mcp:
client:
toolcallback:
enabled: true
enabled: true
type: ASYNC
sse:
connections:
weather:
url: http://localhost:9999
openai:
api-key: ***your api key***
base-url: ***base_url***
chat:
options:
model: deepseek-r1
3.新增一个模型调用工具
package com.zm.ai.client.component; import jakarta.annotation.Resource; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.stereotype.Service; @Service public class ChatTestTool { @Resource private OpenAiChatModel openAiChatModel; @Resource private ToolCallbackProvider callbackProvider; public void call(){ ChatClient client = ChatClient.builder(openAiChatModel).defaultTools(callbackProvider).defaultSystem("你是一个很可爱的智能助手,不论回答什么问题,都请用比较可爱的语气回答").build(); String res = client.prompt("能帮我查询一下北京的天气吗").call().content(); System.out.println("获取到结果res==========="); System.out.println(res); } }
4.application启动
package com.zm.ai.client; import com.zm.ai.client.component.ChatTestTool; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication public class ServerApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(ServerApplication.class, args); ChatTestTool testTool = context.getBean(ChatTestTool.class); testTool.call(); } }
5.得到结果

总结
到此,我们成功的创建了mcp server服务,并且使用java sdk,deepchat工具进行了调试和使用。
也成功的创建了一个ai-agent整合了mcp功能,如果追踪源代码我们可以得知,在mcp-client中,openai-model实现自动调用mcp服务的流程也比较易懂。通过mcp协议调用server的tools/list获取到tools信息,将tools的name,description和input-schema转换为标准的llm的tool-message信息。通过llm的回复递归决定是否调用tools,直到最终不需要调用tools后进行返回。
这个流程可以参考上一篇文章https://www.cnblogs.com/hetutu-5238/p/18904347
当然应该也有不支持tools的chatmodel,比如我引入了百度千帆的依赖,发现在调用call方法时,并没有把mcp提供的tool带入模型或者调用tool,个人猜测spring-ai在编写各个平台的chatmodel时应该也是根据该模型平台是否支持tools来决定是否加入tools相关代码的
个人猜测如果我们在对方不不支持tools的情况下,想要利用,可能要自己增加上下文提示,让模型输出中包含工具调用相关结构化内容,我们自己解析然后决定是否要调用工具
个人感觉mcp的功能更像是整合了外部工具的使用标准,和模型上下文。这样不用再自己手动根据结果解析是否调用工具以及编写参数逻辑。这样工具提供方可以只按照mcp协议制作工具即可直接给外部的ai应用使用。
附录
附录1:stdio形式的server使用
stdio形式的server,pom如下 主要增加了个打包插件,这个等会儿需要打jar包
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.example</groupId> <artifactId>zm-mcp</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>mcp-server-stdio</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webflux</artifactId> </dependency> </dependencies> <build> <finalName>mcp-server-stdio</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
yml如下,对stdio做修改
spring:
ai:
mcp:
server:
type: async
stdio: true
main:
banner-mode: off
logging:
pattern:
console:
对tool稍作修改,测试下来发现只有返回string可以正常处理,其他类均和上面的普通server一致
package com.zm.ai.server.stdio.compnent; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; import java.sql.SQLException; import java.util.Map; @Component public class ToolComponent { @Tool(description = "可以获取到某个城市的天气信息") public String getWeather(@ToolParam(required = true,description = "入参城市,请传入城市拼音") String city) throws SQLException { return city + "的天气为大雪,温度为零下5度到零下10度"; } }
打成jar包后,用mcp-sdk进行简单测试,注意代码的jar包位置要替换为自己打好的jar包
package com.zm.ai.tool; import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.McpSyncClient; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.client.transport.ServerParameters; import io.modelcontextprotocol.client.transport.StdioClientTransport; import io.modelcontextprotocol.spec.McpSchema; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class MCPClientStdioStarter { public static void main(String[] args) { StdioClientTransport clientTransport = new StdioClientTransport(ServerParameters .builder("java") .arg("-jar") .arg("D:/***/mcp-server-stdio.jar") .build()); McpSyncClient client = McpClient.sync(clientTransport).build(); client.initialize(); System.out.println("是否已经初始化:" + client.isInitialized()); McpSchema.ListToolsResult listToolsResult = client.listTools(); List<McpSchema.Tool> tools = listToolsResult.tools(); System.out.println("获取到的tools:" + tools.stream().map(McpSchema.Tool::name).collect(Collectors.joining(","))); for (McpSchema.Tool tool : tools) { McpSchema.JsonSchema jsonSchema = tool.inputSchema(); System.out.println(jsonSchema); McpSchema.CallToolResult callToolResult = client.callTool(new McpSchema.CallToolRequest(tool.name(), Map.of("city", "beijing"))); System.out.println("获取到的结果为:=========="); callToolResult.content().forEach(System.out::println); } } }
可得到结果
是否已经初始化:true 获取到的tools:getWeather JsonSchema[type=object, properties={city={type=string, description=入参城市,请传入城市拼音}}, required=[city], additionalProperties=false, defs=null, definitions=null] 获取到的结果为:========== TextContent[audience=null, priority=null, text="beijing的天气为大雪,温度为零下5度到零下10度"]
当然也可以放在spring-ai-client使用,形式和这个差不多就不再赘述了。
附录2:项目结构


浙公网安备 33010602011771号