From f98c8bc665875e1e7795a332d8d0e7fd9e50fda1 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期四, 12 三月 2026 11:18:26 +0800
Subject: [PATCH] #

---
 /dev/null                                                          |   30 ---
 src/main/java/com/zy/ai/mcp/tool/WcsMcpTools.java                  |   73 +++++++
 src/main/java/com/zy/ai/mcp/service/SpringAiMcpToolManager.java    |  296 +++++++++++++++++++++++++++++
 src/main/java/com/zy/common/config/CoolExceptionHandler.java       |    3 
 src/main/java/com/zy/ai/mcp/config/SpringAiMcpTransportConfig.java |  113 +++++++++++
 src/main/java/com/zy/common/config/AdminInterceptor.java           |    3 
 src/main/java/com/zy/ai/service/WcsDiagnosisService.java           |   35 ---
 src/main/java/com/zy/ai/mcp/config/SpringAiMcpConfig.java          |   17 +
 pom.xml                                                            |    4 
 src/main/resources/application.yml                                 |   20 ++
 10 files changed, 532 insertions(+), 62 deletions(-)

diff --git a/pom.xml b/pom.xml
index dece7d5..0233f3c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -150,6 +150,10 @@
             <artifactId>spring-ai-openai</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.springframework.ai</groupId>
+            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
+        </dependency>
+        <dependency>
             <groupId>com.google.ortools</groupId>
             <artifactId>ortools-java</artifactId>
             <version>${ortools.version}</version>
diff --git a/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java b/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
deleted file mode 100644
index c3ce29e..0000000
--- a/src/main/java/com/zy/ai/mcp/config/McpToolsBootstrap.java
+++ /dev/null
@@ -1,232 +0,0 @@
-package com.zy.ai.mcp.config;
-
-import com.alibaba.fastjson.JSONObject;
-import com.zy.ai.mcp.dto.McpToolHandler;
-import com.zy.ai.mcp.dto.ToolDefinition;
-import com.zy.ai.mcp.dto.ToolRegistry;
-import com.zy.ai.mcp.service.WcsDataFacade;
-
-import java.util.*;
-
-public class McpToolsBootstrap {
-
-    public static void registerAll(ToolRegistry registry, final WcsDataFacade facade) {
-
-        registry.register(tool(
-                "device_get_crn_status",
-                "閫氳繃鍫嗗灈鏈虹紪鍙锋煡璇㈠爢鍨涙満璁惧瀹炴椂鏁版嵁",
-                schemaObj(
-                        propArr("crnNos", true, "integer")
-                ),
-                schemaObj(
-                        propObj("devices", true)
-                ),
-                new McpToolHandler() {
-                    public Object handle(JSONObject args) {
-                        return facade.getCrnDeviceStatus(args);
-                    }
-                }
-        ));
-
-        registry.register(tool(
-                "device_get_station_status",
-                "鏌ヨ杈撻�佺嚎绔欑偣璁惧瀹炴椂鏁版嵁",
-                schemaObj(
-
-                ),
-                schemaObj(
-                        propObj("stations", true)
-                ),
-                new McpToolHandler() {
-                    public Object handle(JSONObject args) {
-                        return facade.getStationDeviceStatus(args);
-                    }
-                }
-        ));
-
-        registry.register(tool(
-                "device_get_rgv_status",
-                "閫氳繃RGV缂栧彿鏌ヨRGV璁惧瀹炴椂鏁版嵁",
-                schemaObj(
-                        propArr("rgvNos", true, "integer")
-                ),
-                schemaObj(
-                        propObj("devices", true)
-                ),
-                new McpToolHandler() {
-                    public Object handle(JSONObject args) {
-                        return facade.getRgvDeviceStatus(args);
-                    }
-                }
-        ));
-
-        registry.register(tool(
-                "task_list",
-                "閫氳繃绛涢�夋潯浠舵煡璇换鍔℃暟鎹�",
-                schemaObj(
-                        propInt("crnNo", false),
-                        propInt("rgvNo", false),
-                        propArr("taskNos", false, "integer"),
-                        propInt("limit", false)
-                ),
-                schemaObj(propArr("tasks", true, "object")),
-                new McpToolHandler() {
-                    public Object handle(JSONObject args) {
-                        return facade.getTasks(args);
-                    }
-                }
-        ));
-
-        registry.register(tool(
-                "log_query",
-                "閫氳繃绛涢�夋潯浠舵煡璇㈡棩蹇楁暟鎹�",
-                schemaObj(
-                        propInt("limit", false)
-                ),
-                schemaObj(propArr("logs", true, "object")),
-                new McpToolHandler() {
-                    public Object handle(JSONObject args) {
-                        return facade.getLogs(args);
-                    }
-                }
-        ));
-
-        registry.register(tool(
-                "config_get_device_config",
-                "閫氳繃璁惧缂栧彿鏌ヨ璁惧閰嶇疆鏁版嵁",
-                schemaObj(
-                        propArr("crnNos", false, "integer"),
-                        propArr("rgvNos", false, "integer"),
-                        propArr("devpNos", false, "integer")
-                ),
-                schemaObj(propObj("deviceConfigs", true)),
-                new McpToolHandler() {
-                    public Object handle(JSONObject args) {
-                        return facade.getDeviceConfig(args);
-                    }
-                }
-        ));
-
-        registry.register(tool(
-                "config_get_system_config",
-                "鏌ヨ绯荤粺閰嶇疆鏁版嵁",
-                schemaObj(
-
-                ),
-                schemaObj(
-                        propObj("systemConfigs", true)
-                ),
-                new McpToolHandler() {
-                    public Object handle(JSONObject args) {
-                        return facade.getSystemConfig(args);
-                    }
-                }
-        ));
-
-//        // 鈽� 璇婃柇鑱氬悎蹇収锛氫竴娆℃嬁鍏�
-//        registry.register(tool(
-//                "build_diagnosis_snapshot",
-//                "Aggregate diagnosis snapshot: tasks + device realtime + configs + clipped logs for diagnosis.",
-//                schemaObj(
-//                        propStr("warehouseCode", true),
-//                        propArr("deviceCodes", false, "string"),     // 涓嶄紶鍒欐寜浠诲姟娑夊強璁惧鎺ㄥ
-//                        propStr("taskNo", false),
-//                        propStr("fromTime", true),
-//                        propStr("toTime", true),
-//                        propInt("taskLimit", false),
-//                        propInt("logLimit", false),
-//                        propArr("logKeywords", false, "string"),
-//                        propBool("includeSystemConfig", false),
-//                        propBool("includeDeviceConfig", false)
-//                ),
-//                schemaObj(
-//                        propObj("snapshot", true),
-//                        propArr("hints", false, "string")
-//                ),
-//                new McpToolHandler() {
-//                    public Object handle(JSONObject args) {
-//                        return facade.buildDiagnosisSnapshot(args);
-//                    }
-//                }
-//        ));
-    }
-
-    // ---------- schema helpers ----------
-    private static ToolDefinition tool(String name, String desc,
-                                       Map<String, Object> in, Map<String, Object> out,
-                                       McpToolHandler handler) {
-        ToolDefinition d = new ToolDefinition();
-        d.setName(name);
-        d.setDescription(desc);
-        d.setInputSchema(in);
-        d.setOutputSchema(out);
-        d.setHandler(handler);
-        return d;
-    }
-
-    private static Map<String, Object> schemaObj(Object... props) {
-        Map<String, Object> m = new LinkedHashMap<String, Object>();
-        m.put("type", "object");
-
-        Map<String, Object> properties = new LinkedHashMap<String, Object>();
-        List<String> required = new ArrayList<String>();
-
-        for (Object p : props) {
-            @SuppressWarnings("unchecked")
-            Map<String, Object> pm = (Map<String, Object>) p;
-            String name = String.valueOf(pm.get("name"));
-            boolean req = Boolean.TRUE.equals(pm.get("required"));
-            pm.remove("name");
-            pm.remove("required");
-            properties.put(name, pm);
-            if (req) required.add(name);
-        }
-
-        m.put("properties", properties);
-        if (!required.isEmpty()) m.put("required", required);
-        return m;
-    }
-
-    private static Map<String, Object> propStr(String name, boolean required) {
-        Map<String, Object> m = new LinkedHashMap<String, Object>();
-        m.put("name", name);
-        m.put("required", required);
-        m.put("type", "string");
-        return m;
-    }
-
-    private static Map<String, Object> propInt(String name, boolean required) {
-        Map<String, Object> m = new LinkedHashMap<String, Object>();
-        m.put("name", name);
-        m.put("required", required);
-        m.put("type", "integer");
-        return m;
-    }
-
-    private static Map<String, Object> propBool(String name, boolean required) {
-        Map<String, Object> m = new LinkedHashMap<String, Object>();
-        m.put("name", name);
-        m.put("required", required);
-        m.put("type", "boolean");
-        return m;
-    }
-
-    private static Map<String, Object> propObj(String name, boolean required) {
-        Map<String, Object> m = new LinkedHashMap<String, Object>();
-        m.put("name", name);
-        m.put("required", required);
-        m.put("type", "object");
-        return m;
-    }
-
-    private static Map<String, Object> propArr(String name, boolean required, String itemType) {
-        Map<String, Object> m = new LinkedHashMap<String, Object>();
-        m.put("name", name);
-        m.put("required", required);
-        m.put("type", "array");
-        Map<String, Object> items = new LinkedHashMap<String, Object>();
-        items.put("type", itemType);
-        m.put("items", items);
-        return m;
-    }
-}
diff --git a/src/main/java/com/zy/ai/mcp/config/SpringAiMcpConfig.java b/src/main/java/com/zy/ai/mcp/config/SpringAiMcpConfig.java
new file mode 100644
index 0000000..dc0aa9e
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/config/SpringAiMcpConfig.java
@@ -0,0 +1,17 @@
+package com.zy.ai.mcp.config;
+
+import com.zy.ai.mcp.tool.WcsMcpTools;
+import org.springframework.ai.support.ToolCallbacks;
+import org.springframework.ai.tool.StaticToolCallbackProvider;
+import org.springframework.ai.tool.ToolCallbackProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SpringAiMcpConfig {
+
+    @Bean("wcsMcpToolCallbackProvider")
+    public ToolCallbackProvider wcsMcpToolCallbackProvider(WcsMcpTools wcsMcpTools) {
+        return new StaticToolCallbackProvider(ToolCallbacks.from(wcsMcpTools));
+    }
+}
diff --git a/src/main/java/com/zy/ai/mcp/config/SpringAiMcpTransportConfig.java b/src/main/java/com/zy/ai/mcp/config/SpringAiMcpTransportConfig.java
new file mode 100644
index 0000000..6661926
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/config/SpringAiMcpTransportConfig.java
@@ -0,0 +1,113 @@
+package com.zy.ai.mcp.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.server.McpServerFeatures;
+import io.modelcontextprotocol.server.McpSyncServer;
+import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
+import io.modelcontextprotocol.spec.McpSchema;
+import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerChangeNotificationProperties;
+import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
+import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.web.context.support.StandardServletEnvironment;
+import org.springframework.web.servlet.function.RouterFunction;
+import org.springframework.web.servlet.function.ServerResponse;
+
+import java.util.Collections;
+import java.util.List;
+
+@Configuration
+@EnableConfigurationProperties(McpServerSseProperties.class)
+public class SpringAiMcpTransportConfig {
+
+    @Bean("wcsOfficialSseMcpSupport")
+    public OfficialSseMcpSupport wcsOfficialSseMcpSupport(
+            @Qualifier("mcpServerObjectMapper") ObjectMapper objectMapper,
+            McpServerSseProperties sseProperties,
+            McpServerProperties serverProperties,
+            McpServerChangeNotificationProperties changeNotificationProperties,
+            @Qualifier("syncTools") ObjectProvider<List<McpServerFeatures.SyncToolSpecification>> syncToolsProvider,
+            Environment environment) {
+
+        WebMvcSseServerTransportProvider transportProvider = WebMvcSseServerTransportProvider.builder()
+                .jsonMapper(new JacksonMcpJsonMapper(objectMapper))
+                .baseUrl(sseProperties.getBaseUrl())
+                .sseEndpoint(sseProperties.getSseEndpoint())
+                .messageEndpoint(sseProperties.getSseMessageEndpoint())
+                .keepAliveInterval(sseProperties.getKeepAliveInterval())
+                .build();
+
+        List<McpServerFeatures.SyncToolSpecification> syncTools = syncToolsProvider.getIfAvailable(Collections::emptyList);
+        McpSyncServer mcpSyncServer = buildSseSyncServer(
+                transportProvider,
+                serverProperties,
+                changeNotificationProperties,
+                syncTools,
+                environment
+        );
+
+        return new OfficialSseMcpSupport(transportProvider, mcpSyncServer);
+    }
+
+    @Bean("webMvcSseServerRouterFunction")
+    public RouterFunction<ServerResponse> webMvcSseServerRouterFunction(
+            @Qualifier("wcsOfficialSseMcpSupport") OfficialSseMcpSupport support) {
+        return support.routerFunction();
+    }
+
+    private McpSyncServer buildSseSyncServer(
+            WebMvcSseServerTransportProvider transportProvider,
+            McpServerProperties serverProperties,
+            McpServerChangeNotificationProperties changeNotificationProperties,
+            List<McpServerFeatures.SyncToolSpecification> syncTools,
+            Environment environment) {
+
+        McpServer.SingleSessionSyncSpecification specification = McpServer.sync(transportProvider);
+        specification.serverInfo(new McpSchema.Implementation(serverProperties.getName(), serverProperties.getVersion()));
+
+        McpSchema.ServerCapabilities.Builder capabilitiesBuilder = McpSchema.ServerCapabilities.builder();
+        if (serverProperties.getCapabilities().isTool()) {
+            capabilitiesBuilder.tools(changeNotificationProperties.isToolChangeNotification());
+            if (syncTools != null && !syncTools.isEmpty()) {
+                specification.tools(syncTools);
+            }
+        }
+
+        specification.capabilities(capabilitiesBuilder.build());
+        specification.instructions(serverProperties.getInstructions());
+        specification.requestTimeout(serverProperties.getRequestTimeout());
+        if (environment instanceof StandardServletEnvironment) {
+            specification.immediateExecution(true);
+        }
+
+        return specification.build();
+    }
+
+    public static final class OfficialSseMcpSupport implements AutoCloseable {
+
+        private final WebMvcSseServerTransportProvider transportProvider;
+        private final McpSyncServer mcpSyncServer;
+
+        public OfficialSseMcpSupport(WebMvcSseServerTransportProvider transportProvider, McpSyncServer mcpSyncServer) {
+            this.transportProvider = transportProvider;
+            this.mcpSyncServer = mcpSyncServer;
+        }
+
+        public RouterFunction<ServerResponse> routerFunction() {
+            return transportProvider.getRouterFunction();
+        }
+
+        @Override
+        public void close() {
+            mcpSyncServer.closeGracefully();
+            mcpSyncServer.close();
+        }
+    }
+}
diff --git a/src/main/java/com/zy/ai/mcp/controller/McpController.java b/src/main/java/com/zy/ai/mcp/controller/McpController.java
deleted file mode 100644
index 3d8cf18..0000000
--- a/src/main/java/com/zy/ai/mcp/controller/McpController.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.zy.ai.mcp.controller;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.zy.ai.mcp.config.McpToolsBootstrap;
-import com.zy.ai.mcp.dto.JsonRpcRequest;
-import com.zy.ai.mcp.dto.JsonRpcResponse;
-import com.zy.ai.mcp.dto.ToolDefinition;
-import com.zy.ai.mcp.dto.ToolRegistry;
-import com.zy.ai.mcp.service.WcsDataFacade;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.*;
-
-import jakarta.annotation.PostConstruct;
-import java.util.*;
-
-@Slf4j
-@RestController
-@RequestMapping("/ai/mcp")
-public class McpController {
-
-    private final ToolRegistry registry = new ToolRegistry();
-
-    @Autowired
-    private WcsDataFacade wcsDataFacade;
-
-    public McpController(WcsDataFacade wcsDataFacade) {
-        this.wcsDataFacade = wcsDataFacade;
-    }
-
-    @PostConstruct
-    public void init() {
-        McpToolsBootstrap.registerAll(registry, wcsDataFacade);
-        log.info("MCP initialized, tools={}", registry.listTools().size());
-    }
-
-    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
-    public Object handle(@RequestBody JsonRpcRequest req,
-                         @RequestHeader(value = "Authorization", required = false) String auth) {
-
-        // 锛堝缓璁級鍋氫竴涓畝鍗曢壌鏉冿細闃叉琚殢渚胯皟鐢ㄧ敓浜х郴缁�
-        // if (!"Bearer your-token".equals(auth)) return JsonRpcResponse.err(null, 401, "Unauthorized", null);
-
-        String id = req.getId();
-        String method = req.getMethod();
-        JSONObject params = JSON.parseObject(JSON.toJSONString(req.getParams()));
-
-        try {
-            if ("initialize".equals(method)) {
-                Map<String, Object> result = new LinkedHashMap<String, Object>();
-                result.put("serverName", "wcs-mcp");
-                result.put("serverVersion", "1.0.0");
-                result.put("capabilities", Arrays.asList("tools"));
-                return JsonRpcResponse.ok(id, result);
-            }
-
-            if ("tools/list".equals(method)) {
-                Map<String, Object> result = new LinkedHashMap<String, Object>();
-                result.put("tools", registry.listTools());
-                // cursor/paging 浣犲悗闈㈤渶瑕佸啀鍔�
-                return JsonRpcResponse.ok(id, result);
-            }
-
-            if ("tools/call".equals(method)) {
-                String toolName = params.getString("name");
-                JSONObject arguments = params.getJSONObject("arguments");
-                if (toolName == null || toolName.trim().isEmpty()) {
-                    return JsonRpcResponse.err(id, -32602, "Invalid params: missing tool name", null);
-                }
-                ToolDefinition def = registry.get(toolName);
-                if (def == null) {
-                    return JsonRpcResponse.err(id, -32601, "Method not found: tool " + toolName, null);
-                }
-                Object output = def.getHandler().handle(arguments == null ? new JSONObject() : arguments);
-
-                Map<String, Object> result = new LinkedHashMap<String, Object>();
-                result.put("content", output); // 浣犱篃鍙互鎸� MCP 甯歌杩斿洖缁撴瀯鍋� text/json 鍒嗘
-                return JsonRpcResponse.ok(id, result);
-            }
-
-            return JsonRpcResponse.err(id, -32601, "Method not found: " + method, null);
-
-        } catch (Exception e) {
-            log.error("MCP handle error, method={}, params={}", method, params, e);
-            return JsonRpcResponse.err(id, -32000, "Server error", e.getMessage());
-        }
-    }
-
-    public List<Map<String, Object>> listTools() {
-        return registry.listTools();
-    }
-
-    public Object callTool(String toolName, JSONObject arguments) throws Exception {
-        if (toolName == null || toolName.trim().isEmpty()) {
-            throw new IllegalArgumentException("missing tool name");
-        }
-        ToolDefinition def = registry.get(toolName);
-        if (def == null) {
-            throw new IllegalArgumentException("tool not found: " + toolName);
-        }
-        return def.getHandler().handle(arguments == null ? new JSONObject() : arguments);
-    }
-}
diff --git a/src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java b/src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java
deleted file mode 100644
index 30da47b..0000000
--- a/src/main/java/com/zy/ai/mcp/dto/JsonRpcError.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.zy.ai.mcp.dto;
-
-import lombok.Data;
-
-@Data
-class JsonRpcError {
-    private int code;
-    private String message;
-    private Object data;
-
-    public JsonRpcError(int code, String message, Object data) {
-        this.code = code;
-        this.message = message;
-        this.data = data;
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java b/src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java
deleted file mode 100644
index ee49c4e..0000000
--- a/src/main/java/com/zy/ai/mcp/dto/JsonRpcRequest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.zy.ai.mcp.dto;
-
-import lombok.Data;
-
-import java.util.Map;
-
-@Data
-public class JsonRpcRequest {
-    private String jsonrpc; // "2.0"
-    private String id;      // string/number 閮借锛岃繖閲岀敤 string
-    private String method;  // "tools/list" | "tools/call"
-    private Map<String, Object> params;
-}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java b/src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java
deleted file mode 100644
index 7759350..0000000
--- a/src/main/java/com/zy/ai/mcp/dto/JsonRpcResponse.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.zy.ai.mcp.dto;
-
-import lombok.Data;
-
-@Data
-public class JsonRpcResponse {
-    private String jsonrpc = "2.0";
-    private String id;
-    private Object result;
-    private JsonRpcError error;
-
-    public static JsonRpcResponse ok(String id, Object result) {
-        JsonRpcResponse r = new JsonRpcResponse();
-        r.id = id;
-        r.result = result;
-        return r;
-    }
-
-    public static JsonRpcResponse err(String id, int code, String message, Object data) {
-        JsonRpcResponse r = new JsonRpcResponse();
-        r.id = id;
-        r.error = new JsonRpcError(code, message, data);
-        return r;
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java b/src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java
deleted file mode 100644
index 0671298..0000000
--- a/src/main/java/com/zy/ai/mcp/dto/McpToolHandler.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.zy.ai.mcp.dto;
-
-import com.alibaba.fastjson.JSONObject;
-
-public interface McpToolHandler {
-    Object handle(JSONObject arguments) throws Exception;
-}
diff --git a/src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java b/src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java
deleted file mode 100644
index ead355f..0000000
--- a/src/main/java/com/zy/ai/mcp/dto/ToolDefinition.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.zy.ai.mcp.dto;
-
-
-import lombok.Data;
-
-import java.util.*;
-
-@Data
-public class ToolDefinition {
-    private String name;
-    private String description;
-    private Map<String, Object> inputSchema;   // JSON Schema as Map
-    private Map<String, Object> outputSchema;  // JSON Schema as Map
-    private McpToolHandler handler;
-
-    public Map<String, Object> toMcpToolJson() {
-        Map<String, Object> m = new LinkedHashMap<String, Object>();
-        m.put("name", name);
-        m.put("description", description);
-        m.put("inputSchema", inputSchema);
-        m.put("outputSchema", outputSchema);
-        return m;
-    }
-}
-
diff --git a/src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java b/src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java
deleted file mode 100644
index 1f44742..0000000
--- a/src/main/java/com/zy/ai/mcp/dto/ToolRegistry.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.zy.ai.mcp.dto;
-
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class ToolRegistry {
-    private final Map<String, ToolDefinition> tools = new ConcurrentHashMap<String, ToolDefinition>();
-
-    public void register(ToolDefinition def) {
-        tools.put(def.getName(), def);
-    }
-
-    public List<Map<String, Object>> listTools() {
-        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
-        for (ToolDefinition def : tools.values()) {
-            list.add(def.toMcpToolJson());
-        }
-        // 涓轰簡绋冲畾杈撳嚭锛屾寜 name 鎺掑簭
-        Collections.sort(list, new Comparator<Map<String, Object>>() {
-            public int compare(Map<String, Object> a, Map<String, Object> b) {
-                return String.valueOf(a.get("name")).compareTo(String.valueOf(b.get("name")));
-            }
-        });
-        return list;
-    }
-
-    public ToolDefinition get(String name) {
-        return tools.get(name);
-    }
-}
diff --git a/src/main/java/com/zy/ai/mcp/service/SpringAiMcpToolManager.java b/src/main/java/com/zy/ai/mcp/service/SpringAiMcpToolManager.java
new file mode 100644
index 0000000..a40a717
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/service/SpringAiMcpToolManager.java
@@ -0,0 +1,296 @@
+package com.zy.ai.mcp.service;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.client.McpSyncClient;
+import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
+import io.modelcontextprotocol.spec.McpSchema;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
+import org.springframework.ai.tool.ToolCallback;
+import org.springframework.ai.tool.ToolCallbackProvider;
+import org.springframework.ai.tool.definition.ToolDefinition;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import jakarta.annotation.PreDestroy;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class SpringAiMcpToolManager {
+
+    private static final McpSchema.Implementation CLIENT_INFO =
+            new McpSchema.Implementation("wcs-ai-assistant", "1.0.0");
+
+    private final Object clientMonitor = new Object();
+
+    @Value("${server.port:9090}")
+    private Integer serverPort;
+
+    @Value("${server.servlet.context-path:}")
+    private String contextPath;
+
+    @Value("${spring.ai.mcp.server.sse-endpoint:/ai/mcp/sse}")
+    private String sseEndpoint;
+
+    @Value("${spring.ai.mcp.server.request-timeout:20s}")
+    private Duration requestTimeout;
+
+    @Value("${app.ai.mcp.client.base-url:}")
+    private String configuredBaseUrl;
+
+    private volatile ClientSession clientSession;
+
+    public List<Map<String, Object>> listTools() {
+        List<Map<String, Object>> tools = new ArrayList<Map<String, Object>>();
+        for (ToolCallback callback : getToolCallbacks()) {
+            if (callback == null || callback.getToolDefinition() == null) {
+                continue;
+            }
+            ToolDefinition definition = callback.getToolDefinition();
+            Map<String, Object> item = new LinkedHashMap<String, Object>();
+            item.put("name", definition.name());
+            item.put("description", definition.description());
+            item.put("inputSchema", parseSchema(definition.inputSchema()));
+            tools.add(item);
+        }
+        tools.sort(new Comparator<Map<String, Object>>() {
+            @Override
+            public int compare(Map<String, Object> left, Map<String, Object> right) {
+                return String.valueOf(left.get("name")).compareTo(String.valueOf(right.get("name")));
+            }
+        });
+        return tools;
+    }
+
+    public List<Object> buildOpenAiTools() {
+        List<Object> tools = new ArrayList<Object>();
+        for (Map<String, Object> item : listTools()) {
+            Object name = item.get("name");
+            if (name == null) {
+                continue;
+            }
+
+            Map<String, Object> function = new LinkedHashMap<String, Object>();
+            function.put("name", String.valueOf(name));
+            Object description = item.get("description");
+            if (description != null) {
+                function.put("description", String.valueOf(description));
+            }
+            Object inputSchema = item.get("inputSchema");
+            function.put("parameters", inputSchema == null ? new LinkedHashMap<String, Object>() : inputSchema);
+
+            Map<String, Object> tool = new LinkedHashMap<String, Object>();
+            tool.put("type", "function");
+            tool.put("function", function);
+            tools.add(tool);
+        }
+        return tools;
+    }
+
+    public Object callTool(String toolName, JSONObject arguments) {
+        if (toolName == null || toolName.trim().isEmpty()) {
+            throw new IllegalArgumentException("missing tool name");
+        }
+
+        ToolCallback callback = findCallback(toolName);
+        if (callback == null) {
+            throw new IllegalArgumentException("tool not found: " + toolName);
+        }
+
+        String rawResult = callback.call(arguments == null ? "{}" : arguments.toJSONString());
+        return parseToolResult(rawResult);
+    }
+
+    private ToolCallback findCallback(String toolName) {
+        for (ToolCallback callback : getToolCallbacks()) {
+            if (callback == null || callback.getToolDefinition() == null) {
+                continue;
+            }
+            if (toolName.equals(callback.getToolDefinition().name())) {
+                return callback;
+            }
+        }
+        return null;
+    }
+
+    private ToolCallback[] getToolCallbacks() {
+        try {
+            ToolCallback[] callbacks = ensureToolCallbackProvider().getToolCallbacks();
+            return callbacks == null ? new ToolCallback[0] : callbacks;
+        } catch (Exception e) {
+            log.warn("Failed to load MCP tools through SSE client, baseUrl={}, sseEndpoint={}",
+                    resolveBaseUrl(), resolveClientSseEndpoint(), e);
+            resetClientSession();
+            return new ToolCallback[0];
+        }
+    }
+
+    private Object parseToolResult(String rawResult) {
+        if (rawResult == null || rawResult.trim().isEmpty()) {
+            return rawResult;
+        }
+        try {
+            return JSON.parse(rawResult);
+        } catch (Exception ignore) {
+            return rawResult;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> parseSchema(String inputSchema) {
+        if (inputSchema == null || inputSchema.trim().isEmpty()) {
+            return Collections.emptyMap();
+        }
+        try {
+            Object parsed = JSON.parse(inputSchema);
+            if (parsed instanceof Map) {
+                return new LinkedHashMap<String, Object>((Map<String, Object>) parsed);
+            }
+        } catch (Exception e) {
+            log.warn("Failed to parse MCP tool schema: {}", inputSchema, e);
+        }
+        Map<String, Object> fallback = new LinkedHashMap<String, Object>();
+        fallback.put("type", "object");
+        return fallback;
+    }
+
+    private ToolCallbackProvider ensureToolCallbackProvider() {
+        return ensureClientSession().toolCallbackProvider;
+    }
+
+    private ClientSession ensureClientSession() {
+        ClientSession current = clientSession;
+        if (current != null) {
+            return current;
+        }
+
+        synchronized (clientMonitor) {
+            current = clientSession;
+            if (current != null) {
+                return current;
+            }
+
+            String baseUrl = resolveBaseUrl();
+            String clientSseEndpoint = resolveClientSseEndpoint();
+            HttpClientSseClientTransport transport = HttpClientSseClientTransport.builder(baseUrl)
+                    .sseEndpoint(clientSseEndpoint)
+                    .connectTimeout(requestTimeout)
+                    .build();
+
+            McpSyncClient syncClient = McpClient.sync(transport)
+                    .clientInfo(CLIENT_INFO)
+                    .requestTimeout(requestTimeout)
+                    .initializationTimeout(requestTimeout)
+                    .build();
+            syncClient.initialize();
+
+            SyncMcpToolCallbackProvider callbackProvider = new SyncMcpToolCallbackProvider(syncClient);
+            current = new ClientSession(syncClient, callbackProvider, baseUrl);
+            clientSession = current;
+            log.info("Spring AI MCP SSE client initialized, baseUrl={}, sseEndpoint={}, tools={}",
+                    baseUrl, clientSseEndpoint, current.toolCallbackProvider.getToolCallbacks().length);
+            return current;
+        }
+    }
+
+    private void resetClientSession() {
+        synchronized (clientMonitor) {
+            ClientSession current = clientSession;
+            clientSession = null;
+            if (current != null) {
+                current.close();
+            }
+        }
+    }
+
+    private String resolveBaseUrl() {
+        if (configuredBaseUrl != null && !configuredBaseUrl.trim().isEmpty()) {
+            return trimTrailingSlash(configuredBaseUrl.trim());
+        }
+        StringBuilder url = new StringBuilder("http://127.0.0.1:");
+        url.append(serverPort == null ? 9090 : serverPort);
+        return trimTrailingSlash(url.toString());
+    }
+
+    private String resolveClientSseEndpoint() {
+        String endpoint = normalizePath(sseEndpoint);
+        if (configuredBaseUrl != null && !configuredBaseUrl.trim().isEmpty()) {
+            return endpoint;
+        }
+        String context = normalizeContextPath(contextPath);
+        if (context.isEmpty()) {
+            return endpoint;
+        }
+        return context + endpoint;
+    }
+
+    private String normalizeContextPath(String path) {
+        if (path == null || path.trim().isEmpty() || "/".equals(path.trim())) {
+            return "";
+        }
+        String value = path.trim();
+        if (!value.startsWith("/")) {
+            value = "/" + value;
+        }
+        return trimTrailingSlash(value);
+    }
+
+    private String normalizePath(String path) {
+        if (path == null || path.trim().isEmpty()) {
+            return "/";
+        }
+        String value = path.trim();
+        if (!value.startsWith("/")) {
+            value = "/" + value;
+        }
+        return value;
+    }
+
+    private String trimTrailingSlash(String value) {
+        if (value == null || value.isEmpty()) {
+            return "";
+        }
+        return value.endsWith("/") && value.length() > 1 ? value.substring(0, value.length() - 1) : value;
+    }
+
+    @PreDestroy
+    public void destroy() {
+        resetClientSession();
+    }
+
+    private static final class ClientSession implements AutoCloseable {
+
+        private final McpSyncClient syncClient;
+        private final ToolCallbackProvider toolCallbackProvider;
+        private final String baseUrl;
+
+        private ClientSession(McpSyncClient syncClient, ToolCallbackProvider toolCallbackProvider, String baseUrl) {
+            this.syncClient = syncClient;
+            this.toolCallbackProvider = toolCallbackProvider;
+            this.baseUrl = baseUrl;
+        }
+
+        @Override
+        public void close() {
+            try {
+                syncClient.closeGracefully();
+            } catch (Exception e) {
+                log.debug("Close MCP SSE client gracefully failed, baseUrl={}", baseUrl, e);
+            }
+            try {
+                syncClient.close();
+            } catch (Exception e) {
+                log.debug("Close MCP SSE client failed, baseUrl={}", baseUrl, e);
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/zy/ai/mcp/tool/WcsMcpTools.java b/src/main/java/com/zy/ai/mcp/tool/WcsMcpTools.java
new file mode 100644
index 0000000..ebcd402
--- /dev/null
+++ b/src/main/java/com/zy/ai/mcp/tool/WcsMcpTools.java
@@ -0,0 +1,73 @@
+package com.zy.ai.mcp.tool;
+
+import com.alibaba.fastjson.JSONObject;
+import com.zy.ai.mcp.service.WcsDataFacade;
+import lombok.RequiredArgsConstructor;
+import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.annotation.ToolParam;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+@RequiredArgsConstructor
+public class WcsMcpTools {
+
+    private final WcsDataFacade wcsDataFacade;
+
+    @Tool(name = "device_get_crn_status", description = "閫氳繃鍫嗗灈鏈虹紪鍙锋煡璇㈠爢鍨涙満璁惧瀹炴椂鏁版嵁")
+    public Object getCrnDeviceStatus(
+            @ToolParam(description = "鍫嗗灈鏈虹紪鍙峰垪琛紝涓嶄紶鍒欐煡璇㈠叏閮ㄥ爢鍨涙満", required = false) List<Integer> crnNos) {
+        return wcsDataFacade.getCrnDeviceStatus(json().fluentPut("crnNos", crnNos));
+    }
+
+    @Tool(name = "device_get_station_status", description = "鏌ヨ杈撻�佺嚎绔欑偣璁惧瀹炴椂鏁版嵁")
+    public Object getStationDeviceStatus() {
+        return wcsDataFacade.getStationDeviceStatus(json());
+    }
+
+    @Tool(name = "device_get_rgv_status", description = "閫氳繃RGV缂栧彿鏌ヨRGV璁惧瀹炴椂鏁版嵁")
+    public Object getRgvDeviceStatus(
+            @ToolParam(description = "RGV缂栧彿鍒楄〃锛屼笉浼犲垯鏌ヨ鍏ㄩ儴RGV", required = false) List<Integer> rgvNos) {
+        return wcsDataFacade.getRgvDeviceStatus(json().fluentPut("rgvNos", rgvNos));
+    }
+
+    @Tool(name = "task_list", description = "閫氳繃绛涢�夋潯浠舵煡璇换鍔℃暟鎹�")
+    public Object getTasks(
+            @ToolParam(description = "鍫嗗灈鏈虹紪鍙�", required = false) Integer crnNo,
+            @ToolParam(description = "RGV缂栧彿", required = false) Integer rgvNo,
+            @ToolParam(description = "浠诲姟鍗曞彿鍒楄〃", required = false) List<Integer> taskNos,
+            @ToolParam(description = "杩斿洖鏉℃暟涓婇檺锛岄粯璁� 200", required = false) Integer limit) {
+        return wcsDataFacade.getTasks(json()
+                .fluentPut("crnNo", crnNo)
+                .fluentPut("rgvNo", rgvNo)
+                .fluentPut("taskNos", taskNos)
+                .fluentPut("limit", limit));
+    }
+
+    @Tool(name = "log_query", description = "閫氳繃绛涢�夋潯浠舵煡璇� AI 鏃ュ織鏁版嵁")
+    public Object getLogs(
+            @ToolParam(description = "杩斿洖鏃ュ織琛屾暟涓婇檺锛岄粯璁� 500", required = false) Integer limit) {
+        return wcsDataFacade.getLogs(json().fluentPut("limit", limit));
+    }
+
+    @Tool(name = "config_get_device_config", description = "閫氳繃璁惧缂栧彿鏌ヨ璁惧閰嶇疆鏁版嵁")
+    public Object getDeviceConfig(
+            @ToolParam(description = "鍫嗗灈鏈虹紪鍙峰垪琛�", required = false) List<Integer> crnNos,
+            @ToolParam(description = "RGV缂栧彿鍒楄〃", required = false) List<Integer> rgvNos,
+            @ToolParam(description = "杈撻�佺嚎缂栧彿鍒楄〃", required = false) List<Integer> devpNos) {
+        return wcsDataFacade.getDeviceConfig(json()
+                .fluentPut("crnNos", crnNos)
+                .fluentPut("rgvNos", rgvNos)
+                .fluentPut("devpNos", devpNos));
+    }
+
+    @Tool(name = "config_get_system_config", description = "鏌ヨ绯荤粺閰嶇疆鏁版嵁")
+    public Object getSystemConfig() {
+        return wcsDataFacade.getSystemConfig(json());
+    }
+
+    private JSONObject json() {
+        return new JSONObject();
+    }
+}
diff --git a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
index 45dea58..83f0380 100644
--- a/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
+++ b/src/main/java/com/zy/ai/service/WcsDiagnosisService.java
@@ -5,7 +5,7 @@
 import com.zy.ai.entity.ChatCompletionRequest;
 import com.zy.ai.entity.ChatCompletionResponse;
 import com.zy.ai.entity.WcsDiagnosisRequest;
-import com.zy.ai.mcp.controller.McpController;
+import com.zy.ai.mcp.service.SpringAiMcpToolManager;
 import com.zy.ai.utils.AiPromptUtils;
 import com.zy.ai.utils.AiUtils;
 import com.zy.common.utils.RedisUtil;
@@ -36,7 +36,7 @@
     @Autowired
     private AiUtils aiUtils;
     @Autowired(required = false)
-    private McpController mcpController;
+    private SpringAiMcpToolManager mcpToolManager;
 
     public void diagnoseStream(WcsDiagnosisRequest request, SseEmitter emitter) {
         List<ChatCompletionRequest.Message> messages = new ArrayList<>();
@@ -257,8 +257,8 @@
                                              SseEmitter emitter,
                                              String chatId) {
         try {
-            if (mcpController == null) return false;
-            List<Object> tools = buildOpenAiTools();
+            if (mcpToolManager == null) return false;
+            List<Object> tools = mcpToolManager.buildOpenAiTools();
             if (tools.isEmpty()) return false;
 
             baseMessages.add(systemPrompt);
@@ -303,7 +303,7 @@
                     }
                     Object output;
                     try {
-                        output = mcpController.callTool(toolName, args);
+                        output = mcpToolManager.callTool(toolName, args);
                     } catch (Exception e) {
                         java.util.LinkedHashMap<String, Object> err = new java.util.LinkedHashMap<String, Object>();
                         err.put("tool", toolName);
@@ -386,31 +386,6 @@
         } catch (Exception e) {
             log.warn("SSE send failed", e);
         }
-    }
-
-    private List<Object> buildOpenAiTools() {
-        if (mcpController == null) return java.util.Collections.emptyList();
-        List<Map<String, Object>> mcpTools = mcpController.listTools();
-        if (mcpTools == null || mcpTools.isEmpty()) return java.util.Collections.emptyList();
-
-        List<Object> tools = new ArrayList<>();
-        for (Map<String, Object> t : mcpTools) {
-            if (t == null) continue;
-            Object name = t.get("name");
-            if (name == null) continue;
-            Object inputSchema = t.get("inputSchema");
-            java.util.LinkedHashMap<String, Object> function = new java.util.LinkedHashMap<String, Object>();
-            function.put("name", String.valueOf(name));
-            Object desc = t.get("description");
-            if (desc != null) function.put("description", String.valueOf(desc));
-            function.put("parameters", inputSchema == null ? new java.util.LinkedHashMap<String, Object>() : inputSchema);
-
-            java.util.LinkedHashMap<String, Object> tool = new java.util.LinkedHashMap<String, Object>();
-            tool.put("type", "function");
-            tool.put("function", function);
-            tools.add(tool);
-        }
-        return tools;
     }
 
     private void sendLargeText(SseEmitter emitter, String text) {
diff --git a/src/main/java/com/zy/common/config/AdminInterceptor.java b/src/main/java/com/zy/common/config/AdminInterceptor.java
index a9badbf..f2b9668 100644
--- a/src/main/java/com/zy/common/config/AdminInterceptor.java
+++ b/src/main/java/com/zy/common/config/AdminInterceptor.java
@@ -64,6 +64,9 @@
         }
         // 璺ㄥ煙璁剧疆
         // response.setHeader("Access-Control-Allow-Origin", "*");
+        if (!(handler instanceof HandlerMethod)) {
+            return true;
+        }
         HandlerMethod handlerMethod = (HandlerMethod) handler;
         Method method = handlerMethod.getMethod();
         if (method.isAnnotationPresent(ManagerAuth.class)){
diff --git a/src/main/java/com/zy/common/config/CoolExceptionHandler.java b/src/main/java/com/zy/common/config/CoolExceptionHandler.java
index 79cfc08..c33ca24 100644
--- a/src/main/java/com/zy/common/config/CoolExceptionHandler.java
+++ b/src/main/java/com/zy/common/config/CoolExceptionHandler.java
@@ -7,7 +7,6 @@
 import org.springframework.web.HttpRequestMethodNotSupportedException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
-import org.springframework.web.method.HandlerMethod;
 
 /**
  * Created by vincent on 2019-06-09
@@ -19,7 +18,7 @@
     private I18nMessageService i18nMessageService;
 
     @ExceptionHandler(Exception.class)
-    public R handlerException(HandlerMethod handler, Exception e) {
+    public R handlerException(Exception e) {
         e.printStackTrace();
         return R.error(i18nMessageService.getMessage("response.common.systemError"));
     }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 6c3a42f..99d3d5e 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -51,6 +51,26 @@
         await-termination-period: 30s
   lifecycle:
     timeout-per-shutdown-phase: 20s
+  ai:
+    mcp:
+      server:
+        base-url: "${app.ai.mcp.server.public-base-url:http://127.0.0.1:${server.port:9090}${server.servlet.context-path:}}"
+        name: wcs-mcp
+        version: 1.0.0
+        protocol: STREAMABLE
+        type: SYNC
+        sse-endpoint: /ai/mcp/sse
+        sse-message-endpoint: /ai/mcp/message
+        streamable-http:
+          mcp-endpoint: /ai/mcp
+        instructions: 鎻愪緵 WCS 璁惧鐘舵�併�佷换鍔°�佹棩蹇楀拰閰嶇疆鏌ヨ鑳藉姏
+        annotation-scanner:
+          enabled: false
+        capabilities:
+          tool: true
+          resource: false
+          prompt: false
+          completion: false
 
 mybatis-plus:
   mapper-locations: classpath:mapper/*.xml

--
Gitblit v1.9.1