From 8a3fa0452075df8290d4542e64ced002ff4b476d Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 19 三月 2026 09:51:53 +0800
Subject: [PATCH] #AI

---
 rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiParam.java                           |  109 +
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/McpMountRuntimeFactoryImpl.java  |  211 ++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatDoneDto.java                        |   19 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatMemoryDto.java                      |   15 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiConfigResolverServiceImpl.java |   31 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java                    |   98 +
 rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiPromptMapper.java                    |   11 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiPromptServiceImpl.java         |   68 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java       |  208 ++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiPrompt.java                          |   90 +
 rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiMcpMountMapper.java                  |   11 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatRequest.java                        |   18 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatServiceImpl.java           |  349 ++++
 rsf-admin/src/i18n/en.js                                                                         |    3 
 rsf-admin/src/page/ResourceContent.js                                                            |    9 
 rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx                                    |  205 ++
 rsf-admin/src/page/system/aiParam/AiParamEdit.jsx                                                |   26 
 rsf-admin/src/page/system/aiMcpMount/AiMcpMountCreate.jsx                                        |   13 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatSessionDto.java                     |   21 
 rsf-admin/src/page/system/aiMcpMount/AiMcpMountEdit.jsx                                          |   26 
 rsf-admin/src/page/system/aiParam/index.jsx                                                      |   12 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiResolvedConfig.java                     |   22 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiMcpMount.java                        |  112 +
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiConfigResolverService.java          |    8 
 rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx                                          |   93 +
 rsf-admin/src/page/system/aiPrompt/AiPromptCreate.jsx                                            |   13 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolTestRequest.java                 |   11 
 rsf-admin/src/config/authProvider.js                                                             |    3 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiChatService.java                    |   19 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java                     |  123 +
 rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiChatMessage.java                     |   54 
 rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx                                              |  229 ++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatMessageDto.java                     |   11 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatSessionMapper.java               |    9 
 rsf-admin/src/page/system/aiParam/AiParamForm.jsx                                                |   59 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiPromptController.java            |   95 +
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiMcpMountService.java                |   22 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiDefaults.java                        |   21 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/McpMountRuntimeFactory.java           |   25 
 rsf-admin/src/api/ai/mcpMount.js                                                                 |   19 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolTestDto.java                     |   15 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiChatMemoryService.java              |   21 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java     |  288 +++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiChatSession.java                     |   64 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java  |   69 
 rsf-admin/src/api/ai/chat.js                                                                     |  103 +
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatRuntimeDto.java                     |   29 
 rsf-admin/src/page/system/aiParam/AiParamList.jsx                                                |  258 +++
 rsf-admin/src/page/system/aiPrompt/index.jsx                                                     |   12 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiParamController.java             |   95 +
 rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java              |    2 
 rsf-admin/src/i18n/zh.js                                                                         |    3 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiChatController.java              |   44 
 rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java         |    2 
 rsf-server/pom.xml                                                                               |   24 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamServiceImpl.java          |   97 +
 rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx                                              |   37 
 rsf-admin/src/page/system/aiParam/AiParamCreate.jsx                                              |   13 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiMcpMountController.java          |  108 +
 rsf-admin/src/layout/AppBarToolbar.jsx                                                           |   30 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiParamMapper.java                     |   11 
 version/db/ai_feature.sql                                                                        |  225 ++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java                     |  147 +
 rsf-admin/src/page/system/aiPrompt/AiPromptEdit.jsx                                              |   26 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java                  |   17 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatMessageMapper.java               |    9 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiParamService.java                   |   13 
 rsf-admin/src/page/system/aiMcpMount/index.jsx                                                   |   12 
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptService.java                  |   13 
 rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx                                          |  254 ++
 rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java           |   13 
 rsf-admin/src/layout/AiChatDrawer.jsx                                                            |  475 +++++
 rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx                                            |   99 +
 73 files changed, 5,117 insertions(+), 12 deletions(-)

diff --git a/rsf-admin/src/api/ai/chat.js b/rsf-admin/src/api/ai/chat.js
new file mode 100644
index 0000000..b11da93
--- /dev/null
+++ b/rsf-admin/src/api/ai/chat.js
@@ -0,0 +1,103 @@
+import request from "@/utils/request";
+import { PREFIX_BASE_URL, TOKEN_HEADER_NAME } from "@/config/setting";
+import { getToken } from "@/utils/token-util";
+
+export const getAiRuntime = async (promptCode = "home.default", sessionId = null) => {
+    const res = await request.get("ai/chat/runtime", {
+        params: { promptCode, sessionId },
+    });
+    const { code, msg, data } = res.data;
+    if (code === 200) {
+        return data;
+    }
+    throw new Error(msg || "鑾峰彇 AI 杩愯鏃朵俊鎭け璐�");
+};
+
+export const getAiSessions = async (promptCode = "home.default") => {
+    const res = await request.get("ai/chat/sessions", {
+        params: { promptCode },
+    });
+    const { code, msg, data } = res.data;
+    if (code === 200) {
+        return data || [];
+    }
+    throw new Error(msg || "鑾峰彇 AI 浼氳瘽鍒楄〃澶辫触");
+};
+
+export const removeAiSession = async (sessionId) => {
+    const res = await request.post(`ai/chat/session/remove/${sessionId}`);
+    const { code, msg, data } = res.data;
+    if (code === 200) {
+        return data;
+    }
+    throw new Error(msg || "鍒犻櫎 AI 浼氳瘽澶辫触");
+};
+
+export const streamAiChat = async (payload, { signal, onEvent } = {}) => {
+    const token = getToken();
+    const response = await fetch(`${PREFIX_BASE_URL}ai/chat/stream`, {
+        method: "POST",
+        headers: {
+            "Content-Type": "application/json",
+            "Accept": "text/event-stream",
+            ...(token ? { [TOKEN_HEADER_NAME]: token } : {}),
+        },
+        body: JSON.stringify(payload),
+        signal,
+    });
+
+    if (!response.ok) {
+        throw new Error(`AI 璇锋眰澶辫触 (${response.status})`);
+    }
+    if (!response.body) {
+        throw new Error("AI 鍝嶅簲娴佷笉鍙敤");
+    }
+
+    const reader = response.body.getReader();
+    const decoder = new TextDecoder("utf-8");
+    let buffer = "";
+
+    while (true) {
+        const { done, value } = await reader.read();
+        if (done) {
+            break;
+        }
+        buffer += decoder.decode(value, { stream: true });
+        const events = buffer.split(/\r?\n\r?\n/);
+        buffer = events.pop() || "";
+        events.forEach((item) => dispatchSseEvent(item, onEvent));
+    }
+
+    if (buffer.trim()) {
+        dispatchSseEvent(buffer, onEvent);
+    }
+};
+
+const dispatchSseEvent = (rawEvent, onEvent) => {
+    const lines = rawEvent.split(/\r?\n/);
+    let eventName = "message";
+    const dataLines = [];
+
+    lines.forEach((line) => {
+        if (line.startsWith("event:")) {
+            eventName = line.slice(6).trim();
+        }
+        if (line.startsWith("data:")) {
+            dataLines.push(line.slice(5).trim());
+        }
+    });
+
+    if (!dataLines.length) {
+        return;
+    }
+
+    const rawData = dataLines.join("\n");
+    let payload = rawData;
+    try {
+        payload = JSON.parse(rawData);
+    } catch (error) {
+    }
+    if (onEvent) {
+        onEvent(eventName, payload);
+    }
+};
diff --git a/rsf-admin/src/api/ai/mcpMount.js b/rsf-admin/src/api/ai/mcpMount.js
new file mode 100644
index 0000000..399d0c9
--- /dev/null
+++ b/rsf-admin/src/api/ai/mcpMount.js
@@ -0,0 +1,19 @@
+import request from "@/utils/request";
+
+export const previewMcpTools = async (mountId) => {
+    const res = await request.get(`aiMcpMount/${mountId}/tools`);
+    const { code, msg, data } = res.data;
+    if (code === 200) {
+        return data || [];
+    }
+    throw new Error(msg || "鑾峰彇宸ュ叿鍒楄〃澶辫触");
+};
+
+export const testMcpTool = async (mountId, payload) => {
+    const res = await request.post(`aiMcpMount/${mountId}/tool/test`, payload);
+    const { code, msg, data } = res.data;
+    if (code === 200) {
+        return data;
+    }
+    throw new Error(msg || "宸ュ叿娴嬭瘯澶辫触");
+};
diff --git a/rsf-admin/src/config/authProvider.js b/rsf-admin/src/config/authProvider.js
index cb8083e..24655a3 100644
--- a/rsf-admin/src/config/authProvider.js
+++ b/rsf-admin/src/config/authProvider.js
@@ -5,14 +5,11 @@
 import avatar from '/avatar.jpg'
 
 const AI_COMPONENTS = new Set([
-  'aiParam',
-  'aiPrompt',
   'aiDiagnosis',
   'aiDiagnosisPlan',
   'aiCallLog',
   'aiRoute',
   'aiToolConfig',
-  'aiMcpMount',
 ]);
 
 const filterAiMenus = (items = []) =>
diff --git a/rsf-admin/src/i18n/en.js b/rsf-admin/src/i18n/en.js
index 00fd50a..1eaeaa2 100644
--- a/rsf-admin/src/i18n/en.js
+++ b/rsf-admin/src/i18n/en.js
@@ -150,6 +150,9 @@
         token: 'Token',
         operation: 'Operation',
         config: 'Config',
+        aiParam: 'AI Params',
+        aiPrompt: 'Prompts',
+        aiMcpMount: 'MCP Mounts',
         tenant: 'Tenant',
         userLogin: 'Token',
         customer: 'Customer',
diff --git a/rsf-admin/src/i18n/zh.js b/rsf-admin/src/i18n/zh.js
index f54083c..95e5cb2 100644
--- a/rsf-admin/src/i18n/zh.js
+++ b/rsf-admin/src/i18n/zh.js
@@ -151,6 +151,9 @@
         token: '鐧诲綍鏃ュ織',
         operation: '鎿嶄綔鏃ュ織',
         config: '閰嶇疆鍙傛暟',
+        aiParam: 'AI 鍙傛暟',
+        aiPrompt: 'Prompt 绠$悊',
+        aiMcpMount: 'MCP 鎸傝浇',
         tenant: '绉熸埛绠$悊',
         userLogin: '鐧诲綍鏃ュ織',
         customer: '瀹㈡埛琛�',
diff --git a/rsf-admin/src/layout/AiChatDrawer.jsx b/rsf-admin/src/layout/AiChatDrawer.jsx
new file mode 100644
index 0000000..d837069
--- /dev/null
+++ b/rsf-admin/src/layout/AiChatDrawer.jsx
@@ -0,0 +1,475 @@
+import React, { useEffect, useMemo, useRef, useState } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
+import { useNotify } from "react-admin";
+import {
+    Alert,
+    Box,
+    Button,
+    Chip,
+    Divider,
+    Drawer,
+    IconButton,
+    List,
+    ListItemButton,
+    ListItemText,
+    Paper,
+    Stack,
+    TextField,
+    Typography,
+} from "@mui/material";
+import SmartToyOutlinedIcon from "@mui/icons-material/SmartToyOutlined";
+import SendRoundedIcon from "@mui/icons-material/SendRounded";
+import StopCircleOutlinedIcon from "@mui/icons-material/StopCircleOutlined";
+import SettingsSuggestOutlinedIcon from "@mui/icons-material/SettingsSuggestOutlined";
+import PsychologyAltOutlinedIcon from "@mui/icons-material/PsychologyAltOutlined";
+import CableOutlinedIcon from "@mui/icons-material/CableOutlined";
+import CloseIcon from "@mui/icons-material/Close";
+import AddCommentOutlinedIcon from "@mui/icons-material/AddCommentOutlined";
+import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined";
+import { getAiRuntime, getAiSessions, removeAiSession, streamAiChat } from "@/api/ai/chat";
+
+const DEFAULT_PROMPT_CODE = "home.default";
+
+const quickLinks = [
+    { label: "AI 鍙傛暟", path: "/aiParam", icon: <SettingsSuggestOutlinedIcon fontSize="small" /> },
+    { label: "Prompt", path: "/aiPrompt", icon: <PsychologyAltOutlinedIcon fontSize="small" /> },
+    { label: "MCP", path: "/aiMcpMount", icon: <CableOutlinedIcon fontSize="small" /> },
+];
+
+const AiChatDrawer = ({ open, onClose }) => {
+    const navigate = useNavigate();
+    const location = useLocation();
+    const notify = useNotify();
+    const abortRef = useRef(null);
+    const [runtime, setRuntime] = useState(null);
+    const [sessionId, setSessionId] = useState(null);
+    const [sessions, setSessions] = useState([]);
+    const [persistedMessages, setPersistedMessages] = useState([]);
+    const [messages, setMessages] = useState([]);
+    const [input, setInput] = useState("");
+    const [loadingRuntime, setLoadingRuntime] = useState(false);
+    const [streaming, setStreaming] = useState(false);
+    const [usage, setUsage] = useState(null);
+    const [drawerError, setDrawerError] = useState("");
+
+    const promptCode = runtime?.promptCode || DEFAULT_PROMPT_CODE;
+
+    const runtimeSummary = useMemo(() => {
+        return {
+            promptName: runtime?.promptName || "--",
+            model: runtime?.model || "--",
+            mountedMcpCount: runtime?.mountedMcpCount ?? 0,
+        };
+    }, [runtime]);
+
+    useEffect(() => {
+        if (open) {
+            initializeDrawer();
+        } else {
+            stopStream(false);
+        }
+    }, [open]);
+
+    useEffect(() => () => {
+        stopStream(false);
+    }, []);
+
+    const initializeDrawer = async (targetSessionId = null) => {
+        await Promise.all([
+            loadRuntime(targetSessionId),
+            loadSessions(),
+        ]);
+    };
+
+    const loadRuntime = async (targetSessionId = null) => {
+        setLoadingRuntime(true);
+        setDrawerError("");
+        try {
+            const data = await getAiRuntime(DEFAULT_PROMPT_CODE, targetSessionId);
+            const historyMessages = data?.persistedMessages || [];
+            setRuntime(data);
+            setSessionId(data?.sessionId || null);
+            setPersistedMessages(historyMessages);
+            setMessages(historyMessages);
+        } catch (error) {
+            const message = error.message || "鑾峰彇 AI 杩愯鏃跺け璐�";
+            setDrawerError(message);
+        } finally {
+            setLoadingRuntime(false);
+        }
+    };
+
+    const loadSessions = async () => {
+        try {
+            const data = await getAiSessions(DEFAULT_PROMPT_CODE);
+            setSessions(data);
+        } catch (error) {
+            const message = error.message || "鑾峰彇 AI 浼氳瘽鍒楄〃澶辫触";
+            setDrawerError(message);
+        }
+    };
+
+    const startNewSession = () => {
+        if (streaming) {
+            return;
+        }
+        setSessionId(null);
+        setPersistedMessages([]);
+        setMessages([]);
+        setUsage(null);
+        setDrawerError("");
+    };
+
+    const handleSwitchSession = async (targetSessionId) => {
+        if (streaming || targetSessionId === sessionId) {
+            return;
+        }
+        setUsage(null);
+        await loadRuntime(targetSessionId);
+    };
+
+    const handleDeleteSession = async (targetSessionId) => {
+        if (streaming || !targetSessionId) {
+            return;
+        }
+        try {
+            await removeAiSession(targetSessionId);
+            notify("浼氳瘽宸插垹闄�");
+            if (targetSessionId === sessionId) {
+                startNewSession();
+                await loadRuntime(null);
+            }
+            await loadSessions();
+        } catch (error) {
+            const message = error.message || "鍒犻櫎 AI 浼氳瘽澶辫触";
+            setDrawerError(message);
+            notify(message, { type: "error" });
+        }
+    };
+
+    const stopStream = (showTip = true) => {
+        if (abortRef.current) {
+            abortRef.current.abort();
+            abortRef.current = null;
+            setStreaming(false);
+            if (showTip) {
+                notify("宸插仠姝㈠綋鍓嶅璇濊緭鍑�");
+            }
+        }
+    };
+
+    const appendAssistantDelta = (delta) => {
+        setMessages((prev) => {
+            const next = [...prev];
+            const last = next[next.length - 1];
+            if (last && last.role === "assistant") {
+                next[next.length - 1] = {
+                    ...last,
+                    content: `${last.content || ""}${delta}`,
+                };
+                return next;
+            }
+            next.push({ role: "assistant", content: delta });
+            return next;
+        });
+    };
+
+    const ensureAssistantPlaceholder = (seedMessages) => {
+        const next = [...seedMessages];
+        const last = next[next.length - 1];
+        if (!last || last.role !== "assistant") {
+            next.push({ role: "assistant", content: "" });
+        }
+        return next;
+    };
+
+    const handleSend = async () => {
+        const content = input.trim();
+        if (!content || streaming) {
+            return;
+        }
+        const memoryMessages = [{ role: "user", content }];
+        const nextMessages = [...messages, ...memoryMessages];
+        setInput("");
+        setUsage(null);
+        setDrawerError("");
+        setMessages(ensureAssistantPlaceholder(nextMessages));
+        setStreaming(true);
+
+        const controller = new AbortController();
+        abortRef.current = controller;
+
+        let completed = false;
+        let completedSessionId = sessionId;
+
+        try {
+            await streamAiChat(
+                {
+                    sessionId,
+                    promptCode,
+                    messages: memoryMessages,
+                    metadata: {
+                        path: location.pathname,
+                    },
+                },
+                {
+                    signal: controller.signal,
+                    onEvent: (eventName, payload) => {
+                        if (eventName === "start") {
+                            setRuntime(payload);
+                            if (payload?.sessionId) {
+                                setSessionId(payload.sessionId);
+                                completedSessionId = payload.sessionId;
+                            }
+                        }
+                        if (eventName === "delta") {
+                            appendAssistantDelta(payload?.content || "");
+                        }
+                        if (eventName === "done") {
+                            setUsage(payload);
+                            completed = true;
+                            if (payload?.sessionId) {
+                                completedSessionId = payload.sessionId;
+                            }
+                        }
+                        if (eventName === "error") {
+                            const message = payload?.message || "AI 瀵硅瘽澶辫触";
+                            setDrawerError(message);
+                            notify(message, { type: "error" });
+                        }
+                    },
+                }
+            );
+        } catch (error) {
+            if (error?.name !== "AbortError") {
+                const message = error.message || "AI 瀵硅瘽澶辫触";
+                setDrawerError(message);
+                notify(message, { type: "error" });
+            }
+        } finally {
+            abortRef.current = null;
+            setStreaming(false);
+            if (completed) {
+                await Promise.all([
+                    loadRuntime(completedSessionId),
+                    loadSessions(),
+                ]);
+            }
+        }
+    };
+
+    const handleKeyDown = (event) => {
+        if (event.key === "Enter" && !event.shiftKey) {
+            event.preventDefault();
+            handleSend();
+        }
+    };
+
+    return (
+        <Drawer
+            anchor="right"
+            open={open}
+            onClose={onClose}
+            ModalProps={{ keepMounted: true }}
+            sx={{
+                zIndex: 1400,
+                "& .MuiDrawer-paper": {
+                    top: 0,
+                    height: "100vh",
+                    width: { xs: "100vw", md: "50vw" },
+                },
+            }}
+        >
+            <Box display="flex" flexDirection="column" height="100%">
+                <Stack direction="row" alignItems="center" spacing={1} px={2} py={1.5}>
+                    <SmartToyOutlinedIcon color="primary" />
+                    <Typography variant="h6" flex={1}>
+                        AI 瀵硅瘽
+                    </Typography>
+                    <IconButton size="small" onClick={startNewSession} title="鏂板缓浼氳瘽" disabled={streaming}>
+                        <AddCommentOutlinedIcon fontSize="small" />
+                    </IconButton>
+                    <IconButton size="small" onClick={onClose} title="鍏抽棴">
+                        <CloseIcon fontSize="small" />
+                    </IconButton>
+                </Stack>
+                <Divider />
+
+                <Box flex={1} display="flex" flexDirection={{ xs: "column", md: "row" }} minHeight={0}>
+                    <Box
+                        width={{ xs: "100%", md: 240 }}
+                        borderRight={{ xs: "none", md: "1px solid rgba(224, 224, 224, 1)" }}
+                        borderBottom={{ xs: "1px solid rgba(224, 224, 224, 1)", md: "none" }}
+                        display="flex"
+                        flexDirection="column"
+                        minHeight={0}
+                    >
+                        <Box px={2} py={1.5}>
+                            <Stack direction="row" alignItems="center" justifyContent="space-between" mb={1}>
+                                <Typography variant="subtitle2">浼氳瘽鍒楄〃</Typography>
+                                <Button size="small" onClick={startNewSession} disabled={streaming}>
+                                    鏂板缓浼氳瘽
+                                </Button>
+                            </Stack>
+                            <Paper variant="outlined" sx={{ overflow: "hidden" }}>
+                                {!sessions.length ? (
+                                    <Box px={1.5} py={1.25}>
+                                        <Typography variant="body2" color="text.secondary">
+                                            鏆傛棤鍘嗗彶浼氳瘽
+                                        </Typography>
+                                    </Box>
+                                ) : (
+                                    <List disablePadding sx={{ maxHeight: { xs: 180, md: "calc(100vh - 260px)" }, overflow: "auto" }}>
+                                        {sessions.map((item) => (
+                                            <ListItemButton
+                                                key={item.sessionId}
+                                                selected={item.sessionId === sessionId}
+                                                onClick={() => handleSwitchSession(item.sessionId)}
+                                                disabled={streaming}
+                                                alignItems="flex-start"
+                                            >
+                                                <ListItemText
+                                                    primary={item.title || `浼氳瘽 ${item.sessionId}`}
+                                                    secondary={item.lastMessageTime || `Session ${item.sessionId}`}
+                                                    primaryTypographyProps={{
+                                                        noWrap: true,
+                                                        fontSize: 14,
+                                                    }}
+                                                    secondaryTypographyProps={{
+                                                        noWrap: true,
+                                                        fontSize: 12,
+                                                    }}
+                                                />
+                                                <IconButton
+                                                    size="small"
+                                                    edge="end"
+                                                    disabled={streaming}
+                                                    onClick={(event) => {
+                                                        event.stopPropagation();
+                                                        handleDeleteSession(item.sessionId);
+                                                    }}
+                                                    title="鍒犻櫎浼氳瘽"
+                                                >
+                                                    <DeleteOutlineOutlinedIcon fontSize="small" />
+                                                </IconButton>
+                                            </ListItemButton>
+                                        ))}
+                                    </List>
+                                )}
+                            </Paper>
+                        </Box>
+                    </Box>
+
+                    <Box flex={1} display="flex" flexDirection="column" minHeight={0}>
+                        <Box px={2} py={1.5}>
+                            <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
+                                <Chip size="small" label={`Session: ${sessionId || "--"}`} />
+                                <Chip size="small" label={`Prompt: ${runtimeSummary.promptName}`} />
+                                <Chip size="small" label={`Model: ${runtimeSummary.model}`} />
+                                <Chip size="small" label={`MCP: ${runtimeSummary.mountedMcpCount}`} />
+                                <Chip size="small" label={`History: ${persistedMessages.length}`} />
+                            </Stack>
+                            <Stack direction="row" spacing={1} mt={1.5} flexWrap="wrap" useFlexGap>
+                                {quickLinks.map((item) => (
+                                    <Button
+                                        key={item.path}
+                                        size="small"
+                                        variant="outlined"
+                                        startIcon={item.icon}
+                                        onClick={() => navigate(item.path)}
+                                    >
+                                        {item.label}
+                                    </Button>
+                                ))}
+                            </Stack>
+                            {loadingRuntime && (
+                                <Typography variant="body2" color="text.secondary" mt={1}>
+                                    姝e湪鍔犺浇 AI 杩愯鏃朵俊鎭�...
+                                </Typography>
+                            )}
+                            {!!drawerError && (
+                                <Alert severity="warning" sx={{ mt: 1.5 }}>
+                                    {drawerError}
+                                </Alert>
+                            )}
+                        </Box>
+
+                        <Divider />
+
+                        <Box flex={1} overflow="auto" px={2} py={2} display="flex" flexDirection="column" gap={1.5}>
+                            {!messages.length && (
+                                <Paper variant="outlined" sx={{ p: 2, bgcolor: "grey.50" }}>
+                                    <Typography variant="body2" color="text.secondary">
+                                        杩欓噷浼氶�氳繃 SSE 娴佸紡杩斿洖 AI 鍥炲銆備綘涔熷彲浠ュ厛鍘讳笂闈㈢殑蹇嵎鍏ュ彛缁存姢鍙傛暟銆丳rompt 鍜� MCP 鎸傝浇銆�
+                                    </Typography>
+                                </Paper>
+                            )}
+                            {messages.map((message, index) => (
+                                <Box
+                                    key={`${message.role}-${index}`}
+                                    display="flex"
+                                    justifyContent={message.role === "user" ? "flex-end" : "flex-start"}
+                                >
+                                    <Paper
+                                        elevation={0}
+                                        sx={{
+                                            px: 1.5,
+                                            py: 1.25,
+                                            maxWidth: "85%",
+                                            borderRadius: 2,
+                                            bgcolor: message.role === "user" ? "primary.main" : "grey.100",
+                                            color: message.role === "user" ? "primary.contrastText" : "text.primary",
+                                            whiteSpace: "pre-wrap",
+                                            wordBreak: "break-word",
+                                        }}
+                                    >
+                                        <Typography variant="caption" display="block" sx={{ opacity: 0.72, mb: 0.5 }}>
+                                            {message.role === "user" ? "浣�" : "AI"}
+                                        </Typography>
+                                        <Typography variant="body2">
+                                            {message.content || (streaming && index === messages.length - 1 ? "鎬濊�冧腑..." : "")}
+                                        </Typography>
+                                    </Paper>
+                                </Box>
+                            ))}
+                        </Box>
+
+                        <Divider />
+
+                        <Box px={2} py={1.5}>
+                            {usage?.totalTokens != null && (
+                                <Typography variant="caption" color="text.secondary" display="block" mb={1}>
+                                    Tokens: prompt {usage?.promptTokens ?? 0} / completion {usage?.completionTokens ?? 0} / total {usage?.totalTokens ?? 0}
+                                </Typography>
+                            )}
+                            <TextField
+                                value={input}
+                                onChange={(event) => setInput(event.target.value)}
+                                onKeyDown={handleKeyDown}
+                                fullWidth
+                                multiline
+                                minRows={3}
+                                maxRows={6}
+                                placeholder="杈撳叆浣犵殑闂锛屾寜 Enter 鍙戦�侊紝Shift + Enter 鎹㈣"
+                            />
+                            <Stack direction="row" spacing={1} justifyContent="flex-end" mt={1.25}>
+                                <Button onClick={() => setInput("")}>娓呯┖杈撳叆</Button>
+                                {streaming ? (
+                                    <Button variant="outlined" color="warning" startIcon={<StopCircleOutlinedIcon />} onClick={() => stopStream(true)}>
+                                        鍋滄
+                                    </Button>
+                                ) : (
+                                    <Button variant="contained" startIcon={<SendRoundedIcon />} onClick={handleSend}>
+                                        鍙戦��
+                                    </Button>
+                                )}
+                            </Stack>
+                        </Box>
+                    </Box>
+                </Box>
+            </Box>
+        </Drawer>
+    );
+};
+
+export default AiChatDrawer;
diff --git a/rsf-admin/src/layout/AppBarToolbar.jsx b/rsf-admin/src/layout/AppBarToolbar.jsx
index 45cf9db..50c0f60 100644
--- a/rsf-admin/src/layout/AppBarToolbar.jsx
+++ b/rsf-admin/src/layout/AppBarToolbar.jsx
@@ -1,12 +1,26 @@
+import { useState } from 'react';
 import { LoadingIndicator, LocalesMenuButton } from 'react-admin';
+import { IconButton, Tooltip } from '@mui/material';
+import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
 import { ThemeSwapper } from '../themes/ThemeSwapper';
 import { TenantTip } from './TenantTip';
+import AiChatDrawer from './AiChatDrawer';
 
-export const AppBarToolbar = () => (
-    <>
-        <LocalesMenuButton />
-        <ThemeSwapper />
-        <LoadingIndicator />
-        <TenantTip />
-    </>
-);
+export const AppBarToolbar = () => {
+    const [drawerOpen, setDrawerOpen] = useState(false);
+
+    return (
+        <>
+            <Tooltip title="AI 瀵硅瘽">
+                <IconButton color="inherit" onClick={() => setDrawerOpen(true)}>
+                    <SmartToyOutlinedIcon />
+                </IconButton>
+            </Tooltip>
+            <LocalesMenuButton />
+            <ThemeSwapper />
+            <LoadingIndicator />
+            <TenantTip />
+            <AiChatDrawer open={drawerOpen} onClose={() => setDrawerOpen(false)} />
+        </>
+    );
+};
diff --git a/rsf-admin/src/page/ResourceContent.js b/rsf-admin/src/page/ResourceContent.js
index 22fdb9a..910f2c5 100644
--- a/rsf-admin/src/page/ResourceContent.js
+++ b/rsf-admin/src/page/ResourceContent.js
@@ -70,6 +70,9 @@
 import taskPathTemplate from './taskPathTemplate';
 import taskPathTemplateMerge from './taskPathTemplateMerge';
 import basStationArea from './basStationArea';
+import aiParam from "./system/aiParam";
+import aiPrompt from "./system/aiPrompt";
+import aiMcpMount from "./system/aiMcpMount";
 
 const ResourceContent = (node) => {
   switch (node.component) {
@@ -205,6 +208,12 @@
       return taskPathTemplateMerge;
     case 'basStationArea':
       return basStationArea;
+    case "aiParam":
+      return aiParam;
+    case "aiPrompt":
+      return aiPrompt;
+    case "aiMcpMount":
+      return aiMcpMount;
     // case "locItem":
     //   return locItem;
     default:
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountCreate.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountCreate.jsx
new file mode 100644
index 0000000..1363252
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountCreate.jsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { Create, SimpleForm } from "react-admin";
+import AiMcpMountForm from "./AiMcpMountForm";
+
+const AiMcpMountCreate = () => (
+    <Create redirect="list">
+        <SimpleForm defaultValues={{ transportType: "SSE_HTTP", endpoint: "/sse", requestTimeoutMs: 60000, sort: 0, status: 1 }}>
+            <AiMcpMountForm />
+        </SimpleForm>
+    </Create>
+);
+
+export default AiMcpMountCreate;
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountEdit.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountEdit.jsx
new file mode 100644
index 0000000..60bd6e4
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountEdit.jsx
@@ -0,0 +1,26 @@
+import React from "react";
+import {
+    DeleteButton,
+    Edit,
+    SaveButton,
+    SimpleForm,
+    Toolbar,
+} from "react-admin";
+import AiMcpMountForm from "./AiMcpMountForm";
+
+const FormToolbar = () => (
+    <Toolbar sx={{ justifyContent: "space-between" }}>
+        <SaveButton />
+        <DeleteButton mutationMode="pessimistic" />
+    </Toolbar>
+);
+
+const AiMcpMountEdit = () => (
+    <Edit redirect="list" mutationMode="pessimistic">
+        <SimpleForm warnWhenUnsavedChanges toolbar={<FormToolbar />}>
+            <AiMcpMountForm />
+        </SimpleForm>
+    </Edit>
+);
+
+export default AiMcpMountEdit;
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx
new file mode 100644
index 0000000..b88807a
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx
@@ -0,0 +1,93 @@
+import React from "react";
+import {
+    FormDataConsumer,
+    NumberInput,
+    SelectInput,
+    TextInput,
+} from "react-admin";
+import { Grid, Typography } from "@mui/material";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+
+const transportChoices = [
+    { id: "SSE_HTTP", name: "SSE_HTTP" },
+    { id: "STDIO", name: "STDIO" },
+    { id: "BUILTIN", name: "BUILTIN" },
+];
+
+const AiMcpMountForm = ({ readOnly = false }) => (
+    <Grid container spacing={2} width={{ xs: "100%", xl: "80%" }}>
+        <Grid item xs={12}>
+            <Typography variant="h6">MCP 鎸傝浇閰嶇疆</Typography>
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <TextInput source="name" label="鍚嶇О" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <SelectInput source="transportType" label="浼犺緭绫诲瀷" choices={transportChoices} fullWidth disabled={readOnly} />
+        </Grid>
+        <FormDataConsumer>
+            {({ formData }) => (
+                <>
+                    {formData.transportType === "BUILTIN" && (
+                        <>
+                            <Grid item xs={12}>
+                                <SelectInput
+                                    source="builtinCode"
+                                    label="鍐呯疆 MCP"
+                                    choices={[
+                                        { id: "RSF_WMS", name: "RSF_WMS" },
+                                        { id: "RSF_WMS_STOCK", name: "RSF_WMS_STOCK" },
+                                        { id: "RSF_WMS_TASK", name: "RSF_WMS_TASK" },
+                                        { id: "RSF_WMS_BASE", name: "RSF_WMS_BASE" },
+                                    ]}
+                                    fullWidth
+                                    disabled={readOnly}
+                                />
+                            </Grid>
+                        </>
+                    )}
+                    {formData.transportType === "SSE_HTTP" && (
+                        <>
+                            <Grid item xs={12}>
+                                <TextInput source="serverUrl" label="鏈嶅姟鍦板潃" fullWidth disabled={readOnly} />
+                            </Grid>
+                            <Grid item xs={12}>
+                                <TextInput source="endpoint" label="SSE Endpoint" fullWidth disabled={readOnly} />
+                            </Grid>
+                            <Grid item xs={12}>
+                                <TextInput source="headersJson" label="Headers JSON" fullWidth multiline minRows={4} disabled={readOnly} />
+                            </Grid>
+                        </>
+                    )}
+                    {formData.transportType === "STDIO" && (
+                        <>
+                            <Grid item xs={12}>
+                                <TextInput source="command" label="鍛戒护" fullWidth disabled={readOnly} />
+                            </Grid>
+                            <Grid item xs={12}>
+                                <TextInput source="argsJson" label="Args JSON" fullWidth multiline minRows={4} disabled={readOnly} />
+                            </Grid>
+                            <Grid item xs={12}>
+                                <TextInput source="envJson" label="Env JSON" fullWidth multiline minRows={4} disabled={readOnly} />
+                            </Grid>
+                        </>
+                    )}
+                </>
+            )}
+        </FormDataConsumer>
+        <Grid item xs={12} md={4}>
+            <NumberInput source="requestTimeoutMs" label="Timeout(ms)" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={4}>
+            <NumberInput source="sort" label="鎺掑簭" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={4}>
+            <StatusSelectInput disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12}>
+            <TextInput source="memo" label="澶囨敞" fullWidth multiline minRows={3} disabled={readOnly} />
+        </Grid>
+    </Grid>
+);
+
+export default AiMcpMountForm;
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx
new file mode 100644
index 0000000..a543ed8
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx
@@ -0,0 +1,254 @@
+import React, { useMemo, useState } from "react";
+import {
+    FilterButton,
+    List,
+    SearchInput,
+    SelectInput,
+    TopToolbar,
+    useDelete,
+    useListContext,
+    useNotify,
+    useRefresh,
+} from "react-admin";
+import {
+    Box,
+    Button,
+    Card,
+    CardActions,
+    CardContent,
+    Chip,
+    CircularProgress,
+    Divider,
+    Grid,
+    Stack,
+    Typography,
+} from "@mui/material";
+import AddRoundedIcon from "@mui/icons-material/AddRounded";
+import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined";
+import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
+import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
+import MyExportButton from "@/page/components/MyExportButton";
+import AiMcpMountForm from "./AiMcpMountForm";
+import AiConfigDialog from "../aiShared/AiConfigDialog";
+import AiMcpMountToolsPanel from "./AiMcpMountToolsPanel";
+
+const filters = [
+    <SearchInput source="condition" alwaysOn />,
+    <SelectInput
+        source="transportType"
+        label="浼犺緭绫诲瀷"
+        choices={[
+            { id: "SSE_HTTP", name: "SSE_HTTP" },
+            { id: "STDIO", name: "STDIO" },
+            { id: "BUILTIN", name: "BUILTIN" },
+        ]}
+    />,
+    <SelectInput
+        source="status"
+        label="鐘舵��"
+        choices={[
+            { id: "1", name: "common.enums.statusTrue" },
+            { id: "0", name: "common.enums.statusFalse" },
+        ]}
+    />,
+];
+
+const defaultValues = {
+    transportType: "SSE_HTTP",
+    endpoint: "/sse",
+    requestTimeoutMs: 60000,
+    sort: 0,
+    status: 1,
+};
+
+const truncateText = (value, max = 96) => {
+    if (!value) {
+        return "--";
+    }
+    return value.length > max ? `${value.slice(0, max)}...` : value;
+};
+
+const resolveTargetLabel = (record) => {
+    if (record.transportType === "BUILTIN") {
+        return record.builtinCode || "--";
+    }
+    if (record.transportType === "STDIO") {
+        return record.command || "--";
+    }
+    return record.serverUrl || "--";
+};
+
+const AiMcpMountCards = ({ onView, onEdit, onDelete, deleting }) => {
+    const { data, isLoading } = useListContext();
+    const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
+
+    if (isLoading) {
+        return (
+            <Box display="flex" justifyContent="center" py={8}>
+                <CircularProgress size={28} />
+            </Box>
+        );
+    }
+
+    if (!records.length) {
+        return (
+            <Box px={2} py={6}>
+                <Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
+                    <Typography variant="subtitle1">鏆傛棤 MCP 鎸傝浇</Typography>
+                    <Typography variant="body2" color="text.secondary" mt={1}>
+                        鍙互鏂板缓鍐呯疆 MCP銆佽繙绋� SSE 鎸傝浇鎴栨湰鍦� STDIO 鎸傝浇銆�
+                    </Typography>
+                </Card>
+            </Box>
+        );
+    }
+
+    return (
+        <Box px={2} py={2}>
+            <Grid container spacing={2}>
+                {records.map((record) => (
+                    <Grid item xs={12} md={6} xl={4} key={record.id}>
+                        <Card
+                            variant="outlined"
+                            sx={{
+                                height: "100%",
+                                borderRadius: 3,
+                                boxShadow: "0 8px 24px rgba(15, 23, 42, 0.06)",
+                            }}
+                        >
+                            <CardContent sx={{ pb: 1.5 }}>
+                                <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+                                    <Box>
+                                        <Typography variant="h6" sx={{ mb: 0.5 }}>
+                                            {record.name}
+                                        </Typography>
+                                        <Typography variant="body2" color="text.secondary">
+                                            {record.transportType$ || record.transportType || "--"}
+                                        </Typography>
+                                    </Box>
+                                    <Chip
+                                        size="small"
+                                        color={record.statusBool ? "success" : "default"}
+                                        label={record.statusBool ? "鍚敤" : "鍋滅敤"}
+                                    />
+                                </Stack>
+                                <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
+                                    <Chip size="small" variant="outlined" label={`鎺掑簭 ${record.sort ?? 0}`} />
+                                    <Chip size="small" variant="outlined" label={`${record.requestTimeoutMs ?? "--"} ms`} />
+                                </Stack>
+                                <Divider sx={{ my: 1.5 }} />
+                                <Typography variant="caption" color="text.secondary">鐩爣</Typography>
+                                <Typography variant="body2" sx={{ mt: 0.5, wordBreak: "break-all" }}>
+                                    {truncateText(resolveTargetLabel(record), 120)}
+                                </Typography>
+                                <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
+                                    澶囨敞
+                                </Typography>
+                                <Typography variant="body2">{truncateText(record.memo)}</Typography>
+                            </CardContent>
+                            <CardActions sx={{ px: 2, pb: 2, pt: 0, justifyContent: "space-between" }}>
+                                <Stack direction="row" spacing={1}>
+                                    <Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record.id)}>
+                                        璇︽儏
+                                    </Button>
+                                    <Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}>
+                                        缂栬緫
+                                    </Button>
+                                </Stack>
+                                <Button
+                                    size="small"
+                                    color="error"
+                                    startIcon={<DeleteOutlineOutlinedIcon />}
+                                    onClick={() => onDelete(record)}
+                                    disabled={deleting}
+                                >
+                                    鍒犻櫎
+                                </Button>
+                            </CardActions>
+                        </Card>
+                    </Grid>
+                ))}
+            </Grid>
+        </Box>
+    );
+};
+
+const AiMcpMountList = () => {
+    const notify = useNotify();
+    const refresh = useRefresh();
+    const [deleteOne, { isPending: deleting }] = useDelete();
+    const [dialogState, setDialogState] = useState({ open: false, mode: "create", recordId: null });
+
+    const openDialog = (mode, recordId = null) => setDialogState({ open: true, mode, recordId });
+    const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null });
+
+    const handleDelete = (record) => {
+        if (!record?.id || !window.confirm(`纭鍒犻櫎鈥�${record.name}鈥濆悧锛焋)) {
+            return;
+        }
+        deleteOne(
+            "aiMcpMount",
+            { id: record.id },
+            {
+                onSuccess: () => {
+                    notify("鍒犻櫎鎴愬姛");
+                    refresh();
+                },
+                onError: (error) => {
+                    notify(error?.message || "鍒犻櫎澶辫触", { type: "error" });
+                },
+            }
+        );
+    };
+
+    const dialogTitle = {
+        create: "鏂板缓 MCP 鎸傝浇",
+        edit: "缂栬緫 MCP 鎸傝浇",
+        show: "鏌ョ湅 MCP 鎸傝浇璇︽儏",
+    }[dialogState.mode];
+
+    return (
+        <>
+            <List
+                title="menu.aiMcpMount"
+                filters={filters}
+                sort={{ field: "sort", order: "asc" }}
+                actions={(
+                    <TopToolbar>
+                        <FilterButton />
+                        <Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}>
+                            鏂板缓
+                        </Button>
+                        <MyExportButton />
+                    </TopToolbar>
+                )}
+            >
+                <AiMcpMountCards
+                    onView={(id) => openDialog("show", id)}
+                    onEdit={(id) => openDialog("edit", id)}
+                    onDelete={handleDelete}
+                    deleting={deleting}
+                />
+            </List>
+            <AiConfigDialog
+                open={dialogState.open}
+                mode={dialogState.mode}
+                title={dialogTitle}
+                resource="aiMcpMount"
+                recordId={dialogState.recordId}
+                defaultValues={defaultValues}
+                maxWidth="lg"
+                onClose={closeDialog}
+            >
+                <>
+                    <AiMcpMountForm readOnly={dialogState.mode === "show"} />
+                    {dialogState.mode !== "create" && (
+                        <AiMcpMountToolsPanel mountId={dialogState.recordId} />
+                    )}
+                </>
+            </AiConfigDialog>
+        </>
+    );
+};
+
+export default AiMcpMountList;
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
new file mode 100644
index 0000000..1901478
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
@@ -0,0 +1,205 @@
+import React, { useEffect, useState } from "react";
+import {
+    Accordion,
+    AccordionDetails,
+    AccordionSummary,
+    Alert,
+    Box,
+    Button,
+    Card,
+    CardContent,
+    CircularProgress,
+    Divider,
+    Grid,
+    Stack,
+    TextField,
+    Typography,
+} from "@mui/material";
+import PlayCircleOutlineOutlinedIcon from "@mui/icons-material/PlayCircleOutlineOutlined";
+import PreviewOutlinedIcon from "@mui/icons-material/PreviewOutlined";
+import ExpandMoreOutlinedIcon from "@mui/icons-material/ExpandMoreOutlined";
+import { useNotify } from "react-admin";
+import { previewMcpTools, testMcpTool } from "@/api/ai/mcpMount";
+
+const AiMcpMountToolsPanel = ({ mountId }) => {
+    const notify = useNotify();
+    const [loading, setLoading] = useState(false);
+    const [tools, setTools] = useState([]);
+    const [error, setError] = useState("");
+    const [inputs, setInputs] = useState({});
+    const [outputs, setOutputs] = useState({});
+    const [testingToolName, setTestingToolName] = useState("");
+
+    useEffect(() => {
+        if (!mountId) {
+            setTools([]);
+            setInputs({});
+            setOutputs({});
+            setError("");
+            return;
+        }
+        loadTools();
+    }, [mountId]);
+
+    const loadTools = async () => {
+        setLoading(true);
+        setError("");
+        try {
+            const data = await previewMcpTools(mountId);
+            setTools(data);
+            setOutputs({});
+        } catch (requestError) {
+            setError(requestError.message || "鑾峰彇宸ュ叿鍒楄〃澶辫触");
+        } finally {
+            setLoading(false);
+        }
+    };
+
+    const handleInputChange = (toolName, value) => {
+        setInputs((prev) => ({
+            ...prev,
+            [toolName]: value,
+        }));
+    };
+
+    const handleTest = async (toolName) => {
+        const inputJson = inputs[toolName];
+        if (!inputJson || !inputJson.trim()) {
+            notify("璇疯緭鍏ュ伐鍏锋祴璇� JSON", { type: "warning" });
+            return;
+        }
+        setTestingToolName(toolName);
+        try {
+            const result = await testMcpTool(mountId, {
+                toolName,
+                inputJson,
+            });
+            setOutputs((prev) => ({
+                ...prev,
+                [toolName]: result?.output || "",
+            }));
+            notify(`宸ュ叿 ${toolName} 娴嬭瘯瀹屾垚`);
+        } catch (requestError) {
+            const message = requestError.message || "宸ュ叿娴嬭瘯澶辫触";
+            setOutputs((prev) => ({
+                ...prev,
+                [toolName]: message,
+            }));
+            notify(message, { type: "error" });
+        } finally {
+            setTestingToolName("");
+        }
+    };
+
+    if (!mountId) {
+        return (
+            <Alert severity="info" sx={{ mt: 2 }}>
+                淇濆瓨鎸傝浇鍚庡嵆鍙瑙堝伐鍏峰苟鎵ц娴嬭瘯銆�
+            </Alert>
+        );
+    }
+
+    return (
+        <Box mt={3}>
+            <Accordion defaultExpanded={false} sx={{ borderRadius: 3, overflow: "hidden" }}>
+                <AccordionSummary expandIcon={<ExpandMoreOutlinedIcon />}>
+                    <Box flex={1}>
+                        <Typography variant="h6">宸ュ叿棰勮涓庢祴璇�</Typography>
+                        <Typography variant="body2" color="text.secondary">
+                            褰撳墠鎸傝浇瑙f瀽鍑虹殑鍏ㄩ儴宸ュ叿閮芥樉绀哄湪杩欓噷锛屽彲鐩存帴杈撳叆 JSON 鍋氭祴璇曘��
+                        </Typography>
+                    </Box>
+                </AccordionSummary>
+                <AccordionDetails>
+                    <Stack direction="row" justifyContent="flex-end" alignItems="center" mb={1.5}>
+                        <Button size="small" startIcon={<PreviewOutlinedIcon />} onClick={loadTools} disabled={loading}>
+                            鍒锋柊宸ュ叿
+                        </Button>
+                    </Stack>
+                    {loading && (
+                        <Box display="flex" justifyContent="center" py={4}>
+                            <CircularProgress size={28} />
+                        </Box>
+                    )}
+                    {!!error && !loading && (
+                        <Alert severity="warning" sx={{ mb: 2 }}>
+                            {error}
+                        </Alert>
+                    )}
+                    {!loading && !error && !tools.length && (
+                        <Alert severity="info">褰撳墠鎸傝浇鏈В鏋愬嚭浠讳綍宸ュ叿銆�</Alert>
+                    )}
+                    <Grid container spacing={2}>
+                        {tools.map((tool) => (
+                            <Grid item xs={12} key={tool.name}>
+                                <Accordion defaultExpanded={false} sx={{ borderRadius: 3, overflow: "hidden" }}>
+                                    <AccordionSummary expandIcon={<ExpandMoreOutlinedIcon />}>
+                                        <Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2} width="100%" pr={1}>
+                                            <Box>
+                                                <Typography variant="subtitle1">{tool.name}</Typography>
+                                                <Typography variant="body2" color="text.secondary">
+                                                    {tool.description || "鏆傛棤鎻忚堪"}
+                                                </Typography>
+                                            </Box>
+                                            <Typography variant="caption" color="text.secondary">
+                                                {tool.returnDirect ? "returnDirect" : "normal"}
+                                            </Typography>
+                                        </Stack>
+                                    </AccordionSummary>
+                                    <AccordionDetails>
+                                        <Card variant="outlined" sx={{ borderRadius: 3 }}>
+                                            <CardContent>
+                                                <TextField
+                                                    label="Input Schema"
+                                                    value={tool.inputSchema || ""}
+                                                    fullWidth
+                                                    multiline
+                                                    minRows={5}
+                                                    maxRows={12}
+                                                    InputProps={{ readOnly: true }}
+                                                />
+                                                <TextField
+                                                    label="娴嬭瘯杈撳叆 JSON"
+                                                    value={inputs[tool.name] || ""}
+                                                    onChange={(event) => handleInputChange(tool.name, event.target.value)}
+                                                    fullWidth
+                                                    multiline
+                                                    minRows={5}
+                                                    maxRows={12}
+                                                    sx={{ mt: 2 }}
+                                                    placeholder='渚嬪锛歿"code":"A01"}'
+                                                />
+                                                <Stack direction="row" justifyContent="flex-end" mt={1.5}>
+                                                    <Button
+                                                        variant="contained"
+                                                        startIcon={<PlayCircleOutlineOutlinedIcon />}
+                                                        onClick={() => handleTest(tool.name)}
+                                                        disabled={testingToolName === tool.name}
+                                                    >
+                                                        {testingToolName === tool.name ? "娴嬭瘯涓�..." : "鎵ц娴嬭瘯"}
+                                                    </Button>
+                                                </Stack>
+                                                <TextField
+                                                    label="娴嬭瘯缁撴灉"
+                                                    value={outputs[tool.name] || ""}
+                                                    fullWidth
+                                                    multiline
+                                                    minRows={5}
+                                                    maxRows={16}
+                                                    sx={{ mt: 2 }}
+                                                    InputProps={{ readOnly: true }}
+                                                />
+                                            </CardContent>
+                                        </Card>
+                                    </AccordionDetails>
+                                </Accordion>
+                            </Grid>
+                        ))}
+                    </Grid>
+                </AccordionDetails>
+            </Accordion>
+        </Box>
+    );
+};
+
+export default AiMcpMountToolsPanel;
diff --git a/rsf-admin/src/page/system/aiMcpMount/index.jsx b/rsf-admin/src/page/system/aiMcpMount/index.jsx
new file mode 100644
index 0000000..4ad923c
--- /dev/null
+++ b/rsf-admin/src/page/system/aiMcpMount/index.jsx
@@ -0,0 +1,12 @@
+import { ShowGuesser } from "react-admin";
+import AiMcpMountList from "./AiMcpMountList";
+import AiMcpMountCreate from "./AiMcpMountCreate";
+import AiMcpMountEdit from "./AiMcpMountEdit";
+
+export default {
+    list: AiMcpMountList,
+    create: AiMcpMountCreate,
+    edit: AiMcpMountEdit,
+    show: ShowGuesser,
+    recordRepresentation: (record) => `${record?.name || ''}`,
+};
diff --git a/rsf-admin/src/page/system/aiParam/AiParamCreate.jsx b/rsf-admin/src/page/system/aiParam/AiParamCreate.jsx
new file mode 100644
index 0000000..d6309e9
--- /dev/null
+++ b/rsf-admin/src/page/system/aiParam/AiParamCreate.jsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { Create, SimpleForm } from "react-admin";
+import AiParamForm from "./AiParamForm";
+
+const AiParamCreate = () => (
+    <Create redirect="list">
+        <SimpleForm defaultValues={{ providerType: "OPENAI_COMPATIBLE", temperature: 0.7, topP: 1, timeoutMs: 60000, streamingEnabled: true, status: 1 }}>
+            <AiParamForm />
+        </SimpleForm>
+    </Create>
+);
+
+export default AiParamCreate;
diff --git a/rsf-admin/src/page/system/aiParam/AiParamEdit.jsx b/rsf-admin/src/page/system/aiParam/AiParamEdit.jsx
new file mode 100644
index 0000000..e1ec9b8
--- /dev/null
+++ b/rsf-admin/src/page/system/aiParam/AiParamEdit.jsx
@@ -0,0 +1,26 @@
+import React from "react";
+import {
+    DeleteButton,
+    Edit,
+    SaveButton,
+    SimpleForm,
+    Toolbar,
+} from "react-admin";
+import AiParamForm from "./AiParamForm";
+
+const FormToolbar = () => (
+    <Toolbar sx={{ justifyContent: "space-between" }}>
+        <SaveButton />
+        <DeleteButton mutationMode="pessimistic" />
+    </Toolbar>
+);
+
+const AiParamEdit = () => (
+    <Edit redirect="list" mutationMode="pessimistic">
+        <SimpleForm warnWhenUnsavedChanges toolbar={<FormToolbar />}>
+            <AiParamForm />
+        </SimpleForm>
+    </Edit>
+);
+
+export default AiParamEdit;
diff --git a/rsf-admin/src/page/system/aiParam/AiParamForm.jsx b/rsf-admin/src/page/system/aiParam/AiParamForm.jsx
new file mode 100644
index 0000000..07fbd25
--- /dev/null
+++ b/rsf-admin/src/page/system/aiParam/AiParamForm.jsx
@@ -0,0 +1,59 @@
+import React from "react";
+import {
+    BooleanInput,
+    NumberInput,
+    SelectInput,
+    TextInput,
+} from "react-admin";
+import { Grid, Typography } from "@mui/material";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+
+const providerChoices = [
+    { id: "OPENAI_COMPATIBLE", name: "OPENAI_COMPATIBLE" },
+];
+
+const AiParamForm = ({ readOnly = false }) => (
+    <Grid container spacing={2} width={{ xs: "100%", xl: "80%" }}>
+        <Grid item xs={12}>
+            <Typography variant="h6">涓昏閰嶇疆</Typography>
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <TextInput source="name" label="鍚嶇О" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <SelectInput source="providerType" label="鎻愪緵鏂圭被鍨�" choices={providerChoices} fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12}>
+            <TextInput source="baseUrl" label="Base URL" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <TextInput source="apiKey" label="API Key" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <TextInput source="model" label="妯″瀷" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={3}>
+            <NumberInput source="temperature" label="Temperature" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={3}>
+            <NumberInput source="topP" label="Top P" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={3}>
+            <NumberInput source="maxTokens" label="Max Tokens" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={3}>
+            <NumberInput source="timeoutMs" label="Timeout(ms)" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <BooleanInput source="streamingEnabled" label="鍚敤娴佸紡鍝嶅簲" disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <StatusSelectInput disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12}>
+            <TextInput source="memo" label="澶囨敞" fullWidth multiline minRows={3} disabled={readOnly} />
+        </Grid>
+    </Grid>
+);
+
+export default AiParamForm;
diff --git a/rsf-admin/src/page/system/aiParam/AiParamList.jsx b/rsf-admin/src/page/system/aiParam/AiParamList.jsx
new file mode 100644
index 0000000..f7d167f
--- /dev/null
+++ b/rsf-admin/src/page/system/aiParam/AiParamList.jsx
@@ -0,0 +1,258 @@
+import React, { useMemo, useState } from "react";
+import {
+    FilterButton,
+    List,
+    SearchInput,
+    SelectInput,
+    TextInput,
+    TopToolbar,
+    useDelete,
+    useListContext,
+    useNotify,
+    useRefresh,
+} from "react-admin";
+import {
+    Box,
+    Button,
+    Card,
+    CardActions,
+    CardContent,
+    Chip,
+    CircularProgress,
+    Divider,
+    Grid,
+    Stack,
+    Typography,
+} from "@mui/material";
+import AddRoundedIcon from "@mui/icons-material/AddRounded";
+import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined";
+import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
+import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
+import MyExportButton from "@/page/components/MyExportButton";
+import AiParamForm from "./AiParamForm";
+import AiConfigDialog from "../aiShared/AiConfigDialog";
+
+const filters = [
+    <SearchInput source="condition" alwaysOn />,
+    <TextInput source="providerType" label="鎻愪緵鏂圭被鍨�" />,
+    <TextInput source="model" label="妯″瀷" />,
+    <SelectInput
+        source="status"
+        label="鐘舵��"
+        choices={[
+            { id: "1", name: "common.enums.statusTrue" },
+            { id: "0", name: "common.enums.statusFalse" },
+        ]}
+    />,
+];
+
+const defaultValues = {
+    providerType: "OPENAI_COMPATIBLE",
+    temperature: 0.7,
+    topP: 1,
+    timeoutMs: 60000,
+    streamingEnabled: true,
+    status: 1,
+};
+
+const truncateText = (value, max = 84) => {
+    if (!value) {
+        return "--";
+    }
+    return value.length > max ? `${value.slice(0, max)}...` : value;
+};
+
+const AiParamCards = ({ onView, onEdit, onDelete, deleting }) => {
+    const { data, isLoading } = useListContext();
+    const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
+
+    if (isLoading) {
+        return (
+            <Box display="flex" justifyContent="center" py={8}>
+                <CircularProgress size={28} />
+            </Box>
+        );
+    }
+
+    if (!records.length) {
+        return (
+            <Box px={2} py={6}>
+                <Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
+                    <Typography variant="subtitle1">鏆傛棤 AI 鍙傛暟閰嶇疆</Typography>
+                    <Typography variant="body2" color="text.secondary" mt={1}>
+                        鍙互鍏堟柊寤轰竴涓� OpenAI 鍏煎妯″瀷鍙傛暟鍗$墖銆�
+                    </Typography>
+                </Card>
+            </Box>
+        );
+    }
+
+    return (
+        <Box px={2} py={2}>
+            <Grid container spacing={2}>
+                {records.map((record) => (
+                    <Grid item xs={12} md={6} xl={4} key={record.id}>
+                        <Card
+                            variant="outlined"
+                            sx={{
+                                height: "100%",
+                                borderRadius: 3,
+                                boxShadow: "0 8px 24px rgba(15, 23, 42, 0.06)",
+                            }}
+                        >
+                            <CardContent sx={{ pb: 1.5 }}>
+                                <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+                                    <Box>
+                                        <Typography variant="h6" sx={{ mb: 0.5 }}>
+                                            {record.name}
+                                        </Typography>
+                                        <Typography variant="body2" color="text.secondary">
+                                            {record.model || "--"}
+                                        </Typography>
+                                    </Box>
+                                    <Chip
+                                        size="small"
+                                        color={record.statusBool ? "success" : "default"}
+                                        label={record.statusBool ? "鍚敤" : "鍋滅敤"}
+                                    />
+                                </Stack>
+                                <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
+                                    <Chip size="small" variant="outlined" label={record.providerType$ || "OPENAI_COMPATIBLE"} />
+                                    <Chip
+                                        size="small"
+                                        variant="outlined"
+                                        color={record.streamingEnabled ? "info" : "default"}
+                                        label={record.streamingEnabled ? "娴佸紡鍝嶅簲" : "闈炴祦寮�"}
+                                    />
+                                </Stack>
+                                <Divider sx={{ my: 1.5 }} />
+                                <Typography variant="body2" color="text.secondary">
+                                    Base URL
+                                </Typography>
+                                <Typography variant="body2" sx={{ mb: 1.5, wordBreak: "break-all" }}>
+                                    {truncateText(record.baseUrl, 120)}
+                                </Typography>
+                                <Grid container spacing={1}>
+                                    <Grid item xs={6}>
+                                        <Typography variant="caption" color="text.secondary">Temperature</Typography>
+                                        <Typography variant="body2">{record.temperature ?? "--"}</Typography>
+                                    </Grid>
+                                    <Grid item xs={6}>
+                                        <Typography variant="caption" color="text.secondary">Top P</Typography>
+                                        <Typography variant="body2">{record.topP ?? "--"}</Typography>
+                                    </Grid>
+                                    <Grid item xs={6}>
+                                        <Typography variant="caption" color="text.secondary">Max Tokens</Typography>
+                                        <Typography variant="body2">{record.maxTokens ?? "--"}</Typography>
+                                    </Grid>
+                                    <Grid item xs={6}>
+                                        <Typography variant="caption" color="text.secondary">Timeout</Typography>
+                                        <Typography variant="body2">{record.timeoutMs ?? "--"} ms</Typography>
+                                    </Grid>
+                                </Grid>
+                                <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
+                                    澶囨敞
+                                </Typography>
+                                <Typography variant="body2">{truncateText(record.memo)}</Typography>
+                            </CardContent>
+                            <CardActions sx={{ px: 2, pb: 2, pt: 0, justifyContent: "space-between" }}>
+                                <Stack direction="row" spacing={1}>
+                                    <Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record.id)}>
+                                        璇︽儏
+                                    </Button>
+                                    <Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}>
+                                        缂栬緫
+                                    </Button>
+                                </Stack>
+                                <Button
+                                    size="small"
+                                    color="error"
+                                    startIcon={<DeleteOutlineOutlinedIcon />}
+                                    onClick={() => onDelete(record)}
+                                    disabled={deleting}
+                                >
+                                    鍒犻櫎
+                                </Button>
+                            </CardActions>
+                        </Card>
+                    </Grid>
+                ))}
+            </Grid>
+        </Box>
+    );
+};
+
+const AiParamList = () => {
+    const notify = useNotify();
+    const refresh = useRefresh();
+    const [deleteOne, { isPending: deleting }] = useDelete();
+    const [dialogState, setDialogState] = useState({ open: false, mode: "create", recordId: null });
+
+    const openDialog = (mode, recordId = null) => setDialogState({ open: true, mode, recordId });
+    const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null });
+
+    const handleDelete = (record) => {
+        if (!record?.id || !window.confirm(`纭鍒犻櫎鈥�${record.name}鈥濆悧锛焋)) {
+            return;
+        }
+        deleteOne(
+            "aiParam",
+            { id: record.id },
+            {
+                onSuccess: () => {
+                    notify("鍒犻櫎鎴愬姛");
+                    refresh();
+                },
+                onError: (error) => {
+                    notify(error?.message || "鍒犻櫎澶辫触", { type: "error" });
+                },
+            }
+        );
+    };
+
+    const dialogTitle = {
+        create: "鏂板缓 AI 鍙傛暟",
+        edit: "缂栬緫 AI 鍙傛暟",
+        show: "鏌ョ湅 AI 鍙傛暟璇︽儏",
+    }[dialogState.mode];
+
+    return (
+        <>
+            <List
+                title="menu.aiParam"
+                filters={filters}
+                sort={{ field: "create_time", order: "desc" }}
+                actions={(
+                    <TopToolbar>
+                        <FilterButton />
+                        <Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}>
+                            鏂板缓
+                        </Button>
+                        <MyExportButton />
+                    </TopToolbar>
+                )}
+            >
+                <AiParamCards
+                    onView={(id) => openDialog("show", id)}
+                    onEdit={(id) => openDialog("edit", id)}
+                    onDelete={handleDelete}
+                    deleting={deleting}
+                />
+            </List>
+            <AiConfigDialog
+                open={dialogState.open}
+                mode={dialogState.mode}
+                title={dialogTitle}
+                resource="aiParam"
+                recordId={dialogState.recordId}
+                defaultValues={defaultValues}
+                maxWidth="md"
+                onClose={closeDialog}
+            >
+                <AiParamForm readOnly={dialogState.mode === "show"} />
+            </AiConfigDialog>
+        </>
+    );
+};
+
+export default AiParamList;
diff --git a/rsf-admin/src/page/system/aiParam/index.jsx b/rsf-admin/src/page/system/aiParam/index.jsx
new file mode 100644
index 0000000..85adece
--- /dev/null
+++ b/rsf-admin/src/page/system/aiParam/index.jsx
@@ -0,0 +1,12 @@
+import { ShowGuesser } from "react-admin";
+import AiParamList from "./AiParamList";
+import AiParamCreate from "./AiParamCreate";
+import AiParamEdit from "./AiParamEdit";
+
+export default {
+    list: AiParamList,
+    create: AiParamCreate,
+    edit: AiParamEdit,
+    show: ShowGuesser,
+    recordRepresentation: (record) => `${record?.name || ''}`,
+};
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptCreate.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptCreate.jsx
new file mode 100644
index 0000000..5354b5c
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptCreate.jsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { Create, SimpleForm } from "react-admin";
+import AiPromptForm from "./AiPromptForm";
+
+const AiPromptCreate = () => (
+    <Create redirect="list">
+        <SimpleForm defaultValues={{ code: "home.default", scene: "home", status: 1 }}>
+            <AiPromptForm />
+        </SimpleForm>
+    </Create>
+);
+
+export default AiPromptCreate;
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptEdit.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptEdit.jsx
new file mode 100644
index 0000000..dd336e7
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptEdit.jsx
@@ -0,0 +1,26 @@
+import React from "react";
+import {
+    DeleteButton,
+    Edit,
+    SaveButton,
+    SimpleForm,
+    Toolbar,
+} from "react-admin";
+import AiPromptForm from "./AiPromptForm";
+
+const FormToolbar = () => (
+    <Toolbar sx={{ justifyContent: "space-between" }}>
+        <SaveButton />
+        <DeleteButton mutationMode="pessimistic" />
+    </Toolbar>
+);
+
+const AiPromptEdit = () => (
+    <Edit redirect="list" mutationMode="pessimistic">
+        <SimpleForm warnWhenUnsavedChanges toolbar={<FormToolbar />}>
+            <AiPromptForm />
+        </SimpleForm>
+    </Edit>
+);
+
+export default AiPromptEdit;
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx
new file mode 100644
index 0000000..5920bd5
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx
@@ -0,0 +1,37 @@
+import React from "react";
+import {
+    TextInput,
+} from "react-admin";
+import { Grid, Typography } from "@mui/material";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+
+const AiPromptForm = ({ readOnly = false }) => (
+    <Grid container spacing={2} width={{ xs: "100%", xl: "80%" }}>
+        <Grid item xs={12}>
+            <Typography variant="h6">Prompt 閰嶇疆</Typography>
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <TextInput source="name" label="鍚嶇О" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <TextInput source="code" label="缂栫爜" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12}>
+            <TextInput source="scene" label="鍦烘櫙" fullWidth disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12}>
+            <TextInput source="systemPrompt" label="System Prompt" fullWidth multiline minRows={6} disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12}>
+            <TextInput source="userPromptTemplate" label="User Prompt Template" fullWidth multiline minRows={5} disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12} md={6}>
+            <StatusSelectInput disabled={readOnly} />
+        </Grid>
+        <Grid item xs={12}>
+            <TextInput source="memo" label="澶囨敞" fullWidth multiline minRows={3} disabled={readOnly} />
+        </Grid>
+    </Grid>
+);
+
+export default AiPromptForm;
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx
new file mode 100644
index 0000000..325b6dc
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx
@@ -0,0 +1,229 @@
+import React, { useMemo, useState } from "react";
+import {
+    FilterButton,
+    List,
+    SearchInput,
+    SelectInput,
+    TextInput,
+    TopToolbar,
+    useDelete,
+    useListContext,
+    useNotify,
+    useRefresh,
+} from "react-admin";
+import {
+    Box,
+    Button,
+    Card,
+    CardActions,
+    CardContent,
+    Chip,
+    CircularProgress,
+    Divider,
+    Grid,
+    Stack,
+    Typography,
+} from "@mui/material";
+import AddRoundedIcon from "@mui/icons-material/AddRounded";
+import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined";
+import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
+import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
+import MyExportButton from "@/page/components/MyExportButton";
+import AiPromptForm from "./AiPromptForm";
+import AiConfigDialog from "../aiShared/AiConfigDialog";
+
+const filters = [
+    <SearchInput source="condition" alwaysOn />,
+    <TextInput source="code" label="缂栫爜" />,
+    <TextInput source="scene" label="鍦烘櫙" />,
+    <SelectInput
+        source="status"
+        label="鐘舵��"
+        choices={[
+            { id: "1", name: "common.enums.statusTrue" },
+            { id: "0", name: "common.enums.statusFalse" },
+        ]}
+    />,
+];
+
+const defaultValues = {
+    code: "home.default",
+    scene: "home",
+    status: 1,
+};
+
+const truncateText = (value, max = 120) => {
+    if (!value) {
+        return "--";
+    }
+    return value.length > max ? `${value.slice(0, max)}...` : value;
+};
+
+const AiPromptCards = ({ onView, onEdit, onDelete, deleting }) => {
+    const { data, isLoading } = useListContext();
+    const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
+
+    if (isLoading) {
+        return (
+            <Box display="flex" justifyContent="center" py={8}>
+                <CircularProgress size={28} />
+            </Box>
+        );
+    }
+
+    if (!records.length) {
+        return (
+            <Box px={2} py={6}>
+                <Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
+                    <Typography variant="subtitle1">鏆傛棤 Prompt 閰嶇疆</Typography>
+                    <Typography variant="body2" color="text.secondary" mt={1}>
+                        鏂板缓涓�寮� Prompt 鍗$墖鍚庯紝AI 瀵硅瘽浼氬姩鎬佸姞杞借繖閲岀殑鍐呭銆�
+                    </Typography>
+                </Card>
+            </Box>
+        );
+    }
+
+    return (
+        <Box px={2} py={2}>
+            <Grid container spacing={2}>
+                {records.map((record) => (
+                    <Grid item xs={12} md={6} xl={4} key={record.id}>
+                        <Card
+                            variant="outlined"
+                            sx={{
+                                height: "100%",
+                                borderRadius: 3,
+                                boxShadow: "0 8px 24px rgba(15, 23, 42, 0.06)",
+                            }}
+                        >
+                            <CardContent sx={{ pb: 1.5 }}>
+                                <Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
+                                    <Box>
+                                        <Typography variant="h6" sx={{ mb: 0.5 }}>
+                                            {record.name}
+                                        </Typography>
+                                        <Typography variant="body2" color="text.secondary">
+                                            {record.code || "--"}
+                                        </Typography>
+                                    </Box>
+                                    <Chip
+                                        size="small"
+                                        color={record.statusBool ? "success" : "default"}
+                                        label={record.statusBool ? "鍚敤" : "鍋滅敤"}
+                                    />
+                                </Stack>
+                                <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
+                                    <Chip size="small" variant="outlined" label={`Scene: ${record.scene || "--"}`} />
+                                </Stack>
+                                <Divider sx={{ my: 1.5 }} />
+                                <Typography variant="caption" color="text.secondary">System Prompt</Typography>
+                                <Typography variant="body2" sx={{ mt: 0.5 }}>
+                                    {truncateText(record.systemPrompt)}
+                                </Typography>
+                                <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
+                                    User Prompt Template
+                                </Typography>
+                                <Typography variant="body2">{truncateText(record.userPromptTemplate, 100)}</Typography>
+                            </CardContent>
+                            <CardActions sx={{ px: 2, pb: 2, pt: 0, justifyContent: "space-between" }}>
+                                <Stack direction="row" spacing={1}>
+                                    <Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record.id)}>
+                                        璇︽儏
+                                    </Button>
+                                    <Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}>
+                                        缂栬緫
+                                    </Button>
+                                </Stack>
+                                <Button
+                                    size="small"
+                                    color="error"
+                                    startIcon={<DeleteOutlineOutlinedIcon />}
+                                    onClick={() => onDelete(record)}
+                                    disabled={deleting}
+                                >
+                                    鍒犻櫎
+                                </Button>
+                            </CardActions>
+                        </Card>
+                    </Grid>
+                ))}
+            </Grid>
+        </Box>
+    );
+};
+
+const AiPromptList = () => {
+    const notify = useNotify();
+    const refresh = useRefresh();
+    const [deleteOne, { isPending: deleting }] = useDelete();
+    const [dialogState, setDialogState] = useState({ open: false, mode: "create", recordId: null });
+
+    const openDialog = (mode, recordId = null) => setDialogState({ open: true, mode, recordId });
+    const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null });
+
+    const handleDelete = (record) => {
+        if (!record?.id || !window.confirm(`纭鍒犻櫎鈥�${record.name}鈥濆悧锛焋)) {
+            return;
+        }
+        deleteOne(
+            "aiPrompt",
+            { id: record.id },
+            {
+                onSuccess: () => {
+                    notify("鍒犻櫎鎴愬姛");
+                    refresh();
+                },
+                onError: (error) => {
+                    notify(error?.message || "鍒犻櫎澶辫触", { type: "error" });
+                },
+            }
+        );
+    };
+
+    const dialogTitle = {
+        create: "鏂板缓 Prompt",
+        edit: "缂栬緫 Prompt",
+        show: "鏌ョ湅 Prompt 璇︽儏",
+    }[dialogState.mode];
+
+    return (
+        <>
+            <List
+                title="menu.aiPrompt"
+                filters={filters}
+                sort={{ field: "create_time", order: "desc" }}
+                actions={(
+                    <TopToolbar>
+                        <FilterButton />
+                        <Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}>
+                            鏂板缓
+                        </Button>
+                        <MyExportButton />
+                    </TopToolbar>
+                )}
+            >
+                <AiPromptCards
+                    onView={(id) => openDialog("show", id)}
+                    onEdit={(id) => openDialog("edit", id)}
+                    onDelete={handleDelete}
+                    deleting={deleting}
+                />
+            </List>
+            <AiConfigDialog
+                open={dialogState.open}
+                mode={dialogState.mode}
+                title={dialogTitle}
+                resource="aiPrompt"
+                recordId={dialogState.recordId}
+                defaultValues={defaultValues}
+                maxWidth="lg"
+                onClose={closeDialog}
+            >
+                <AiPromptForm readOnly={dialogState.mode === "show"} />
+            </AiConfigDialog>
+        </>
+    );
+};
+
+export default AiPromptList;
diff --git a/rsf-admin/src/page/system/aiPrompt/index.jsx b/rsf-admin/src/page/system/aiPrompt/index.jsx
new file mode 100644
index 0000000..776c963
--- /dev/null
+++ b/rsf-admin/src/page/system/aiPrompt/index.jsx
@@ -0,0 +1,12 @@
+import { ShowGuesser } from "react-admin";
+import AiPromptList from "./AiPromptList";
+import AiPromptCreate from "./AiPromptCreate";
+import AiPromptEdit from "./AiPromptEdit";
+
+export default {
+    list: AiPromptList,
+    create: AiPromptCreate,
+    edit: AiPromptEdit,
+    show: ShowGuesser,
+    recordRepresentation: (record) => `${record?.name || ''}`,
+};
diff --git a/rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx b/rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx
new file mode 100644
index 0000000..ea5de3e
--- /dev/null
+++ b/rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx
@@ -0,0 +1,99 @@
+import React from "react";
+import {
+    CreateBase,
+    EditBase,
+    SaveButton,
+    SimpleForm,
+    Toolbar,
+    useNotify,
+    useRefresh,
+} from "react-admin";
+import {
+    Button,
+    Dialog,
+    DialogActions,
+    DialogContent,
+    DialogTitle,
+} from "@mui/material";
+
+const DialogFormToolbar = ({ onClose }) => (
+    <Toolbar sx={{ justifyContent: "space-between", px: 0 }}>
+        <Button onClick={onClose}>鍙栨秷</Button>
+        <SaveButton />
+    </Toolbar>
+);
+
+const AiConfigDialog = ({
+    open,
+    mode,
+    title,
+    resource,
+    recordId,
+    defaultValues,
+    maxWidth = "md",
+    onClose,
+    children,
+}) => {
+    const notify = useNotify();
+    const refresh = useRefresh();
+
+    if (!open) {
+        return null;
+    }
+
+    const handleSuccess = () => {
+        notify(mode === "create" ? "淇濆瓨鎴愬姛" : "鏇存柊鎴愬姛");
+        refresh();
+        onClose();
+    };
+
+    const handleError = (error) => {
+        notify(error?.message || "鎿嶄綔澶辫触", { type: "error" });
+    };
+
+    const formContent = (
+        <SimpleForm
+            defaultValues={mode === "create" ? defaultValues : undefined}
+            toolbar={mode === "show" ? false : <DialogFormToolbar onClose={onClose} />}
+            sx={{
+                "& .RaSimpleForm-form": {
+                    maxWidth: "100%",
+                },
+            }}
+        >
+            {children}
+        </SimpleForm>
+    );
+
+    return (
+        <Dialog open={open} onClose={onClose} fullWidth maxWidth={maxWidth}>
+            <DialogTitle>{title}</DialogTitle>
+            <DialogContent dividers>
+                {mode === "create" ? (
+                    <CreateBase
+                        resource={resource}
+                        mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
+                    >
+                        {formContent}
+                    </CreateBase>
+                ) : (
+                    <EditBase
+                        resource={resource}
+                        id={recordId}
+                        mutationMode="pessimistic"
+                        mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
+                    >
+                        {formContent}
+                    </EditBase>
+                )}
+            </DialogContent>
+            {mode === "show" && (
+                <DialogActions>
+                    <Button onClick={onClose}>鍏抽棴</Button>
+                </DialogActions>
+            )}
+        </Dialog>
+    );
+};
+
+export default AiConfigDialog;
diff --git a/rsf-server/pom.xml b/rsf-server/pom.xml
index 87d51aa..b6aecea 100644
--- a/rsf-server/pom.xml
+++ b/rsf-server/pom.xml
@@ -17,7 +17,19 @@
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+		<spring-ai.version>1.1.2</spring-ai.version>
 	</properties>
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>org.springframework.ai</groupId>
+				<artifactId>spring-ai-bom</artifactId>
+				<version>${spring-ai.version}</version>
+				<type>pom</type>
+				<scope>import</scope>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
 	<dependencies>
 		<dependency>
 			<groupId>com.vincent</groupId>
@@ -33,6 +45,18 @@
 			<artifactId>spring-boot-starter-security</artifactId>
 		</dependency>
 		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-actuator</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.ai</groupId>
+			<artifactId>spring-ai-openai</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.ai</groupId>
+			<artifactId>spring-ai-mcp</artifactId>
+		</dependency>
+		<dependency>
 			<groupId>RouteUtils</groupId>
 			<artifactId>RouteUtils</artifactId>
 			<version>1.0.0</version>
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiDefaults.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiDefaults.java
new file mode 100644
index 0000000..37e43df
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/config/AiDefaults.java
@@ -0,0 +1,21 @@
+package com.vincent.rsf.server.ai.config;
+
+public final class AiDefaults {
+
+    private AiDefaults() {
+    }
+
+    public static final String DEFAULT_PROMPT_CODE = "home.default";
+    public static final String PROVIDER_OPENAI_COMPATIBLE = "OPENAI_COMPATIBLE";
+    public static final String MCP_TRANSPORT_SSE_HTTP = "SSE_HTTP";
+    public static final String MCP_TRANSPORT_STDIO = "STDIO";
+    public static final String MCP_TRANSPORT_BUILTIN = "BUILTIN";
+    public static final String MCP_BUILTIN_RSF_WMS = "RSF_WMS";
+    public static final String MCP_BUILTIN_RSF_WMS_STOCK = "RSF_WMS_STOCK";
+    public static final String MCP_BUILTIN_RSF_WMS_TASK = "RSF_WMS_TASK";
+    public static final String MCP_BUILTIN_RSF_WMS_BASE = "RSF_WMS_BASE";
+    public static final long SSE_TIMEOUT_MS = 0L;
+    public static final int DEFAULT_TIMEOUT_MS = 60000;
+    public static final double DEFAULT_TEMPERATURE = 0.7D;
+    public static final double DEFAULT_TOP_P = 1.0D;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiChatController.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiChatController.java
new file mode 100644
index 0000000..63a2073
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiChatController.java
@@ -0,0 +1,44 @@
+package com.vincent.rsf.server.ai.controller;
+
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.server.ai.dto.AiChatRequest;
+import com.vincent.rsf.server.ai.service.AiChatService;
+import com.vincent.rsf.server.system.controller.BaseController;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+@RestController
+@RequiredArgsConstructor
+public class AiChatController extends BaseController {
+
+    private final AiChatService aiChatService;
+
+    @PreAuthorize("isAuthenticated()")
+    @GetMapping("/ai/chat/runtime")
+    public R runtime(@RequestParam(required = false) String promptCode,
+                     @RequestParam(required = false) Long sessionId) {
+        return R.ok().add(aiChatService.getRuntime(promptCode, sessionId, getLoginUserId(), getTenantId()));
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @GetMapping("/ai/chat/sessions")
+    public R sessions(@RequestParam(required = false) String promptCode) {
+        return R.ok().add(aiChatService.listSessions(promptCode, getLoginUserId(), getTenantId()));
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @PostMapping("/ai/chat/session/remove/{sessionId}")
+    public R removeSession(@PathVariable Long sessionId) {
+        aiChatService.removeSession(sessionId, getLoginUserId(), getTenantId());
+        return R.ok("Delete Success").add(sessionId);
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @PostMapping(value = "/ai/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    public SseEmitter stream(@RequestBody AiChatRequest request) {
+        return aiChatService.stream(request, getLoginUserId(), getTenantId());
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiMcpMountController.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiMcpMountController.java
new file mode 100644
index 0000000..57cf3ca
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiMcpMountController.java
@@ -0,0 +1,108 @@
+package com.vincent.rsf.server.ai.controller;
+
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.server.ai.dto.AiMcpToolTestRequest;
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+import com.vincent.rsf.server.ai.service.AiMcpMountService;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.controller.BaseController;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+
+@RestController
+public class AiMcpMountController extends BaseController {
+
+    @Autowired
+    private AiMcpMountService aiMcpMountService;
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+    @PostMapping("/aiMcpMount/page")
+    public R page(@RequestBody Map<String, Object> map) {
+        BaseParam baseParam = buildParam(map, BaseParam.class);
+        PageParam<AiMcpMount, BaseParam> pageParam = new PageParam<>(baseParam, AiMcpMount.class);
+        return R.ok().add(aiMcpMountService.page(pageParam, pageParam.buildWrapper(true)));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+    @PostMapping("/aiMcpMount/list")
+    public R list(@RequestBody Map<String, Object> map) {
+        return R.ok().add(aiMcpMountService.list());
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+    @PostMapping({"/aiMcpMount/many/{ids}", "/aiMcpMounts/many/{ids}"})
+    public R many(@PathVariable Long[] ids) {
+        return R.ok().add(aiMcpMountService.listByIds(Arrays.asList(ids)));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+    @GetMapping("/aiMcpMount/{id}")
+    public R get(@PathVariable("id") Long id) {
+        return R.ok().add(aiMcpMountService.getById(id));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+    @GetMapping("/aiMcpMount/{id}/tools")
+    public R previewTools(@PathVariable("id") Long id) {
+        return R.ok().add(aiMcpMountService.previewTools(id, getLoginUserId(), getTenantId()));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:update')")
+    @PostMapping("/aiMcpMount/{id}/tool/test")
+    public R testTool(@PathVariable("id") Long id, @RequestBody AiMcpToolTestRequest request) {
+        return R.ok().add(aiMcpMountService.testTool(id, getLoginUserId(), getTenantId(), request));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:save')")
+    @OperationLog("Create AiMcpMount")
+    @PostMapping("/aiMcpMount/save")
+    public R save(@RequestBody AiMcpMount aiMcpMount) {
+        aiMcpMountService.validateBeforeSave(aiMcpMount);
+        aiMcpMount.setCreateBy(getLoginUserId());
+        aiMcpMount.setCreateTime(new Date());
+        aiMcpMount.setUpdateBy(getLoginUserId());
+        aiMcpMount.setUpdateTime(new Date());
+        if (!aiMcpMountService.save(aiMcpMount)) {
+            return R.error("Save Fail");
+        }
+        return R.ok("Save Success").add(aiMcpMount);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:update')")
+    @OperationLog("Update AiMcpMount")
+    @PostMapping("/aiMcpMount/update")
+    public R update(@RequestBody AiMcpMount aiMcpMount) {
+        aiMcpMountService.validateBeforeUpdate(aiMcpMount);
+        aiMcpMount.setUpdateBy(getLoginUserId());
+        aiMcpMount.setUpdateTime(new Date());
+        if (!aiMcpMountService.updateById(aiMcpMount)) {
+            return R.error("Update Fail");
+        }
+        return R.ok("Update Success").add(aiMcpMount);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:remove')")
+    @OperationLog("Delete AiMcpMount")
+    @PostMapping("/aiMcpMount/remove/{ids}")
+    public R remove(@PathVariable Long[] ids) {
+        if (!aiMcpMountService.removeByIds(Arrays.asList(ids))) {
+            return R.error("Delete Fail");
+        }
+        return R.ok("Delete Success").add(ids);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiMcpMount:list')")
+    @PostMapping("/aiMcpMount/export")
+    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+        ExcelUtil.build(ExcelUtil.create(aiMcpMountService.list(), AiMcpMount.class), response);
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiParamController.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiParamController.java
new file mode 100644
index 0000000..8f1d8b7
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiParamController.java
@@ -0,0 +1,95 @@
+package com.vincent.rsf.server.ai.controller;
+
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.server.ai.entity.AiParam;
+import com.vincent.rsf.server.ai.service.AiParamService;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.controller.BaseController;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+
+@RestController
+public class AiParamController extends BaseController {
+
+    @Autowired
+    private AiParamService aiParamService;
+
+    @PreAuthorize("hasAuthority('system:aiParam:list')")
+    @PostMapping("/aiParam/page")
+    public R page(@RequestBody Map<String, Object> map) {
+        BaseParam baseParam = buildParam(map, BaseParam.class);
+        PageParam<AiParam, BaseParam> pageParam = new PageParam<>(baseParam, AiParam.class);
+        return R.ok().add(aiParamService.page(pageParam, pageParam.buildWrapper(true)));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiParam:list')")
+    @PostMapping("/aiParam/list")
+    public R list(@RequestBody Map<String, Object> map) {
+        return R.ok().add(aiParamService.list());
+    }
+
+    @PreAuthorize("hasAuthority('system:aiParam:list')")
+    @PostMapping({"/aiParam/many/{ids}", "/aiParams/many/{ids}"})
+    public R many(@PathVariable Long[] ids) {
+        return R.ok().add(aiParamService.listByIds(Arrays.asList(ids)));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiParam:list')")
+    @GetMapping("/aiParam/{id}")
+    public R get(@PathVariable("id") Long id) {
+        return R.ok().add(aiParamService.getById(id));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiParam:save')")
+    @OperationLog("Create AiParam")
+    @PostMapping("/aiParam/save")
+    public R save(@RequestBody AiParam aiParam) {
+        aiParamService.validateBeforeSave(aiParam);
+        aiParam.setCreateBy(getLoginUserId());
+        aiParam.setCreateTime(new Date());
+        aiParam.setUpdateBy(getLoginUserId());
+        aiParam.setUpdateTime(new Date());
+        if (!aiParamService.save(aiParam)) {
+            return R.error("Save Fail");
+        }
+        return R.ok("Save Success").add(aiParam);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiParam:update')")
+    @OperationLog("Update AiParam")
+    @PostMapping("/aiParam/update")
+    public R update(@RequestBody AiParam aiParam) {
+        aiParamService.validateBeforeUpdate(aiParam);
+        aiParam.setUpdateBy(getLoginUserId());
+        aiParam.setUpdateTime(new Date());
+        if (!aiParamService.updateById(aiParam)) {
+            return R.error("Update Fail");
+        }
+        return R.ok("Update Success").add(aiParam);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiParam:remove')")
+    @OperationLog("Delete AiParam")
+    @PostMapping("/aiParam/remove/{ids}")
+    public R remove(@PathVariable Long[] ids) {
+        if (!aiParamService.removeByIds(Arrays.asList(ids))) {
+            return R.error("Delete Fail");
+        }
+        return R.ok("Delete Success").add(ids);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiParam:list')")
+    @PostMapping("/aiParam/export")
+    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+        ExcelUtil.build(ExcelUtil.create(aiParamService.list(), AiParam.class), response);
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiPromptController.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiPromptController.java
new file mode 100644
index 0000000..8c40c39
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/controller/AiPromptController.java
@@ -0,0 +1,95 @@
+package com.vincent.rsf.server.ai.controller;
+
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.server.ai.entity.AiPrompt;
+import com.vincent.rsf.server.ai.service.AiPromptService;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.system.controller.BaseController;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+
+@RestController
+public class AiPromptController extends BaseController {
+
+    @Autowired
+    private AiPromptService aiPromptService;
+
+    @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+    @PostMapping("/aiPrompt/page")
+    public R page(@RequestBody Map<String, Object> map) {
+        BaseParam baseParam = buildParam(map, BaseParam.class);
+        PageParam<AiPrompt, BaseParam> pageParam = new PageParam<>(baseParam, AiPrompt.class);
+        return R.ok().add(aiPromptService.page(pageParam, pageParam.buildWrapper(true)));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+    @PostMapping("/aiPrompt/list")
+    public R list(@RequestBody Map<String, Object> map) {
+        return R.ok().add(aiPromptService.list());
+    }
+
+    @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+    @PostMapping({"/aiPrompt/many/{ids}", "/aiPrompts/many/{ids}"})
+    public R many(@PathVariable Long[] ids) {
+        return R.ok().add(aiPromptService.listByIds(Arrays.asList(ids)));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+    @GetMapping("/aiPrompt/{id}")
+    public R get(@PathVariable("id") Long id) {
+        return R.ok().add(aiPromptService.getById(id));
+    }
+
+    @PreAuthorize("hasAuthority('system:aiPrompt:save')")
+    @OperationLog("Create AiPrompt")
+    @PostMapping("/aiPrompt/save")
+    public R save(@RequestBody AiPrompt aiPrompt) {
+        aiPromptService.validateBeforeSave(aiPrompt);
+        aiPrompt.setCreateBy(getLoginUserId());
+        aiPrompt.setCreateTime(new Date());
+        aiPrompt.setUpdateBy(getLoginUserId());
+        aiPrompt.setUpdateTime(new Date());
+        if (!aiPromptService.save(aiPrompt)) {
+            return R.error("Save Fail");
+        }
+        return R.ok("Save Success").add(aiPrompt);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiPrompt:update')")
+    @OperationLog("Update AiPrompt")
+    @PostMapping("/aiPrompt/update")
+    public R update(@RequestBody AiPrompt aiPrompt) {
+        aiPromptService.validateBeforeUpdate(aiPrompt);
+        aiPrompt.setUpdateBy(getLoginUserId());
+        aiPrompt.setUpdateTime(new Date());
+        if (!aiPromptService.updateById(aiPrompt)) {
+            return R.error("Update Fail");
+        }
+        return R.ok("Update Success").add(aiPrompt);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiPrompt:remove')")
+    @OperationLog("Delete AiPrompt")
+    @PostMapping("/aiPrompt/remove/{ids}")
+    public R remove(@PathVariable Long[] ids) {
+        if (!aiPromptService.removeByIds(Arrays.asList(ids))) {
+            return R.error("Delete Fail");
+        }
+        return R.ok("Delete Success").add(ids);
+    }
+
+    @PreAuthorize("hasAuthority('system:aiPrompt:list')")
+    @PostMapping("/aiPrompt/export")
+    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+        ExcelUtil.build(ExcelUtil.create(aiPromptService.list(), AiPrompt.class), response);
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatDoneDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatDoneDto.java
new file mode 100644
index 0000000..4d158c3
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatDoneDto.java
@@ -0,0 +1,19 @@
+package com.vincent.rsf.server.ai.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class AiChatDoneDto {
+
+    private Long sessionId;
+
+    private String model;
+
+    private Integer promptTokens;
+
+    private Integer completionTokens;
+
+    private Integer totalTokens;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatMemoryDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatMemoryDto.java
new file mode 100644
index 0000000..afd6421
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatMemoryDto.java
@@ -0,0 +1,15 @@
+package com.vincent.rsf.server.ai.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Builder
+public class AiChatMemoryDto {
+
+    private Long sessionId;
+
+    private List<AiChatMessageDto> persistedMessages;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatMessageDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatMessageDto.java
new file mode 100644
index 0000000..f86f188
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatMessageDto.java
@@ -0,0 +1,11 @@
+package com.vincent.rsf.server.ai.dto;
+
+import lombok.Data;
+
+@Data
+public class AiChatMessageDto {
+
+    private String role;
+
+    private String content;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatRequest.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatRequest.java
new file mode 100644
index 0000000..c1edb09
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatRequest.java
@@ -0,0 +1,18 @@
+package com.vincent.rsf.server.ai.dto;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class AiChatRequest {
+
+    private Long sessionId;
+
+    private List<AiChatMessageDto> messages;
+
+    private String promptCode;
+
+    private Map<String, Object> metadata;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatRuntimeDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatRuntimeDto.java
new file mode 100644
index 0000000..78e25e3
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatRuntimeDto.java
@@ -0,0 +1,29 @@
+package com.vincent.rsf.server.ai.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Builder
+public class AiChatRuntimeDto {
+
+    private Long sessionId;
+
+    private String promptCode;
+
+    private String promptName;
+
+    private String model;
+
+    private Integer configuredMcpCount;
+
+    private Integer mountedMcpCount;
+
+    private List<String> mountedMcpNames;
+
+    private List<String> mountErrors;
+
+    private List<AiChatMessageDto> persistedMessages;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatSessionDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatSessionDto.java
new file mode 100644
index 0000000..a54b23c
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiChatSessionDto.java
@@ -0,0 +1,21 @@
+package com.vincent.rsf.server.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@Builder
+public class AiChatSessionDto {
+
+    private Long sessionId;
+
+    private String title;
+
+    private String promptCode;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date lastMessageTime;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java
new file mode 100644
index 0000000..1d59c15
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolPreviewDto.java
@@ -0,0 +1,17 @@
+package com.vincent.rsf.server.ai.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class AiMcpToolPreviewDto {
+
+    private String name;
+
+    private String description;
+
+    private String inputSchema;
+
+    private Boolean returnDirect;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolTestDto.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolTestDto.java
new file mode 100644
index 0000000..1016b11
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolTestDto.java
@@ -0,0 +1,15 @@
+package com.vincent.rsf.server.ai.dto;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class AiMcpToolTestDto {
+
+    private String toolName;
+
+    private String inputJson;
+
+    private String output;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolTestRequest.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolTestRequest.java
new file mode 100644
index 0000000..f80f21a
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiMcpToolTestRequest.java
@@ -0,0 +1,11 @@
+package com.vincent.rsf.server.ai.dto;
+
+import lombok.Data;
+
+@Data
+public class AiMcpToolTestRequest {
+
+    private String toolName;
+
+    private String inputJson;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiResolvedConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiResolvedConfig.java
new file mode 100644
index 0000000..e96332f
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/dto/AiResolvedConfig.java
@@ -0,0 +1,22 @@
+package com.vincent.rsf.server.ai.dto;
+
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+import com.vincent.rsf.server.ai.entity.AiParam;
+import com.vincent.rsf.server.ai.entity.AiPrompt;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@Builder
+public class AiResolvedConfig {
+
+    private String promptCode;
+
+    private AiParam aiParam;
+
+    private AiPrompt prompt;
+
+    private List<AiMcpMount> mcpMounts;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiChatMessage.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiChatMessage.java
new file mode 100644
index 0000000..41753d0
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiChatMessage.java
@@ -0,0 +1,54 @@
+package com.vincent.rsf.server.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_chat_message")
+public class AiChatMessage implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "浼氳瘽 ID")
+    private Long sessionId;
+
+    @ApiModelProperty(value = "娑堟伅搴忓彿")
+    private Integer seqNo;
+
+    @ApiModelProperty(value = "娑堟伅瑙掕壊")
+    private String role;
+
+    @ApiModelProperty(value = "娑堟伅鍐呭")
+    private String content;
+
+    @ApiModelProperty(value = "鐢ㄦ埛 ID")
+    private Long userId;
+
+    @ApiModelProperty(value = "绉熸埛 ID")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "鏄惁鍒犻櫎")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "娣诲姞浜哄憳")
+    private Long createBy;
+
+    @ApiModelProperty(value = "娣诲姞鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiChatSession.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiChatSession.java
new file mode 100644
index 0000000..18a8bab
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiChatSession.java
@@ -0,0 +1,64 @@
+package com.vincent.rsf.server.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_chat_session")
+public class AiChatSession implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "鏍囬")
+    private String title;
+
+    @ApiModelProperty(value = "Prompt 缂栫爜")
+    private String promptCode;
+
+    @ApiModelProperty(value = "鐢ㄦ埛 ID")
+    private Long userId;
+
+    @ApiModelProperty(value = "绉熸埛 ID")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "鏈�鍚庢秷鎭椂闂�")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date lastMessageTime;
+
+    @ApiModelProperty(value = "鐘舵��")
+    private Integer status;
+
+    @ApiModelProperty(value = "鏄惁鍒犻櫎")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "娣诲姞浜哄憳")
+    private Long createBy;
+
+    @ApiModelProperty(value = "娣诲姞鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "淇敼浜哄憳")
+    private Long updateBy;
+
+    @ApiModelProperty(value = "淇敼鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiMcpMount.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiMcpMount.java
new file mode 100644
index 0000000..ffaec3c
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiMcpMount.java
@@ -0,0 +1,112 @@
+package com.vincent.rsf.server.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_mcp_mount")
+public class AiMcpMount implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "鍚嶇О")
+    private String name;
+
+    @ApiModelProperty(value = "浼犺緭绫诲瀷")
+    private String transportType;
+
+    @ApiModelProperty(value = "鍐呯疆 MCP 缂栫爜")
+    private String builtinCode;
+
+    @ApiModelProperty(value = "鏈嶅姟鍦板潃")
+    private String serverUrl;
+
+    @ApiModelProperty(value = "SSE绔偣")
+    private String endpoint;
+
+    @ApiModelProperty(value = "鍛戒护")
+    private String command;
+
+    @ApiModelProperty(value = "鍛戒护鍙傛暟JSON")
+    private String argsJson;
+
+    @ApiModelProperty(value = "鐜鍙橀噺JSON")
+    private String envJson;
+
+    @ApiModelProperty(value = "璇锋眰澶碕SON")
+    private String headersJson;
+
+    @ApiModelProperty(value = "瓒呮椂鏃堕棿")
+    private Integer requestTimeoutMs;
+
+    @ApiModelProperty(value = "鎺掑簭")
+    private Integer sort;
+
+    @ApiModelProperty(value = "鐘舵��")
+    private Integer status;
+
+    @ApiModelProperty(value = "鏄惁鍒犻櫎")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "绉熸埛")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "娣诲姞浜哄憳")
+    private Long createBy;
+
+    @ApiModelProperty(value = "娣诲姞鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "淇敼浜哄憳")
+    private Long updateBy;
+
+    @ApiModelProperty(value = "淇敼鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "澶囨敞")
+    private String memo;
+
+    public String getTransportType$() {
+        return this.transportType;
+    }
+
+    public Boolean getStatusBool() {
+        if (this.status == null) {
+            return null;
+        }
+        return this.status == 1;
+    }
+
+    public String getCreateTime$() {
+        if (this.createTime == null) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+    }
+
+    public String getUpdateTime$() {
+        if (this.updateTime == null) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiParam.java
new file mode 100644
index 0000000..49ee4cc
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiParam.java
@@ -0,0 +1,109 @@
+package com.vincent.rsf.server.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_param")
+public class AiParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "鍚嶇О")
+    private String name;
+
+    @ApiModelProperty(value = "鎻愪緵鏂圭被鍨�")
+    private String providerType;
+
+    @ApiModelProperty(value = "鍩虹鍦板潃")
+    private String baseUrl;
+
+    @ApiModelProperty(value = "瀵嗛挜")
+    private String apiKey;
+
+    @ApiModelProperty(value = "妯″瀷")
+    private String model;
+
+    @ApiModelProperty(value = "temperature")
+    private Double temperature;
+
+    @ApiModelProperty(value = "topP")
+    private Double topP;
+
+    @ApiModelProperty(value = "maxTokens")
+    private Integer maxTokens;
+
+    @ApiModelProperty(value = "timeoutMs")
+    private Integer timeoutMs;
+
+    @ApiModelProperty(value = "streamingEnabled")
+    private Boolean streamingEnabled;
+
+    @ApiModelProperty(value = "鐘舵��")
+    private Integer status;
+
+    @ApiModelProperty(value = "鏄惁鍒犻櫎")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "绉熸埛")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "娣诲姞浜哄憳")
+    private Long createBy;
+
+    @ApiModelProperty(value = "娣诲姞鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "淇敼浜哄憳")
+    private Long updateBy;
+
+    @ApiModelProperty(value = "淇敼鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "澶囨敞")
+    private String memo;
+
+    public String getProviderType$() {
+        return this.providerType;
+    }
+
+    public Boolean getStatusBool() {
+        if (this.status == null) {
+            return null;
+        }
+        return this.status == 1;
+    }
+
+    public String getCreateTime$() {
+        if (this.createTime == null) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+    }
+
+    public String getUpdateTime$() {
+        if (this.updateTime == null) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiPrompt.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiPrompt.java
new file mode 100644
index 0000000..6b61576
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/entity/AiPrompt.java
@@ -0,0 +1,90 @@
+package com.vincent.rsf.server.ai.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Data
+@Accessors(chain = true)
+@TableName("sys_ai_prompt")
+public class AiPrompt implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "鍚嶇О")
+    private String name;
+
+    @ApiModelProperty(value = "缂栫爜")
+    private String code;
+
+    @ApiModelProperty(value = "鍦烘櫙")
+    private String scene;
+
+    @ApiModelProperty(value = "绯荤粺鎻愮ず璇�")
+    private String systemPrompt;
+
+    @ApiModelProperty(value = "鐢ㄦ埛鎻愮ず璇嶆ā鏉�")
+    private String userPromptTemplate;
+
+    @ApiModelProperty(value = "鐘舵��")
+    private Integer status;
+
+    @ApiModelProperty(value = "鏄惁鍒犻櫎")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "绉熸埛")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "娣诲姞浜哄憳")
+    private Long createBy;
+
+    @ApiModelProperty(value = "娣诲姞鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ApiModelProperty(value = "淇敼浜哄憳")
+    private Long updateBy;
+
+    @ApiModelProperty(value = "淇敼鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "澶囨敞")
+    private String memo;
+
+    public Boolean getStatusBool() {
+        if (this.status == null) {
+            return null;
+        }
+        return this.status == 1;
+    }
+
+    public String getCreateTime$() {
+        if (this.createTime == null) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+    }
+
+    public String getUpdateTime$() {
+        if (this.updateTime == null) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatMessageMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatMessageMapper.java
new file mode 100644
index 0000000..81a0c64
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatMessageMapper.java
@@ -0,0 +1,9 @@
+package com.vincent.rsf.server.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.ai.entity.AiChatMessage;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AiChatMessageMapper extends BaseMapper<AiChatMessage> {
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatSessionMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatSessionMapper.java
new file mode 100644
index 0000000..c26a10d
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiChatSessionMapper.java
@@ -0,0 +1,9 @@
+package com.vincent.rsf.server.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.ai.entity.AiChatSession;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface AiChatSessionMapper extends BaseMapper<AiChatSession> {
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiMcpMountMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiMcpMountMapper.java
new file mode 100644
index 0000000..87e7a09
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiMcpMountMapper.java
@@ -0,0 +1,11 @@
+package com.vincent.rsf.server.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiMcpMountMapper extends BaseMapper<AiMcpMount> {
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiParamMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiParamMapper.java
new file mode 100644
index 0000000..1cb1090
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiParamMapper.java
@@ -0,0 +1,11 @@
+package com.vincent.rsf.server.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.ai.entity.AiParam;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiParamMapper extends BaseMapper<AiParam> {
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiPromptMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiPromptMapper.java
new file mode 100644
index 0000000..044fdf7
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/mapper/AiPromptMapper.java
@@ -0,0 +1,11 @@
+package com.vincent.rsf.server.ai.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.vincent.rsf.server.ai.entity.AiPrompt;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface AiPromptMapper extends BaseMapper<AiPrompt> {
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiChatMemoryService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiChatMemoryService.java
new file mode 100644
index 0000000..404f30b
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiChatMemoryService.java
@@ -0,0 +1,21 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.vincent.rsf.server.ai.dto.AiChatMemoryDto;
+import com.vincent.rsf.server.ai.dto.AiChatMessageDto;
+import com.vincent.rsf.server.ai.dto.AiChatSessionDto;
+import com.vincent.rsf.server.ai.entity.AiChatSession;
+
+import java.util.List;
+
+public interface AiChatMemoryService {
+
+    AiChatMemoryDto getMemory(Long userId, Long tenantId, String promptCode, Long sessionId);
+
+    List<AiChatSessionDto> listSessions(Long userId, Long tenantId, String promptCode);
+
+    AiChatSession resolveSession(Long userId, Long tenantId, String promptCode, Long sessionId, String titleSeed);
+
+    void saveRound(AiChatSession session, Long userId, Long tenantId, List<AiChatMessageDto> memoryMessages, String assistantContent);
+
+    void removeSession(Long userId, Long tenantId, Long sessionId);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiChatService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiChatService.java
new file mode 100644
index 0000000..27ecf4f
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiChatService.java
@@ -0,0 +1,19 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.vincent.rsf.server.ai.dto.AiChatRequest;
+import com.vincent.rsf.server.ai.dto.AiChatRuntimeDto;
+import com.vincent.rsf.server.ai.dto.AiChatSessionDto;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+import java.util.List;
+
+public interface AiChatService {
+
+    AiChatRuntimeDto getRuntime(String promptCode, Long sessionId, Long userId, Long tenantId);
+
+    List<AiChatSessionDto> listSessions(String promptCode, Long userId, Long tenantId);
+
+    SseEmitter stream(AiChatRequest request, Long userId, Long tenantId);
+
+    void removeSession(Long sessionId, Long userId, Long tenantId);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiConfigResolverService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiConfigResolverService.java
new file mode 100644
index 0000000..23b125c
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiConfigResolverService.java
@@ -0,0 +1,8 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.vincent.rsf.server.ai.dto.AiResolvedConfig;
+
+public interface AiConfigResolverService {
+
+    AiResolvedConfig resolve(String promptCode);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiMcpMountService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiMcpMountService.java
new file mode 100644
index 0000000..79faeb5
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiMcpMountService.java
@@ -0,0 +1,22 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.ai.dto.AiMcpToolPreviewDto;
+import com.vincent.rsf.server.ai.dto.AiMcpToolTestDto;
+import com.vincent.rsf.server.ai.dto.AiMcpToolTestRequest;
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+
+import java.util.List;
+
+public interface AiMcpMountService extends IService<AiMcpMount> {
+
+    List<AiMcpMount> listActiveMounts();
+
+    void validateBeforeSave(AiMcpMount aiMcpMount);
+
+    void validateBeforeUpdate(AiMcpMount aiMcpMount);
+
+    List<AiMcpToolPreviewDto> previewTools(Long mountId, Long userId, Long tenantId);
+
+    AiMcpToolTestDto testTool(Long mountId, Long userId, Long tenantId, AiMcpToolTestRequest request);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiParamService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiParamService.java
new file mode 100644
index 0000000..9cd1f9e
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiParamService.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.ai.entity.AiParam;
+
+public interface AiParamService extends IService<AiParam> {
+
+    AiParam getActiveParam();
+
+    void validateBeforeSave(AiParam aiParam);
+
+    void validateBeforeUpdate(AiParam aiParam);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptService.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptService.java
new file mode 100644
index 0000000..f8a7913
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/AiPromptService.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.ai.entity.AiPrompt;
+
+public interface AiPromptService extends IService<AiPrompt> {
+
+    AiPrompt getActivePrompt(String code);
+
+    void validateBeforeSave(AiPrompt aiPrompt);
+
+    void validateBeforeUpdate(AiPrompt aiPrompt);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java
new file mode 100644
index 0000000..8d26eeb
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/BuiltinMcpToolRegistry.java
@@ -0,0 +1,13 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+import org.springframework.ai.tool.ToolCallback;
+
+import java.util.List;
+
+public interface BuiltinMcpToolRegistry {
+
+    void validateBuiltinCode(String builtinCode);
+
+    List<ToolCallback> createToolCallbacks(AiMcpMount mount, Long userId);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/McpMountRuntimeFactory.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/McpMountRuntimeFactory.java
new file mode 100644
index 0000000..8f3daa3
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/McpMountRuntimeFactory.java
@@ -0,0 +1,25 @@
+package com.vincent.rsf.server.ai.service;
+
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+import org.springframework.ai.tool.ToolCallback;
+
+import java.util.List;
+
+public interface McpMountRuntimeFactory {
+
+    McpMountRuntime create(List<AiMcpMount> mounts, Long userId);
+
+    interface McpMountRuntime extends AutoCloseable {
+
+        ToolCallback[] getToolCallbacks();
+
+        List<String> getMountedNames();
+
+        List<String> getErrors();
+
+        int getMountedCount();
+
+        @Override
+        void close();
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java
new file mode 100644
index 0000000..b663a6a
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatMemoryServiceImpl.java
@@ -0,0 +1,288 @@
+package com.vincent.rsf.server.ai.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.ai.dto.AiChatMemoryDto;
+import com.vincent.rsf.server.ai.dto.AiChatMessageDto;
+import com.vincent.rsf.server.ai.dto.AiChatSessionDto;
+import com.vincent.rsf.server.ai.entity.AiChatMessage;
+import com.vincent.rsf.server.ai.entity.AiChatSession;
+import com.vincent.rsf.server.ai.mapper.AiChatMessageMapper;
+import com.vincent.rsf.server.ai.mapper.AiChatSessionMapper;
+import com.vincent.rsf.server.ai.service.AiChatMemoryService;
+import com.vincent.rsf.server.system.enums.StatusType;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class AiChatMemoryServiceImpl implements AiChatMemoryService {
+
+    private final AiChatSessionMapper aiChatSessionMapper;
+    private final AiChatMessageMapper aiChatMessageMapper;
+
+    @Override
+    public AiChatMemoryDto getMemory(Long userId, Long tenantId, String promptCode, Long sessionId) {
+        ensureIdentity(userId, tenantId);
+        String resolvedPromptCode = requirePromptCode(promptCode);
+        AiChatSession session = sessionId == null
+                ? findLatestSession(userId, tenantId, resolvedPromptCode)
+                : getSession(sessionId, userId, tenantId, resolvedPromptCode);
+        if (session == null) {
+            return AiChatMemoryDto.builder()
+                    .sessionId(null)
+                    .persistedMessages(List.of())
+                    .build();
+        }
+        return AiChatMemoryDto.builder()
+                .sessionId(session.getId())
+                .persistedMessages(listMessages(session.getId()))
+                .build();
+    }
+
+    @Override
+    public List<AiChatSessionDto> listSessions(Long userId, Long tenantId, String promptCode) {
+        ensureIdentity(userId, tenantId);
+        String resolvedPromptCode = requirePromptCode(promptCode);
+        List<AiChatSession> sessions = aiChatSessionMapper.selectList(new LambdaQueryWrapper<AiChatSession>()
+                .eq(AiChatSession::getUserId, userId)
+                .eq(AiChatSession::getTenantId, tenantId)
+                .eq(AiChatSession::getPromptCode, resolvedPromptCode)
+                .eq(AiChatSession::getDeleted, 0)
+                .eq(AiChatSession::getStatus, StatusType.ENABLE.val)
+                .orderByDesc(AiChatSession::getLastMessageTime)
+                .orderByDesc(AiChatSession::getId));
+        if (Cools.isEmpty(sessions)) {
+            return List.of();
+        }
+        List<AiChatSessionDto> result = new ArrayList<>();
+        for (AiChatSession session : sessions) {
+            result.add(AiChatSessionDto.builder()
+                    .sessionId(session.getId())
+                    .title(session.getTitle())
+                    .promptCode(session.getPromptCode())
+                    .lastMessageTime(session.getLastMessageTime())
+                    .build());
+        }
+        return result;
+    }
+
+    @Override
+    public AiChatSession resolveSession(Long userId, Long tenantId, String promptCode, Long sessionId, String titleSeed) {
+        ensureIdentity(userId, tenantId);
+        String resolvedPromptCode = requirePromptCode(promptCode);
+        if (sessionId != null) {
+            return getSession(sessionId, userId, tenantId, resolvedPromptCode);
+        }
+        Date now = new Date();
+        AiChatSession session = new AiChatSession()
+                .setTitle(buildSessionTitle(titleSeed))
+                .setPromptCode(resolvedPromptCode)
+                .setUserId(userId)
+                .setTenantId(tenantId)
+                .setLastMessageTime(now)
+                .setStatus(StatusType.ENABLE.val)
+                .setDeleted(0)
+                .setCreateBy(userId)
+                .setCreateTime(now)
+                .setUpdateBy(userId)
+                .setUpdateTime(now);
+        aiChatSessionMapper.insert(session);
+        return session;
+    }
+
+    @Override
+    public void saveRound(AiChatSession session, Long userId, Long tenantId, List<AiChatMessageDto> memoryMessages, String assistantContent) {
+        if (session == null || session.getId() == null) {
+            throw new CoolException("AI 浼氳瘽涓嶅瓨鍦�");
+        }
+        ensureIdentity(userId, tenantId);
+        List<AiChatMessageDto> normalizedMessages = normalizeMessages(memoryMessages);
+        if (normalizedMessages.isEmpty()) {
+            throw new CoolException("鏈疆娌℃湁鍙繚瀛樼殑瀵硅瘽娑堟伅");
+        }
+        int nextSeqNo = findNextSeqNo(session.getId());
+        Date now = new Date();
+        for (AiChatMessageDto message : normalizedMessages) {
+            aiChatMessageMapper.insert(buildMessageEntity(session.getId(), nextSeqNo++, message.getRole(), message.getContent(), userId, tenantId, now));
+        }
+        if (StringUtils.hasText(assistantContent)) {
+            aiChatMessageMapper.insert(buildMessageEntity(session.getId(), nextSeqNo, "assistant", assistantContent, userId, tenantId, now));
+        }
+        AiChatSession update = new AiChatSession()
+                .setId(session.getId())
+                .setTitle(resolveUpdatedTitle(session.getTitle(), normalizedMessages))
+                .setLastMessageTime(now)
+                .setUpdateBy(userId)
+                .setUpdateTime(now);
+        aiChatSessionMapper.updateById(update);
+    }
+
+    @Override
+    public void removeSession(Long userId, Long tenantId, Long sessionId) {
+        ensureIdentity(userId, tenantId);
+        if (sessionId == null) {
+            throw new CoolException("AI 浼氳瘽 ID 涓嶈兘涓虹┖");
+        }
+        AiChatSession session = aiChatSessionMapper.selectOne(new LambdaQueryWrapper<AiChatSession>()
+                .eq(AiChatSession::getId, sessionId)
+                .eq(AiChatSession::getUserId, userId)
+                .eq(AiChatSession::getTenantId, tenantId)
+                .eq(AiChatSession::getDeleted, 0)
+                .last("limit 1"));
+        if (session == null) {
+            throw new CoolException("AI 浼氳瘽涓嶅瓨鍦ㄦ垨鏃犳潈鍒犻櫎");
+        }
+        Date now = new Date();
+        AiChatSession updateSession = new AiChatSession()
+                .setId(sessionId)
+                .setDeleted(1)
+                .setUpdateBy(userId)
+                .setUpdateTime(now);
+        aiChatSessionMapper.updateById(updateSession);
+        List<AiChatMessage> messages = aiChatMessageMapper.selectList(new LambdaQueryWrapper<AiChatMessage>()
+                .eq(AiChatMessage::getSessionId, sessionId)
+                .eq(AiChatMessage::getDeleted, 0));
+        for (AiChatMessage message : messages) {
+            AiChatMessage updateMessage = new AiChatMessage()
+                    .setId(message.getId())
+                    .setDeleted(1);
+            aiChatMessageMapper.updateById(updateMessage);
+        }
+    }
+
+    private AiChatSession findLatestSession(Long userId, Long tenantId, String promptCode) {
+        return aiChatSessionMapper.selectOne(new LambdaQueryWrapper<AiChatSession>()
+                .eq(AiChatSession::getUserId, userId)
+                .eq(AiChatSession::getTenantId, tenantId)
+                .eq(AiChatSession::getPromptCode, promptCode)
+                .eq(AiChatSession::getDeleted, 0)
+                .eq(AiChatSession::getStatus, StatusType.ENABLE.val)
+                .orderByDesc(AiChatSession::getLastMessageTime)
+                .orderByDesc(AiChatSession::getId)
+                .last("limit 1"));
+    }
+
+    private AiChatSession getSession(Long sessionId, Long userId, Long tenantId, String promptCode) {
+        AiChatSession session = aiChatSessionMapper.selectOne(new LambdaQueryWrapper<AiChatSession>()
+                .eq(AiChatSession::getId, sessionId)
+                .eq(AiChatSession::getUserId, userId)
+                .eq(AiChatSession::getTenantId, tenantId)
+                .eq(AiChatSession::getPromptCode, promptCode)
+                .eq(AiChatSession::getDeleted, 0)
+                .eq(AiChatSession::getStatus, StatusType.ENABLE.val)
+                .last("limit 1"));
+        if (session == null) {
+            throw new CoolException("AI 浼氳瘽涓嶅瓨鍦ㄦ垨鏃犳潈璁块棶");
+        }
+        return session;
+    }
+
+    private List<AiChatMessageDto> listMessages(Long sessionId) {
+        List<AiChatMessage> records = aiChatMessageMapper.selectList(new LambdaQueryWrapper<AiChatMessage>()
+                .eq(AiChatMessage::getSessionId, sessionId)
+                .eq(AiChatMessage::getDeleted, 0)
+                .orderByAsc(AiChatMessage::getSeqNo)
+                .orderByAsc(AiChatMessage::getId));
+        if (Cools.isEmpty(records)) {
+            return List.of();
+        }
+        List<AiChatMessageDto> messages = new ArrayList<>();
+        for (AiChatMessage record : records) {
+            if (!StringUtils.hasText(record.getContent())) {
+                continue;
+            }
+            AiChatMessageDto item = new AiChatMessageDto();
+            item.setRole(record.getRole());
+            item.setContent(record.getContent());
+            messages.add(item);
+        }
+        return messages;
+    }
+
+    private List<AiChatMessageDto> normalizeMessages(List<AiChatMessageDto> memoryMessages) {
+        List<AiChatMessageDto> normalized = new ArrayList<>();
+        if (Cools.isEmpty(memoryMessages)) {
+            return normalized;
+        }
+        for (AiChatMessageDto item : memoryMessages) {
+            if (item == null || !StringUtils.hasText(item.getContent())) {
+                continue;
+            }
+            String role = item.getRole() == null ? "user" : item.getRole().toLowerCase();
+            if ("system".equals(role)) {
+                continue;
+            }
+            AiChatMessageDto normalizedItem = new AiChatMessageDto();
+            normalizedItem.setRole("assistant".equals(role) ? "assistant" : "user");
+            normalizedItem.setContent(item.getContent().trim());
+            normalized.add(normalizedItem);
+        }
+        return normalized;
+    }
+
+    private int findNextSeqNo(Long sessionId) {
+        AiChatMessage lastMessage = aiChatMessageMapper.selectOne(new LambdaQueryWrapper<AiChatMessage>()
+                .eq(AiChatMessage::getSessionId, sessionId)
+                .eq(AiChatMessage::getDeleted, 0)
+                .orderByDesc(AiChatMessage::getSeqNo)
+                .orderByDesc(AiChatMessage::getId)
+                .last("limit 1"));
+        return lastMessage == null || lastMessage.getSeqNo() == null ? 1 : lastMessage.getSeqNo() + 1;
+    }
+
+    private AiChatMessage buildMessageEntity(Long sessionId, int seqNo, String role, String content, Long userId, Long tenantId, Date createTime) {
+        return new AiChatMessage()
+                .setSessionId(sessionId)
+                .setSeqNo(seqNo)
+                .setRole(role)
+                .setContent(content)
+                .setUserId(userId)
+                .setTenantId(tenantId)
+                .setDeleted(0)
+                .setCreateBy(userId)
+                .setCreateTime(createTime);
+    }
+
+    private String resolveUpdatedTitle(String currentTitle, List<AiChatMessageDto> memoryMessages) {
+        if (StringUtils.hasText(currentTitle)) {
+            return currentTitle;
+        }
+        for (AiChatMessageDto item : memoryMessages) {
+            if ("user".equals(item.getRole()) && StringUtils.hasText(item.getContent())) {
+                return buildSessionTitle(item.getContent());
+            }
+        }
+        return null;
+    }
+
+    private String buildSessionTitle(String titleSeed) {
+        if (!StringUtils.hasText(titleSeed)) {
+            throw new CoolException("AI 浼氳瘽鏍囬涓嶈兘涓虹┖");
+        }
+        String title = titleSeed.trim().replace("\r", " ").replace("\n", " ");
+        return title.length() > 60 ? title.substring(0, 60) : title;
+    }
+
+    private void ensureIdentity(Long userId, Long tenantId) {
+        if (userId == null) {
+            throw new CoolException("褰撳墠鐧诲綍鐢ㄦ埛涓嶅瓨鍦�");
+        }
+        if (tenantId == null) {
+            throw new CoolException("褰撳墠绉熸埛涓嶅瓨鍦�");
+        }
+    }
+
+    private String requirePromptCode(String promptCode) {
+        if (!StringUtils.hasText(promptCode)) {
+            throw new CoolException("Prompt 缂栫爜涓嶈兘涓虹┖");
+        }
+        return promptCode;
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatServiceImpl.java
new file mode 100644
index 0000000..7fc31fc
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiChatServiceImpl.java
@@ -0,0 +1,349 @@
+package com.vincent.rsf.server.ai.service.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.ai.config.AiDefaults;
+import com.vincent.rsf.server.ai.dto.AiChatDoneDto;
+import com.vincent.rsf.server.ai.dto.AiChatMemoryDto;
+import com.vincent.rsf.server.ai.dto.AiChatMessageDto;
+import com.vincent.rsf.server.ai.dto.AiChatRequest;
+import com.vincent.rsf.server.ai.dto.AiChatRuntimeDto;
+import com.vincent.rsf.server.ai.dto.AiChatSessionDto;
+import com.vincent.rsf.server.ai.dto.AiResolvedConfig;
+import com.vincent.rsf.server.ai.entity.AiParam;
+import com.vincent.rsf.server.ai.entity.AiPrompt;
+import com.vincent.rsf.server.ai.entity.AiChatSession;
+import com.vincent.rsf.server.ai.service.AiChatService;
+import com.vincent.rsf.server.ai.service.AiChatMemoryService;
+import com.vincent.rsf.server.ai.service.AiConfigResolverService;
+import com.vincent.rsf.server.ai.service.McpMountRuntimeFactory;
+import io.micrometer.observation.ObservationRegistry;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.messages.AssistantMessage;
+import org.springframework.ai.chat.messages.Message;
+import org.springframework.ai.chat.messages.SystemMessage;
+import org.springframework.ai.chat.messages.UserMessage;
+import org.springframework.ai.chat.metadata.ChatResponseMetadata;
+import org.springframework.ai.chat.metadata.Usage;
+import org.springframework.ai.chat.model.ChatResponse;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.model.tool.DefaultToolCallingManager;
+import org.springframework.ai.model.tool.ToolCallingManager;
+import org.springframework.ai.openai.OpenAiChatModel;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.ai.openai.api.OpenAiApi;
+import org.springframework.ai.tool.ToolCallback;
+import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor;
+import org.springframework.ai.tool.resolution.SpringBeanToolCallbackResolver;
+import org.springframework.ai.util.json.schema.SchemaType;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+import reactor.core.publisher.Flux;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AiChatServiceImpl implements AiChatService {
+
+    private final AiConfigResolverService aiConfigResolverService;
+    private final AiChatMemoryService aiChatMemoryService;
+    private final McpMountRuntimeFactory mcpMountRuntimeFactory;
+    private final GenericApplicationContext applicationContext;
+    private final ObservationRegistry observationRegistry;
+    private final ObjectMapper objectMapper;
+
+    @Override
+    public AiChatRuntimeDto getRuntime(String promptCode, Long sessionId, Long userId, Long tenantId) {
+        AiResolvedConfig config = aiConfigResolverService.resolve(promptCode);
+        AiChatMemoryDto memory = aiChatMemoryService.getMemory(userId, tenantId, config.getPromptCode(), sessionId);
+        return AiChatRuntimeDto.builder()
+                .sessionId(memory.getSessionId())
+                .promptCode(config.getPromptCode())
+                .promptName(config.getPrompt().getName())
+                .model(config.getAiParam().getModel())
+                .configuredMcpCount(config.getMcpMounts().size())
+                .mountedMcpCount(config.getMcpMounts().size())
+                .mountedMcpNames(config.getMcpMounts().stream().map(item -> item.getName()).toList())
+                .mountErrors(List.of())
+                .persistedMessages(memory.getPersistedMessages())
+                .build();
+    }
+
+    @Override
+    public List<AiChatSessionDto> listSessions(String promptCode, Long userId, Long tenantId) {
+        AiResolvedConfig config = aiConfigResolverService.resolve(promptCode);
+        return aiChatMemoryService.listSessions(userId, tenantId, config.getPromptCode());
+    }
+
+    @Override
+    public void removeSession(Long sessionId, Long userId, Long tenantId) {
+        aiChatMemoryService.removeSession(userId, tenantId, sessionId);
+    }
+
+    @Override
+    public SseEmitter stream(AiChatRequest request, Long userId, Long tenantId) {
+        SseEmitter emitter = new SseEmitter(AiDefaults.SSE_TIMEOUT_MS);
+        CompletableFuture.runAsync(() -> doStream(request, userId, tenantId, emitter));
+        return emitter;
+    }
+
+    private void doStream(AiChatRequest request, Long userId, Long tenantId, SseEmitter emitter) {
+        try {
+            AiResolvedConfig config = aiConfigResolverService.resolve(request.getPromptCode());
+            AiChatSession session = aiChatMemoryService.resolveSession(userId, tenantId, config.getPromptCode(), request.getSessionId(), resolveTitleSeed(request.getMessages()));
+            AiChatMemoryDto memory = aiChatMemoryService.getMemory(userId, tenantId, config.getPromptCode(), session.getId());
+            List<AiChatMessageDto> mergedMessages = mergeMessages(memory.getPersistedMessages(), request.getMessages());
+            try (McpMountRuntimeFactory.McpMountRuntime runtime = mcpMountRuntimeFactory.create(config.getMcpMounts(), userId)) {
+                emit(emitter, "start", AiChatRuntimeDto.builder()
+                        .sessionId(session.getId())
+                        .promptCode(config.getPromptCode())
+                        .promptName(config.getPrompt().getName())
+                        .model(config.getAiParam().getModel())
+                        .configuredMcpCount(config.getMcpMounts().size())
+                        .mountedMcpCount(runtime.getMountedCount())
+                        .mountedMcpNames(runtime.getMountedNames())
+                        .mountErrors(runtime.getErrors())
+                        .persistedMessages(memory.getPersistedMessages())
+                        .build());
+
+                Prompt prompt = new Prompt(
+                        buildPromptMessages(mergedMessages, config.getPrompt(), request.getMetadata()),
+                        buildChatOptions(config.getAiParam(), runtime.getToolCallbacks(), userId, request.getMetadata())
+                );
+                OpenAiChatModel chatModel = createChatModel(config.getAiParam());
+                if (Boolean.FALSE.equals(config.getAiParam().getStreamingEnabled())) {
+                    ChatResponse response = chatModel.call(prompt);
+                    String content = extractContent(response);
+                    aiChatMemoryService.saveRound(session, userId, tenantId, request.getMessages(), content);
+                    if (StringUtils.hasText(content)) {
+                        emit(emitter, "delta", buildMessagePayload("content", content));
+                    }
+                    emitDone(emitter, response.getMetadata(), config.getAiParam().getModel(), session.getId());
+                    emitter.complete();
+                    return;
+                }
+
+                Flux<ChatResponse> responseFlux = chatModel.stream(prompt);
+                AtomicReference<ChatResponseMetadata> lastMetadata = new AtomicReference<>();
+                StringBuilder assistantContent = new StringBuilder();
+                responseFlux.doOnNext(response -> {
+                            lastMetadata.set(response.getMetadata());
+                            String content = extractContent(response);
+                            if (StringUtils.hasText(content)) {
+                                assistantContent.append(content);
+                                emit(emitter, "delta", buildMessagePayload("content", content));
+                            }
+                        })
+                        .doOnError(error -> emit(emitter, "error", buildMessagePayload("message", error == null ? "AI 瀵硅瘽澶辫触" : error.getMessage())))
+                        .blockLast();
+                aiChatMemoryService.saveRound(session, userId, tenantId, request.getMessages(), assistantContent.toString());
+                emitDone(emitter, lastMetadata.get(), config.getAiParam().getModel(), session.getId());
+                emitter.complete();
+            }
+        } catch (Exception e) {
+            log.error("AI stream error", e);
+            emit(emitter, "error", buildMessagePayload("message", e == null ? "AI 瀵硅瘽澶辫触" : e.getMessage()));
+            emitter.completeWithError(e);
+        }
+    }
+
+    private OpenAiChatModel createChatModel(AiParam aiParam) {
+        OpenAiApi openAiApi = buildOpenAiApi(aiParam);
+        ToolCallingManager toolCallingManager = DefaultToolCallingManager.builder()
+                .observationRegistry(observationRegistry)
+                .toolCallbackResolver(new SpringBeanToolCallbackResolver(applicationContext, SchemaType.OPEN_API_SCHEMA))
+                .toolExecutionExceptionProcessor(new DefaultToolExecutionExceptionProcessor(false))
+                .build();
+        return new OpenAiChatModel(
+                openAiApi,
+                OpenAiChatOptions.builder()
+                        .model(aiParam.getModel())
+                        .temperature(aiParam.getTemperature())
+                        .topP(aiParam.getTopP())
+                        .maxTokens(aiParam.getMaxTokens())
+                        .streamUsage(true)
+                        .build(),
+                toolCallingManager,
+                org.springframework.retry.support.RetryTemplate.builder().maxAttempts(1).build(),
+                observationRegistry
+        );
+    }
+
+    private OpenAiApi buildOpenAiApi(AiParam aiParam) {
+        int timeoutMs = aiParam.getTimeoutMs() == null ? AiDefaults.DEFAULT_TIMEOUT_MS : aiParam.getTimeoutMs();
+        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+        requestFactory.setConnectTimeout(timeoutMs);
+        requestFactory.setReadTimeout(timeoutMs);
+
+        return OpenAiApi.builder()
+                .baseUrl(aiParam.getBaseUrl())
+                .apiKey(aiParam.getApiKey())
+                .restClientBuilder(RestClient.builder().requestFactory(requestFactory))
+                .webClientBuilder(WebClient.builder())
+                .build();
+    }
+
+    private OpenAiChatOptions buildChatOptions(AiParam aiParam, ToolCallback[] toolCallbacks, Long userId, Map<String, Object> metadata) {
+        if (userId == null) {
+            throw new CoolException("褰撳墠鐧诲綍鐢ㄦ埛涓嶅瓨鍦�");
+        }
+        OpenAiChatOptions.Builder builder = OpenAiChatOptions.builder()
+                .model(aiParam.getModel())
+                .temperature(aiParam.getTemperature())
+                .topP(aiParam.getTopP())
+                .maxTokens(aiParam.getMaxTokens())
+                .streamUsage(true)
+                .user(String.valueOf(userId));
+        if (!Cools.isEmpty(toolCallbacks)) {
+            builder.toolCallbacks(Arrays.asList(toolCallbacks));
+        }
+        Map<String, String> metadataMap = new LinkedHashMap<>();
+        if (metadata != null) {
+            metadata.forEach((key, value) -> metadataMap.put(key, value == null ? "" : String.valueOf(value)));
+        }
+        if (!metadataMap.isEmpty()) {
+            builder.metadata(metadataMap);
+        }
+        return builder.build();
+    }
+
+    private List<Message> buildPromptMessages(List<AiChatMessageDto> sourceMessages, AiPrompt aiPrompt, Map<String, Object> metadata) {
+        if (Cools.isEmpty(sourceMessages)) {
+            throw new CoolException("瀵硅瘽娑堟伅涓嶈兘涓虹┖");
+        }
+        List<Message> messages = new ArrayList<>();
+        if (StringUtils.hasText(aiPrompt.getSystemPrompt())) {
+            messages.add(new SystemMessage(aiPrompt.getSystemPrompt()));
+        }
+        int lastUserIndex = -1;
+        for (int i = 0; i < sourceMessages.size(); i++) {
+            AiChatMessageDto item = sourceMessages.get(i);
+            if (item != null && "user".equalsIgnoreCase(item.getRole())) {
+                lastUserIndex = i;
+            }
+        }
+        for (int i = 0; i < sourceMessages.size(); i++) {
+            AiChatMessageDto item = sourceMessages.get(i);
+            if (item == null || !StringUtils.hasText(item.getContent())) {
+                continue;
+            }
+            String role = item.getRole() == null ? "user" : item.getRole().toLowerCase();
+            if ("system".equals(role)) {
+                continue;
+            }
+            String content = item.getContent();
+            if ("user".equals(role) && i == lastUserIndex) {
+                content = renderUserPrompt(aiPrompt.getUserPromptTemplate(), content, metadata);
+            }
+            if ("assistant".equals(role)) {
+                messages.add(new AssistantMessage(content));
+            } else {
+                messages.add(new UserMessage(content));
+            }
+        }
+        if (messages.stream().noneMatch(item -> item instanceof UserMessage)) {
+            throw new CoolException("鑷冲皯闇�瑕佷竴鏉$敤鎴锋秷鎭�");
+        }
+        return messages;
+    }
+
+    private List<AiChatMessageDto> mergeMessages(List<AiChatMessageDto> persistedMessages, List<AiChatMessageDto> memoryMessages) {
+        List<AiChatMessageDto> merged = new ArrayList<>();
+        if (!Cools.isEmpty(persistedMessages)) {
+            merged.addAll(persistedMessages);
+        }
+        if (!Cools.isEmpty(memoryMessages)) {
+            merged.addAll(memoryMessages);
+        }
+        if (merged.isEmpty()) {
+            throw new CoolException("瀵硅瘽娑堟伅涓嶈兘涓虹┖");
+        }
+        return merged;
+    }
+
+    private String resolveTitleSeed(List<AiChatMessageDto> messages) {
+        if (Cools.isEmpty(messages)) {
+            throw new CoolException("瀵硅瘽娑堟伅涓嶈兘涓虹┖");
+        }
+        for (int i = messages.size() - 1; i >= 0; i--) {
+            AiChatMessageDto item = messages.get(i);
+            if (item != null && "user".equalsIgnoreCase(item.getRole()) && StringUtils.hasText(item.getContent())) {
+                return item.getContent();
+            }
+        }
+        throw new CoolException("鑷冲皯闇�瑕佷竴鏉$敤鎴锋秷鎭�");
+    }
+
+    private String renderUserPrompt(String userPromptTemplate, String content, Map<String, Object> metadata) {
+        if (!StringUtils.hasText(userPromptTemplate)) {
+            return content;
+        }
+        String rendered = userPromptTemplate
+                .replace("{{input}}", content)
+                .replace("{input}", content);
+        if (metadata != null) {
+            for (Map.Entry<String, Object> entry : metadata.entrySet()) {
+                String value = entry.getValue() == null ? "" : String.valueOf(entry.getValue());
+                rendered = rendered.replace("{{" + entry.getKey() + "}}", value);
+                rendered = rendered.replace("{" + entry.getKey() + "}", value);
+            }
+        }
+        if (Objects.equals(rendered, userPromptTemplate)) {
+            return userPromptTemplate + "\n\n" + content;
+        }
+        return rendered;
+    }
+
+    private String extractContent(ChatResponse response) {
+        if (response == null || response.getResult() == null || response.getResult().getOutput() == null) {
+            return null;
+        }
+        return response.getResult().getOutput().getText();
+    }
+
+    private void emitDone(SseEmitter emitter, ChatResponseMetadata metadata, String fallbackModel, Long sessionId) {
+        Usage usage = metadata == null ? null : metadata.getUsage();
+        emit(emitter, "done", AiChatDoneDto.builder()
+                .sessionId(sessionId)
+                .model(metadata != null && StringUtils.hasText(metadata.getModel()) ? metadata.getModel() : fallbackModel)
+                .promptTokens(usage == null ? null : usage.getPromptTokens())
+                .completionTokens(usage == null ? null : usage.getCompletionTokens())
+                .totalTokens(usage == null ? null : usage.getTotalTokens())
+                .build());
+    }
+
+    private Map<String, String> buildMessagePayload(String key, String value) {
+        Map<String, String> payload = new LinkedHashMap<>();
+        payload.put(key, value == null ? "" : value);
+        return payload;
+    }
+
+    private void emit(SseEmitter emitter, String eventName, Object payload) {
+        try {
+            String data = objectMapper.writeValueAsString(payload);
+            emitter.send(SseEmitter.event()
+                    .name(eventName)
+                    .data(data, MediaType.APPLICATION_JSON));
+        } catch (IOException e) {
+            throw new CoolException("SSE 杈撳嚭澶辫触: " + e.getMessage());
+        }
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiConfigResolverServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiConfigResolverServiceImpl.java
new file mode 100644
index 0000000..366e1a9
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiConfigResolverServiceImpl.java
@@ -0,0 +1,31 @@
+package com.vincent.rsf.server.ai.service.impl;
+
+import com.vincent.rsf.server.ai.config.AiDefaults;
+import com.vincent.rsf.server.ai.dto.AiResolvedConfig;
+import com.vincent.rsf.server.ai.service.AiConfigResolverService;
+import com.vincent.rsf.server.ai.service.AiMcpMountService;
+import com.vincent.rsf.server.ai.service.AiParamService;
+import com.vincent.rsf.server.ai.service.AiPromptService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+@Service
+@RequiredArgsConstructor
+public class AiConfigResolverServiceImpl implements AiConfigResolverService {
+
+    private final AiParamService aiParamService;
+    private final AiPromptService aiPromptService;
+    private final AiMcpMountService aiMcpMountService;
+
+    @Override
+    public AiResolvedConfig resolve(String promptCode) {
+        String finalPromptCode = StringUtils.hasText(promptCode) ? promptCode : AiDefaults.DEFAULT_PROMPT_CODE;
+        return AiResolvedConfig.builder()
+                .promptCode(finalPromptCode)
+                .aiParam(aiParamService.getActiveParam())
+                .prompt(aiPromptService.getActivePrompt(finalPromptCode))
+                .mcpMounts(aiMcpMountService.listActiveMounts())
+                .build();
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java
new file mode 100644
index 0000000..822d55c
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiMcpMountServiceImpl.java
@@ -0,0 +1,208 @@
+package com.vincent.rsf.server.ai.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.ai.config.AiDefaults;
+import com.vincent.rsf.server.ai.dto.AiMcpToolPreviewDto;
+import com.vincent.rsf.server.ai.dto.AiMcpToolTestDto;
+import com.vincent.rsf.server.ai.dto.AiMcpToolTestRequest;
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+import com.vincent.rsf.server.ai.mapper.AiMcpMountMapper;
+import com.vincent.rsf.server.ai.service.AiMcpMountService;
+import com.vincent.rsf.server.ai.service.BuiltinMcpToolRegistry;
+import com.vincent.rsf.server.ai.service.McpMountRuntimeFactory;
+import com.vincent.rsf.server.system.enums.StatusType;
+import lombok.RequiredArgsConstructor;
+import org.springframework.ai.chat.model.ToolContext;
+import org.springframework.ai.tool.ToolCallback;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@Service("aiMcpMountService")
+@RequiredArgsConstructor
+public class AiMcpMountServiceImpl extends ServiceImpl<AiMcpMountMapper, AiMcpMount> implements AiMcpMountService {
+
+    private final BuiltinMcpToolRegistry builtinMcpToolRegistry;
+    private final McpMountRuntimeFactory mcpMountRuntimeFactory;
+    private final ObjectMapper objectMapper;
+
+    @Override
+    public List<AiMcpMount> listActiveMounts() {
+        return this.list(new LambdaQueryWrapper<AiMcpMount>()
+                .eq(AiMcpMount::getStatus, StatusType.ENABLE.val)
+                .orderByAsc(AiMcpMount::getSort)
+                .orderByAsc(AiMcpMount::getId));
+    }
+
+    @Override
+    public void validateBeforeSave(AiMcpMount aiMcpMount) {
+        fillDefaults(aiMcpMount);
+        ensureRequiredFields(aiMcpMount);
+    }
+
+    @Override
+    public void validateBeforeUpdate(AiMcpMount aiMcpMount) {
+        fillDefaults(aiMcpMount);
+        if (aiMcpMount.getId() == null) {
+            throw new CoolException("MCP 鎸傝浇 ID 涓嶈兘涓虹┖");
+        }
+        ensureRequiredFields(aiMcpMount);
+    }
+
+    @Override
+    public List<AiMcpToolPreviewDto> previewTools(Long mountId, Long userId, Long tenantId) {
+        AiMcpMount mount = requireMount(mountId);
+        try (McpMountRuntimeFactory.McpMountRuntime runtime = mcpMountRuntimeFactory.create(List.of(mount), userId)) {
+            List<AiMcpToolPreviewDto> tools = new ArrayList<>();
+            for (ToolCallback callback : runtime.getToolCallbacks()) {
+                if (callback == null || callback.getToolDefinition() == null) {
+                    continue;
+                }
+                tools.add(AiMcpToolPreviewDto.builder()
+                        .name(callback.getToolDefinition().name())
+                        .description(callback.getToolDefinition().description())
+                        .inputSchema(callback.getToolDefinition().inputSchema())
+                        .returnDirect(callback.getToolMetadata() == null ? null : callback.getToolMetadata().returnDirect())
+                        .build());
+            }
+            return tools;
+        }
+    }
+
+    @Override
+    public AiMcpToolTestDto testTool(Long mountId, Long userId, Long tenantId, AiMcpToolTestRequest request) {
+        if (userId == null) {
+            throw new CoolException("褰撳墠鐧诲綍鐢ㄦ埛涓嶅瓨鍦�");
+        }
+        if (tenantId == null) {
+            throw new CoolException("褰撳墠绉熸埛涓嶅瓨鍦�");
+        }
+        if (request == null) {
+            throw new CoolException("宸ュ叿娴嬭瘯鍙傛暟涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(request.getToolName())) {
+            throw new CoolException("宸ュ叿鍚嶇О涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(request.getInputJson())) {
+            throw new CoolException("宸ュ叿杈撳叆 JSON 涓嶈兘涓虹┖");
+        }
+        try {
+            objectMapper.readTree(request.getInputJson());
+        } catch (Exception e) {
+            throw new CoolException("宸ュ叿杈撳叆 JSON 鏍煎紡閿欒: " + e.getMessage());
+        }
+        AiMcpMount mount = requireMount(mountId);
+        try (McpMountRuntimeFactory.McpMountRuntime runtime = mcpMountRuntimeFactory.create(List.of(mount), userId)) {
+            ToolCallback callback = Arrays.stream(runtime.getToolCallbacks())
+                    .filter(item -> item != null && item.getToolDefinition() != null)
+                    .filter(item -> request.getToolName().equals(item.getToolDefinition().name()))
+                    .findFirst()
+                    .orElseThrow(() -> new CoolException("鏈壘鍒拌娴嬭瘯鐨勫伐鍏�: " + request.getToolName()));
+            String output = callback.call(
+                    request.getInputJson(),
+                    new ToolContext(Map.of("userId", userId, "tenantId", tenantId, "mountId", mountId))
+            );
+            return AiMcpToolTestDto.builder()
+                    .toolName(request.getToolName())
+                    .inputJson(request.getInputJson())
+                    .output(output)
+                    .build();
+        }
+    }
+
+    private void fillDefaults(AiMcpMount aiMcpMount) {
+        if (!StringUtils.hasText(aiMcpMount.getTransportType())) {
+            aiMcpMount.setTransportType(AiDefaults.MCP_TRANSPORT_SSE_HTTP);
+        }
+        if (aiMcpMount.getRequestTimeoutMs() == null) {
+            aiMcpMount.setRequestTimeoutMs(AiDefaults.DEFAULT_TIMEOUT_MS);
+        }
+        if (aiMcpMount.getSort() == null) {
+            aiMcpMount.setSort(0);
+        }
+        if (aiMcpMount.getStatus() == null) {
+            aiMcpMount.setStatus(StatusType.ENABLE.val);
+        }
+    }
+
+    private void ensureRequiredFields(AiMcpMount aiMcpMount) {
+        if (!StringUtils.hasText(aiMcpMount.getName())) {
+            throw new CoolException("MCP 鎸傝浇鍚嶇О涓嶈兘涓虹┖");
+        }
+        if (AiDefaults.MCP_TRANSPORT_BUILTIN.equals(aiMcpMount.getTransportType())) {
+            builtinMcpToolRegistry.validateBuiltinCode(aiMcpMount.getBuiltinCode());
+            ensureBuiltinConflictFree(aiMcpMount);
+            return;
+        }
+        if (AiDefaults.MCP_TRANSPORT_SSE_HTTP.equals(aiMcpMount.getTransportType())) {
+            if (!StringUtils.hasText(aiMcpMount.getServerUrl())) {
+                throw new CoolException("杩滅▼ MCP 鏈嶅姟鍦板潃涓嶈兘涓虹┖");
+            }
+            return;
+        }
+        if (AiDefaults.MCP_TRANSPORT_STDIO.equals(aiMcpMount.getTransportType())) {
+            if (!StringUtils.hasText(aiMcpMount.getCommand())) {
+                throw new CoolException("STDIO MCP 鍛戒护涓嶈兘涓虹┖");
+            }
+            return;
+        }
+        throw new CoolException("涓嶆敮鎸佺殑 MCP 浼犺緭绫诲瀷: " + aiMcpMount.getTransportType());
+    }
+
+    private AiMcpMount requireMount(Long mountId) {
+        if (mountId == null) {
+            throw new CoolException("MCP 鎸傝浇 ID 涓嶈兘涓虹┖");
+        }
+        AiMcpMount mount = this.getById(mountId);
+        if (mount == null || (mount.getDeleted() != null && mount.getDeleted() == 1)) {
+            throw new CoolException("MCP 鎸傝浇涓嶅瓨鍦�");
+        }
+        return mount;
+    }
+
+    private void ensureBuiltinConflictFree(AiMcpMount aiMcpMount) {
+        if (aiMcpMount.getStatus() == null || aiMcpMount.getStatus() != StatusType.ENABLE.val) {
+            return;
+        }
+        List<String> conflictCodes = resolveConflictCodes(aiMcpMount.getBuiltinCode());
+        if (conflictCodes.isEmpty()) {
+            return;
+        }
+        LambdaQueryWrapper<AiMcpMount> queryWrapper = new LambdaQueryWrapper<AiMcpMount>()
+                .eq(AiMcpMount::getTransportType, AiDefaults.MCP_TRANSPORT_BUILTIN)
+                .eq(AiMcpMount::getStatus, StatusType.ENABLE.val)
+                .in(AiMcpMount::getBuiltinCode, conflictCodes);
+        if (aiMcpMount.getId() != null) {
+            queryWrapper.ne(AiMcpMount::getId, aiMcpMount.getId());
+        }
+        List<AiMcpMount> conflictMounts = this.list(queryWrapper);
+        if (conflictMounts.isEmpty()) {
+            return;
+        }
+        String conflictNames = String.join("銆�", conflictMounts.stream().map(AiMcpMount::getName).toList());
+        throw new CoolException("褰撳墠鍐呯疆 MCP 涓庡凡鍚敤鎸傝浇鍐茬獊锛岃鍏抽棴鍚庡啀鍚敤: " + conflictNames);
+    }
+
+    private List<String> resolveConflictCodes(String builtinCode) {
+        List<String> codes = new ArrayList<>();
+        if (AiDefaults.MCP_BUILTIN_RSF_WMS.equals(builtinCode)) {
+            codes.add(AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK);
+            codes.add(AiDefaults.MCP_BUILTIN_RSF_WMS_TASK);
+            codes.add(AiDefaults.MCP_BUILTIN_RSF_WMS_BASE);
+            return codes;
+        }
+        if (AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK.equals(builtinCode)
+                || AiDefaults.MCP_BUILTIN_RSF_WMS_TASK.equals(builtinCode)
+                || AiDefaults.MCP_BUILTIN_RSF_WMS_BASE.equals(builtinCode)) {
+            codes.add(AiDefaults.MCP_BUILTIN_RSF_WMS);
+        }
+        return codes;
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamServiceImpl.java
new file mode 100644
index 0000000..41a014b
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiParamServiceImpl.java
@@ -0,0 +1,97 @@
+package com.vincent.rsf.server.ai.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.ai.config.AiDefaults;
+import com.vincent.rsf.server.ai.entity.AiParam;
+import com.vincent.rsf.server.ai.mapper.AiParamMapper;
+import com.vincent.rsf.server.ai.service.AiParamService;
+import com.vincent.rsf.server.system.enums.StatusType;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+@Service("aiParamService")
+public class AiParamServiceImpl extends ServiceImpl<AiParamMapper, AiParam> implements AiParamService {
+
+    @Override
+    public AiParam getActiveParam() {
+        AiParam aiParam = this.getOne(new LambdaQueryWrapper<AiParam>()
+                .eq(AiParam::getStatus, StatusType.ENABLE.val)
+                .last("limit 1"));
+        if (aiParam == null) {
+            throw new CoolException("鏈壘鍒板惎鐢ㄤ腑鐨� AI 鍙傛暟閰嶇疆");
+        }
+        return aiParam;
+    }
+
+    @Override
+    public void validateBeforeSave(AiParam aiParam) {
+        fillDefaults(aiParam);
+        ensureBaseFields(aiParam);
+        ensureSingleActive(aiParam, null);
+    }
+
+    @Override
+    public void validateBeforeUpdate(AiParam aiParam) {
+        fillDefaults(aiParam);
+        if (aiParam.getId() == null) {
+            throw new CoolException("AI 鍙傛暟 ID 涓嶈兘涓虹┖");
+        }
+        ensureBaseFields(aiParam);
+        ensureSingleActive(aiParam, aiParam.getId());
+    }
+
+    private void ensureBaseFields(AiParam aiParam) {
+        if (!StringUtils.hasText(aiParam.getName())) {
+            throw new CoolException("AI 鍙傛暟鍚嶇О涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(aiParam.getProviderType())) {
+            aiParam.setProviderType(AiDefaults.PROVIDER_OPENAI_COMPATIBLE);
+        }
+        if (!StringUtils.hasText(aiParam.getBaseUrl())) {
+            throw new CoolException("AI Base URL 涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(aiParam.getApiKey())) {
+            throw new CoolException("AI API Key 涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(aiParam.getModel())) {
+            throw new CoolException("AI 妯″瀷涓嶈兘涓虹┖");
+        }
+    }
+
+    private void ensureSingleActive(AiParam aiParam, Long selfId) {
+        if (aiParam.getStatus() == null || aiParam.getStatus() != StatusType.ENABLE.val) {
+            return;
+        }
+        LambdaQueryWrapper<AiParam> wrapper = new LambdaQueryWrapper<AiParam>()
+                .eq(AiParam::getStatus, StatusType.ENABLE.val);
+        if (selfId != null) {
+            wrapper.ne(AiParam::getId, selfId);
+        }
+        if (this.count(wrapper) > 0) {
+            throw new CoolException("鍚屼竴绉熸埛浠呭厑璁镐竴鏉″惎鐢ㄤ腑鐨� AI 鍙傛暟閰嶇疆");
+        }
+    }
+
+    private void fillDefaults(AiParam aiParam) {
+        if (!StringUtils.hasText(aiParam.getProviderType())) {
+            aiParam.setProviderType(AiDefaults.PROVIDER_OPENAI_COMPATIBLE);
+        }
+        if (aiParam.getTemperature() == null) {
+            aiParam.setTemperature(AiDefaults.DEFAULT_TEMPERATURE);
+        }
+        if (aiParam.getTopP() == null) {
+            aiParam.setTopP(AiDefaults.DEFAULT_TOP_P);
+        }
+        if (aiParam.getTimeoutMs() == null) {
+            aiParam.setTimeoutMs(AiDefaults.DEFAULT_TIMEOUT_MS);
+        }
+        if (aiParam.getStreamingEnabled() == null) {
+            aiParam.setStreamingEnabled(Boolean.TRUE);
+        }
+        if (aiParam.getStatus() == null) {
+            aiParam.setStatus(StatusType.ENABLE.val);
+        }
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiPromptServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiPromptServiceImpl.java
new file mode 100644
index 0000000..7603c97
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/AiPromptServiceImpl.java
@@ -0,0 +1,68 @@
+package com.vincent.rsf.server.ai.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.ai.entity.AiPrompt;
+import com.vincent.rsf.server.ai.mapper.AiPromptMapper;
+import com.vincent.rsf.server.ai.service.AiPromptService;
+import com.vincent.rsf.server.system.enums.StatusType;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+@Service("aiPromptService")
+public class AiPromptServiceImpl extends ServiceImpl<AiPromptMapper, AiPrompt> implements AiPromptService {
+
+    @Override
+    public AiPrompt getActivePrompt(String code) {
+        AiPrompt aiPrompt = this.getOne(new LambdaQueryWrapper<AiPrompt>()
+                .eq(AiPrompt::getCode, code)
+                .eq(AiPrompt::getStatus, StatusType.ENABLE.val)
+                .last("limit 1"));
+        if (aiPrompt == null) {
+            throw new CoolException("鏈壘鍒板惎鐢ㄤ腑鐨� Prompt锛�" + code);
+        }
+        return aiPrompt;
+    }
+
+    @Override
+    public void validateBeforeSave(AiPrompt aiPrompt) {
+        ensureRequiredFields(aiPrompt);
+        ensureUniqueCode(aiPrompt.getCode(), null);
+    }
+
+    @Override
+    public void validateBeforeUpdate(AiPrompt aiPrompt) {
+        if (aiPrompt.getId() == null) {
+            throw new CoolException("Prompt ID 涓嶈兘涓虹┖");
+        }
+        ensureRequiredFields(aiPrompt);
+        ensureUniqueCode(aiPrompt.getCode(), aiPrompt.getId());
+    }
+
+    private void ensureRequiredFields(AiPrompt aiPrompt) {
+        if (!StringUtils.hasText(aiPrompt.getName())) {
+            throw new CoolException("Prompt 鍚嶇О涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(aiPrompt.getCode())) {
+            throw new CoolException("Prompt 缂栫爜涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(aiPrompt.getSystemPrompt())) {
+            throw new CoolException("绯荤粺 Prompt 涓嶈兘涓虹┖");
+        }
+        if (aiPrompt.getStatus() == null) {
+            aiPrompt.setStatus(StatusType.ENABLE.val);
+        }
+    }
+
+    private void ensureUniqueCode(String code, Long selfId) {
+        LambdaQueryWrapper<AiPrompt> wrapper = new LambdaQueryWrapper<AiPrompt>()
+                .eq(AiPrompt::getCode, code);
+        if (selfId != null) {
+            wrapper.ne(AiPrompt::getId, selfId);
+        }
+        if (this.count(wrapper) > 0) {
+            throw new CoolException("Prompt 缂栫爜宸插瓨鍦�");
+        }
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java
new file mode 100644
index 0000000..01e72e0
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/BuiltinMcpToolRegistryImpl.java
@@ -0,0 +1,69 @@
+package com.vincent.rsf.server.ai.service.impl;
+
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.ai.config.AiDefaults;
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+import com.vincent.rsf.server.ai.service.BuiltinMcpToolRegistry;
+import com.vincent.rsf.server.ai.tool.RsfWmsBaseTools;
+import com.vincent.rsf.server.ai.tool.RsfWmsStockTools;
+import com.vincent.rsf.server.ai.tool.RsfWmsTaskTools;
+import lombok.RequiredArgsConstructor;
+import org.springframework.ai.support.ToolCallbacks;
+import org.springframework.ai.tool.ToolCallback;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class BuiltinMcpToolRegistryImpl implements BuiltinMcpToolRegistry {
+
+    private final RsfWmsStockTools rsfWmsStockTools;
+    private final RsfWmsTaskTools rsfWmsTaskTools;
+    private final RsfWmsBaseTools rsfWmsBaseTools;
+
+    @Override
+    public void validateBuiltinCode(String builtinCode) {
+        if (!StringUtils.hasText(builtinCode)) {
+            throw new CoolException("鍐呯疆 MCP 缂栫爜涓嶈兘涓虹┖");
+        }
+        if (!supportedBuiltinCodes().contains(builtinCode)) {
+            throw new CoolException("涓嶆敮鎸佺殑鍐呯疆 MCP 缂栫爜: " + builtinCode);
+        }
+    }
+
+    @Override
+    public List<ToolCallback> createToolCallbacks(AiMcpMount mount, Long userId) {
+        String builtinCode = mount.getBuiltinCode();
+        validateBuiltinCode(builtinCode);
+        if (AiDefaults.MCP_BUILTIN_RSF_WMS.equals(builtinCode)) {
+            List<ToolCallback> callbacks = new ArrayList<>();
+            callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsStockTools)));
+            callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsTaskTools)));
+            callbacks.addAll(Arrays.asList(ToolCallbacks.from(rsfWmsBaseTools)));
+            return callbacks;
+        }
+        if (AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK.equals(builtinCode)) {
+            return Arrays.asList(ToolCallbacks.from(rsfWmsStockTools));
+        }
+        if (AiDefaults.MCP_BUILTIN_RSF_WMS_TASK.equals(builtinCode)) {
+            return Arrays.asList(ToolCallbacks.from(rsfWmsTaskTools));
+        }
+        if (AiDefaults.MCP_BUILTIN_RSF_WMS_BASE.equals(builtinCode)) {
+            return Arrays.asList(ToolCallbacks.from(rsfWmsBaseTools));
+        }
+        throw new CoolException("涓嶆敮鎸佺殑鍐呯疆 MCP 缂栫爜: " + builtinCode);
+    }
+
+    private List<String> supportedBuiltinCodes() {
+        return List.of(
+                AiDefaults.MCP_BUILTIN_RSF_WMS,
+                AiDefaults.MCP_BUILTIN_RSF_WMS_STOCK,
+                AiDefaults.MCP_BUILTIN_RSF_WMS_TASK,
+                AiDefaults.MCP_BUILTIN_RSF_WMS_BASE
+        );
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/McpMountRuntimeFactoryImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/McpMountRuntimeFactoryImpl.java
new file mode 100644
index 0000000..6b36785
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/service/impl/McpMountRuntimeFactoryImpl.java
@@ -0,0 +1,211 @@
+package com.vincent.rsf.server.ai.service.impl;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.ai.config.AiDefaults;
+import com.vincent.rsf.server.ai.entity.AiMcpMount;
+import com.vincent.rsf.server.ai.service.BuiltinMcpToolRegistry;
+import com.vincent.rsf.server.ai.service.McpMountRuntimeFactory;
+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.json.jackson.JacksonMcpJsonMapper;
+import io.modelcontextprotocol.spec.McpSchema;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
+import org.springframework.ai.tool.ToolCallback;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class McpMountRuntimeFactoryImpl implements McpMountRuntimeFactory {
+
+    private final ObjectMapper objectMapper;
+    private final BuiltinMcpToolRegistry builtinMcpToolRegistry;
+
+    @Override
+    public McpMountRuntime create(List<AiMcpMount> mounts, Long userId) {
+        List<McpSyncClient> clients = new ArrayList<>();
+        List<ToolCallback> callbacks = new ArrayList<>();
+        List<String> mountedNames = new ArrayList<>();
+        List<String> errors = new ArrayList<>();
+        for (AiMcpMount mount : mounts) {
+            try {
+                if (AiDefaults.MCP_TRANSPORT_BUILTIN.equals(mount.getTransportType())) {
+                    callbacks.addAll(builtinMcpToolRegistry.createToolCallbacks(mount, userId));
+                    mountedNames.add(mount.getName());
+                    continue;
+                }
+                McpSyncClient client = createClient(mount);
+                client.initialize();
+                client.listTools();
+                clients.add(client);
+                mountedNames.add(mount.getName());
+            } catch (Exception e) {
+                String message = mount.getName() + " 鎸傝浇澶辫触: " + e.getMessage();
+                errors.add(message);
+                log.warn(message, e);
+            }
+        }
+        if (!clients.isEmpty()) {
+            callbacks.addAll(Arrays.asList(
+                    SyncMcpToolCallbackProvider.builder().mcpClients(clients).build().getToolCallbacks()
+            ));
+        }
+        ensureUniqueToolNames(callbacks);
+        return new DefaultMcpMountRuntime(clients, callbacks.toArray(new ToolCallback[0]), mountedNames, errors);
+    }
+
+    private void ensureUniqueToolNames(List<ToolCallback> callbacks) {
+        LinkedHashSet<String> duplicateNames = new LinkedHashSet<>();
+        LinkedHashSet<String> seenNames = new LinkedHashSet<>();
+        for (ToolCallback callback : callbacks) {
+            if (callback == null || callback.getToolDefinition() == null) {
+                continue;
+            }
+            String name = callback.getToolDefinition().name();
+            if (!StringUtils.hasText(name)) {
+                continue;
+            }
+            if (!seenNames.add(name)) {
+                duplicateNames.add(name);
+            }
+        }
+        if (!duplicateNames.isEmpty()) {
+            throw new CoolException("MCP 宸ュ叿鍚嶇О閲嶅锛岃璋冩暣鎸傝浇閰嶇疆: " + String.join(", ", duplicateNames));
+        }
+    }
+
+    private McpSyncClient createClient(AiMcpMount mount) {
+        Duration timeout = Duration.ofMillis(mount.getRequestTimeoutMs() == null
+                ? AiDefaults.DEFAULT_TIMEOUT_MS
+                : mount.getRequestTimeoutMs());
+        JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(objectMapper);
+        if (AiDefaults.MCP_TRANSPORT_STDIO.equals(mount.getTransportType())) {
+            ServerParameters.Builder parametersBuilder = ServerParameters.builder(mount.getCommand());
+            List<String> args = readStringList(mount.getArgsJson());
+            if (!args.isEmpty()) {
+                parametersBuilder.args(args);
+            }
+            Map<String, String> env = readStringMap(mount.getEnvJson());
+            if (!env.isEmpty()) {
+                parametersBuilder.env(env);
+            }
+            StdioClientTransport transport = new StdioClientTransport(parametersBuilder.build(), jsonMapper);
+            transport.setStdErrorHandler(message -> log.warn("MCP STDIO stderr [{}]: {}", mount.getName(), message));
+            return McpClient.sync(transport)
+                    .requestTimeout(timeout)
+                    .initializationTimeout(timeout)
+                    .clientInfo(new McpSchema.Implementation("rsf-ai-client", "RSF AI Client", "1.0.0"))
+                    .build();
+        }
+        if (!AiDefaults.MCP_TRANSPORT_SSE_HTTP.equals(mount.getTransportType())) {
+            throw new CoolException("涓嶆敮鎸佺殑 MCP 浼犺緭绫诲瀷: " + mount.getTransportType());
+        }
+
+        if (!StringUtils.hasText(mount.getServerUrl())) {
+            throw new CoolException("MCP 鏈嶅姟鍦板潃涓嶈兘涓虹┖");
+        }
+        HttpClientSseClientTransport.Builder transportBuilder = HttpClientSseClientTransport.builder(mount.getServerUrl())
+                .jsonMapper(jsonMapper)
+                .connectTimeout(timeout);
+        if (StringUtils.hasText(mount.getEndpoint())) {
+            transportBuilder.sseEndpoint(mount.getEndpoint());
+        }
+        Map<String, String> headers = readStringMap(mount.getHeadersJson());
+        if (!headers.isEmpty()) {
+            transportBuilder.customizeRequest(builder -> headers.forEach(builder::header));
+        }
+        return McpClient.sync(transportBuilder.build())
+                .requestTimeout(timeout)
+                .initializationTimeout(timeout)
+                .clientInfo(new McpSchema.Implementation("rsf-ai-client", "RSF AI Client", "1.0.0"))
+                .build();
+    }
+
+    private List<String> readStringList(String json) {
+        if (!StringUtils.hasText(json)) {
+            return Collections.emptyList();
+        }
+        try {
+            return objectMapper.readValue(json, new TypeReference<List<String>>() {
+            });
+        } catch (Exception e) {
+            throw new CoolException("瑙f瀽 MCP 鍒楄〃閰嶇疆澶辫触: " + e.getMessage());
+        }
+    }
+
+    private Map<String, String> readStringMap(String json) {
+        if (!StringUtils.hasText(json)) {
+            return Collections.emptyMap();
+        }
+        try {
+            Map<String, String> result = objectMapper.readValue(json, new TypeReference<LinkedHashMap<String, String>>() {
+            });
+            return result == null ? Collections.emptyMap() : result;
+        } catch (Exception e) {
+            throw new CoolException("瑙f瀽 MCP Map 閰嶇疆澶辫触: " + e.getMessage());
+        }
+    }
+
+    private static class DefaultMcpMountRuntime implements McpMountRuntime {
+
+        private final List<McpSyncClient> clients;
+        private final ToolCallback[] callbacks;
+        private final List<String> mountedNames;
+        private final List<String> errors;
+
+        private DefaultMcpMountRuntime(List<McpSyncClient> clients, ToolCallback[] callbacks, List<String> mountedNames, List<String> errors) {
+            this.clients = clients;
+            this.callbacks = callbacks;
+            this.mountedNames = mountedNames;
+            this.errors = errors;
+        }
+
+        @Override
+        public ToolCallback[] getToolCallbacks() {
+            return callbacks;
+        }
+
+        @Override
+        public List<String> getMountedNames() {
+            return mountedNames;
+        }
+
+        @Override
+        public List<String> getErrors() {
+            return errors;
+        }
+
+        @Override
+        public int getMountedCount() {
+            return mountedNames.size();
+        }
+
+        @Override
+        public void close() {
+            for (McpSyncClient client : clients) {
+                try {
+                    client.close();
+                } catch (Exception e) {
+                    log.warn("鍏抽棴 MCP Client 澶辫触", e);
+                }
+            }
+        }
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java
new file mode 100644
index 0000000..0a8350d
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsBaseTools.java
@@ -0,0 +1,147 @@
+package com.vincent.rsf.server.ai.tool;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.manager.entity.BasStation;
+import com.vincent.rsf.server.manager.entity.Warehouse;
+import com.vincent.rsf.server.manager.service.BasStationService;
+import com.vincent.rsf.server.manager.service.WarehouseService;
+import com.vincent.rsf.server.system.entity.DictData;
+import com.vincent.rsf.server.system.service.DictDataService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.annotation.ToolParam;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class RsfWmsBaseTools {
+
+    private final WarehouseService warehouseService;
+    private final BasStationService basStationService;
+    private final DictDataService dictDataService;
+
+    @Tool(name = "rsf_query_warehouses", description = "鎸変粨搴撶紪鐮佹垨鍚嶇О鏌ヨ浠撳簱鍩虹淇℃伅銆�")
+    public List<Map<String, Object>> queryWarehouses(
+            @ToolParam(description = "浠撳簱缂栫爜锛屽彲閫�") String code,
+            @ToolParam(description = "浠撳簱鍚嶇О锛屽彲閫�") String name,
+            @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 10锛屾渶澶� 50") Integer limit) {
+        LambdaQueryWrapper<Warehouse> queryWrapper = new LambdaQueryWrapper<>();
+        int finalLimit = normalizeLimit(limit, 10, 50);
+        if (StringUtils.hasText(code)) {
+            queryWrapper.like(Warehouse::getCode, code);
+        }
+        if (StringUtils.hasText(name)) {
+            queryWrapper.like(Warehouse::getName, name);
+        }
+        queryWrapper.orderByAsc(Warehouse::getCode).last("LIMIT " + finalLimit);
+        List<Warehouse> warehouses = warehouseService.list(queryWrapper);
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (Warehouse warehouse : warehouses) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("id", warehouse.getId());
+            item.put("code", warehouse.getCode());
+            item.put("name", warehouse.getName());
+            item.put("factory", warehouse.getFactory());
+            item.put("address", warehouse.getAddress());
+            item.put("status", warehouse.getStatus());
+            item.put("statusLabel", warehouse.getStatus$());
+            item.put("memo", warehouse.getMemo());
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Tool(name = "rsf_query_bas_stations", description = "鎸夌珯鐐圭紪鍙枫�佺珯鐐瑰悕绉版垨浣跨敤鐘舵�佹煡璇㈠熀纭�绔欑偣銆�")
+    public List<Map<String, Object>> queryBasStations(
+            @ToolParam(description = "绔欑偣缂栧彿锛屽彲閫�") String stationName,
+            @ToolParam(description = "绔欑偣鍚嶇О锛屽彲閫�") String stationId,
+            @ToolParam(description = "浣跨敤鐘舵�侊紝鍙��") String useStatus,
+            @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 10锛屾渶澶� 50") Integer limit) {
+        LambdaQueryWrapper<BasStation> queryWrapper = new LambdaQueryWrapper<>();
+        int finalLimit = normalizeLimit(limit, 10, 50);
+        if (StringUtils.hasText(stationName)) {
+            queryWrapper.like(BasStation::getStationName, stationName);
+        }
+        if (StringUtils.hasText(stationId)) {
+            queryWrapper.like(BasStation::getStationId, stationId);
+        }
+        if (StringUtils.hasText(useStatus)) {
+            queryWrapper.eq(BasStation::getUseStatus, useStatus);
+        }
+        queryWrapper.orderByAsc(BasStation::getStationName).last("LIMIT " + finalLimit);
+        List<BasStation> stations = basStationService.list(queryWrapper);
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (BasStation station : stations) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("id", station.getId());
+            item.put("stationName", station.getStationName());
+            item.put("stationId", station.getStationId());
+            item.put("type", station.getType());
+            item.put("typeLabel", station.getType$());
+            item.put("useStatus", station.getUseStatus());
+            item.put("useStatusLabel", station.getUseStatus$());
+            item.put("area", station.getArea());
+            item.put("areaLabel", station.getArea$());
+            item.put("isWcs", station.getIsWcs());
+            item.put("inAble", station.getInAble());
+            item.put("outAble", station.getOutAble());
+            item.put("status", station.getStatus());
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Tool(name = "rsf_query_dict_data", description = "鏍规嵁瀛楀吀绫诲瀷缂栫爜鏌ヨ瀛楀吀鏁版嵁锛屽彲鎸夊�兼垨鏍囩杩涗竴姝ヨ繃婊ゃ��")
+    public List<Map<String, Object>> queryDictData(
+            @ToolParam(required = true, description = "瀛楀吀绫诲瀷缂栫爜") String dictTypeCode,
+            @ToolParam(description = "瀛楀吀鍊硷紝鍙��") String value,
+            @ToolParam(description = "瀛楀吀鏍囩锛屽彲閫�") String label,
+            @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 20锛屾渶澶� 100") Integer limit) {
+        if (!StringUtils.hasText(dictTypeCode)) {
+            throw new CoolException("瀛楀吀绫诲瀷缂栫爜涓嶈兘涓虹┖");
+        }
+        int finalLimit = normalizeLimit(limit, 20, 100);
+        LambdaQueryWrapper<DictData> queryWrapper = new LambdaQueryWrapper<DictData>()
+                .eq(DictData::getDictTypeCode, dictTypeCode);
+        if (StringUtils.hasText(value)) {
+            queryWrapper.like(DictData::getValue, value);
+        }
+        if (StringUtils.hasText(label)) {
+            queryWrapper.like(DictData::getLabel, label);
+        }
+        queryWrapper.orderByAsc(DictData::getSort).last("LIMIT " + finalLimit);
+        List<DictData> dictDataList = dictDataService.list(queryWrapper);
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (DictData dictData : dictDataList) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("id", dictData.getId());
+            item.put("dictTypeCode", dictData.getDictTypeCode());
+            item.put("value", dictData.getValue());
+            item.put("label", dictData.getLabel());
+            item.put("sort", dictData.getSort());
+            item.put("color", dictData.getColor());
+            item.put("group", dictData.getGroup());
+            item.put("status", dictData.getStatus());
+            item.put("statusLabel", dictData.getStatus$());
+            result.add(item);
+        }
+        return result;
+    }
+
+    private int normalizeLimit(Integer limit, int defaultValue, int maxValue) {
+        if (limit == null) {
+            return defaultValue;
+        }
+        if (limit < 1 || limit > maxValue) {
+            throw new CoolException("limit 蹇呴』鍦� 1 鍒� " + maxValue + " 涔嬮棿");
+        }
+        return limit;
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java
new file mode 100644
index 0000000..78994a2
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsStockTools.java
@@ -0,0 +1,98 @@
+package com.vincent.rsf.server.ai.tool;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.common.utils.FieldsUtils;
+import com.vincent.rsf.server.manager.entity.DeviceSite;
+import com.vincent.rsf.server.manager.entity.LocItem;
+import com.vincent.rsf.server.manager.enums.LocStsType;
+import com.vincent.rsf.server.manager.service.DeviceSiteService;
+import com.vincent.rsf.server.manager.service.LocItemService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.annotation.ToolParam;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@Component
+@RequiredArgsConstructor
+public class RsfWmsStockTools {
+
+    private final LocItemService locItemService;
+    private final DeviceSiteService deviceSiteService;
+
+    @Tool(name = "rsf_query_available_inventory", description = "鏍规嵁鐗╂枡缂栫爜鎴栫墿鏂欏悕绉版煡璇㈠綋鍓嶅湪搴撲笖鍙敤浜庡嚭搴撶殑搴撳瓨鏄庣粏銆�")
+    public List<Map<String, Object>> queryAvailableInventory(
+            @ToolParam(description = "鐗╂枡缂栫爜锛屼紭鍏堜娇鐢�") String matnr,
+            @ToolParam(description = "鐗╂枡鍚嶇О锛屽綋娌℃湁鐗╂枡缂栫爜鏃朵娇鐢�") String maktx) {
+        if (!StringUtils.hasText(matnr) && !StringUtils.hasText(maktx)) {
+            throw new CoolException("鐗╂枡缂栫爜鎴栫墿鏂欏悕绉拌嚦灏戦渶瑕佹彁渚涗竴涓�");
+        }
+        LambdaQueryWrapper<LocItem> queryWrapper = new LambdaQueryWrapper<>();
+        if (StringUtils.hasText(matnr)) {
+            queryWrapper.eq(LocItem::getMatnrCode, matnr);
+        } else {
+            queryWrapper.eq(LocItem::getMaktx, maktx);
+        }
+        queryWrapper.apply(
+                "EXISTS (SELECT 1 FROM man_loc ml WHERE ml.use_status = {0} AND ml.id = man_loc_item.loc_id)",
+                LocStsType.LOC_STS_TYPE_F.type
+        );
+        List<LocItem> locItems = locItemService.list(queryWrapper);
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (LocItem locItem : locItems) {
+            if (!Objects.isNull(locItem.getFieldsIndex())) {
+                locItem.setExtendFields(FieldsUtils.getFields(locItem.getFieldsIndex()));
+            }
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("id", locItem.getId());
+            item.put("locId", locItem.getLocId());
+            item.put("locCode", locItem.getLocCode());
+            item.put("matnrCode", locItem.getMatnrCode());
+            item.put("maktx", locItem.getMaktx());
+            item.put("trackCode", locItem.getTrackCode());
+            item.put("batch", locItem.getBatch());
+            item.put("spec", locItem.getSpec());
+            item.put("model", locItem.getModel());
+            item.put("unit", locItem.getUnit());
+            item.put("anfme", locItem.getAnfme());
+            item.put("status", locItem.getStatus());
+            item.put("extendFields", locItem.getExtendFields());
+            result.add(item);
+        }
+        return result;
+    }
+
+    @Tool(name = "rsf_query_station_list", description = "鏍规嵁浣滀笟绫诲瀷鍒楄〃鏌ヨ鍙敤绔欑偣锛岃繑鍥炵珯鐐圭紪鍙枫�佸悕绉般�佺洰鏍囦綅缃拰鐘舵�佺瓑淇℃伅銆�")
+    public List<Map<String, Object>> queryStationList(
+            @ToolParam(required = true, description = "浣滀笟绫诲瀷鍒楄〃") List<String> types) {
+        if (types == null || types.isEmpty()) {
+            throw new CoolException("绔欑偣绫诲瀷鍒楄〃涓嶈兘涓虹┖");
+        }
+        List<DeviceSite> sites = deviceSiteService.list(new LambdaQueryWrapper<DeviceSite>()
+                .in(DeviceSite::getType, types));
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (DeviceSite site : sites) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("id", site.getId());
+            item.put("type", site.getType());
+            item.put("site", site.getSite());
+            item.put("name", site.getName());
+            item.put("target", site.getTarget());
+            item.put("label", site.getLabel());
+            item.put("device", site.getDevice());
+            item.put("deviceCode", site.getDeviceCode());
+            item.put("deviceSite", site.getDeviceSite());
+            item.put("channel", site.getChannel());
+            item.put("status", site.getStatus());
+            result.add(item);
+        }
+        return result;
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java
new file mode 100644
index 0000000..ba6d622
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/ai/tool/RsfWmsTaskTools.java
@@ -0,0 +1,123 @@
+package com.vincent.rsf.server.ai.tool;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.manager.entity.Task;
+import com.vincent.rsf.server.manager.service.TaskService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.annotation.ToolParam;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+@RequiredArgsConstructor
+public class RsfWmsTaskTools {
+
+    private final TaskService taskService;
+
+    @Tool(name = "rsf_query_task_list", description = "鎸変换鍔″彿銆佺姸鎬併�佷换鍔$被鍨嬨�佹簮绔欑偣銆佺洰鏍囩珯鐐圭瓑鏉′欢鏌ヨ浠诲姟鍒楄〃銆�")
+    public List<Map<String, Object>> queryTaskList(
+            @ToolParam(description = "浠诲姟鍙凤紝鍙ā绯婃煡璇�") String taskCode,
+            @ToolParam(description = "浠诲姟鐘舵�侊紝鍙��") Integer taskStatus,
+            @ToolParam(description = "浠诲姟绫诲瀷锛屽彲閫�") Integer taskType,
+            @ToolParam(description = "婧愮珯鐐癸紝鍙��") String orgSite,
+            @ToolParam(description = "鐩爣绔欑偣锛屽彲閫�") String targSite,
+            @ToolParam(description = "杩斿洖鏉℃暟锛岄粯璁� 10锛屾渶澶� 50") Integer limit) {
+        LambdaQueryWrapper<Task> queryWrapper = new LambdaQueryWrapper<>();
+        int finalLimit = normalizeLimit(limit, 10, 50);
+        if (StringUtils.hasText(taskCode)) {
+            queryWrapper.like(Task::getTaskCode, taskCode);
+        }
+        if (taskStatus != null) {
+            queryWrapper.eq(Task::getTaskStatus, taskStatus);
+        }
+        if (taskType != null) {
+            queryWrapper.eq(Task::getTaskType, taskType);
+        }
+        if (StringUtils.hasText(orgSite)) {
+            queryWrapper.eq(Task::getOrgSite, orgSite);
+        }
+        if (StringUtils.hasText(targSite)) {
+            queryWrapper.eq(Task::getTargSite, targSite);
+        }
+        queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT " + finalLimit);
+        List<Task> tasks = taskService.list(queryWrapper);
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (Task task : tasks) {
+            result.add(buildTaskSummary(task));
+        }
+        return result;
+    }
+
+    @Tool(name = "rsf_query_task_detail", description = "鏍规嵁浠诲姟 ID 鎴栦换鍔″彿鏌ヨ浠诲姟璇︽儏銆�")
+    public Map<String, Object> queryTaskDetail(
+            @ToolParam(description = "浠诲姟 ID") Long taskId,
+            @ToolParam(description = "浠诲姟鍙�") String taskCode) {
+        if (taskId == null && !StringUtils.hasText(taskCode)) {
+            throw new CoolException("浠诲姟 ID 鍜屼换鍔″彿鑷冲皯闇�瑕佹彁渚涗竴涓�");
+        }
+        Task task;
+        if (taskId != null) {
+            task = taskService.getById(taskId);
+        } else {
+            task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, taskCode));
+        }
+        if (task == null) {
+            throw new CoolException("鏈煡璇㈠埌浠诲姟");
+        }
+        Map<String, Object> result = buildTaskSummary(task);
+        result.put("resource", task.getResource());
+        result.put("exceStatus", task.getExceStatus());
+        result.put("orgLoc", task.getOrgLoc());
+        result.put("targLoc", task.getTargLoc());
+        result.put("orgSite", task.getOrgSite());
+        result.put("orgSiteLabel", task.getOrgSite$());
+        result.put("targSite", task.getTargSite());
+        result.put("targSiteLabel", task.getTargSite$());
+        result.put("barcode", task.getBarcode());
+        result.put("robotCode", task.getRobotCode());
+        result.put("memo", task.getMemo());
+        result.put("expCode", task.getExpCode());
+        result.put("expDesc", task.getExpDesc());
+        result.put("startTime", task.getStartTime$());
+        result.put("endTime", task.getEndTime$());
+        result.put("createTime", task.getCreateTime$());
+        result.put("updateTime", task.getUpdateTime$());
+        return result;
+    }
+
+    private Map<String, Object> buildTaskSummary(Task task) {
+        Map<String, Object> item = new LinkedHashMap<>();
+        item.put("id", task.getId());
+        item.put("taskCode", task.getTaskCode());
+        item.put("taskStatus", task.getTaskStatus());
+        item.put("taskStatusLabel", task.getTaskStatus$());
+        item.put("taskType", task.getTaskType());
+        item.put("taskTypeLabel", task.getTaskType$());
+        item.put("orgSite", task.getOrgSite());
+        item.put("orgSiteLabel", task.getOrgSite$());
+        item.put("targSite", task.getTargSite());
+        item.put("targSiteLabel", task.getTargSite$());
+        item.put("status", task.getStatus());
+        item.put("statusLabel", task.getStatus$());
+        item.put("createTime", task.getCreateTime$());
+        item.put("updateTime", task.getUpdateTime$());
+        return item;
+    }
+
+    private int normalizeLimit(Integer limit, int defaultValue, int maxValue) {
+        if (limit == null) {
+            return defaultValue;
+        }
+        if (limit < 1 || limit > maxValue) {
+            throw new CoolException("limit 蹇呴』鍦� 1 鍒� " + maxValue + " 涔嬮棿");
+        }
+        return limit;
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
index ee84fdf..bb74163 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
@@ -289,7 +289,7 @@
         if (Cools.isEmpty(param.getTransferStationNo())) {
             return R.error("鏃犲弬鏁�");
         }
-        BasStation basStation = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationId, param.getTransferStationNo()));
+        BasStation basStation = basStationService.getOne(new LambdaQueryWrapper<BasStation>().eq(BasStation::getStationName, param.getTransferStationNo()));
         if (Cools.isEmpty(basStation)) {
             return R.error("鏈壘鍒板尮閰嶇珯鐐�");
         }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java
index d3bfe01..4efd031 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/security/SecurityConfig.java
@@ -17,6 +17,7 @@
 import org.springframework.stereotype.Component;
 
 import jakarta.servlet.ServletException;
+import jakarta.servlet.DispatcherType;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.annotation.Resource;
@@ -69,6 +70,7 @@
     public SecurityFilterChain securityFilterChain(org.springframework.security.config.annotation.web.builders.HttpSecurity http)
             throws Exception {
         http.authorizeHttpRequests(authorize -> authorize
+                        .dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.ERROR).permitAll()
                         .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                         .requestMatchers(HttpMethod.GET, "/file/**", "/captcha", "/").permitAll()
                         .requestMatchers(FILTER_PATH).permitAll()
diff --git a/version/db/ai_feature.sql b/version/db/ai_feature.sql
new file mode 100644
index 0000000..d5cd06f
--- /dev/null
+++ b/version/db/ai_feature.sql
@@ -0,0 +1,225 @@
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+CREATE TABLE IF NOT EXISTS `sys_ai_param` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `name` varchar(255) NOT NULL COMMENT '鍚嶇О',
+  `provider_type` varchar(64) NOT NULL COMMENT '鎻愪緵鏂圭被鍨�',
+  `base_url` varchar(500) NOT NULL COMMENT '鍩虹鍦板潃',
+  `api_key` varchar(1024) NOT NULL COMMENT 'API Key',
+  `model` varchar(255) NOT NULL COMMENT '妯″瀷',
+  `temperature` decimal(10,4) DEFAULT NULL COMMENT 'temperature',
+  `top_p` decimal(10,4) DEFAULT NULL COMMENT 'topP',
+  `max_tokens` int(11) DEFAULT NULL COMMENT '鏈�澶oken',
+  `timeout_ms` int(11) DEFAULT NULL COMMENT '瓒呮椂鏃堕棿',
+  `streaming_enabled` tinyint(1) DEFAULT '1' COMMENT '鏄惁鍚敤娴佸紡鍝嶅簲',
+  `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛',
+  `status` int(11) DEFAULT '1' COMMENT '鐘舵��',
+  `deleted` int(11) DEFAULT '0' COMMENT '鍒犻櫎鏍囪',
+  `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+  `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+  `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+  `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+  `memo` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+  PRIMARY KEY (`id`),
+  KEY `idx_sys_ai_param_tenant_status` (`tenant_id`,`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 鍙傛暟閰嶇疆';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_prompt` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `name` varchar(255) NOT NULL COMMENT '鍚嶇О',
+  `code` varchar(128) NOT NULL COMMENT '缂栫爜',
+  `scene` varchar(128) DEFAULT NULL COMMENT '鍦烘櫙',
+  `system_prompt` text COMMENT '绯荤粺 Prompt',
+  `user_prompt_template` text COMMENT '鐢ㄦ埛 Prompt 妯℃澘',
+  `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛',
+  `status` int(11) DEFAULT '1' COMMENT '鐘舵��',
+  `deleted` int(11) DEFAULT '0' COMMENT '鍒犻櫎鏍囪',
+  `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+  `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+  `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+  `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+  `memo` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_sys_ai_prompt_code_tenant` (`tenant_id`,`code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI Prompt 閰嶇疆';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_mcp_mount` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `name` varchar(255) NOT NULL COMMENT '鍚嶇О',
+  `transport_type` varchar(64) NOT NULL COMMENT '浼犺緭绫诲瀷',
+  `builtin_code` varchar(128) DEFAULT NULL COMMENT '鍐呯疆 MCP 缂栫爜',
+  `server_url` varchar(500) DEFAULT NULL COMMENT '鏈嶅姟鍦板潃',
+  `endpoint` varchar(255) DEFAULT NULL COMMENT 'SSE Endpoint',
+  `command` varchar(500) DEFAULT NULL COMMENT '鏈湴鍛戒护',
+  `args_json` text COMMENT '鍛戒护鍙傛暟 JSON',
+  `env_json` text COMMENT '鐜鍙橀噺 JSON',
+  `headers_json` text COMMENT '璇锋眰澶� JSON',
+  `request_timeout_ms` int(11) DEFAULT NULL COMMENT '璇锋眰瓒呮椂鏃堕棿',
+  `sort` int(11) DEFAULT '0' COMMENT '鎺掑簭',
+  `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛',
+  `status` int(11) DEFAULT '1' COMMENT '鐘舵��',
+  `deleted` int(11) DEFAULT '0' COMMENT '鍒犻櫎鏍囪',
+  `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+  `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+  `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+  `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+  `memo` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+  PRIMARY KEY (`id`),
+  KEY `idx_sys_ai_mcp_mount_tenant_status` (`tenant_id`,`status`,`sort`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI MCP 鎸傝浇閰嶇疆';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_chat_session` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `title` varchar(255) NOT NULL COMMENT '浼氳瘽鏍囬',
+  `prompt_code` varchar(128) NOT NULL COMMENT 'Prompt 缂栫爜',
+  `user_id` bigint(20) NOT NULL COMMENT '鐢ㄦ埛 ID',
+  `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛',
+  `last_message_time` datetime DEFAULT NULL COMMENT '鏈�鍚庢秷鎭椂闂�',
+  `status` int(11) DEFAULT '1' COMMENT '鐘舵��',
+  `deleted` int(11) DEFAULT '0' COMMENT '鍒犻櫎鏍囪',
+  `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+  `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+  `update_time` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+  `update_by` bigint(20) DEFAULT NULL COMMENT '鏇存柊浜�',
+  PRIMARY KEY (`id`),
+  KEY `idx_sys_ai_chat_session_user_prompt` (`tenant_id`,`user_id`,`prompt_code`,`last_message_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 瀵硅瘽浼氳瘽';
+
+CREATE TABLE IF NOT EXISTS `sys_ai_chat_message` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
+  `session_id` bigint(20) NOT NULL COMMENT '浼氳瘽 ID',
+  `seq_no` int(11) NOT NULL COMMENT '娑堟伅搴忓彿',
+  `role` varchar(32) NOT NULL COMMENT '娑堟伅瑙掕壊',
+  `content` longtext COMMENT '娑堟伅鍐呭',
+  `user_id` bigint(20) NOT NULL COMMENT '鐢ㄦ埛 ID',
+  `tenant_id` bigint(20) DEFAULT NULL COMMENT '绉熸埛',
+  `deleted` int(11) DEFAULT '0' COMMENT '鍒犻櫎鏍囪',
+  `create_time` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+  `create_by` bigint(20) DEFAULT NULL COMMENT '鍒涘缓浜�',
+  PRIMARY KEY (`id`),
+  KEY `idx_sys_ai_chat_message_session_seq` (`session_id`,`seq_no`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='AI 瀵硅瘽娑堟伅';
+
+SET @builtin_code_exists := (
+  SELECT COUNT(1)
+  FROM `information_schema`.`COLUMNS`
+  WHERE `TABLE_SCHEMA` = DATABASE()
+    AND `TABLE_NAME` = 'sys_ai_mcp_mount'
+    AND `COLUMN_NAME` = 'builtin_code'
+);
+SET @builtin_code_sql := IF(
+  @builtin_code_exists = 0,
+  'ALTER TABLE `sys_ai_mcp_mount` ADD COLUMN `builtin_code` varchar(128) DEFAULT NULL COMMENT ''鍐呯疆 MCP 缂栫爜'' AFTER `transport_type`',
+  'SELECT 1'
+);
+PREPARE builtin_code_stmt FROM @builtin_code_sql;
+EXECUTE builtin_code_stmt;
+DEALLOCATE PREPARE builtin_code_stmt;
+
+BEGIN;
+INSERT INTO `sys_ai_prompt`
+(`id`, `name`, `code`, `scene`, `system_prompt`, `user_prompt_template`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+VALUES
+(1, '棣栭〉榛樿鍔╂墜', 'home.default', 'home', '浣犳槸 RSF 绯荤粺鐨� AI 鍔╂墜锛岃缁撳悎褰撳墠涓婁笅鏂囦负鐢ㄦ埛鎻愪緵鍑嗙‘銆佺畝娲併�佸彲鎵ц鐨勫府鍔┿��', '璇峰熀浜庡綋鍓嶉〉闈笂涓嬫枃鍥炵瓟鐢ㄦ埛闂锛歿{input}}', 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, '棣栭〉榛樿 Prompt')
+ON DUPLICATE KEY UPDATE
+`name` = VALUES(`name`),
+`scene` = VALUES(`scene`),
+`system_prompt` = VALUES(`system_prompt`),
+`user_prompt_template` = VALUES(`user_prompt_template`),
+`status` = VALUES(`status`),
+`deleted` = VALUES(`deleted`),
+`update_time` = VALUES(`update_time`),
+`update_by` = VALUES(`update_by`),
+`memo` = VALUES(`memo`);
+
+INSERT INTO `sys_ai_mcp_mount`
+(`name`, `transport_type`, `builtin_code`, `request_timeout_ms`, `sort`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'RSF WMS 鍐呯疆 MCP', 'BUILTIN', 'RSF_WMS', 60000, 0, 1, 1, 0, '2026-03-19 10:00:00', 2, '2026-03-19 10:00:00', 2, '鍐呯疆 WMS 鏌ヨ涓庝换鍔″伐鍏�'
+WHERE NOT EXISTS (
+  SELECT 1 FROM `sys_ai_mcp_mount`
+  WHERE `tenant_id` = 1 AND `transport_type` = 'BUILTIN' AND `builtin_code` = 'RSF_WMS' AND `deleted` = 0
+);
+
+INSERT INTO `sys_ai_mcp_mount`
+(`name`, `transport_type`, `builtin_code`, `request_timeout_ms`, `sort`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'RSF WMS 搴撳瓨浣滀笟鍐呯疆 MCP', 'BUILTIN', 'RSF_WMS_STOCK', 60000, 1, 1, 0, 0, '2026-03-19 10:00:00', 2, '2026-03-19 10:00:00', 2, '鍐呯疆搴撳瓨鏌ヨ鍜岀珯鐐规煡璇㈠伐鍏�'
+WHERE NOT EXISTS (
+  SELECT 1 FROM `sys_ai_mcp_mount`
+  WHERE `tenant_id` = 1 AND `transport_type` = 'BUILTIN' AND `builtin_code` = 'RSF_WMS_STOCK' AND `deleted` = 0
+);
+
+INSERT INTO `sys_ai_mcp_mount`
+(`name`, `transport_type`, `builtin_code`, `request_timeout_ms`, `sort`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'RSF WMS 浠诲姟鏌ヨ鍐呯疆 MCP', 'BUILTIN', 'RSF_WMS_TASK', 60000, 2, 1, 0, 0, '2026-03-19 10:00:00', 2, '2026-03-19 10:00:00', 2, '鍐呯疆浠诲姟鍒楄〃涓庝换鍔¤鎯呮煡璇㈠伐鍏�'
+WHERE NOT EXISTS (
+  SELECT 1 FROM `sys_ai_mcp_mount`
+  WHERE `tenant_id` = 1 AND `transport_type` = 'BUILTIN' AND `builtin_code` = 'RSF_WMS_TASK' AND `deleted` = 0
+);
+
+INSERT INTO `sys_ai_mcp_mount`
+(`name`, `transport_type`, `builtin_code`, `request_timeout_ms`, `sort`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+SELECT 'RSF WMS 鍩虹璧勬枡鍐呯疆 MCP', 'BUILTIN', 'RSF_WMS_BASE', 60000, 3, 1, 0, 0, '2026-03-19 10:00:00', 2, '2026-03-19 10:00:00', 2, '鍐呯疆浠撳簱銆佸熀纭�绔欑偣鍜屽瓧鍏告暟鎹煡璇㈠伐鍏�'
+WHERE NOT EXISTS (
+  SELECT 1 FROM `sys_ai_mcp_mount`
+  WHERE `tenant_id` = 1 AND `transport_type` = 'BUILTIN' AND `builtin_code` = 'RSF_WMS_BASE' AND `deleted` = 0
+);
+
+INSERT INTO `sys_menu`
+(`id`, `name`, `parent_id`, `parent_name`, `path`, `path_name`, `route`, `component`, `brief`, `code`, `type`, `authority`, `icon`, `sort`, `meta`, `tenant_id`, `status`, `deleted`, `create_time`, `create_by`, `update_time`, `update_by`, `memo`)
+VALUES
+(5301, 'menu.aiParam', 1, 'menu.system', '1', 'menu.system', '/system/aiParam', 'aiParam', NULL, NULL, 0, NULL, 'SmartToy', 11, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5302, 'Query AI Param', 5301, NULL, '1,5301', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:list', NULL, 0, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5303, 'Create AI Param', 5301, NULL, '1,5301', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:save', NULL, 1, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5304, 'Update AI Param', 5301, NULL, '1,5301', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:update', NULL, 2, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5305, 'Delete AI Param', 5301, NULL, '1,5301', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiParam:remove', NULL, 3, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5306, 'menu.aiPrompt', 1, 'menu.system', '1', 'menu.system', '/system/aiPrompt', 'aiPrompt', NULL, NULL, 0, NULL, 'PsychologyAlt', 12, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5307, 'Query AI Prompt', 5306, NULL, '1,5306', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:list', NULL, 0, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5308, 'Create AI Prompt', 5306, NULL, '1,5306', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:save', NULL, 1, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5309, 'Update AI Prompt', 5306, NULL, '1,5306', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:update', NULL, 2, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5310, 'Delete AI Prompt', 5306, NULL, '1,5306', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiPrompt:remove', NULL, 3, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5311, 'menu.aiMcpMount', 1, 'menu.system', '1', 'menu.system', '/system/aiMcpMount', 'aiMcpMount', NULL, NULL, 0, NULL, 'Cable', 13, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5312, 'Query AI MCP Mount', 5311, NULL, '1,5311', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiMcpMount:list', NULL, 0, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5313, 'Create AI MCP Mount', 5311, NULL, '1,5311', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiMcpMount:save', NULL, 1, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5314, 'Update AI MCP Mount', 5311, NULL, '1,5311', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiMcpMount:update', NULL, 2, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL),
+(5315, 'Delete AI MCP Mount', 5311, NULL, '1,5311', NULL, NULL, NULL, NULL, NULL, 1, 'system:aiMcpMount:remove', NULL, 3, NULL, 1, 1, 0, '2026-03-18 19:00:00', 2, '2026-03-18 19:00:00', 2, NULL)
+ON DUPLICATE KEY UPDATE
+`name` = VALUES(`name`),
+`parent_id` = VALUES(`parent_id`),
+`parent_name` = VALUES(`parent_name`),
+`path` = VALUES(`path`),
+`path_name` = VALUES(`path_name`),
+`route` = VALUES(`route`),
+`component` = VALUES(`component`),
+`authority` = VALUES(`authority`),
+`icon` = VALUES(`icon`),
+`sort` = VALUES(`sort`),
+`tenant_id` = VALUES(`tenant_id`),
+`status` = VALUES(`status`),
+`deleted` = VALUES(`deleted`),
+`update_time` = VALUES(`update_time`),
+`update_by` = VALUES(`update_by`);
+
+INSERT INTO `sys_role_menu` (`id`, `role_id`, `menu_id`)
+VALUES
+(5301, 1, 5301),
+(5302, 1, 5302),
+(5303, 1, 5303),
+(5304, 1, 5304),
+(5305, 1, 5305),
+(5306, 1, 5306),
+(5307, 1, 5307),
+(5308, 1, 5308),
+(5309, 1, 5309),
+(5310, 1, 5310),
+(5311, 1, 5311),
+(5312, 1, 5312),
+(5313, 1, 5313),
+(5314, 1, 5314),
+(5315, 1, 5315)
+ON DUPLICATE KEY UPDATE
+`role_id` = VALUES(`role_id`),
+`menu_id` = VALUES(`menu_id`);
+COMMIT;
+
+SET FOREIGN_KEY_CHECKS = 1;

--
Gitblit v1.9.1