206-Spring AI Alibaba MCP 股票查询服务器示例

本示例演示如何使用 Java 开发一个 MCP (Model Context Protocol) 服务器,实现股票查询功能。该服务器可以获取指定股票代码的实时信息,包括当前价格、最高/最低价、开盘价、交易量和交易金额等。
1. 示例目标
我们将创建一个 MCP 服务器,实现以下功能:
- 股票信息查询:通过股票代码查询实时股票信息,包括当前价格、最高/最低价、开盘价、交易量和交易金额等。
- MCP 协议支持:实现 MCP 协议,使 AI 模型能够调用此服务器获取股票信息。
2. 技术栈与核心依赖
- Spring Boot 3.x
- Spring AI MCP Server (用于实现 MCP 协议)
- Spring Web (用于 HTTP 请求)
- Maven (项目构建工具)
在 pom.xml 中,你需要引入以下核心依赖:
org.springframework.ai
spring-ai-starter-mcp-server
org.springframework
spring-web
org.springframework.boot
spring-boot-starter
3. 项目配置
在 src/main/resources/application.yml 文件中,配置 MCP 服务器信息。
spring:
main:
web-application-type: none
banner-mode: off
ai:
mcp:
server:
name: my-stock-server
version: 0.0.1
# NOTE: You must disable the banner and the console logging
# to allow the STDIO transport to work !!!
重要提示:MCP 服务器需要通过 STDIO 传输工作,因此必须禁用 Web 应用程序类型、横幅和控制台日志。
4. 实现股票服务
4.1 StockService.java
实现股票信息查询的核心服务。
package com.alibaba.cloud.ai.example.stock.service;
import java.io.Serializable;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
/**
* Stock service for retrieving real-time stock information from Eastmoney API.
* This service provides functionality to fetch stock data including current price,
* high/low prices, opening price, trading volume, and amount.
*
*/
@Service
public class StockService {
private static final Logger logger = LoggerFactory.getLogger(StockService.class);
private static final String BASE_URL = "https://push2.eastmoney.com/api/qt/stock/get";
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final ObjectMapper objectMapper = new ObjectMapper();
private final RestClient restClient;
public StockService() {
this.restClient = RestClient.builder()
.baseUrl(BASE_URL)
.defaultHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
.build();
}
@JsonIgnoreProperties(ignoreUnknown = true)
public record StockData(
@JsonProperty("f43") Double currentPrice, // Latest price (in cents)
@JsonProperty("f44") Double highPrice, // Highest price (in cents)
@JsonProperty("f45") Double lowPrice, // Lowest price (in cents)
@JsonProperty("f46") Double openPrice, // Opening price (in cents)
@JsonProperty("f47") Double volume, // Trading volume (in lots)
@JsonProperty("f48") Double amount, // Trading amount (in yuan)
@JsonProperty("f57") String code, // Stock code
@JsonProperty("f58") String name) { // Stock name
}
@JsonSerialize
public record StockInfo(
@JsonProperty("code") String code,
@JsonProperty("name") String name,
@JsonProperty("currentPrice") Double currentPrice,
@JsonProperty("highPrice") Double highPrice,
@JsonProperty("lowPrice") Double lowPrice,
@JsonProperty("openPrice") Double openPrice,
@JsonProperty("volume") Double volume,
@JsonProperty("amount") Double amount
) implements Serializable {
}
@Tool(name = "getStockInfo", description = "Get real-time stock information for the specified stock code")
public StockInfo getStockInfo(String stockCode) {
try {
// Validate stock code format
if (!stockCode.matches("^[0-9]{6}$")) {
throw new IllegalArgumentException("Stock code must be 6 digits");
}
logger.info("Fetching stock information for {}", stockCode);
// Eastmoney API parameters
String secid = stockCode.startsWith("6") ? "1." + stockCode : "0." + stockCode;
String response = restClient.get()
.uri(uriBuilder -> uriBuilder
.queryParam("secid", secid)
.queryParam("fields", "f43,f44,f45,f46,f47,f48,f57,f58")
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.body(String.class);
logger.info("Raw response: {}", response);
JsonNode root = objectMapper.readTree(response);
JsonNode data = root.path("data");
if (data.isMissingNode()) {
logger.warn("No stock data found");
throw new IllegalArgumentException("No information found for stock code " + stockCode);
}
StockData stockData = objectMapper.treeToValue(data, StockData.class);
logger.info("Parsed data: {}", stockData);
if (stockData == null || stockData.name() == null) {
throw new IllegalArgumentException("Invalid data format for stock code " + stockCode);
}
// Convert data format
return new StockInfo(
stockCode,
stockData.name(),
stockData.currentPrice() / 100.0, // Convert to yuan
stockData.highPrice() / 100.0, // Convert to yuan
stockData.lowPrice() / 100.0, // Convert to yuan
stockData.openPrice() / 100.0, // Convert to yuan
stockData.volume() / 10000.0, // Convert to 10,000 lots
stockData.amount() / 100000000.0 // Convert to 100 million yuan
);
} catch (IllegalArgumentException e) {
logger.error("Parameter error: {}", e.getMessage());
throw e;
} catch (Exception e) {
logger.error("Failed to get stock {} information: {}", stockCode, e.getMessage(), e);
throw new RuntimeException("Failed to get stock " + stockCode + " information: " + e.getMessage());
}
}
}
4.2 StockServerApplication.java
实现主应用程序类,配置 MCP 服务器。
package com.alibaba.cloud.ai.example.stock;
import com.alibaba.cloud.ai.example.stock.service.StockService;
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 StockServerApplication {
public static void main(String[] args) {
SpringApplication.run(StockServerApplication.class, args);
}
@Bean
public ToolCallbackProvider stockTools(StockService stockService) {
return MethodToolCallbackProvider.builder().toolObjects(stockService).build();
}
}
5. 实现原理与关键点
5.1 MCP 服务器实现
本示例的核心是使用 Spring AI MCP Server 框架实现一个 MCP 服务器。关键点包括:
- 配置 MCP 服务器:在
application.yml中配置服务器名称和版本。 - 禁用 Web 应用程序:设置
web-application-type: none,使应用程序不启动 Web 容器。 - 禁用横幅和控制台日志:设置
banner-mode: off,避免干扰 STDIO 传输。 - 注册工具回调:通过
ToolCallbackProvider将股票服务注册为 MCP 工具。
5.2 股票数据获取
股票数据通过东方财富 API 获取,关键实现点包括:
- API 请求:使用
RestClient向东方财富 API 发送 HTTP GET 请求。 - 参数构造:根据股票代码构造 API 参数,上海证券交易所股票(6开头)使用 "1." 前缀,深圳证券交易所股票使用 "0." 前缀。
- 数据解析:使用 Jackson 解析 JSON 响应,提取股票信息。
- 数据转换:将 API 返回的数据(如价格以分为单位)转换为更易读的格式(如价格以元为单位)。
5.3 工具注解
使用 @Tool 注解将股票查询方法标记为 MCP 工具:
@Tool(name = "getStockInfo", description = "Get real-time stock information for the specified stock code")
public StockInfo getStockInfo(String stockCode) {
// 方法实现
}
这个注解使 AI 模型能够识别并调用此方法,获取股票信息。
6. 运行与测试
6.1 构建和运行
使用 Maven 构建并运行应用程序:
# 构建项目
mvn clean package
# 运行应用程序
java -jar target/starter-stock-server-0.0.1-SNAPSHOT.jar
6.2 测试股票查询
您可以通过以下方式测试股票查询功能:
- 将 MCP 服务器连接到支持 MCP 协议的 AI 客户端。
- 在 AI 客户端中请求查询股票信息,例如:"查询股票代码 000001 的信息"。
- AI 客户端将通过 MCP 协议调用
getStockInfo工具,并传入股票代码 "000001"。 - 服务器将返回股票信息,包括当前价格、最高/最低价、开盘价、交易量和交易金额等。
6.3 预期输出示例
查询股票代码 "000001"(平安银行)的预期输出:
{
"code": "000001",
"name": "平安银行",
"currentPrice": 10.50,
"highPrice": 10.65,
"lowPrice": 10.45,
"openPrice": 10.60,
"volume": 125000.0,
"amount": 1312500000.0
}
7. 扩展建议
7.1 添加更多股票相关工具
可以扩展此服务器,添加更多股票相关功能,例如:
- 股票历史数据查询:添加查询历史股票价格的功能。
- 股票筛选:添加根据特定条件筛选股票的功能。
- 股票分析:添加基本的技术分析功能,如移动平均线、相对强弱指数等。
7.2 添加其他金融工具
除了股票,还可以添加其他金融工具的查询功能:
- 基金查询:添加查询基金信息的功能。
- 债券查询:添加查询债券信息的功能。
- 汇率查询:添加查询汇率信息的功能。
7.3 集成更多数据源
可以集成多个数据源,提供更全面的金融信息:
- 多数据源集成:集成多个金融数据提供商,提供更可靠的数据。
- 数据缓存:添加缓存层,减少 API 调用次数,提高响应速度。
- 数据验证:添加数据验证机制,确保数据的准确性和一致性。
7.4 安全性和性能优化
- API 密钥管理:如果使用需要 API 密钥的数据源,添加安全的密钥管理机制。
- 请求限流:添加请求限流机制,避免超过数据源的 API 调用限制。
- 错误处理:增强错误处理机制,提供更友好的错误信息。








