文章目錄
- 項目簡介
- Gitee
- MCP 簡介
- 環境要求
- 項目代碼
- 核心實現代碼
- MCP 服務端(批量注冊 Tool)
- MCP 客戶端(調用 DeepSeek)
- DeepSeek API
- Docker
- sse 連接
- ws 連接(推薦)
- http 連接
- Cherry Studio
- 配置模型
- 配置 MCP
- 調用 MCP
項目簡介
在本項目中,我們基于 Spring Boot 構建并通過 Docker 部署了 MCP 服務端與 MCP 客戶端,通過 HTTP 協議 實現與現有系統的遠程控制與集成。整個系統作為獨立模塊運行,服務端直接連接數據庫,核心業務邏輯抽象于 tool 層中,具備良好的擴展性與解耦性。
為提升系統的實時交互能力,項目支持通過 Cherry Studio 使用 SSE(Server-Sent Events) 與 MCP 服務端保持長連接,實現消息的實時推送與指令響應。
此外,還新增了對 WebSocket 通信 的支持。相比于 HTTP 和 SSE,WebSocket 是一種 全雙工、低延遲、連接持久 的通信協議,特別適用于高頻次、強交互的控制場景。通過 WebSocket,客戶端不僅可以實時獲取狀態更新,還可主動發送控制指令并立即獲取響應,進一步增強了系統的交互性和用戶體驗。
Gitee
- https://gitee.com/wufengsheng/spring-mcp-server
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;@Beanpublic 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;@Getterprivate 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-xxxxxbase-url: https://api.deepseek.comchat:options:model: deepseek-chatmcp:client:type: syncname: spring-mcp-clientsse: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-xxxxxbase-url: https://api.deepseek.comchat: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; # 需要配置支持websocketproxy_set_header Connection 'upgrade'; # 需要配置支持websocketproxy_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
- 下載地址:https://cherry-ai.com/
配置模型
配置 MCP
調用 MCP