AI Agent 设计模式系列(十)——模型上下文协议 (MCP)

要使 LLM 作为 Agent 有效运作,其能力必须超越多模态生成。与外部环境交互不可或缺,包括访问实时数 据、使用外部软件以及执行特定操作任务。模型上下文协议(MCP)通过提供标准化接口使 LLM 能与外部资 源交互,是实现一致性和可预测性集成的关键机制。

MCP 模式概述

想象一个通用适配器,允许任何 LLM 连接到任何外部系统、数据库或工具,无需为每个连接进行自定义集 成。这本质上就是模型上下文协议(MCP)的功能。它作为开放标准,旨在标准化 Gemini、OpenAI 的 GPT 模型、Mixtral 和 Claude 等 LLM 与外部应用程序、数据源和工具的通信方式。可将其视为通用连接机制,简 化 LLM 获取上下文、执行操作以及与各类系统交互的方式。

MCP 基于客户端‐服务器架构运行。它定义了不同元素------数据(称为资源)、交互模板(本质是提示)和可 操作函数(称为工具)------如何由 MCP 服务器公开。这些元素随后由 MCP 客户端使用,客户端可以是 LLM 宿主应用程序或 AI Agent 本身。这种标准化方法显著降低了将 LLM 集成到多样化操作环境中的复杂性。

然而,MCP 是"Agent 接口"的契约,其有效性很大程度上取决于其所公开底层 API 的设计。存在开发人员 仅简单包装现有遗留 API 而不进行修改的风险,这对 Agent 可能并非最优。例如,若票务系统 API 仅允许逐 个检索完整票务详情,被要求总结高优先级票务的 Agent 在处理大量数据时将变得缓慢且不准确。为真正有 效,底层 API 应通过过滤和排序等确定性特性进行改进,以帮助非确定性 Agent 高效工作。这凸显了 Agent 无法神奇替代确定性工作流;它们通常需要更强确定性支持才能成功。

此外,MCP 可包装输入或输出对 Agent 仍非固有可理解的 API。仅当 API 数据格式对 Agent 友好时才有用, 而 MCP 本身无法保证此点。例如,为返回 PDF 文件的文档存储创建 MCP 服务器基本无用,若使用该服务的 Agent 无法解析 PDF 内容。更好方法是首先创建返回文档文本版本(如 Markdown)的 API,使 Agent 能实 际阅读和处理。这表明开发人员必须考虑的不只是连接,还有数据交换的性质,以确保真正兼容性。

MCP 与工具函数调用

模型上下文协议(MCP)和工具函数调用是使 LLM 能与外部能力(含工具)交互并执行操作的不同机制。虽 然两者都服务于扩展 LLM 超越文本生成的能力,但它们在方法和抽象级别上存在差异。

工具函数调用可视为 LLM 对特定预定义工具或函数的直接请求。请注意,在此上下文中我们交替使用"工 具"和"函数"二词。这种交互采用一对一通信模型,LLM 根据其对需要外部操作用户意图的理解格式化请 求。应用程序代码执行此请求并将结果返回 LLM。此过程通常为专有,且在不同 LLM 提供商间存在差异。

相比之下,模型上下文协议(MCP)作为 LLM 发现、通信和使用外部能力的标准化接口运行。它作为开放协 议促进与各种工具和系统交互,旨在建立任何兼容工具可被任何兼容 LLM 访问的生态系统。这促进了不同 系统和实现间的互操作性、可组合性和可重用性。通过采用联合模型,我们显著提升互操作性并释放现有资 产价值。此策略允许我们通过简单包装符合 MCP 接口将分散和遗留服务引入现代生态系统。这些服务继续 独立运行,但现可组合到新应用程序和工作流中,其协作由 LLM 协调。这促进了敏捷性和可重用性,而无需 对基础系统进行昂贵重写。

以下是 MCP 与工具函数调用的基本区别:

特性 工具函数调用 模型上下文协议(MCP)
标准化 专有且供应商特定:格式和实现因LLM提供商而异,互操作性差。 开放标准化协议:促进不同LLM与工具之间的广泛互操作性。
范围 直接机制:仅限于LLM请求执行特定、预定义的函数。 通用框架:定义LLM与外部工具如何相互发现、连接并进行通信的完整体系。
架构 一对一交互:LLM与应用程序内部的工具处理逻辑直接耦合。 客户端-服务器架构:LLM应用(客户端)可动态连接并利用多个独立的MCP服务器(工具)。
发现 静态告知:LLM需在对话上下文中被显式告知具体有哪些工具可用。 动态发现:MCP客户端可主动查询服务器,以发现其动态提供的能力和工具。
可重用性 紧密耦合:工具集成通常绑定于特定的应用程序和LLM,难以复用。 促进重用:支持开发独立的、可复用的MCP服务器,可被任何兼容的客户端应用访问。

主要对比总结

  • 工具函数调用 是一种紧耦合、专有化的集成方式,适合快速、直接的特定功能对接。

  • 模型上下文协议 则提供了一个标准化、松耦合、可扩展的生态框架,旨在实现工具与AI应用之间更通用、灵活的互联互通。

可将工具函数调用想象为给 AI 一组特定定制工具,如特定扳手和螺丝刀。这对具有固定任务集的车间高效。 另一方面,MCP(模型上下文协议)如同创建通用标准化电源插座系统。它本身不提供工具,但允许任何制 造商任何兼容工具插入工作,从而实现动态不断扩展的车间。

简而言之,函数调用提供对少数特定函数的直接访问,而 MCP 是标准化通信框架,让 LLM 发现和使用广泛 外部资源。对简单应用程序,特定工具足够;对需要适应的复杂互联 AI 系统,像 MCP 的通用标准必不可少。

MCP 的其他考虑因素

虽然 MCP 提供了强大框架,但全面评估需考虑影响其适用性的几个关键方面。让我们详细探讨某些方面:

  • **工具 vs. 资源 vs. 提示:**理解这些组件的特定角色很重要。资源是静态数据(如 PDF 文件、数据库记 录)。工具是执行操作的可执行函数(如发送电子邮件、查询 API)。提示是指导 LLM 如何与资源或工 具交互的模板,确保交互结构化和有效

  • **可发现性:**MCP 的关键优势是 MCP 客户端可动态查询服务器了解其提供的工具和资源。这种"即时" 发现机制对需要适应新能力而无需重新部署的 Agent 非常强大

  • **安全性:**通过任何协议公开工具和数据都需要强大安全措施。MCP 实现必须包含身份验证和授权,以 控制哪些客户端可访问哪些服务器及允许执行哪些特定操作

  • **实现:**虽然MCP是开放标准,但其实现可能复杂。然而提供商正开始简化此过程。例如Anthropic或 FastMCP 等模型提供商提供 SDK,抽象大部分样板代码,使开发人员更易创建和连接 MCP 客户端和 服务器

  • **错误处理:**全面错误处理策略至关重要。协议必须定义如何将错误(如工具执行失败、服务器不可用、 无效请求)传达回 LLM,使其能理解失败并可能尝试替代方法

  • **本地 vs. 远程服务器:**MCP 服务器可部署在与 Agent 相同机器本地,或远程部署在不同服务器。本地 服务器可能因速度和敏感数据安全性被选择,而远程服务器架构允许组织内共享可扩展访问公共工具

  • **按需 vs. 批处理:**MCP 可支持按需交互式会话和大规模批处理。选择取决于应用程序,从需要立即工 具访问的实时对话 Agent 到批量处理记录的数据分析管道

  • **传输机制:**协议还定义通信的底层传输层。对本地交互,使用基于STDIO(标准输入/输出)的JSON‐RPC 实现高效进程间通信。对远程连接,利用 Web 友好协议如可流式 HTTP 和服务器发送事件(SSE)实 现持久高效客户端‐服务器通信

模型上下文协议使用客户端‐服务器模型标准化信息流。

理解组件交互是 MCP 高级 Agent 行为关键:

  1. **大型语言模型(LLM):**核心智能。处理用户请求,制定计划,决定何时需要访问外部信息或执行操作

  2. **MCP客户端:**围绕LLM的应用程序或包装器。充当中介,将LLM意图转换为符合MCP标准的正式请

    求。负责发现、连接和与 MCP 服务器通信

  3. **MCP 服务器:**通往外部世界的网关。向任何授权 MCP 客户端公开一组工具、资源和提示。每个服务器通常负责特定领域,如连接公司内部数据库、电子邮件服务或公共 API。

  4. **可选的第三方(3P)服务:**代表MCP服务器管理和公开的实际外部工具、应用程序或数据源。是执行 请求操作的最终端点,如查询专有数据库、与 SaaS 平台交互或调用公共天气 API。

交互流程如下:

  1. **发现:**MCP 客户端代表 LLM 查询 MCP 服务器询问其提供能力。服务器响应清单列出可用工具(如 send_email)、资源(如 customer_database)和提示

  2. **请求制定:**LLM 确定需要使用发现的工具之一。例如决定发送电子邮件。制定请求指定要使用的工具(send_email)和必要参数(收件人、主题、正文)

  3. **客户端通信:**MCP客户端获取LLM制定的请求,将其作为标准化调用发送到适当MCP服务器

  4. **服务器执行:**MCP 服务器接收请求。对客户端进行身份验证,验证请求,然后通过与底层软件交互执行指定操作(如调用电子邮件 API 的 send() 函数)

  5. **响应和上下文更新:**执行后,MCP 服务器将标准化响应发送回 MCP 客户端。此响应指示操作是否成功,包括任何相关输出(如已发送电子邮件的确认 ID)。然后客户端将此结果传递回 LLM,更新其上下 文并使其能继续任务的下一步。

实际应用和用例

MCP 显著扩展了 AI/LLM 能力,使其更加多功能强大。以下是九个关键用例:

  • **数据库集成:**MCP允许LLM和Agent无缝访问数据库中结构化数据并与之交互。例如使用数据库MCP 工具箱,Agent 可查询 Google BigQuery 数据集检索实时信息、生成报告或更新记录,所有由自然语 言命令驱动。

  • **生成媒体编排:**MCP使Agent能与高级生成媒体服务集成。通过生成媒体服务的MCP工具,Agent可 编排涉及 Google Imagen 图像生成、Google Veo 视频创建、Google Chirp 3 HD 逼真语音或 Google Lyria 音乐创作的工作流,允许在 AI 应用程序中进行动态内容创建。

  • **外部API交互:**MCP为LLM提供调用任何外部API并接收响应的标准化方式。这意味着Agent可获取 实时天气数据、拉取股票价格、发送电子邮件或与 CRM 系统交互,将其能力扩展到核心语言模型之外

  • **基于推理的信息提取:**利用LLM强大推理能力,MCP促进有效的依赖查询信息提取,超越传统搜索和 检索系统。Agent 可分析文本并提取精确回答用户复杂问题的特定条款、数字或陈述,而非传统搜索工具返回整个文档。

  • **自定义工具开发:**开发人员可构建自定义工具并通过 MCP 服务器公开(如使用 FastMCP)。这允许以标准化易于使用格式向 LLM 和其他 Agent 提供专门内部函数或专有系统,而无需直接修改 LLM。

  • **标准化的 LLM 到应用程序通信:**MCP 确保 LLM 与它们交互的应用程序间有一致通信层。这减少了集成开销,促进不同 LLM 提供商和宿主应用程序间互操作性,并简化复杂 Agent 系统开发。

  • **复杂工作流编排:**通过组合各种MCP公开工具和数据源,Agent可编排高度复杂多步骤工作流。例如 Agent 可从数据库检索客户数据,生成个性化营销图像,起草定制电子邮件,然后发送,所有通过与不同 MCP 服务交互完成。

  • **物联网设备控制:**MCP 可促进 LLM 与物联网(IoT)设备交互。Agent 可使用 MCP 向智能家居电器、 工业传感器或机器人发送命令,实现自然语言控制和物理系统自动化。

  • **金融服务自动化:**在金融服务中,MCP可使LLM与各种金融数据源、交易平台或合规系统交互。Agent 可能分析市场数据、执行交易、生成个性化财务建议或自动化监管报告,同时保持安全和标准化通信。

    简而言之,模型上下文协议(MCP)使 Agent 能从数据库、API 和 Web 资源访问实时信息。还允许 Agent 执 行如发送电子邮件、更新记录、控制设备以及通过集成处理多源数据执行复杂任务等操作。此外,MCP 支持 AI 应用程序的媒体生成工具。

概览

是什么:

要作为有效 Agent 运作,LLM 必须超越简单文本生成。它们需要与外部环境交互能力以访问当前数 据并使用外部软件。若无标准化通信方法,LLM 与外部工具或数据源间每次集成都成为定制复杂不可重用工 作。这种临时方法阻碍可扩展性,并使构建复杂互联 AI 系统变得困难低效

为什么:

模型上下文协议(MCP)通过充当 LLM 和外部系统间通用接口提供标准化解决方案。它建立开放 标准化协议,定义如何发现和使用外部能力。基于客户端‐服务器模型运行,MCP 允许服务器向任何兼容客 户端公开工具、数据资源和交互式提示。LLM 驱动应用程序充当这些客户端,以可预测方式动态发现和与可 用资源交互。这种标准化方法促进了可互操作和可重用组件生态系统,显著简化复杂 Agent 工作流开发

经验法则:

在构建需要与各种不断发展的外部工具、数据源和 API 交互的复杂可扩展或企业级 Agent 系统 时,使用模型上下文协议(MCP)。当不同 LLM 和工具间互操作性是优先考虑事项时,以及当 Agent 需要能 够动态发现新能力而无需重新部署时,它是理想选择。对具有固定有限数量预定义函数的简单应用程序,直接工具函数调用可能足够

图 1:模型上下文协议

关键要点

以下是本文核心要点:

・ 模型上下文协议(MCP)是开放标准,促进LLM与外部应用程序、数据源和工具间标准化通信。

・ 它采用客户端‐服务器架构,定义公开和使用资源、提示和工具的方法。

・ Agent 开发工具包(ADK)支持使用现有 MCP 服务器以及通过 MCP 服务器公开 ADK 工具

・ FastMCP简化了MCP服务器开发和管理,特别用于公开在Python中实现的工具。

・ 生成媒体服务的MCP工具允许Agent与GoogleCloud的生成媒体能力(Imagen、Veo、Chirp3HD、Lyria)集成。

・ MCP使LLM和Agent能与现实世界系统交互,访问动态信息,并执行超越文本生成的操作。

外部API交互等用例DEMO

MCP(Model Context Protocol)是一个开放协议,用于连接LLM和外部工具。目前,对JAVA技术栈,LangChain4j 并没有直接支持MCP,但我们可以通过LangChain4j的工具调用(Tool)功能来模拟类似的能力。

MCP是一个独立的协议,它定义了一组标准接口,而LangChain4j的工具调用是其内置的功能,允许LLM调用Java方法。因此,我们可以将每个MCP工具映射为一个LangChain4j的Tool。

由于我们无法直接实现完整的MCP协议栈,这里我们将使用LangChain4j的工具调用功能来模拟实现上述用例。我们将为每个用例创建一个或多个Tool,然后让Agent(即LLM)根据用户输入决定调用哪个工具。

我们将模拟以下四个用例,每个用例创建一个工具:

  1. 数据库集成:模拟查询Google BigQuery,这里我们用一个简单的数据库查询工具代替。

  2. 生成媒体编排:模拟调用生成媒体服务,这里我们用一个生成图像的工具代替。

  3. 外部API交互:模拟获取实时天气数据。

  4. 基于推理的信息提取:模拟从文本中提取信息。

注意:由于实际环境中每个工具都需要具体的实现(如连接数据库、调用真实API等),这里我们只给出框架代码,具体实现将用模拟数据代替。

我们将使用LangChain4j的Spring Boot Starter来快速构建。假设你已经有一个Spring Boot项目,并添加了langchain4j-spring-boot-starter依赖。

步骤:

  1. 定义每个工具,实现Tool接口或使用@Tool注解。

  2. 将这些工具注册到ToolProvider中。

  3. 创建一个Agent(即ChatLanguageModel)并启用工具调用。

由于我们只是演示,这里将使用模拟的实现。下面给出关键代码片段。

假设我们有一个Spring Boot应用,并且已经配置了OpenAI的API密钥(或其他LLM提供商)。

首先,我们定义四个工具,分别对应四个用例。

用例1:数据库集成工具 - 模拟查询BigQuery

用例2:生成媒体编排工具 - 模拟生成图像

用例3:外部API交互工具 - 模拟获取天气

用例4:基于推理的信息提取工具 - 模拟从文本中提取信息

我们将使用@Tool注解来标记这些工具方法。注意:每个工具方法应该有一个清晰的描述,以便LLM理解何时调用它。我们创建一个Service类,包含这些工具方法:

注意:LangChain4j的Tool注解需要方法返回String类型,并且方法参数要有明确的描述。

由于我们无法在同一个类中定义多个工具(因为Tool注解是方法级别的,并且需要在一个Bean中),我们可以将这些工具方法放在一个或多个Bean中。

下面是一个示例代码,将展示如何创建工具函数并与 LLM 集成,实现数据库集成、外部 API 交互等功能。

一、项目基础配置

  1. Maven 依赖配置
XML 复制代码
<!-- pom.xml -->
<dependencies>
    <!-- Spring Boot 基础 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- LangChain4j 核心 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>0.31.0</version>
    </dependency>
    
    <!-- LangChain4j Spring Boot 集成 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
        <version>0.31.0</version>
    </dependency>
    
    <!-- 数据库相关 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- HTTP客户端 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>
  1. 应用配置
XML 复制代码
# application.yml
spring:
  application:
    name: langchain4j-mcp-demo

# OpenAI 配置
langchain4j:
  open-ai:
    chat-model:
      api-key: ${OPENAI_API_KEY}
      model-name: gpt-4
      temperature: 0.7
      timeout: 60s
      max-retries: 3
    embedding-model:
      api-key: ${OPENAI_API_KEY}
      model-name: text-embedding-3-small
      
# 外部 API 配置
api:
  weather:
    base-url: https://api.openweathermap.org/data/2.5
    api-key: ${WEATHER_API_KEY}
  stock:
    base-url: https://api.twelvedata.com
    api-key: ${STOCK_API_KEY}

二、数据库集成 MCP 工具实现

  1. 数据库实体类
java 复制代码
// Customer.java
@Entity
@Table(name = "customers")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    @Column(name = "total_purchases")
    private BigDecimal totalPurchases;
    
    @Column(name = "customer_tier")
    private String customerTier; // BASIC, PREMIUM, VIP
    
    @PrePersist
    protected void onCreate() {
        createdAt = LocalDateTime.now();
    }
}

// Product.java
@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(length = 1000)
    private String description;
    
    @Column(nullable = false)
    private BigDecimal price;
    
    @Column(name = "category")
    private String category;
    
    @Column(name = "stock_quantity")
    private Integer stockQuantity;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
}

// Order.java
@Entity
@Table(name = "orders")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "customer_id", nullable = false)
    private Customer customer;
    
    @Column(name = "order_date")
    private LocalDateTime orderDate;
    
    @Column(name = "total_amount")
    private BigDecimal totalAmount;
    
    @Column(name = "status")
    private String status; // PENDING, PROCESSING, SHIPPED, DELIVERED
    
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> items = new ArrayList<>();
}

// OrderItem.java
@Entity
@Table(name = "order_items")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderItem {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "order_id", nullable = false)
    private Order order;
    
    @ManyToOne
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;
    
    @Column(nullable = false)
    private Integer quantity;
    
    @Column(nullable = false)
    private BigDecimal unitPrice;
    
    @Column(nullable = false)
    private BigDecimal subtotal;
}
  1. 数据库 MCP 工具实现
java 复制代码
// DatabaseMCPTools.java
@Service
@Slf4j
public class DatabaseMCPTools {
    
    @Autowired
    private CustomerRepository customerRepository;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Autowired
    private OrderRepository orderRepository;
    
    /**
     * 查询客户信息
     */
    @Tool(name = "queryCustomerInfo", description = """
        根据客户ID、姓名或邮箱查询客户信息。
        参数:
        - customerId: 客户ID(可选)
        - name: 客户姓名(可选)
        - email: 客户邮箱(可选)
        - includeOrders: 是否包含订单信息(默认false)
        """)
    public String queryCustomerInfo(
            @P("客户ID,如不提供则根据姓名或邮箱查询") Optional<Long> customerId,
            @P("客户姓名,支持模糊查询") Optional<String> name,
            @P("客户邮箱") Optional<String> email,
            @P("是否包含订单信息") @Optional(defaultValue = "false") boolean includeOrders) {
        
        try {
            List<Customer> customers;
            
            if (customerId.isPresent()) {
                // 按ID查询
                Optional<Customer> customer = customerRepository.findById(customerId.get());
                customers = customer.map(List::of).orElse(Collections.emptyList());
            } else if (name.isPresent() && email.isPresent()) {
                // 按姓名和邮箱组合查询
                customers = customerRepository.findByNameContainingAndEmail(
                    name.get(), email.get());
            } else if (name.isPresent()) {
                // 按姓名查询
                customers = customerRepository.findByNameContaining(name.get());
            } else if (email.isPresent()) {
                // 按邮箱查询
                customers = customerRepository.findByEmail(email.get())
                    .map(List::of).orElse(Collections.emptyList());
            } else {
                return "请提供至少一个查询条件:customerId、name 或 email";
            }
            
            if (customers.isEmpty()) {
                return "未找到符合条件的客户";
            }
            
            // 构建返回结果
            StringBuilder result = new StringBuilder();
            for (Customer customer : customers) {
                result.append(String.format("""
                    客户ID: %d
                    姓名: %s
                    邮箱: %s
                    客户等级: %s
                    总消费金额: %s
                    注册时间: %s
                    """,
                    customer.getId(),
                    customer.getName(),
                    customer.getEmail(),
                    customer.getCustomerTier(),
                    customer.getTotalPurchases(),
                    customer.getCreatedAt()));
                
                // 如果需要包含订单信息
                if (includeOrders) {
                    List<Order> orders = orderRepository.findByCustomerId(customer.getId());
                    if (!orders.isEmpty()) {
                        result.append("订单记录:\n");
                        for (Order order : orders) {
                            result.append(String.format("""
                                - 订单ID: %d, 日期: %s, 金额: %s, 状态: %s
                                """,
                                order.getId(),
                                order.getOrderDate(),
                                order.getTotalAmount(),
                                order.getStatus()));
                        }
                    }
                }
                result.append("\n");
            }
            
            return result.toString();
            
        } catch (Exception e) {
            log.error("查询客户信息失败", e);
            return "查询失败: " + e.getMessage();
        }
    }
    
    /**
     * 查询产品库存
     */
    @Tool(name = "queryProductStock", description = """
        查询产品库存信息。
        参数:
        - productId: 产品ID(可选)
        - productName: 产品名称,支持模糊查询(可选)
        - category: 产品类别(可选)
        - lowStockThreshold: 低库存阈值(默认10)
        """)
    public String queryProductStock(
            @P("产品ID") Optional<Long> productId,
            @P("产品名称") Optional<String> productName,
            @P("产品类别") Optional<String> category,
            @P("低库存阈值") @Optional(defaultValue = "10") int lowStockThreshold) {
        
        try {
            List<Product> products;
            
            if (productId.isPresent()) {
                // 按ID查询
                Optional<Product> product = productRepository.findById(productId.get());
                products = product.map(List::of).orElse(Collections.emptyList());
            } else if (productName.isPresent() && category.isPresent()) {
                // 按名称和类别组合查询
                products = productRepository.findByNameContainingAndCategory(
                    productName.get(), category.get());
            } else if (productName.isPresent()) {
                // 按名称查询
                products = productRepository.findByNameContaining(productName.get());
            } else if (category.isPresent()) {
                // 按类别查询
                products = productRepository.findByCategory(category.get());
            } else {
                // 查询所有产品
                products = productRepository.findAll();
            }
            
            if (products.isEmpty()) {
                return "未找到符合条件的商品";
            }
            
            // 构建返回结果
            StringBuilder result = new StringBuilder();
            int lowStockCount = 0;
            
            for (Product product : products) {
                String stockStatus = product.getStockQuantity() <= lowStockThreshold ? 
                    "⚠️ 低库存" : "✅ 库存充足";
                
                if (product.getStockQuantity() <= lowStockThreshold) {
                    lowStockCount++;
                }
                
                result.append(String.format("""
                    产品ID: %d
                    名称: %s
                    类别: %s
                    价格: %s
                    库存: %d (%s)
                    描述: %s
                    """,
                    product.getId(),
                    product.getName(),
                    product.getCategory(),
                    product.getPrice(),
                    product.getStockQuantity(),
                    stockStatus,
                    product.getDescription().length() > 100 ? 
                        product.getDescription().substring(0, 100) + "..." : 
                        product.getDescription()));
                
                result.append("\n");
            }
            
            // 添加库存摘要
            result.append(String.format("""
                ====== 库存摘要 ======
                总产品数: %d
                低库存产品数: %d (阈值: %d)
                建议: %s
                """,
                products.size(),
                lowStockCount,
                lowStockThreshold,
                lowStockCount > 0 ? "请及时补充库存" : "库存状况良好"));
            
            return result.toString();
            
        } catch (Exception e) {
            log.error("查询产品库存失败", e);
            return "查询失败: " + e.getMessage();
        }
    }
    
    /**
     * 生成销售报告
     */
    @Tool(name = "generateSalesReport", description = """
        生成指定时间段的销售报告。
        参数:
        - startDate: 开始日期(格式:yyyy-MM-dd)
        - endDate: 结束日期(格式:yyyy-MM-dd)
        - groupBy: 分组方式(可选:day, week, month, category, customer_tier)
        - reportType: 报告类型(可选:summary, detailed, comparison)
        """)
    public String generateSalesReport(
            @P("开始日期,格式:yyyy-MM-dd") String startDate,
            @P("结束日期,格式:yyyy-MM-dd") String endDate,
            @P("分组方式") @Optional(defaultValue = "month") String groupBy,
            @P("报告类型") @Optional(defaultValue = "summary") String reportType) {
        
        try {
            LocalDate start = LocalDate.parse(startDate);
            LocalDate end = LocalDate.parse(endDate);
            
            // 查询指定时间段的订单
            List<Order> orders = orderRepository.findByOrderDateBetween(
                start.atStartOfDay(),
                end.atTime(LocalTime.MAX));
            
            if (orders.isEmpty()) {
                return String.format("在 %s 到 %s 时间段内没有销售记录", startDate, endDate);
            }
            
            // 计算统计信息
            BigDecimal totalRevenue = orders.stream()
                .map(Order::getTotalAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
            
            int totalOrders = orders.size();
            long totalCustomers = orders.stream()
                .map(Order::getCustomer)
                .distinct()
                .count();
            
            // 按分组方式聚合数据
            Map<String, BigDecimal> groupedData = new HashMap<>();
            
            switch (groupBy.toLowerCase()) {
                case "day":
                    orders.forEach(order -> {
                        String day = order.getOrderDate().toLocalDate().toString();
                        groupedData.merge(day, order.getTotalAmount(), BigDecimal::add);
                    });
                    break;
                    
                case "week":
                    orders.forEach(order -> {
                        String week = "第" + order.getOrderDate().get(
                            java.time.temporal.WeekFields.of(Locale.getDefault()).weekOfYear()) + "周";
                        groupedData.merge(week, order.getTotalAmount(), BigDecimal::add);
                    });
                    break;
                    
                case "month":
                    orders.forEach(order -> {
                        String month = order.getOrderDate().getMonth().getDisplayName(
                            TextStyle.FULL, Locale.CHINA);
                        groupedData.merge(month, order.getTotalAmount(), BigDecimal::add);
                    });
                    break;
                    
                case "category":
                    // 这里需要根据订单中的产品类别进行分组
                    orders.forEach(order -> {
                        order.getItems().forEach(item -> {
                            String category = item.getProduct().getCategory();
                            groupedData.merge(category, item.getSubtotal(), BigDecimal::add);
                        });
                    });
                    break;
                    
                default:
                    groupedData.put("总计", totalRevenue);
            }
            
            // 构建报告
            StringBuilder report = new StringBuilder();
            report.append(String.format("""
                ====== 销售报告 ======
                报告期间: %s 至 %s
                总订单数: %d
                总客户数: %d
                总销售额: %s
                平均订单价值: %s
                """,
                startDate, endDate,
                totalOrders,
                totalCustomers,
                totalRevenue,
                totalRevenue.divide(BigDecimal.valueOf(totalOrders), 2, RoundingMode.HALF_UP)));
            
            // 添加分组数据
            if (!groupedData.isEmpty()) {
                report.append("\n按").append(groupBy).append("分组:\n");
                groupedData.forEach((key, value) -> {
                    BigDecimal percentage = value.multiply(BigDecimal.valueOf(100))
                        .divide(totalRevenue, 2, RoundingMode.HALF_UP);
                    report.append(String.format("- %s: %s (%.1f%%)\n", key, value, percentage));
                });
            }
            
            // 如果是详细报告,添加订单列表
            if ("detailed".equalsIgnoreCase(reportType)) {
                report.append("\n===== 详细订单列表 =====\n");
                for (Order order : orders) {
                    report.append(String.format("""
                        订单ID: %d
                        客户: %s
                        日期: %s
                        金额: %s
                        状态: %s
                        项目数: %d
                        """,
                        order.getId(),
                        order.getCustomer().getName(),
                        order.getOrderDate(),
                        order.getTotalAmount(),
                        order.getStatus(),
                        order.getItems().size()));
                }
            }
            
            // 添加建议
            report.append("\n===== 建议 =====\n");
            if (totalRevenue.compareTo(BigDecimal.valueOf(10000)) < 0) {
                report.append("销售额较低,建议开展促销活动\n");
            }
            
            BigDecimal avgOrderValue = totalRevenue.divide(
                BigDecimal.valueOf(totalOrders), 2, RoundingMode.HALF_UP);
            if (avgOrderValue.compareTo(BigDecimal.valueOf(500)) < 0) {
                report.append("平均订单价值较低,建议实施交叉销售策略\n");
            }
            
            return report.toString();
            
        } catch (Exception e) {
            log.error("生成销售报告失败", e);
            return "生成报告失败: " + e.getMessage();
        }
    }
    
    /**
     * 更新客户信息
     */
    @Tool(name = "updateCustomerRecord", description = """
        更新客户记录。
        参数:
        - customerId: 客户ID(必填)
        - name: 新姓名(可选)
        - email: 新邮箱(可选)
        - customerTier: 新客户等级(可选)
        - totalPurchases: 新总消费金额(可选)
        """)
    public String updateCustomerRecord(
            @P("客户ID") Long customerId,
            @P("新姓名") Optional<String> name,
            @P("新邮箱") Optional<String> email,
            @P("新客户等级") Optional<String> customerTier,
            @P("新总消费金额") Optional<BigDecimal> totalPurchases) {
        
        try {
            Optional<Customer> optionalCustomer = customerRepository.findById(customerId);
            if (!optionalCustomer.isPresent()) {
                return "客户ID " + customerId + " 不存在";
            }
            
            Customer customer = optionalCustomer.get();
            List<String> updates = new ArrayList<>();
            
            // 更新字段
            if (name.isPresent() && !name.get().isEmpty()) {
                customer.setName(name.get());
                updates.add("姓名更新为: " + name.get());
            }
            
            if (email.isPresent() && !email.get().isEmpty()) {
                customer.setEmail(email.get());
                updates.add("邮箱更新为: " + email.get());
            }
            
            if (customerTier.isPresent() && !customerTier.get().isEmpty()) {
                customer.setCustomerTier(customerTier.get());
                updates.add("客户等级更新为: " + customerTier.get());
            }
            
            if (totalPurchases.isPresent()) {
                customer.setTotalPurchases(totalPurchases.get());
                updates.add("总消费金额更新为: " + totalPurchases.get());
            }
            
            if (updates.isEmpty()) {
                return "未提供任何更新字段";
            }
            
            customerRepository.save(customer);
            
            return String.format("客户 %d 更新成功:\n%s",
                customerId,
                String.join("\n", updates));
            
        } catch (Exception e) {
            log.error("更新客户记录失败", e);
            return "更新失败: " + e.getMessage();
        }
    }
}
  1. 数据访问层(Repository)
java 复制代码
// CustomerRepository.java
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
    
    List<Customer> findByNameContaining(String name);
    
    List<Customer> findByNameContainingAndEmail(String name, String email);
    
    Optional<Customer> findByEmail(String email);
    
    List<Customer> findByCustomerTier(String tier);
}

// ProductRepository.java
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
    
    List<Product> findByNameContaining(String name);
    
    List<Product> findByCategory(String category);
    
    List<Product> findByNameContainingAndCategory(String name, String category);
    
    List<Product> findByStockQuantityLessThanEqual(int threshold);
}

// OrderRepository.java
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
    
    List<Order> findByCustomerId(Long customerId);
    
    List<Order> findByOrderDateBetween(LocalDateTime start, LocalDateTime end);
    
    List<Order> findByStatus(String status);
    
    @Query("SELECT o FROM Order o WHERE o.customer.customerTier = :tier")
    List<Order> findByCustomerTier(@Param("tier") String tier);
}

三、外部 API 交互 MCP 工具实现

  1. 天气 API 工具
java 复制代码
// WeatherMCPTools.java
@Service
@Slf4j
public class WeatherMCPTools {
    
    @Value("${api.weather.base-url}")
    private String weatherBaseUrl;
    
    @Value("${api.weather.api-key}")
    private String weatherApiKey;
    
    @Autowired
    private WebClient.Builder webClientBuilder;
    
    /**
     * 获取实时天气数据
     */
    @Tool(name = "getRealTimeWeather", description = """
        获取指定城市的实时天气数据。
        参数:
        - city: 城市名称(支持中文和英文)
        - countryCode: 国家代码(可选,如 CN 表示中国)
        - units: 单位制(可选:metric 公制,imperial 英制,默认 metric)
        - language: 返回语言(可选:zh_cn, en,默认 zh_cn)
        """)
    public String getRealTimeWeather(
            @P("城市名称") String city,
            @P("国家代码") @Optional(defaultValue = "CN") String countryCode,
            @P("单位制") @Optional(defaultValue = "metric") String units,
            @P("返回语言") @Optional(defaultValue = "zh_cn") String language) {
        
        try {
            // 构建 API 请求
            String url = String.format("%s/weather?q=%s,%s&appid=%s&units=%s&lang=%s",
                weatherBaseUrl,
                URLEncoder.encode(city, StandardCharsets.UTF_8),
                countryCode,
                weatherApiKey,
                units,
                language);
            
            // 发送请求
            Mono<String> responseMono = webClientBuilder.build()
                .get()
                .uri(url)
                .retrieve()
                .bodyToMono(String.class);
            
            String response = responseMono.block();
            
            // 解析 JSON 响应
            ObjectMapper mapper = new ObjectMapper();
            JsonNode root = mapper.readTree(response);
            
            if (root.has("cod") && !root.get("cod").asText().equals("200")) {
                return "获取天气数据失败: " + root.get("message").asText();
            }
            
            // 提取天气信息
            String cityName = root.get("name").asText();
            String country = root.get("sys").get("country").asText();
            JsonNode main = root.get("main");
            JsonNode weather = root.get("weather").get(0);
            JsonNode wind = root.get("wind");
            
            double temperature = main.get("temp").asDouble();
            double feelsLike = main.get("feels_like").asDouble();
            int humidity = main.get("humidity").asInt();
            double windSpeed = wind.get("speed").asDouble();
            String description = weather.get("description").asText();
            
            // 构建友好的响应
            String temperatureUnit = "metric".equals(units) ? "°C" : "°F";
            String speedUnit = "metric".equals(units) ? "米/秒" : "英里/小时";
            
            return String.format("""
                ====== %s, %s 实时天气 ======
                🌡️ 温度: %.1f%s (体感: %.1f%s)
                💧 湿度: %d%%
                💨 风速: %.1f%s
                🌤️ 天气状况: %s
                
                建议: %s
                """,
                cityName, country,
                temperature, temperatureUnit,
                feelsLike, temperatureUnit,
                humidity,
                windSpeed, speedUnit,
                description,
                getWeatherSuggestion(temperature, humidity, description));
            
        } catch (Exception e) {
            log.error("获取天气数据失败", e);
            return "获取天气数据失败: " + e.getMessage();
        }
    }
    
    /**
     * 获取天气预报
     */
    @Tool(name = "getWeatherForecast", description = """
        获取指定城市的5天天气预报。
        参数:
        - city: 城市名称
        - countryCode: 国家代码(可选)
        - days: 预报天数(1-5,默认3)
        - units: 单位制(可选)
        """)
    public String getWeatherForecast(
            @P("城市名称") String city,
            @P("国家代码") @Optional(defaultValue = "CN") String countryCode,
            @P("预报天数") @Optional(defaultValue = "3") int days,
            @P("单位制") @Optional(defaultValue = "metric") String units) {
        
        try {
            // 构建 API 请求
            String url = String.format("%s/forecast?q=%s,%s&appid=%s&units=%s&cnt=%d",
                weatherBaseUrl,
                URLEncoder.encode(city, StandardCharsets.UTF_8),
                countryCode,
                weatherApiKey,
                units,
                days * 8); // 每天8个数据点
            
            // 发送请求
            String response = webClientBuilder.build()
                .get()
                .uri(url)
                .retrieve()
                .bodyToMono(String.class)
                .block();
            
            // 解析 JSON 响应
            ObjectMapper mapper = new ObjectMapper();
            JsonNode root = mapper.readTree(response);
            
            if (!root.has("list")) {
                return "获取天气预报失败";
            }
            
            // 按天分组预报数据
            Map<String, List<JsonNode>> dailyForecasts = new LinkedHashMap<>();
            DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            
            root.get("list").forEach(item -> {
                String dtText = item.get("dt_txt").asText();
                String date = dtText.split(" ")[0];
                dailyForecasts.computeIfAbsent(date, k -> new ArrayList<>()).add(item);
            });
            
            // 构建响应
            StringBuilder forecast = new StringBuilder();
            forecast.append(String.format("====== %s, %s 天气预报 ======\n\n", 
                root.get("city").get("name").asText(),
                root.get("city").get("country").asText()));
            
            String temperatureUnit = "metric".equals(units) ? "°C" : "°F";
            
            int dayCount = 0;
            for (Map.Entry<String, List<JsonNode>> entry : dailyForecasts.entrySet()) {
                if (dayCount >= days) break;
                
                List<JsonNode> dayData = entry.getValue();
                
                // 计算当天的统计信息
                double maxTemp = Double.MIN_VALUE;
                double minTemp = Double.MAX_VALUE;
                Map<String, Integer> weatherCount = new HashMap<>();
                
                for (JsonNode data : dayData) {
                    double temp = data.get("main").get("temp").asDouble();
                    maxTemp = Math.max(maxTemp, temp);
                    minTemp = Math.min(minTemp, temp);
                    
                    String weatherDesc = data.get("weather").get(0).get("description").asText();
                    weatherCount.put(weatherDesc, weatherCount.getOrDefault(weatherDesc, 0) + 1);
                }
                
                // 找出主要天气状况
                String mainWeather = weatherCount.entrySet().stream()
                    .max(Map.Entry.comparingByValue())
                    .map(Map.Entry::getKey)
                    .orElse("未知");
                
                // 格式化日期
                LocalDate date = LocalDate.parse(entry.getKey());
                String dayOfWeek = date.getDayOfWeek().getDisplayName(
                    TextStyle.FULL, Locale.CHINA);
                
                forecast.append(String.format("📅 %s (%s):\n", entry.getKey(), dayOfWeek));
                forecast.append(String.format("   温度范围: %.1f%s ~ %.1f%s\n", 
                    minTemp, temperatureUnit, maxTemp, temperatureUnit));
                forecast.append(String.format("   主要天气: %s\n", mainWeather));
                forecast.append("\n");
                
                dayCount++;
            }
            
            return forecast.toString();
            
        } catch (Exception e) {
            log.error("获取天气预报失败", e);
            return "获取天气预报失败: " + e.getMessage();
        }
    }
    
    /**
     * 根据天气条件获取建议
     */
    private String getWeatherSuggestion(double temperature, int humidity, String description) {
        List<String> suggestions = new ArrayList<>();
        
        if (temperature > 30) {
            suggestions.add("天气炎热,注意防暑降温,多喝水");
        } else if (temperature < 10) {
            suggestions.add("天气寒冷,注意保暖");
        }
        
        if (humidity > 80) {
            suggestions.add("湿度较高,感觉闷热");
        } else if (humidity < 30) {
            suggestions.add("空气干燥,注意保湿");
        }
        
        if (description.contains("雨")) {
            suggestions.add("有雨,请携带雨具");
        } else if (description.contains("雪")) {
            suggestions.add("有雪,注意交通安全");
        } else if (description.contains("风")) {
            suggestions.add("风力较大,注意安全");
        } else if (description.contains("晴")) {
            suggestions.add("天气晴朗,适合户外活动");
        }
        
        return String.join(";", suggestions);
    }
}
  1. 股票 API 工具
java 复制代码
// StockMCPTools.java
@Service
@Slf4j
public class StockMCPTools {
    
    @Value("${api.stock.base-url}")
    private String stockBaseUrl;
    
    @Value("${api.stock.api-key}")
    private String stockApiKey;
    
    @Autowired
    private WebClient.Builder webClientBuilder;
    
    /**
     * 获取实时股票价格
     */
    @Tool(name = "getStockPrice", description = """
        获取股票实时价格。
        参数:
        - symbol: 股票代码(如 AAPL, GOOGL, 000001.SS)
        - exchange: 交易所(可选:NYSE, NASDAQ, SSE, SZSE)
        """)
    public String getStockPrice(
            @P("股票代码") String symbol,
            @P("交易所") @Optional(defaultValue = "") String exchange) {
        
        try {
            // 构建 API 请求
            String url = String.format("%s/quote?symbol=%s&apikey=%s",
                stockBaseUrl,
                URLEncoder.encode(symbol, StandardCharsets.UTF_8),
                stockApiKey);
            
            // 发送请求
            String response = webClientBuilder.build()
                .get()
                .uri(url)
                .retrieve()
                .bodyToMono(String.class)
                .block();
            
            // 解析 JSON 响应
            ObjectMapper mapper = new ObjectMapper();
            JsonNode root = mapper.readTree(response);
            
            if (root.has("code") && root.get("code").asInt() != 200) {
                return "获取股票价格失败: " + root.get("message").asText();
            }
            
            // 提取股票信息
            String symbolName = root.has("symbol") ? root.get("symbol").asText() : symbol;
            String name = root.has("name") ? root.get("name").asText() : "未知";
            double price = root.has("price") ? root.get("price").asDouble() : 0;
            double change = root.has("change") ? root.get("change").asDouble() : 0;
            double changePercent = root.has("change_percent") ? 
                root.get("change_percent").asDouble() : 0;
            String currency = root.has("currency") ? root.get("currency").asText() : "USD";
            
            String trend = change >= 0 ? "📈 上涨" : "📉 下跌";
            
            return String.format("""
                ====== %s (%s) ======
                💰 当前价格: %.2f %s
                📊 涨跌: %s%.2f (%.2f%%)
                ⏰ 更新时间: %s
                
                技术指标:
                %s
                """,
                name, symbolName,
                price, currency,
                change >= 0 ? "+" : "", change, changePercent,
                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
                getStockAnalysis(price, changePercent));
            
        } catch (Exception e) {
            log.error("获取股票价格失败", e);
            return "获取股票价格失败: " + e.getMessage();
        }
    }
    
    /**
     * 获取股票历史数据
     */
    @Tool(name = "getStockHistory", description = """
        获取股票历史价格数据。
        参数:
        - symbol: 股票代码
        - interval: 时间间隔(可选:1min, 5min, 15min, 30min, 1hour, 1day)
        - startDate: 开始日期(格式:yyyy-MM-dd)
        - endDate: 结束日期(格式:yyyy-MM-dd)
        """)
    public String getStockHistory(
            @P("股票代码") String symbol,
            @P("时间间隔") @Optional(defaultValue = "1day") String interval,
            @P("开始日期") String startDate,
            @P("结束日期") String endDate) {
        
        try {
            // 构建 API 请求
            String url = String.format("%s/time_series?symbol=%s&interval=%s&start_date=%s&end_date=%s&apikey=%s",
                stockBaseUrl,
                URLEncoder.encode(symbol, StandardCharsets.UTF_8),
                interval,
                startDate,
                endDate,
                stockApiKey);
            
            // 发送请求
            String response = webClientBuilder.build()
                .get()
                .uri(url)
                .retrieve()
                .bodyToMono(String.class)
                .block();
            
            // 解析 JSON 响应
            ObjectMapper mapper = new ObjectMapper();
            JsonNode root = mapper.readTree(response);
            
            if (!root.has("values") || !root.get("values").isArray()) {
                return "获取股票历史数据失败";
            }
            
            JsonNode values = root.get("values");
            
            // 分析历史数据
            List<Double> prices = new ArrayList<>();
            List<Double> volumes = new ArrayList<>();
            
            values.forEach(value -> {
                if (value.has("close")) {
                    prices.add(value.get("close").asDouble());
                }
                if (value.has("volume")) {
                    volumes.add(value.get("volume").asDouble());
                }
            });
            
            if (prices.isEmpty()) {
                return "没有找到历史价格数据";
            }
            
            // 计算统计信息
            double currentPrice = prices.get(0);
            double maxPrice = prices.stream().max(Double::compare).orElse(0.0);
            double minPrice = prices.stream().min(Double::compare).orElse(0.0);
            double avgPrice = prices.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
            
            // 计算价格变化
            double firstPrice = prices.get(prices.size() - 1);
            double totalChange = currentPrice - firstPrice;
            double totalChangePercent = (totalChange / firstPrice) * 100;
            
            double avgVolume = volumes.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
            
            return String.format("""
                ====== %s 历史数据分析 (%s 至 %s) ======
                📈 数据点数: %d 个
                💰 当前价格: %.2f
                📊 价格区间: %.2f ~ %.2f
                ⚖️ 平均价格: %.2f
                📉 期间变化: %+.2f (%+.2f%%)
                🔢 平均交易量: %.0f
                
                趋势分析:
                %s
                """,
                symbol,
                startDate, endDate,
                prices.size(),
                currentPrice,
                minPrice, maxPrice,
                avgPrice,
                totalChange, totalChangePercent,
                avgVolume,
                analyzePriceTrend(prices));
            
        } catch (Exception e) {
            log.error("获取股票历史数据失败", e);
            return "获取股票历史数据失败: " + e.getMessage();
        }
    }
    
    /**
     * 分析价格趋势
     */
    private String analyzePriceTrend(List<Double> prices) {
        if (prices.size() < 2) {
            return "数据不足进行趋势分析";
        }
        
        List<String> analyses = new ArrayList<>();
        double firstPrice = prices.get(prices.size() - 1);
        double lastPrice = prices.get(0);
        double change = lastPrice - firstPrice;
        double changePercent = (change / firstPrice) * 100;
        
        if (changePercent > 5) {
            analyses.add("🔺 强势上涨趋势");
        } else if (changePercent < -5) {
            analyses.add("🔻 明显下跌趋势");
        } else {
            analyses.add("➡️ 横盘整理");
        }
        
        // 计算最近几天的变化
        int recentDays = Math.min(5, prices.size());
        double recentChange = 0;
        for (int i = 0; i < recentDays - 1; i++) {
            recentChange += prices.get(i) - prices.get(i + 1);
        }
        
        if (recentChange > 0) {
            analyses.add("近期呈现上涨势头");
        } else if (recentChange < 0) {
            analyses.add("近期呈现下跌势头");
        }
        
        // 波动性分析
        double mean = prices.stream().mapToDouble(Double::doubleValue).average().orElse(0);
        double variance = prices.stream()
            .mapToDouble(price -> Math.pow(price - mean, 2))
            .average().orElse(0);
        double volatility = Math.sqrt(variance) / mean * 100;
        
        if (volatility > 3) {
            analyses.add("⚠️ 波动性较高");
        } else {
            analyses.add("📊 波动性较低");
        }
        
        return String.join("\n", analyses);
    }
    
    /**
     * 获取股票分析
     */
    private String getStockAnalysis(double price, double changePercent) {
        List<String> analyses = new ArrayList<>();
        
        if (changePercent > 3) {
            analyses.add("💪 表现强劲,可能继续上涨");
        } else if (changePercent < -3) {
            analyses.add("⚠️ 表现疲软,注意风险");
        } else {
            analyses.add("📊 表现平稳");
        }
        
        if (price > 100) {
            analyses.add("💰 高价股,适合长期投资");
        } else if (price < 10) {
            analyses.add("💹 低价股,波动可能较大");
        }
        
        if (changePercent > 0) {
            analyses.add("📈 建议关注买入机会");
        } else {
            analyses.add("💡 建议观察等待");
        }
        
        return String.join("\n", analyses);
    }
}
  1. 电子邮件 MCP 工具
java 复制代码
// EmailMCPTools.java
@Service
@Slf4j
public class EmailMCPTools {
    
    @Value("${spring.mail.username}")
    private String fromEmail;
    
    @Autowired
    private JavaMailSender mailSender;
    
    @Autowired
    private CustomerRepository customerRepository;
    
    /**
     * 发送电子邮件
     */
    @Tool(name = "sendEmail", description = """
        发送电子邮件。
        参数:
        - to: 收件人邮箱地址(多个用逗号分隔)
        - subject: 邮件主题
        - body: 邮件正文
        - cc: 抄送邮箱地址(可选,多个用逗号分隔)
        - bcc: 密送邮箱地址(可选,多个用逗号分隔)
        - isHtml: 是否为HTML格式(默认false)
        """)
    public String sendEmail(
            @P("收件人邮箱地址") String to,
            @P("邮件主题") String subject,
            @P("邮件正文") String body,
            @P("抄送邮箱地址") @Optional(defaultValue = "") String cc,
            @P("密送邮箱地址") @Optional(defaultValue = "") String bcc,
            @P("是否为HTML格式") @Optional(defaultValue = "false") boolean isHtml) {
        
        try {
            // 创建邮件
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
            
            helper.setFrom(fromEmail);
            helper.setTo(to.split("\\s*,\\s*"));
            helper.setSubject(subject);
            helper.setText(body, isHtml);
            
            if (!cc.isEmpty()) {
                helper.setCc(cc.split("\\s*,\\s*"));
            }
            
            if (!bcc.isEmpty()) {
                helper.setBcc(bcc.split("\\s*,\\s*"));
            }
            
            // 发送邮件
            mailSender.send(message);
            
            return String.format("""
                ✅ 邮件发送成功!
                收件人: %s
                主题: %s
                发送时间: %s
                邮件大小: %d 字符
                """,
                to,
                subject,
                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
                body.length());
            
        } catch (Exception e) {
            log.error("发送邮件失败", e);
            return "发送邮件失败: " + e.getMessage();
        }
    }
    
    /**
     * 批量发送客户邮件
     */
    @Tool(name = "sendBulkCustomerEmails", description = """
        批量发送邮件给符合条件的客户。
        参数:
        - customerTier: 客户等级筛选(可选)
        - minPurchases: 最低消费金额筛选(可选)
        - startDate: 注册开始日期(格式:yyyy-MM-dd,可选)
        - subject: 邮件主题
        - bodyTemplate: 邮件正文模板,可用变量:{name}, {email}, {totalPurchases}
        - testMode: 测试模式(默认true,只显示不实际发送)
        """)
    public String sendBulkCustomerEmails(
            @P("客户等级筛选") @Optional(defaultValue = "") String customerTier,
            @P("最低消费金额") @Optional(defaultValue = "0") BigDecimal minPurchases,
            @P("注册开始日期") @Optional(defaultValue = "") String startDate,
            @P("邮件主题") String subject,
            @P("邮件正文模板") String bodyTemplate,
            @P("测试模式") @Optional(defaultValue = "true") boolean testMode) {
        
        try {
            // 查询符合条件的客户
            List<Customer> customers = new ArrayList<>();
            
            if (!customerTier.isEmpty() && !startDate.isEmpty()) {
                // 按等级和注册日期筛选
                customers = customerRepository.findByCustomerTier(customerTier).stream()
                    .filter(c -> c.getCreatedAt().toLocalDate().isAfter(
                        LocalDate.parse(startDate).minusDays(1)))
                    .filter(c -> c.getTotalPurchases().compareTo(minPurchases) >= 0)
                    .collect(Collectors.toList());
            } else if (!customerTier.isEmpty()) {
                // 只按等级筛选
                customers = customerRepository.findByCustomerTier(customerTier).stream()
                    .filter(c -> c.getTotalPurchases().compareTo(minPurchases) >= 0)
                    .collect(Collectors.toList());
            } else if (!startDate.isEmpty()) {
                // 只按注册日期筛选
                customers = customerRepository.findAll().stream()
                    .filter(c -> c.getCreatedAt().toLocalDate().isAfter(
                        LocalDate.parse(startDate).minusDays(1)))
                    .filter(c -> c.getTotalPurchases().compareTo(minPurchases) >= 0)
                    .collect(Collectors.toList());
            } else {
                // 只按消费金额筛选
                customers = customerRepository.findAll().stream()
                    .filter(c -> c.getTotalPurchases().compareTo(minPurchases) >= 0)
                    .collect(Collectors.toList());
            }
            
            if (customers.isEmpty()) {
                return "未找到符合条件的客户";
            }
            
            // 生成邮件内容
            StringBuilder report = new StringBuilder();
            report.append(String.format("""
                ====== 批量邮件发送计划 ======
                筛选条件:
                - 客户等级: %s
                - 最低消费: %s
                - 注册开始日期: %s
                - 符合条件的客户数: %d
                - 测试模式: %s
                
                邮件预览:
                """,
                customerTier.isEmpty() ? "不限" : customerTier,
                minPurchases,
                startDate.isEmpty() ? "不限" : startDate,
                customers.size(),
                testMode ? "是" : "否"));
            
            int sentCount = 0;
            int failedCount = 0;
            
            for (int i = 0; i < Math.min(3, customers.size()); i++) {
                Customer customer = customers.get(i);
                String personalizedBody = personalizeTemplate(bodyTemplate, customer);
                
                report.append(String.format("\n--- 客户 %d ---\n", i + 1));
                report.append(String.format("收件人: %s <%s>\n", 
                    customer.getName(), customer.getEmail()));
                report.append(String.format("主题: %s\n", subject));
                report.append(String.format("正文预览: %s...\n", 
                    personalizedBody.substring(0, Math.min(100, personalizedBody.length()))));
            }
            
            if (customers.size() > 3) {
                report.append(String.format("\n... 还有 %d 位客户\n", customers.size() - 3));
            }
            
            // 如果不是测试模式,实际发送邮件
            if (!testMode) {
                report.append("\n====== 发送结果 ======\n");
                
                for (Customer customer : customers) {
                    try {
                        String personalizedBody = personalizeTemplate(bodyTemplate, customer);
                        
                        MimeMessage message = mailSender.createMimeMessage();
                        MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
                        
                        helper.setFrom(fromEmail);
                        helper.setTo(customer.getEmail());
                        helper.setSubject(subject);
                        helper.setText(personalizedBody, true);
                        
                        mailSender.send(message);
                        sentCount++;
                        
                        log.info("已发送邮件给客户: {} <{}>", 
                            customer.getName(), customer.getEmail());
                        
                    } catch (Exception e) {
                        failedCount++;
                        log.error("发送邮件给客户 {} 失败: {}", 
                            customer.getEmail(), e.getMessage());
                    }
                }
                
                report.append(String.format("""
                    ✅ 发送成功: %d 封
                    ❌ 发送失败: %d 封
                    总耗时: %s
                    """,
                    sentCount,
                    failedCount,
                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))));
            } else {
                report.append("\n💡 提示: 当前为测试模式,未实际发送邮件。");
                report.append("\n如需实际发送,请设置 testMode=false");
            }
            
            return report.toString();
            
        } catch (Exception e) {
            log.error("批量发送邮件失败", e);
            return "批量发送邮件失败: " + e.getMessage();
        }
    }
    
    /**
     * 个性化邮件模板
     */
    private String personalizeTemplate(String template, Customer customer) {
        return template
            .replace("{name}", customer.getName())
            .replace("{email}", customer.getEmail())
            .replace("{totalPurchases}", customer.getTotalPurchases().toString())
            .replace("{customerTier}", customer.getCustomerTier())
            .replace("{registrationDate}", 
                customer.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
    }
}

四、基于推理的信息提取 MCP 工具

java 复制代码
// InformationExtractionMCPTools.java
@Service
@Slf4j
public class InformationExtractionMCPTools {
    
    @Autowired
    private ChatLanguageModel chatModel;
    
    /**
     * 提取合同条款信息
     */
    @Tool(name = "extractContractClauses", description = """
        从合同文本中提取特定条款信息。
        参数:
        - contractText: 合同文本
        - clauseTypes: 要提取的条款类型(多个用逗号分隔,如:付款,交付,违约责任)
        - format: 输出格式(可选:json, text, table,默认 text)
        """)
    public String extractContractClauses(
            @P("合同文本") String contractText,
            @P("要提取的条款类型") String clauseTypes,
            @P("输出格式") @Optional(defaultValue = "text") String format) {
        
        try {
            // 构建提示词
            String prompt = String.format("""
                请从以下合同文本中提取以下类型的条款信息:
                需要提取的条款类型:%s
                
                合同文本:
                %s
                
                请按照以下要求提取信息:
                1. 识别合同中与上述条款类型相关的内容
                2. 提取关键信息:条款编号、主要内容、相关方、时间要求、金额等
                3. 如果某类条款不存在,请注明"未找到"
                4. 输出格式:%s
                
                请确保提取的信息准确、完整。
                """,
                clauseTypes,
                contractText.length() > 4000 ? 
                    contractText.substring(0, 4000) + "..." : contractText,
                format);
            
            // 调用 LLM 进行提取
            String extraction = chatModel.generate(prompt);
            
            return String.format("""
                ====== 合同条款信息提取结果 ======
                
                %s
                
                ====== 提取摘要 ======
                - 源文本长度: %d 字符
                - 提取的条款类型: %s
                - 输出格式: %s
                - 提取时间: %s
                """,
                extraction,
                contractText.length(),
                clauseTypes,
                format,
                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            
        } catch (Exception e) {
            log.error("提取合同条款信息失败", e);
            return "提取合同条款信息失败: " + e.getMessage();
        }
    }
    
    /**
     * 提取财务报表数据
     */
    @Tool(name = "extractFinancialData", description = """
        从财务报表文本中提取关键财务数据。
        参数:
        - financialText: 财务报表文本
        - dataTypes: 要提取的数据类型(多个用逗号分隔,如:营收,利润,现金流,负债)
        - period: 报告期间(可选:Q1 2024, FY2023 等)
        - companyName: 公司名称(可选)
        """)
    public String extractFinancialData(
            @P("财务报表文本") String financialText,
            @P("要提取的数据类型") String dataTypes,
            @P("报告期间") @Optional(defaultValue = "") String period,
            @P("公司名称") @Optional(defaultValue = "") String companyName) {
        
        try {
            // 构建提示词
            StringBuilder prompt = new StringBuilder();
            prompt.append("请从以下财务报表中提取指定的财务数据。\n\n");
            
            if (!companyName.isEmpty()) {
                prompt.append("公司名称:").append(companyName).append("\n");
            }
            
            if (!period.isEmpty()) {
                prompt.append("报告期间:").append(period).append("\n");
            }
            
            prompt.append(String.format("""
                需要提取的数据类型:%s
                
                财务报表文本:
                %s
                
                请按照以下要求提取信息:
                1. 识别并提取每种数据类型的数值
                2. 注明数值的单位(元、万元、亿元等)
                3. 如果可能,提供同比增长或环比变化
                4. 用表格形式组织数据
                5. 添加简要的分析和解读
                
                输出格式:
                - 公司名称
                - 报告期间
                - 数据表格
                - 关键发现
                - 风险提示
                """,
                dataTypes,
                financialText.length() > 3000 ? 
                    financialText.substring(0, 3000) + "..." : financialText));
            
            // 调用 LLM 进行提取
            String extraction = chatModel.generate(prompt.toString());
            
            return String.format("""
                ====== 财务报表数据提取结果 ======
                
                %s
                
                ====== 处理信息 ======
                - 源文本长度: %d 字符
                - 提取时间: %s
                - 处理状态: ✅ 完成
                """,
                extraction,
                financialText.length(),
                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            
        } catch (Exception e) {
            log.error("提取财务数据失败", e);
            return "提取财务数据失败: " + e.getMessage();
        }
    }
    
    /**
     * 从技术文档中提取 API 信息
     */
    @Tool(name = "extractApiInfoFromDocs", description = """
        从技术文档中提取 API 接口信息。
        参数:
        - techDocument: 技术文档文本
        - apiNameFilter: API 名称过滤(可选,支持模糊匹配)
        - includeDetails: 是否包含详细参数信息(默认 true)
        - formatAsTable: 是否格式化为表格(默认 true)
        """)
    public String extractApiInfoFromDocs(
            @P("技术文档文本") String techDocument,
            @P("API名称过滤") @Optional(defaultValue = "") String apiNameFilter,
            @P("是否包含详细参数信息") @Optional(defaultValue = "true") boolean includeDetails,
            @P("是否格式化为表格") @Optional(defaultValue = "true") boolean formatAsTable) {
        
        try {
            // 构建提示词
            String prompt = String.format("""
                请从以下技术文档中提取 API 接口信息。
                %s
                
                技术文档内容:
                %s
                
                提取要求:
                1. 识别所有的 API 端点(Endpoint)
                2. 提取每个 API 的 HTTP 方法(GET, POST, PUT, DELETE 等)
                3. 提取请求和响应格式
                %s
                4. 提取认证要求
                5. 提取速率限制信息
                6. %s
                
                请确保提取的信息准确、完整。
                """,
                apiNameFilter.isEmpty() ? "" : "API 名称过滤:" + apiNameFilter,
                techDocument.length() > 5000 ? 
                    techDocument.substring(0, 5000) + "..." : techDocument,
                includeDetails ? "提取详细的请求参数和响应字段" : "简要描述参数",
                formatAsTable ? "用表格形式组织 API 信息" : "用列表形式组织 API 信息");
            
            // 调用 LLM 进行提取
            String extraction = chatModel.generate(prompt);
            
            // 统计提取结果
            int apiCount = countApisInExtraction(extraction);
            
            return String.format("""
                ====== API 信息提取结果 ======
                
                %s
                
                ====== 提取统计 ======
                - 源文档长度: %d 字符
                - 提取的 API 数量: %d
                - 过滤条件: %s
                - 包含详细信息: %s
                - 表格格式: %s
                """,
                extraction,
                techDocument.length(),
                apiCount,
                apiNameFilter.isEmpty() ? "无" : apiNameFilter,
                includeDetails ? "是" : "否",
                formatAsTable ? "是" : "否");
            
        } catch (Exception e) {
            log.error("提取 API 信息失败", e);
            return "提取 API 信息失败: " + e.getMessage();
        }
    }
    
    /**
     * 统计提取的 API 数量
     */
    private int countApisInExtraction(String extraction) {
        // 简单的统计逻辑,实际可能需要更复杂的分析
        String[] lines = extraction.split("\n");
        int count = 0;
        
        for (String line : lines) {
            if (line.matches(".*(API|Endpoint|接口).*[::].*") || 
                line.matches(".*(GET|POST|PUT|DELETE|PATCH).*[/].*")) {
                count++;
            }
        }
        
        return Math.max(1, count / 2); // 粗略估计
    }
}

五、主应用集成与控制器

  1. 应用主类
java 复制代码
// Langchain4jMCPApplication.java
@SpringBootApplication
@EnableScheduling
@Slf4j
public class Langchain4jMCPApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(Langchain4jMCPApplication.class, args);
        log.info("LangChain4j MCP Demo 应用启动成功!");
    }
    
    @Bean
    public ChatLanguageModel chatLanguageModel(
            OpenAiChatModel openAiChatModel) {
        return openAiChatModel;
    }
    
    @Bean
    public Assistant aiAssistant(
            ChatLanguageModel chatModel,
            DatabaseMCPTools dbTools,
            WeatherMCPTools weatherTools,
            StockMCPTools stockTools,
            EmailMCPTools emailTools,
            InformationExtractionMCPTools infoExtractionTools) {
        
        return AiServices.builder(Assistant.class)
            .chatLanguageModel(chatModel)
            .tools(
                dbTools,
                weatherTools,
                stockTools,
                emailTools,
                infoExtractionTools
            )
            .promptTemplate("""
                你是一个智能助手,可以调用各种工具来帮助用户完成任务。
                你可以访问数据库、获取天气信息、查询股票数据、发送邮件以及从文档中提取信息。
                
                用户的问题:{{message}}
                
                请根据用户的问题,决定是否需要调用工具,以及调用哪些工具。
                如果你需要调用工具,请按照以下格式思考:
                1. 我需要调用什么工具?
                2. 需要什么参数?
                3. 如何组合多个工具的结果?
                
                请用专业、友好的方式回答用户的问题。
                """)
            .build();
    }
    
    @Bean
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}

// Assistant.java
interface Assistant {
    
    String chat(String message);
}
  1. 控制器
java 复制代码
// MCPDemoController.java
@RestController
@RequestMapping("/api/mcp")
@CrossOrigin(origins = "*")
@Slf4j
public class MCPDemoController {
    
    @Autowired
    private Assistant assistant;
    
    @Autowired
    private DatabaseMCPTools databaseTools;
    
    @Autowired
    private WeatherMCPTools weatherTools;
    
    @Autowired
    private StockMCPTools stockTools;
    
    /**
     * 智能问答接口
     */
    @PostMapping("/chat")
    public ResponseEntity<Map<String, Object>> chat(@RequestBody ChatRequest request) {
        try {
            long startTime = System.currentTimeMillis();
            
            String response = assistant.chat(request.getMessage());
            
            long processingTime = System.currentTimeMillis() - startTime;
            
            Map<String, Object> result = new HashMap<>();
            result.put("success", true);
            result.put("response", response);
            result.put("processingTime", processingTime + "ms");
            result.put("timestamp", LocalDateTime.now());
            
            log.info("Chat 处理完成,耗时:{}ms", processingTime);
            
            return ResponseEntity.ok(result);
            
        } catch (Exception e) {
            log.error("Chat 处理失败", e);
            
            Map<String, Object> error = new HashMap<>();
            error.put("success", false);
            error.put("error", e.getMessage());
            error.put("timestamp", LocalDateTime.now());
            
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(error);
        }
    }
    
    /**
     * 直接调用数据库工具
     */
    @PostMapping("/database/query")
    public ResponseEntity<Map<String, Object>> queryDatabase(@RequestBody DatabaseQueryRequest request) {
        try {
            String result;
            
            switch (request.getOperation().toLowerCase()) {
                case "customer":
                    result = databaseTools.queryCustomerInfo(
                        Optional.ofNullable(request.getCustomerId()),
                        Optional.ofNullable(request.getCustomerName()),
                        Optional.ofNullable(request.getCustomerEmail()),
                        request.isIncludeOrders());
                    break;
                    
                case "product":
                    result = databaseTools.queryProductStock(
                        Optional.ofNullable(request.getProductId()),
                        Optional.ofNullable(request.getProductName()),
                        Optional.ofNullable(request.getCategory()),
                        request.getLowStockThreshold());
                    break;
                    
                case "sales":
                    result = databaseTools.generateSalesReport(
                        request.getStartDate(),
                        request.getEndDate(),
                        request.getGroupBy(),
                        request.getReportType());
                    break;
                    
                default:
                    throw new IllegalArgumentException("不支持的操作类型: " + request.getOperation());
            }
            
            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("data", result);
            response.put("operation", request.getOperation());
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            log.error("数据库查询失败", e);
            
            Map<String, Object> error = new HashMap<>();
            error.put("success", false);
            error.put("error", e.getMessage());
            
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(error);
        }
    }
    
    /**
     * 获取天气信息
     */
    @GetMapping("/weather/{city}")
    public ResponseEntity<Map<String, Object>> getWeather(
            @PathVariable String city,
            @RequestParam(defaultValue = "CN") String country,
            @RequestParam(defaultValue = "3") int days) {
        
        try {
            String currentWeather = weatherTools.getRealTimeWeather(city, country, "metric", "zh_cn");
            String forecast = weatherTools.getWeatherForecast(city, country, days, "metric");
            
            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("city", city);
            response.put("country", country);
            response.put("currentWeather", currentWeather);
            response.put("forecast", forecast);
            response.put("timestamp", LocalDateTime.now());
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            log.error("获取天气信息失败", e);
            
            Map<String, Object> error = new HashMap<>();
            error.put("success", false);
            error.put("error", e.getMessage());
            
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(error);
        }
    }
    
    /**
     * 获取股票信息
     */
    @GetMapping("/stock/{symbol}")
    public ResponseEntity<Map<String, Object>> getStockInfo(
            @PathVariable String symbol,
            @RequestParam(defaultValue = "1day") String interval,
            @RequestParam(defaultValue = "") String startDate,
            @RequestParam(defaultValue = "") String endDate) {
        
        try {
            String price = stockTools.getStockPrice(symbol, "");
            
            String history = "";
            if (!startDate.isEmpty() && !endDate.isEmpty()) {
                history = stockTools.getStockHistory(symbol, interval, startDate, endDate);
            }
            
            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("symbol", symbol);
            response.put("priceInfo", price);
            response.put("historyInfo", history.isEmpty() ? "未查询历史数据" : history);
            
            return ResponseEntity.ok(response);
            
        } catch (Exception e) {
            log.error("获取股票信息失败", e);
            
            Map<String, Object> error = new HashMap<>();
            error.put("success", false);
            error.put("error", e.getMessage());
            
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(error);
        }
    }
    
    /**
     * 系统状态检查
     */
    @GetMapping("/status")
    public ResponseEntity<Map<String, Object>> getSystemStatus() {
        Map<String, Object> status = new HashMap<>();
        
        status.put("application", "LangChain4j MCP Demo");
        status.put("version", "1.0.0");
        status.put("status", "RUNNING");
        status.put("timestamp", LocalDateTime.now());
        status.put("availableTools", Arrays.asList(
            "Database Tools",
            "Weather Tools", 
            "Stock Tools",
            "Email Tools",
            "Information Extraction Tools"
        ));
        
        return ResponseEntity.ok(status);
    }
    
    // 请求对象定义
    @Data
    public static class ChatRequest {
        private String message;
        private String sessionId;
    }
    
    @Data
    public static class DatabaseQueryRequest {
        private String operation; // customer, product, sales
        private Long customerId;
        private String customerName;
        private String customerEmail;
        private boolean includeOrders = false;
        
        private Long productId;
        private String productName;
        private String category;
        private int lowStockThreshold = 10;
        
        private String startDate;
        private String endDate;
        private String groupBy = "month";
        private String reportType = "summary";
    }
}

六、测试与使用示例

  1. 测试类
java 复制代码
// MCPToolsTest.java
@SpringBootTest
@Slf4j
public class MCPToolsTest {
    
    @Autowired
    private Assistant assistant;
    
    @Test
    public void testDatabaseIntegration() {
        String response = assistant.chat("查询客户ID为1的信息,包含订单记录");
        log.info("数据库集成测试结果:\n{}", response);
        assertNotNull(response);
    }
    
    @Test
    public void testWeatherQuery() {
        String response = assistant.chat("查询北京今天的天气");
        log.info("天气查询测试结果:\n{}", response);
        assertNotNull(response);
    }
    
    @Test
    public void testStockQuery() {
        String response = assistant.chat("查询苹果公司(AAPL)的股票价格");
        log.info("股票查询测试结果:\n{}", response);
        assertNotNull(response);
    }
    
    @Test
    public void testInformationExtraction() {
        String contractText = """
            本合同由甲方(采购方)与乙方(供应方)于2024年1月1日签订。
            1. 付款条款:甲方应在收到货物后30天内支付全部货款。
            2. 交付条款:乙方应在合同签订后15个工作日内完成货物交付。
            3. 违约责任:任何一方违约应支付合同总金额20%的违约金。
            """;
        
        String response = assistant.chat(
            "从以下合同中提取付款和交付条款信息:" + contractText);
        log.info("信息提取测试结果:\n{}", response);
        assertNotNull(response);
    }
    
    @Test
    public void testComplexQuery() {
        String response = assistant.chat("""
            帮我做以下事情:
            1. 查询上海今天的天气
            2. 查询特斯拉(TSLA)的股票价格
            3. 查询库存少于5个的产品
            4. 生成上个月的销售报告
            """);
        
        log.info("复杂查询测试结果:\n{}", response);
        assertNotNull(response);
    }
}
  1. 使用示例
java 复制代码
# 启动应用
mvn spring-boot:run

# 测试接口调用
curl -X POST http://localhost:8080/api/mcp/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "查询北京今天的天气和苹果公司的股票价格"}'

# 直接调用数据库工具
curl -X POST http://localhost:8080/api/mcp/database/query \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "customer",
    "customerName": "张"
  }'

# 获取系统状态
curl http://localhost:8080/api/mcp/status

这个 DEMO 展示了如何使用 Java 和 LangChain4j 实现 MCP 的几个关键用例:

已实现的功能:

  1. 数据库集成

    • 客户信息查询

    • 产品库存管理

    • 销售报告生成

    • 客户记录更新

  2. 外部 API 交互

    • 实时天气查询

    • 天气预报获取

    • 股票价格查询

    • 股票历史数据分析

    • 电子邮件发送

  3. 基于推理的信息提取

    • 合同条款提取

    • 财务报表数据提取

    • 技术文档 API 信息提取

技术特点:

  1. 模块化设计:每个工具都是独立的服务组件

  2. 类型安全:使用 Java 注解确保参数类型安全

  3. 易于扩展:可以轻松添加新的工具

  4. 与 LLM 无缝集成:通过 LangChain4j 的工具调用机制

  5. 完整的错误处理:每个工具都有完善的异常处理

进一步优化方向:

  1. 添加认证和授权:保护敏感操作

  2. 实现异步处理:提高并发性能

  3. 添加缓存机制:减少重复 API 调用

  4. 实现工具注册中心:动态发现和注册工具

  5. 添加监控和日志:更好的运维支持

这个 DEMO 可以作为构建企业级 AI 应用的基础框架,展示了如何将传统 Java 开发与前沿的 AI 技术相结合。

结论

  1. 模型上下文协议(MCP)是开放标准,促进大型语言模型(LLM)与外部系统间通信。它采用客户端‐服务器 架构,使 LLM 能通过标准化工具访问资源、使用提示和执行操作。MCP 允许 LLM 与数据库交互、管理生成 媒体工作流、控制物联网设备以及自动化金融服务。实际示例演示了设置 Agent 与 MCP 服务器通信的方法, 包括文件系统服务器和使用 FastMCP 构建的服务器,说明了其与 Agent 开发工具包(ADK)的集成。MCP 是开发超越基本语言能力的交互式 AI Agent 的关键组件。

参考文献

1、Model Context Protocol (MCP) Documentation. (Latest). Model Context Protocol (MCP). https:// google.github.io/adk‐docs/mcp/

2、FastMCPDocumentation.FastMCP.https://github.com/jlowin/fastmcp

3、MCP Tools for Genmedia Services. MCP Tools for Genmedia Services. https://google.github.io/adk‐docs/mcp/#mcp‐servers‐for‐google‐cloud‐genmedia

相关推荐
沛沛老爹2 小时前
从Web到AI:金融/医疗/教育行业专属Skills生态系统设计实战
java·前端·人工智能·git·金融·架构
Robert--cao2 小时前
ubuntu22.04使用Isaac Sim 4.5.1与Isaac Lab 2.1.0完成BeyondMimic 环境
人工智能·算法·机器人
乾元2 小时前
黑盒之光——机器学习三要素在安全领域的投影
运维·网络·人工智能·网络协议·安全·机器学习·架构
Jouham2 小时前
正式上线!瞬维AI获客新功能解锁:客资自动沉淀+数据可视化,告别手动低效管理
人工智能
UR的出不克2 小时前
基于机器学习的足球比赛预测系统 - 完整开发教程
人工智能·爬虫·python·深度学习·机器学习
Yu_Lijing2 小时前
基于C++的《Head First设计模式》笔记——迭代器模式
笔记·设计模式
石去皿2 小时前
机器学习面试·易错速问速答 30 题
人工智能·机器学习·面试
geffen16882 小时前
支持语音识别并控制的混合高清矩阵:革新视听体验,开启智能控制新时代
人工智能·矩阵·语音识别
石去皿2 小时前
深度学习面试高频问题和答复
人工智能·深度学习·面试