文章目录
项目简介
在本项目中,我们基于 Spring Boot 构建并通过 Docker 部署了 MCP 服务端与 MCP 客户端,通过 HTTP 协议 实现与现有系统的远程控制与集成。整个系统作为独立模块运行,服务端直接连接数据库,核心业务逻辑抽象于 tool 层中,具备良好的扩展性与解耦性。
为提升系统的实时交互能力,项目支持通过 Cherry Studio 使用 SSE(Server-Sent Events) 与 MCP 服务端保持长连接,实现消息的实时推送与指令响应。
此外,还新增了对 WebSocket 通信 的支持。相比于 HTTP 和 SSE,WebSocket 是一种 全双工、低延迟、连接持久 的通信协议,特别适用于高频次、强交互的控制场景。通过 WebSocket,客户端不仅可以实时获取状态更新,还可主动发送控制指令并立即获取响应,进一步增强了系统的交互性和用户体验。
Gitee
MCP 简介
MCP 是一种开放协议,用于标准化应用程序如何向 LLM 提供上下文。可以将 MCP 视为 AI 应用程序的 USB-C 端口。就像 USB-C 提供了一种将设备连接到各种外围设备和配件的标准化方式一样,MCP 提供了一种将 AI 模型连接到不同数据源和工具的标准化方式。
- MCP 主机 (MCP Hosts):例如 Claude Desktop、IDE 或希望通过 MCP 访问数据的 AI 工具等程序
- MCP 客户端 (MCP Clients):与服务器保持 1:1 连接的协议客户端
- MCP 服务器 (MCP Servers):轻量级程序,每个程序通过标准化的模型上下文协议 (MCP) 公开特定的功能
- 本地数据源 (Local Data Sources):您的计算机的文件、数据库和 MCP 服务器可以安全访问的服务
- 远程服务 (Remote Services):可通过互联网访问的外部系统(例如,通过 API),MCP 服务器可以连接到这些系统
环境要求
- Java 环境 >= JDK17
- Spring Boot >= 3.x
项目代码
git clone https://gitee.com/wufengsheng/spring-mcp-server.git
注: Java 开发环境必须 >= JDK17,否则项目编译不过。
核心实现代码
- MCP 依赖 jar 包
<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>
<dependencies>
<!-- mcp 服务端引入 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<!-- mcp 客户端引入 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
...
</dependencies>
MCP 服务端(批量注册 Tool)
- ToolCallbackProviderConfig.java
import lombok.AllArgsConstructor;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
@Configuration
@AllArgsConstructor
public class ToolCallbackProviderConfig {
private final ApplicationContext applicationContext;
@Bean
public ToolCallbackProvider methodToolCallbackProvider() {
Map<String, McpTool> mcpBeanMap = applicationContext.getBeansOfType(McpTool.class);
return MethodToolCallbackProvider.builder()
.toolObjects(mcpBeanMap.values().toArray())
.build();
}
}
- McpDemoService.java
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.server.core.McpTool;
import com.server.module.demo.domain.Demo;
import com.server.module.demo.service.IDemoService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@AllArgsConstructor
public class McpDemoService implements McpTool {
private final IDemoService demoService;
@Tool(description = "根据年龄查询 Demo 列表")
@Transactional(rollbackFor = {RuntimeException.class, Exception.class})
public String queryDemoInfoByAge(@ToolParam(description = "年龄") Integer age) {
Page<Demo> page = new Page<>(1, 10);
List<Demo> demoList = demoService.list(page, Wrappers.<Demo>lambdaQuery().eq(Demo::getAge, age).orderByAsc(Demo::getName));
return JSON.toJSONString(demoList);
}
}
MCP 客户端(调用 DeepSeek)
- ChatService.java
import com.alibaba.fastjson2.JSON;
import io.modelcontextprotocol.client.McpSyncClient;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
public class ChatService {
private final ChatClient chatClient;
@Getter
private final ToolCallback[] toolCallbacks;
public ChatService(OpenAiChatModel openAiChatModel, List<McpSyncClient> mcpSyncClientList) {
log.info("mcpSyncClientList={}", JSON.toJSONString(mcpSyncClientList));
log.info("===============================================\n");
for (McpSyncClient mcpSyncClient : mcpSyncClientList) {
log.info("clientInfo={}", mcpSyncClient.getClientInfo());
log.info("serverInfo={}", mcpSyncClient.getServerInfo());
}
log.info("===============================================\n");
var mcpToolCallbackProvider = new SyncMcpToolCallbackProvider(mcpSyncClientList);
toolCallbacks = mcpToolCallbackProvider.getToolCallbacks();
log.info("toolCallbacks={}", JSON.toJSONString(toolCallbacks));
log.info("===============================================\n");
for (ToolCallback toolCallback : toolCallbacks) {
log.info("toolCallback={}", JSON.toJSON(toolCallback.getToolDefinition()));
}
log.info("===============================================\n");
this.chatClient = ChatClient.builder(openAiChatModel)
.defaultTools(mcpToolCallbackProvider)
.build();
}
public String askQuestion(String prompt) {
return chatClient.prompt(prompt).call().content();
}
}
- application.yml
spring:
ai:
openai:
api-key: sk-xxxxx
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
mcp:
client:
type: sync
name: spring-mcp-client
sse:
connections:
server1:
url: http://localhost:9800
DeepSeek API
- 申请 API_KEY:https://platform.deepseek.com/usage
Docker
- 打包 jar 包并复制到 docker/app/ 对应目录中
- start.sh 脚本按顺序先启动 mcp-server 再启动 mcp-client
- 配置 API_KEY 与 DeepSeek 模型
cd docker/app/mcp-client
vim application-prod.yml
spring:
ai:
openai:
api-key: sk-xxxxx
base-url: https://api.deepseek.com
chat:
options:
model: deepseek-chat
cd docker
docker-compose up -d mcp-server
docker logs -f mcp-server
sse 连接
http://127.0.0.1:9800/sse
ws 连接(推荐)
ws://127.0.0.1:9802/mcp/ws
- js 测试脚本
cd node-ws-client
npm install
node client.js
- Nginx 配置 ws:// 或 wss://
location /mcp/ {
proxy_set_header X-Real_IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X_Forward_For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade; # 需要配置支持websocket
proxy_set_header Connection 'upgrade'; # 需要配置支持websocket
proxy_pass http://192.168.0.160:9802/mcp/;
}
http 连接
http://127.0.0.1:9801/mcp/mcpClient/listTools
http://127.0.0.1:9801/mcp/mcpClient/chat?prompt=查询章若楠用户信息
Cherry Studio
配置模型
配置 MCP
调用 MCP