从 0 开始学习大模型应用开发(加餐二)- 使用Spring AI开发MCP系统

项目源码地址:

GitHub - brianxiadong/llm-langchain-project: llm-langchain-project

路过的朋友请帮忙点个star

一、什么是 Spring AI

Spring AI (https://spring.io/projects/spring-ai)]是 Spring 官方于 2023 年推出的 AI 应用开发框架,它如同 AI 世界的"Spring 生态连接器",致力于简化开发集成了 AI 功能的应用程序。它主要解决两大核心问题:

  1. 统一接口:消除不同 AI 服务(如 OpenAI、智谱 AI、DeepSeek 等)的 API 差异,允许用户灵活地在多个模型之间切换。
  2. 生态整合:将 AI 能力无缝融入 Spring 技术栈(如 Spring Boot、Spring MVC)。

在这里插入图片描述

如上图所示,Spring AI 成为了连接企业数据以及 API 和生成式 AI 的桥梁。

🛠️ 核心功能矩阵

Spring AI 具备的能力如下:

它具备以下特点:

  1. 即插即用:通过更换 application.yml 配置即可切换大模型供应商。
  2. 响应式支持:原生集成 Reactor Streams,支持背压控制的流式响应。
  3. 扩展性强:自定义 ChatClient 实现可接入任意 AI 服务。

😎Spring AI 能做什么?

那么 Spring AI 到底能做什么呢?这里我给出几个常见案例,在咱们这套课程中也会带着大家完成这些案例:

案例 1:智能客服系统

  • 场景:集成自然语言处理的客服机器人,处理用户咨询、订单查询和售后问题。

  • AI 价值:

    • 减少人工客服成本,24/7 响应客户需求。
    • 通过意图识别提升问题解决效率(准确率可达 85%+)。
  • Spring AI 实现:
    使用 Spring AI 的对话模型接口(如 OpenAI 或 智谱 AI),快速构建企业级对话流。


案例 2:智能数据分析平台

  • 场景:自动分析企业销售数据、用户行为日志,生成可视化报告和预测建议。

  • AI 价值:

    • 通过时间序列预测优化库存管理(降低 20%-30% 滞销风险)。
    • 实时异常检测(如金融反欺诈)。
  • Spring AI 实现:
    结合 Spring AI 和 MCP 组件,对数据库进行访问,实现数据预处理与预测 API。


案例 3:自动化文档处理

  • 场景:合同、发票的自动分类、关键词提取和合规性审查。

  • AI 价值:

    • 节省 90% 人工文档处理时间。
    • 通过 OCR 实现非结构化数据标准化。
  • Spring AI 实现:
    调用 Spring AI 的多模态接口,实现文档解析流水线。


案例 4:智能营销内容生成

  • 场景:自动化生成广告文案、社交媒体推文、邮件营销内容,支持多语言适配。

  • AI 价值:

    • 缩短 70% 的创意内容生产周期。
    • 通过 A/B 测试数据反馈优化生成策略(点击率提升 15%-25%)。
  • Spring AI 实现:
    调用 大模型接口,结合企业品牌风格指南定制生成规则。


案例 5:语音客服系统

  • 场景:通过语音交互处理用户来电(如银行催收、快递查询、政务热线),支持多语言、方言识别和情感分析。

  • AI 价值:

    • 成本降低:替代 60% 以上重复性语音服务(如账单查询)。
    • 效率提升:语音响应速度 <1 秒(传统 IVR 需 5-10 秒菜单导航)。
  • Spring AI 实现:

    1. 语音识别
      • 集成 Whisper 模型,将用户语音转为文本。
      • 支持实时流式传输(降低延迟)。
    2. 自然语言处理(NLP)
      • 使用 Spring AI 的对话模型解析用户意图,生成响应文本。
      • 情感分析:识别用户情绪(愤怒/焦虑),触发人工坐席接管。
    3. 语音合成(TTS)
      • 调用 AI 模型生成拟人化语音反馈。

二、MCP的三种类型

1. stdio(标准输入输出)
工作原理:
基于进程间通信,客户端启动 MCP Server 子进程,通过标准输入(stdin)接收请求,标准输出(stdout)返回响应,消息格式为 JSON-RPC 2.0。

技术特点:

  • 低延迟:无需网络开销,适合实时性要求高的本地场景(如 IDE 插件与进程交互)。
  • 强耦合性:客户端与服务器需运行在同一主机,呈 1:1 绑定关系。
  • 安全性高:数据不经过网络传输,可避免中间人攻击等风险。
    适用场景:
    主要用于本地或容器化环境下的进程间通信,简化部署流程,实现跨平台轻量高效部署。

2. sse(Server-Sent Events)
工作原理:
基于 HTTP 请求,服务器到客户端的通信使用 SSE,客户端到服务器的通信使用 POST 请求,消息格式为 JSON-RPC 2.0。
技术特点:

  • 适合远程连接:是 MCP 在网络环境中的主要实现技术,适用于云服务和远程 API 调用。
  • 长连接特性:依赖长连接维持通信,服务器需保持连接不中断,属于有状态连接。
  • 并发处理需求:HTTP 服务器需具备并发能力(如通过进程池提升并行处理能力)。

适用场景:
适用于服务器向客户端实时推送消息的远程场景(如实时数据更新、服务器状态通知),但并发管理较复杂。

3. streamable http
工作原理:
扩展的 HTTP 协议,允许服务器生成数据时逐步发送给客户端,可动态将普通 HTTP 请求升级为 SSE 流(用于通知或请求)。所有客户端到服务器的消息通过统一的 /message 端点传输,客户端通过头部提供 session id。
技术特点:

  • 灵活性高:服务器可自主决定是否支持流式传输,无需单独的 /sse 端点。
  • 支持无状态服务器:消除持久连接需求,资源在请求处理后释放,适合高并发场景。
  • 兼容性强:基于标准 HTTP,可无缝集成 CDN、API 网关、负载均衡器等,支持在 Vercel、Cloudflare、AWS Lambda 等云平台部署。

适用场景:
适用于各种网络环境,尤其适合对服务器灵活性、可扩展性和兼容性要求高的场景,适配现代分布式系统和高并发应用。


三、使用Spring AI搭建stdio的MCP服务端程序

官方文档:
https://docs.spring.io/spring-ai/reference/1.1-SNAPSHOT/api/mcp/mcp-server-boot-starter-docs.html
官方案例:
https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server

环境要求

  • Java 17 或更高版本
  • Maven 3.6 或更高版本
  • Spring Boot 3.4.5
  • Spring AI 1.1.0-SNAPSHOT

项目结构

spring-ai-mcp-weather/
├── pom.xml
├── src/
│   └── main/
│       ├── java/
│       │   └── org/springframework/ai/mcp/saample/server/
│       │       ├── McpServerApplication.java
│       │       └── WeatherService.java
│       └── resources/
│           └── application.properties
├── start-mcp-server.sh
└── mcp-config-example.json

步骤1:创建Maven项目

pom.xml 配置

<?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 http://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.4.5</version>
        <relativePath />
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-ai-mcp-weather</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Spring AI MCP Weather Server</name>
    <description>Spring AI MCP Weather STDIO Server</description>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.1.0-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Spring AI MCP Server Starter -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server</artifactId>
        </dependency>
        
        <!-- Spring Web for RestClient -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        
        <!-- Test Dependencies -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <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>
</project>

步骤2:配置应用属性

application.properties

# 禁用Web应用类型,使用STDIO传输
spring.main.web-application-type=none

# 重要:必须禁用banner和控制台日志以使STDIO传输正常工作
spring.main.banner-mode=off
logging.pattern.console=

# MCP服务器配置
spring.ai.mcp.server.name=weather-mcp
spring.ai.mcp.server.version=0.0.1

# 日志配置
logging.file.name=./logs/mcp-weather-server.log
logging.level.org.springframework.ai.mcp=DEBUG

步骤3:创建主应用类

McpServerApplication.java

package org.springframework.ai.mcp.sample.server;

import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class McpServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }

    @Bean
    public ToolCallbackProvider weatherTools(WeatherService weatherService) {
        return MethodToolCallbackProvider.builder()
            .toolObjects(weatherService)
            .build();
    }
}

步骤4:实现天气服务

WeatherService.java

package org.springframework.ai.mcp.sample.server;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;

@Service
public class WeatherService {

    private static final String BASE_URL = "https://api.weather.gov";
    private final RestClient restClient;

    public WeatherService() {
        this.restClient = RestClient.builder()
                .baseUrl(BASE_URL)
                .defaultHeader("Accept", "application/geo+json")
                .defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
                .build();
    }

    // 数据传输对象
    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Points(@JsonProperty("properties") Props properties) {
        @JsonIgnoreProperties(ignoreUnknown = true)
        public record Props(@JsonProperty("forecast") String forecast) {
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Forecast(@JsonProperty("properties") Props properties) {
        @JsonIgnoreProperties(ignoreUnknown = true)
        public record Props(@JsonProperty("periods") List<Period> periods) {
        }

        @JsonIgnoreProperties(ignoreUnknown = true)
        public record Period(
                @JsonProperty("number") Integer number,
                @JsonProperty("name") String name,
                @JsonProperty("startTime") String startTime,
                @JsonProperty("endTime") String endTime,
                @JsonProperty("isDaytime") Boolean isDayTime,
                @JsonProperty("temperature") Integer temperature,
                @JsonProperty("temperatureUnit") String temperatureUnit,
                @JsonProperty("temperatureTrend") String temperatureTrend,
                @JsonProperty("probabilityOfPrecipitation") Map probabilityOfPrecipitation,
                @JsonProperty("windSpeed") String windSpeed,
                @JsonProperty("windDirection") String windDirection,
                @JsonProperty("icon") String icon,
                @JsonProperty("shortForecast") String shortForecast,
                @JsonProperty("detailedForecast") String detailedForecast) {
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    public record Alert(@JsonProperty("features") List<Feature> features) {
        @JsonIgnoreProperties(ignoreUnknown = true)
        public record Feature(@JsonProperty("properties") Properties properties) {
        }

        @JsonIgnoreProperties(ignoreUnknown = true)
        public record Properties(
                @JsonProperty("event") String event,
                @JsonProperty("areaDesc") String areaDesc,
                @JsonProperty("severity") String severity,
                @JsonProperty("description") String description,
                @JsonProperty("instruction") String instruction) {
        }
    }

    /**
     * 获取指定经纬度的天气预报
     */
    @Tool(description = "Get weather forecast for a specific latitude/longitude")
    public String getWeatherForecastByLocation(double latitude, double longitude) {
        var points = restClient.get()
                .uri("/points/{latitude},{longitude}", latitude, longitude)
                .retrieve()
                .body(Points.class);

        var forecast = restClient.get()
                .uri(points.properties().forecast())
                .retrieve()
                .body(Forecast.class);

        String forecastText = forecast.properties().periods().stream().map(p -> {
            return String.format("""
                    %s:
                    Temperature: %s %s
                    Wind: %s %s
                    Forecast: %s
                    """, p.name(), p.temperature(), p.temperatureUnit(), 
                    p.windSpeed(), p.windDirection(), p.detailedForecast());
        }).collect(Collectors.joining());

        return forecastText;
    }

    /**
     * 获取美国各州的天气警报
     */
    @Tool(description = "Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)")
    public String getAlerts(@ToolParam(description = "Two-letter US state code (e.g. CA, NY") String state) {
        Alert alert = restClient.get()
                .uri("/alerts/active/area/{state}", state)
                .retrieve()
                .body(Alert.class);

        return alert.features()
                .stream()
                .map(f -> String.format("""
                        Event: %s
                        Area: %s
                        Severity: %s
                        Description: %s
                        Instructions: %s
                        """, f.properties().event(), f.properties().areaDesc(), 
                        f.properties().severity(), f.properties().description(), 
                        f.properties().instruction()))
                .collect(Collectors.joining("\n"));
    }
}

步骤5:构建和运行

构建项目

mvn clean package

创建启动脚本

创建 start-mcp-server.sh 文件:

#!/bin/bash

# JAR文件路径
JAR_FILE="target/spring-ai-mcp-weather-0.0.1-SNAPSHOT.jar"

# 检查JAR文件是否存在
if [ ! -f "$JAR_FILE" ]; then
    echo "错误: JAR文件不存在: $JAR_FILE"
    echo "请先运行: mvn clean package"
    exit 1
fi

# 检查Java是否安装
if ! command -v java &> /dev/null; then
    echo "错误: 未找到Java运行环境"
    echo "请安装Java 17或更高版本"
    exit 1
fi

echo "正在启动MCP服务器..."
java -jar "$JAR_FILE"

运行服务器

chmod +x start-mcp-server.sh
./start-mcp-server.sh

步骤6:MCP客户端配置

Claude Desktop配置

创建 mcp-config.json 文件:

{
    "mcpServers": {
        "weather": {
            "command": "java",
            "args": [
                "-jar",
                "全路径/spring-ai-mcp-weather-0.0.1-SNAPSHOT.jar"
            ],
            "env": {
                "JAVA_OPTS": "-Xmx512m"
            }
        }
    }
}

关键配置说明

1. STDIO传输配置

MCP使用STDIO进行通信,因此必须:

  • 设置 spring.main.web-application-type=none
  • 禁用banner:spring.main.banner-mode=off
  • 清空控制台日志格式:logging.pattern.console=

2. Tool注解

  • @Tool:标记方法为可调用的工具
  • @ToolParam:为参数提供描述信息
  • 方法必须是public且返回String类型

3. 依赖管理

  • 使用Spring AI BOM管理版本
  • 核心依赖:spring-ai-starter-mcp-server
  • Web客户端:spring-web

测试工具

可以通过以下方式测试MCP服务器:

  1. 直接调用:在WeatherService中添加main方法进行测试
  2. MCP客户端:配置Claude Desktop或其他MCP客户端
  3. 单元测试:编写Spring Boot测试用例

扩展功能

添加新工具

  1. 在服务类中添加带@Tool注解的方法
  2. 在主应用类中注册ToolCallbackProvider
  3. 重新构建和部署

错误处理

@Tool(description = "Safe weather forecast with error handling")
public String getSafeWeatherForecast(double latitude, double longitude) {
    try {
        return getWeatherForecastByLocation(latitude, longitude);
    } catch (RestClientException e) {
        return "Weather service temporarily unavailable: " + e.getMessage();
    }
}

常见问题

  1. STDIO通信失败:检查是否正确禁用了banner和控制台日志
  2. 工具未注册:确保在主应用类中正确配置了ToolCallbackProvider
  3. API调用失败:检查网络连接和API密钥配置

总结

通过以上步骤,您已经成功创建了一个完整的Spring AI MCP服务端程序。该服务器提供了天气预报和天气警报两个工具,可以被任何支持MCP协议的AI助手调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值