MCP(Model Context Protocol)模型上下文协议
MCP:标准化了应用程序如何像LLM提供上下文。提供了一种标准化的方式将AI模型连接到不同的数据源和工具。
可以将MCP想象成AI应用的USB-C端口,USB-C提供了标准化方式将设备连接到外围设备,MCP提供一种标准化方式将AI模型连接到不同的数据源和工具。
解决多个大模型调用同一个外部工具的需求。
MCP的两种协议,一般使用STDIO。因为是双向流
MCP架构
MCP主机:发起请求的AI程序,比如聊天机器人
MCP客户端:在主机程序内部,与MCP服务器保持1:1的连接
MCP服务器:为MCP客户端提供上下文、工具和提示信息。
本地资源:本地计算机中可供MCP服务器安全访问的资源,如文件
远程资源:MCP服务器可以远程连接到的资源,如通过API提供的数据。
开发者只需写一次MCP服务,所有兼容MCP协议模型都能调用,MCP让大模型从"被动应答"变为"主动调用工具"。
调用一个MCP服务器,就等价调用了带有多个功能的utils工具类。
代码演示
MCP服务端
-
pom依赖
xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--spring-ai-starter-mcp-server-webflux 不能和spring-boot-starter-web 依赖并存, 否则会使用tomcat启动而不是netty。从而导致mcpserver启动失败。但程序正常运行,mcp客户端连不上--> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webflux</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> -
配置文件
propertiesserver.port=8013 # ????????? server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=true spring.application.name=saa14-localmcpserver # mcp server configuration # ???? spring.ai.mcp.server.type=async spring.ai.mcp.server.name=customer-define-mcp-server spring.ai.mcp.server.version=1.0.0 -
配置类
java@Configuration public class McpServerConfig { /** * 将工具方法暴露给外部的MCP client 调用 * @param weatherService * @return */ @Bean public ToolCallbackProvider weatherTools(WeatherService weatherService) { return MethodToolCallbackProvider.builder() .toolObjects(weatherService) .build(); } } -
服务类
java@Service public class WeatherService { @Tool(description = "根据城市名称获取天气信息") public String getWeather(String location) { Map<String, String> map = new HashMap<String, String>(); map.put("北京", "最近下雨很多"); map.put("上海", "一直都是多云"); map.put("深圳", "阴晴不定的"); return map.getOrDefault(location, "未知城市"); } }
MCP客户端
-
pom依赖
xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> -
配置文件
propertiesserver.port=8014 # ????????? server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=true spring.application.name=saa15-localmcpclient #springai ????config spring.ai.dashscope.api-key=${aliQwen-apikey} # mcp client configuration spring.ai.mcp.client.type=async spring.ai.mcp.client.request-timeout=60s spring.ai.mcp.client.toolcallback.enabled=true spring.ai.mcp.client.sse.connections.mcp-server1.url=http://localhost:8013 -
配置类
java@Configuration public class SaaLLMConfig { private final String DEEPSEEk_MODEL="deepseek-v3"; private final String QWEN_MODEL="qwen-max"; // 方式一 @Value("${spring.ai.dashscope.api-key}") private String apiKey; // 配置大模型对外暴露的api @Bean(name = "deepseekChatModel") public ChatModel deepseekChatModel() { return DashScopeChatModel.builder() // 配置apikey .dashScopeApi(DashScopeApi.builder().apiKey(apiKey).build()) // 配置模型 .defaultOptions(DashScopeChatOptions.builder().withModel(DEEPSEEk_MODEL).build()) .build(); } @Bean(name = "qwenChatModel") public ChatModel qwenChatModel() { return DashScopeChatModel.builder() // 配置apikey .dashScopeApi(DashScopeApi.builder().apiKey(apiKey).build()) // 配置模型 .defaultOptions(DashScopeChatOptions.builder().withModel(QWEN_MODEL).build()) .build(); } @Bean(name = "deepseekChatClient") public ChatClient deepseekChatClient(@Qualifier("deepseekChatModel") ChatModel deepseekChatModel) { return ChatClient.builder(deepseekChatModel) .build(); } @Bean("qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwenChatModel") ChatModel qwenChatModel, ToolCallbackProvider toolCallbackProvider) { return ChatClient.builder(qwenChatModel) .defaultToolCallbacks(toolCallbackProvider.getToolCallbacks()) // mcp server端暴露的工具方法 .build(); } } -
控制器类
java@RestController public class McpClientController { @Resource(name = "qwenChatModel") private ChatModel qwenChatModel; @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; /** * 使用mcp server端暴露的工具方法 * @param msg * @return */ @GetMapping("/mcpclient/chat") public Flux<String> chat(@RequestParam(name="msg",defaultValue = "北京") String msg) { return qwenChatClient.prompt(msg).stream().content(); } /** * 不使用mcp server端暴露的工具方法 * @param msg * @return */ @GetMapping("/mcpclient/chat2") public Flux<String> chat2(@RequestParam(name="msg",defaultValue = "北京") String msg) { return qwenChatModel.stream(msg); } }
通过MCP调用百度地图
-
pom依赖
xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> -
配置文件
propertiesserver.port=8015 # ????????? server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=true spring.application.name=saa16-ClientCallBaiduMapMcpServer #springai ????config spring.ai.dashscope.api-key=${aliQwen-apikey} # mcp client configuration spring.ai.mcp.client.request-timeout=60s spring.ai.mcp.client.toolcallback.enabled=true # json5 ???????????json???? spring.ai.mcp.client.stdio.servers-configuration=classpath:mcp-server.json5mcp-server.json5文件内容如下:
json{ "mcpServers": { "baidu-map": { "command": "cmd", "args": [ "/c", "npx", "-y", "@baidumap/mcp-server-baidu-map" ], "env": { "BAIDU_MAP_API_KEY": "1ZGpFxxONtxxzhq3WEd5Z3BnNoB6" } } } } -
配置类
java@Configuration public class SaaLLMConfig { private final String QWEN_MODEL="qwen-max"; // 方式一 @Value("${spring.ai.dashscope.api-key}") private String apiKey; @Bean(name = "qwenChatModel") public ChatModel qwenChatModel() { return DashScopeChatModel.builder() // 配置apikey .dashScopeApi(DashScopeApi.builder().apiKey(apiKey).build()) // 配置模型 .defaultOptions(DashScopeChatOptions.builder().withModel(QWEN_MODEL).build()) .build(); } @Bean("qwenChatClient") public ChatClient qwenChatClient(@Qualifier("qwenChatModel") ChatModel qwenChatModel, ToolCallbackProvider toolCallbackProvider) { return ChatClient.builder(qwenChatModel) .defaultToolCallbacks(toolCallbackProvider.getToolCallbacks()) // mcp server端暴露的工具方法 .build(); } } -
控制器类
java@RestController public class McpClientCallBaiduMcpController { @Resource(name = "qwenChatModel") private ChatModel qwenChatModel; @Resource(name = "qwenChatClient") private ChatClient qwenChatClient; /** * 使用mcp server端暴露的工具方法 * 可以查询天气,IP归属地、路线规划 * demo参数:查询北纬39.9042°,东经116.4074°的天气情况 * demo参数:查询61.149.121.66归属地 * demo参数:查询从北京到上海的路线规划 * @param msg * @return */ @GetMapping("/mcpclient/chat") public Flux<String> chat(@RequestParam(name="msg",defaultValue = "北京") String msg) { return qwenChatClient.prompt(msg).stream().content(); } }