1、MCP概述

Agent(智能体):所谓的智能体就是让大模型拥有额外的能力,以大模型作为核心大脑,具备工具调用、长期记忆、任务规划三大核心能力。用户向智能体下发指令后,智能体依托大模型进行逻辑决策,自动生成任务执行计划(Planning),同时读取记忆(Memory)模块中的历史上下文、对话记录与过往任务数据,结合实时需求与历史信息综合处理,完成复杂任务的拆解、调度与闭环执行。

Tool(工具):

  • 工具是为智能体提供标准化外部能力调用的独立单元,是大模型与外部系统交互的桥梁。
  • 以单一职责为设计原则,每个工具专注完成一项具体操作(如搜索、计算、API 调用、数据查询等)。
  • 不具备自主决策能力,仅在被智能体触发时执行预设逻辑。
  • 需明确定义输入参数与输出格式,例如:
    • 搜索工具:接收查询字符串并返回搜索结果
    • 计算工具:接收数学表达式并返回计算结果
  • 为智能体的任务计划提供稳定、可预期的执行支撑,弥补大模型无法直接操作外部服务、获取实时数据的短板。
  • 智能体通过 Function Call 机制实现对工具的调用与执行。

MCP(Model Context Protocol,模型上下文协议):

  • 由 Anthropic 于 2024 年 11 月底 推出的一种开放标准协议。
  • 核心目标:统一大型语言模型(LLM)与外部数据源、工具之间的通信方式。
  • 支持两种主要通信机制:
    • 本地通信(Stdio 模式):通过Stdio传输数据,适用于同一台机器上运行的客户端与服务器之间的通信,无需依赖网络端口,部署简单、安全性高。
    • 远程通信(SSE + HTTP 模式):利用 SSE(Server-Sent Events)与 HTTP 结合,实现跨网络的实时数据传输,适用于需要访问远程资源或分布式部署的场景,支持鉴权、跨平台调用与流式交互。

Function Calling、MCP 与 AI Agent 的关系:

  • Function Calling:是 AI 模型调用函数的基础机制,是实现工具交互的 “底层能力”。
  • MCP:是一套标准化的通信协议,让 AI 模型与 API / 工具的交互更通用、更无缝,解决了不同模型、不同工具之间的兼容性问题。
  • AI Agent:是自主运行的智能系统,它会利用 Function Calling 和 MCP 协议,完成任务分析、规划、工具调用与执行,最终实现特定目标。

2、MCP的通信机制

在这里插入图片描述

3、使用SpringAI接入MCP

1、项目依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>

    <groupId>cn.aopmin</groupId>
    <artifactId>mcp-server</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>spring ai + mcp 示例项目</name>

    <properties>
        <java.version>17</java.version>
        <!-- 1. 升级版本:使用最新的M7版本 -->
        <spring-ai.version>1.0.0-M7</spring-ai.version>
    </properties>

    <!-- 2. 关键:补全仓库配置 -->
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>

    <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>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-ollama</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-client</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.28</version>
        </dependency>
    </dependencies>

</project>

2、配置

spring:
  application:
    name: mcp-server

  # Ollama 配置
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        options:
          model: qwen2.5:7b    # 替换为你的模型
          temperature: 0.7

    # MCP Server 配置
    mcp:
      server:
        enabled: true
        name: mcp-server
        version: 1.0.0
        protocol: STREAMABLE # 支持 STREAMABLE 和 HTTP 两种协议
        capabilities:
          tools: true
          resources: false
          prompts: false
        streamable-http:
          mcp-endpoint: /mcp

      # MCP Client 配置
      client:
        enabled: true
        server-url: http://localhost:8086
        request-timeout: 30s

server:
  port: 8086

logging:
  level:
    cn.aopmin: DEBUG           # 项目包级别可调为 DEBUG
    org.springframework.ai: INFO     # Spring AI 核心日志

3、编写mcp工具

package cn.aopmin.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

/**
 * MCP 工具服务
 * 通过 @Tool 注解暴露方法,供 AI 模型调用
 * Spring AI 会自动生成 JSON Schema 描述参数格式[reference:8]
 */
@Slf4j
@Service
public class McpToolService {
    
    // 模拟数据存储
    private final Map<String, Map<String, Object>> orders = new HashMap<>();
    
    public McpToolService() {
        // 初始化模拟订单
        Map<String, Object> order1 = new HashMap<>();
        order1.put("orderId", "ORD-001");
        order1.put("status", "已发货");
        order1.put("estimatedDelivery", "2026-05-01");
        order1.put("items", 3);
        orders.put("ORD-001", order1);
        
        Map<String, Object> order2 = new HashMap<>();
        order2.put("orderId", "ORD-002");
        order2.put("status", "处理中");
        order2.put("estimatedDelivery", "2026-05-03");
        order2.put("items", 1);
        orders.put("ORD-002", order2);
    }
    
    /**
     * 获取订单状态
     * @Tool 注解使该方法成为 MCP 工具
     */
    @Tool(description = "根据订单ID查询订单的当前状态和详细信息")
    public Map<String, Object> getOrderStatus(@ToolParam(description = "订单唯一标识符,如 ORD-001") String orderId) {
        log.info("查询订单状态工具被调用=>,orderId: {}", orderId);

        if (orderId == null || orderId.trim().isEmpty()) {
            throw new IllegalArgumentException("订单ID不能为空");
        }
        Map<String, Object> order = orders.get(orderId);
        if (order == null) {
            return Map.of("error", "未找到订单: " + orderId, "orderId", orderId);
        }
        
        Map<String, Object> result = new HashMap<>();
        result.put("orderId", orderId);
        result.put("status", order.get("status"));
        result.put("estimatedDelivery", order.get("estimatedDelivery"));
        result.put("items", order.get("items"));
        result.put("queryTime", LocalDateTime.now().toString());
        return result;
    }
    
    /**
     * 获取服务器时间
     */
    @Tool(description = "获取当前服务器时间和日期")
    public Map<String, String> getServerTime() {
        log.info("获取服务器时间工具被调用=>");
        return Map.of(
            "time", LocalDateTime.now().toString(),
            "timezone", "Asia/Shanghai"
        );
    }
    
    /**
     * 计算器工具
     */
    @Tool(description = "执行基本的数学运算,支持加法、减法、乘法、除法")
    public Map<String, Object> calculate(
            @ToolParam(description = "第一个数字") double a,
            @ToolParam(description = "运算符,可选值: add, subtract, multiply, divide") String operation,
            @ToolParam(description = "第二个数字") double b) {

        log.info("计算器工具被调用=>,a: {}, operation: {}, b: {}", a, operation, b);
        
        double result;
        switch (operation.toLowerCase()) {
            case "add":
                result = a + b;
                break;
            case "subtract":
                result = a - b;
                break;
            case "multiply":
                result = a * b;
                break;
            case "divide":
                if (b == 0) {
                    return Map.of("error", "除数不能为零");
                }
                result = a / b;
                break;
            default:
                return Map.of("error", "不支持的运算符: " + operation);
        }
        
        return Map.of(
            "expression", a + " " + operation + " " + b,
            "result", result
        );
    }
}

4、注册mcp工具,并绑定chatclient上

package cn.aopmin.config;

import cn.aopmin.service.McpToolService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.List;

/**
 * Spring AI 核心配置类
 *
 * 职责:
 * 1. 创建 ChatClient ,用于与 Ollama 模型交互
 * 2. 自动发现并注册所有通过 @Tool 注解声明的 MCP 工具
 */
@Slf4j
@Configuration
public class McpConfig {

    /**
     * 手动注册工具(方式一)
     * 将带有 @Tool 注解的服务类注册为 ToolCallbackProvider
     */
    @Bean
    public ToolCallbackProvider mcpToolProvider(McpToolService mcpToolService) {
        return MethodToolCallbackProvider.builder()
                .toolObjects(mcpToolService)
                .build();
    }


    /**
     * 创建 ChatClient 实例,并绑定所有 MCP 工具
     *
     * @param ollamaChatModel        Spring AI 自动配置的 Ollama 聊天模型
     * @param toolCallbackProviders  容器中所有的 ToolCallbackProvider Bean
     * @return 配置好的 ChatClient
     */
    @Bean
    public ChatClient chatClient(OllamaChatModel ollamaChatModel,
                                 List<ToolCallbackProvider> toolCallbackProviders) {

        // 从所有 ToolCallbackProvider 中提取工具回调函数(FunctionCallback[])
        List<FunctionCallback> functionCallbacks = toolCallbackProviders.stream()
                .flatMap(provider -> Arrays.stream(provider.getToolCallbacks())).toList();

        if (!functionCallbacks.isEmpty()) {
            log.info("✅ 已成功加载 {} 个 MCP 工具:", functionCallbacks.size());
            functionCallbacks.forEach(fc ->
                    log.info("   🔧 {} - {}", fc.getName(), fc.getDescription())
            );
        } else {
            log.warn("⚠️ 未发现任何 @Tool 注解的方法,AI 将无法调用外部工具。");
        }

        // 构建 ChatClient,将工具注册进去(转换为数组)
        return ChatClient.builder(ollamaChatModel)
                .defaultFunctions(functionCallbacks.toArray(new FunctionCallback[0]))
                .build();
    }
}

5、编写service

package cn.aopmin.service;

import lombok.RequiredArgsConstructor;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ChatService {

    private final ChatClient chatClient;
    
    public String chat(String userMessage) {
        return chatClient.prompt()
            .user(userMessage)
            .call()
            .content();
    }
    
    public String chatWithSystemPrompt(String userMessage, String systemPrompt) {
        return chatClient.prompt()
            .system(systemPrompt)
            .user(userMessage)
            .call()
            .content();
    }
}

6、编写controller

package cn.aopmin.controller;

import cn.aopmin.service.ChatService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/api/chat")
@CrossOrigin(origins = "*")
@RequiredArgsConstructor
public class ChatController {
    
    private final ChatService chatService;

    @PostMapping
    public ChatResponse chat(@RequestBody ChatRequest request) {
        log.info("chat message=> {}", request.message());
        String reply = chatService.chat(request.message());
        return new ChatResponse(reply);
    }
    
    public record ChatRequest(String message) {}
    public record ChatResponse(String reply) {}
}

7、启动类

package cn.aopmin;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

@Slf4j
@SpringBootApplication
public class McpServerApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx =SpringApplication.run(McpServerApplication.class, args);
        log.info("=== MCP Server 启动成功 ===");
        Environment environment=ctx.getBean(Environment.class);
        String port=environment.getProperty("server.port");
        log.info("服务地址: http://localhost:{}", port);
        log.info("测试命令(订单查询):\ncurl -X POST http://localhost:{}/api/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"message\":\"查询订单ORD-001的状态\"}'", port);
        log.info("测试命令  (查询时间)  :\ncurl -X POST http://localhost:{}/api/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"message\":\"现在几点?\"}'", port);
        log.info("测试命令  (计算器)  :\ncurl -X POST http://localhost:{}/api/chat \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"message\":\"计算 123 + 456\"}'", port);

    }
}

8、创建前端页面 resources/static/index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Spring AI + Ollama + MCP 智能聊天助手</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .chat-container {
            width: 100%;
            max-width: 900px;
            height: 90vh;
            background: white;
            border-radius: 24px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }

        .chat-header {
            background: linear-gradient(135deg, #4CAF50, #2196F3);
            padding: 20px 24px;
            color: white;
        }

        .chat-header h1 {
            font-size: 1.5rem;
            margin-bottom: 8px;
        }

        .status-bar {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 0.85rem;
            opacity: 0.9;
        }

        .status-indicator {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background-color: #4CAF50;
            animation: pulse 2s infinite;
        }

        .status-indicator.thinking {
            background-color: #FF9800;
            animation: pulse 0.5s infinite;
        }

        .status-indicator.error {
            background-color: #f44336;
            animation: none;
        }

        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.4; }
        }

        .chat-messages {
            flex: 1;
            overflow-y: auto;
            padding: 20px;
            background: #f5f5f5;
        }

        .message {
            margin-bottom: 16px;
            display: flex;
            align-items: flex-start;
        }

        .message.user {
            justify-content: flex-end;
        }

        .message.user .message-content {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            border-radius: 20px 20px 4px 20px;
        }

        .message.assistant .message-content {
            background: white;
            color: #333;
            border-radius: 20px 20px 20px 4px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }

        .message.system .message-content {
            background: #e3f2fd;
            color: #1976d2;
            border-radius: 12px;
            font-size: 0.9rem;
        }

        .message-content {
            max-width: 80%;
            padding: 12px 16px;
            line-height: 1.5;
            word-wrap: break-word;
        }

        .chat-input-container {
            padding: 20px;
            background: white;
            border-top: 1px solid #e0e0e0;
        }

        #message-input {
            width: 100%;
            padding: 12px;
            border: 2px solid #e0e0e0;
            border-radius: 12px;
            font-size: 1rem;
            resize: vertical;
            font-family: inherit;
            transition: border-color 0.3s;
        }

        #message-input:focus {
            outline: none;
            border-color: #667eea;
        }

        .button-group {
            display: flex;
            gap: 12px;
            margin-top: 12px;
        }

        .btn-primary, .btn-secondary {
            padding: 10px 24px;
            border: none;
            border-radius: 30px;
            font-size: 0.95rem;
            cursor: pointer;
            transition: all 0.3s;
            font-weight: 500;
        }

        .btn-primary {
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            flex: 1;
        }

        .btn-primary:hover:not(:disabled) {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(102,126,234,0.4);
        }

        .btn-primary:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }

        .btn-secondary {
            background: #f0f0f0;
            color: #666;
        }

        .btn-secondary:hover {
            background: #e0e0e0;
        }

        .typing-indicator {
            display: inline-flex;
            gap: 4px;
            padding: 12px 16px;
        }

        .typing-indicator span {
            width: 8px;
            height: 8px;
            background: #999;
            border-radius: 50%;
            animation: typing 1.4s infinite ease-in-out both;
        }

        .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
        .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }

        @keyframes typing {
            0%, 80%, 100% { transform: scale(0); }
            40% { transform: scale(1); }
        }
    </style>
</head>
<body>
<div class="chat-container">
    <!-- 头部 -->
    <div class="chat-header">
        <h1>🤖 AI 智能助手</h1>
        <div class="status-bar">
            <span class="status-indicator" id="status-indicator"></span>
            <span id="status-text">已就绪</span>
        </div>
    </div>

    <!-- 聊天消息区域 -->
    <div class="chat-messages" id="chat-messages">
        <div class="message system">
            <div class="message-content">
                你好!我是 AI 智能助手,已接入以下工具能力:<br>
                📦 订单查询 - 根据订单号查询状态<br>
                ⏰ 获取服务器时间<br>
                🧮 计算器 - 支持数学运算<br>
                请输入你的问题开始对话!
            </div>
        </div>
    </div>

    <!-- 输入区域 -->
    <div class="chat-input-container">
        <textarea id="message-input"
                  placeholder="输入你的问题... (Shift+Enter 换行, Enter 发送)"
                  rows="3"></textarea>
        <div class="button-group">
            <button id="send-btn" class="btn-primary">发送 ✨</button>
            <button id="clear-btn" class="btn-secondary">清空对话</button>
        </div>
    </div>
</div>

<script>
    // 聊天交互逻辑 - 与后端 /api/chat 接口通信
    const API_URL = '/api/chat';

    // DOM 元素
    const messagesContainer = document.getElementById('chat-messages');
    const messageInput = document.getElementById('message-input');
    const sendButton = document.getElementById('send-btn');
    const clearButton = document.getElementById('clear-btn');
    const statusIndicator = document.getElementById('status-indicator');
    const statusText = document.getElementById('status-text');

    let isWaitingForResponse = false;

    function updateStatus(status, text) {
        statusIndicator.className = 'status-indicator';
        if (status === 'thinking') {
            statusIndicator.classList.add('thinking');
            statusText.textContent = text || 'AI 正在思考...';
        } else if (status === 'error') {
            statusIndicator.classList.add('error');
            statusText.textContent = text || '连接错误';
        } else {
            statusText.textContent = text || '已就绪';
        }
    }

    function addMessage(content, type) {
        const messageDiv = document.createElement('div');
        messageDiv.className = `message ${type}`;

        const contentDiv = document.createElement('div');
        contentDiv.className = 'message-content';

        // 支持简单 Markdown 样式
        let formattedContent = content;
        formattedContent = formattedContent.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
        formattedContent = formattedContent.replace(/\n/g, '<br>');
        contentDiv.innerHTML = formattedContent;

        messageDiv.appendChild(contentDiv);
        messagesContainer.appendChild(messageDiv);

        // 滚动到底部
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }

    function addLoadingMessage() {
        const loadingDiv = document.createElement('div');
        loadingDiv.className = 'message assistant';
        loadingDiv.id = 'loading-message';

        const contentDiv = document.createElement('div');
        contentDiv.className = 'message-content';
        contentDiv.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';

        loadingDiv.appendChild(contentDiv);
        messagesContainer.appendChild(loadingDiv);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }

    function removeLoadingMessage() {
        const loading = document.getElementById('loading-message');
        if (loading) loading.remove();
    }

    async function sendMessage() {
        const message = messageInput.value.trim();

        if (!message || isWaitingForResponse) return;

        // 清空输入框
        messageInput.value = '';

        // 添加用户消息
        addMessage(message, 'user');

        // 设置等待状态
        isWaitingForResponse = true;
        sendButton.disabled = true;

        // 添加加载动画
        addLoadingMessage();

        // 更新状态为思考中
        updateStatus('thinking', 'AI 正在调用工具并生成回答...');

        try {
            const response = await fetch(API_URL, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ message: message })
            });

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const data = await response.json();

            // 移除加载动画
            removeLoadingMessage();

            // 添加 AI 回复
            addMessage(data.reply, 'assistant');

            updateStatus('ready', '已就绪');

        } catch (error) {
            console.error('Error:', error);
            removeLoadingMessage();
            addMessage('抱歉,发生了错误: ' + error.message, 'assistant');
            updateStatus('error', '连接失败');

            // 3秒后恢复状态
            setTimeout(() => {
                if (statusIndicator.classList.contains('error')) {
                    updateStatus('ready', '已就绪');
                }
            }, 3000);
        } finally {
            isWaitingForResponse = false;
            sendButton.disabled = false;
            messageInput.focus();
        }
    }

    function clearChat() {
        // 保留第一条系统消息
        const messages = messagesContainer.querySelectorAll('.message:not(.system)');
        messages.forEach(msg => msg.remove());

        addMessage('对话已清空,有什么我可以帮你的吗?', 'system');
    }

    // 事件绑定
    sendButton.addEventListener('click', sendMessage);
    clearButton.addEventListener('click', clearChat);

    // 回车发送(Shift+Enter 换行)
    messageInput.addEventListener('keydown', (e) => {
        if (e.key === 'Enter' && !e.shiftKey) {
            e.preventDefault();
            sendMessage();
        }
    });
</script>
</body>
</html>

9、测试

接口测试:

// 测试用例
curl -X POST http://localhost:8086/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"查询订单ORD-001的状态"}'

// 接口返回结果:
{"reply":"您的订单ORD-001的状态如下:\n\n- 订单包含的商品数量:3件\n- 预计送达时间:2026年5月1日\n- 当前状态:已发货\n\n如果有任何问题或需要进一步的帮助,请随时告诉我!"}%  

页面测试:http://localhost:8086/

工具调用流程:

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐