一、MCP 简介
MCP(Model Context Protocol)是 Spring AI 提供的工具调用协议,允许 AI 模型通过标准化接口调用外部工具。本项目是一个基于 Spring Boot 的图片搜索 MCP 服务器实现。
三、开发步骤
步骤 1:创建 Maven 项目
在 pom.xml 中添加以下核心依赖:
xml
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring AI MCP Server WebMVC 依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
<!-- Hutool 工具库(可选,用于HTTP请求和JSON处理) -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.37</version>
</dependency>
步骤 2:创建工具类
创建一个 Spring Service 类,并使用 @Tool 注解标记工具方法:
java
package com.imagesearchmcpserver.tools;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
@Service
public class ImageSearchTool {
@Tool(description = "search image from web")
public String searchImage(@ToolParam(description = "Search query keyword") String query) {
// 实现工具逻辑
return searchMediumImages(query).toString();
}
}
关键注解说明:
| 注解 | 用途 |
|---|---|
@Tool |
标记该方法为 MCP 工具,description 用于描述工具功能 |
@ToolParam |
标记方法参数,description 用于描述参数含义 |
@Service |
注册为 Spring Bean |
步骤 3:配置工具提供者
在启动类中配置 ToolCallbackProvider Bean:
java
@SpringBootApplication
public class YuImageSearchMcpServerApplication {
public static void main(String[] args) {
SpringApplication.run(YuImageSearchMcpServerApplication.class, args);
}
@Bean
public ToolCallbackProvider imageSearchTools(ImageSearchTool imageSearchTool) {
return MethodToolCallbackProvider.builder()
.toolObjects(imageSearchTool)
.build();
}
}
步骤 4:配置应用
创建三种配置文件:
4.1 application.yml(默认配置)
yaml
spring:
application:
name: image-search-mcp-server
profiles:
active: sse # 默认使用 SSE 模式
server:
port: 8127
4.2 application-sse.yml(SSE 模式)
yaml
spring:
ai:
mcp:
server:
name: image-search-mcp-server
version: 0.0.1
type: SYNC
stdio: false # 启用 HTTP/SSE 模式
4.3 application-stdio.yml(标准输入输出模式)
yaml
spring:
ai:
mcp:
server:
name: image-search-mcp-server
version: 0.0.1
type: SYNC
stdio: true # 启用标准输入输出模式
main:
web-application-type: none # 非 Web 应用
banner-mode: off # 关闭启动 Banner
步骤 5:实现业务逻辑
在工具类中实现具体的业务逻辑。本项目以 Pexels 图片搜索 API 为例:
java
public List<String> searchMediumImages(String query) {
// 1. 设置请求头(包含 API 密钥)
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", API_KEY);
// 2. 设置请求参数
Map<String, Object> params = new HashMap<>();
params.put("query", query);
// 3. 发送 HTTP 请求
String response = HttpUtil.createGet(API_URL)
.addHeaders(headers)
.form(params)
.execute()
.body();
// 4. 解析响应并提取图片 URL
return JSONUtil.parseObj(response)
.getJSONArray("photos")
.stream()
.map(photoObj -> (JSONObject) photoObj)
.map(photoObj -> photoObj.getJSONObject("src"))
.map(photo -> photo.getStr("medium"))
.filter(StrUtil::isNotBlank)
.collect(Collectors.toList());
}
四、启动方式
方式 1:SSE 模式(默认)
bash
# 开发环境运行
./mvnw spring-boot:run
# 或打包后运行
./mvnw clean package
java -jar target/image-search-mcp-server-0.0.1-SNAPSHOT.jar
方式 2:Stdio 模式
bash
./mvnw spring-boot:run -Dspring-boot.run.profiles=stdio
# 或
java -jar target/image-search-mcp-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=stdio
五、两种运行模式对比
| 特性 | SSE 模式 | Stdio 模式 |
|---|---|---|
| 通信方式 | HTTP + Server-Sent Events | 标准输入/输出流 |
| 适用场景 | 远程服务、多客户端连接 | 本地进程调用、安全沙箱环境 |
| Web 服务 | 是 | 否 |
| 配置参数 | stdio: false |
stdio: true |
六、MCP 工具调用流程
┌─────────────────┐ HTTP/SSE ┌──────────────────────┐
│ AI 客户端 │ ────────────────► │ MCP Server │
│ (如 LlamaIndex)│ │ (Spring Boot) │
└─────────────────┘ └──────────┬───────────┘
│
▼
┌──────────────────────┐
│ ToolCallbackProvider│
│ 发现并注册工具方法 │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ ImageSearchTool │
│ 执行具体业务逻辑 │
└──────────────────────┘
七、扩展开发
添加新工具
只需创建新的 Service 类并添加 @Tool 注解:
java
@Service
public class WeatherTool {
@Tool(description = "Get current weather")
public String getWeather(@ToolParam(description = "City name") String city) {
// 实现天气查询逻辑
return "Weather in " + city + ": sunny";
}
}
更新工具提供者
在启动类中添加新工具:
java
@Bean
public ToolCallbackProvider tools(ImageSearchTool imageSearchTool, WeatherTool weatherTool) {
return MethodToolCallbackProvider.builder()
.toolObjects(imageSearchTool, weatherTool)
.build();
}
八、注意事项
- API Key 管理:敏感信息如 API Key 应通过环境变量或配置中心管理,避免硬编码
- 错误处理:工具方法应妥善处理异常,返回友好的错误信息
- 参数校验:对输入参数进行必要的校验
- 日志记录:建议添加适当的日志记录便于排查问题
- 依赖版本:确保 Spring AI BOM 版本与 MCP Server 依赖版本一致
九、前端调用方式
9.1 前端项目结构
ai-agent-frontend/
├── src/
│ ├── api/
│ │ └── index.js # API 封装与 SSE 连接
│ ├── components/
│ │ └── ChatRoom.vue # 聊天组件
│ ├── views/
│ │ └── SuperAgent.vue # AI 超级智能体页面
│ └── main.js # 入口文件
├── package.json
└── vite.config.js
9.2 核心调用流程
前端通过 SSE (Server-Sent Events) 与后端建立实时通信,实现流式响应。
9.2.1 SSE 连接封装
javascript
// src/api/index.js
import axios from 'axios'
const API_BASE_URL = process.env.NODE_ENV === 'production'
? '/api'
: 'http://localhost:8123/api'
export const connectSSE = (url, params, onMessage, onError) => {
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&')
const fullUrl = `${API_BASE_URL}${url}?${queryString}`
const eventSource = new EventSource(fullUrl)
eventSource.onmessage = event => {
let data = event.data
if (data === '[DONE]') {
if (onMessage) onMessage('[DONE]')
} else {
if (onMessage) onMessage(data)
}
}
eventSource.onerror = error => {
if (onError) onError(error)
eventSource.close()
}
return eventSource
}
9.2.2 调用 AI 聊天接口
javascript
// AI超级智能体聊天
export const chatWithManus = (message) => {
return connectSSE('/ai/manus/chat', { message })
}
9.3 组件层面调用
在 Vue 组件中使用 SSE 连接:
javascript
// src/views/SuperAgent.vue
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { chatWithManus } from '../api'
const messages = ref([])
const connectionStatus = ref('disconnected')
let eventSource = null
const sendMessage = (message) => {
// 添加用户消息
addMessage(message, true, 'user-question')
// 关闭之前的连接
if (eventSource) {
eventSource.close()
}
connectionStatus.value = 'connecting'
let messageBuffer = []
eventSource = chatWithManus(message)
eventSource.onmessage = (event) => {
const data = event.data
if (data && data !== '[DONE]') {
messageBuffer.push(data)
// 根据标点或长度判断是否创建新消息气泡
const lastChar = data.charAt(data.length - 1)
const hasCompleteSentence = ['。', '!', '?', '...'].includes(lastChar)
const isLongEnough = messageBuffer.join('').length > 40
if (hasCompleteSentence || isLongEnough) {
addMessage(messageBuffer.join(''), false, 'ai-answer')
messageBuffer = []
}
}
if (data === '[DONE]') {
if (messageBuffer.length > 0) {
addMessage(messageBuffer.join(''), false, 'ai-final')
}
connectionStatus.value = 'disconnected'
eventSource.close()
}
}
eventSource.onerror = (error) => {
connectionStatus.value = 'error'
eventSource.close()
}
}
9.4 完整调用链路
┌─────────────────┐ HTTP/SSE ┌──────────────────────┐ 调用工具 ┌──────────────────────┐
│ Vue 前端 │ ────────────────► │ 后端 API Gateway │ ─────────────► │ MCP Server │
│ SuperAgent.vue │ /ai/manus/chat │ (端口 8123) │ /mcp/tools │ (端口 8127) │
└─────────────────┘ └──────────────────────┘ └──────────────────────┘
▲ │ │
│ │ │
└─────────── SSE 流式响应 ◄───────────┴─────────── 工具执行结果 ◄───────────┘
9.5 前端配置说明
| 配置项 | 开发环境 | 生产环境 |
|---|---|---|
| API 地址 | http://localhost:8123/api |
/api |
| 通信方式 | SSE (EventSource) | SSE (EventSource) |
| 超时时间 | 60秒 | 60秒 |
9.6 注意事项
- 跨域处理:后端需配置 CORS 允许前端域名访问
- 连接管理:发送新消息前需关闭旧连接,避免重复响应
- 消息缓冲:SSE 可能分段传输,需缓冲后按语义合并
- 异常处理 :监听
onerror事件,优雅处理连接断开 - 资源清理 :组件销毁前调用
eventSource.close()释放资源