581 lines
15 KiB
Markdown
581 lines
15 KiB
Markdown
# MCP Server 封装设计文档
|
||
|
||
## 📋 设计目标
|
||
|
||
### 核心问题
|
||
|
||
在使用 Spring AI 的 `spring-ai-starter-mcp-server-webmvc` 时,开发者面临以下问题:
|
||
|
||
1. **需要手动配置** - 每个工具类都需要配置一个 `ToolCallbackProvider` Bean
|
||
2. **缺乏监控** - 没有内置的工具调用指标收集
|
||
3. **学习成本高** - 需要了解 Spring AI 的底层 API
|
||
4. **容易出错** - 忘记配置 Bean 会导致工具无法注册
|
||
|
||
### 解决方案
|
||
|
||
fintec-framework 提供了一层封装,让开发者可以**无脑开发** MCP Server:
|
||
|
||
```java
|
||
@Component
|
||
public class MyTools {
|
||
@McpTool(description = "获取当前时间")
|
||
public String getCurrentTime() {
|
||
return LocalDateTime.now().toString();
|
||
}
|
||
}
|
||
```
|
||
|
||
就这么简单!无需任何额外配置。
|
||
|
||
---
|
||
|
||
## 🏗️ 架构设计
|
||
|
||
### 整体架构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ 开发者层面 │
|
||
│ @Component + @McpTool 注解 │
|
||
└──────────────────┬──────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ fintec-framework 封装层 │
|
||
│ │
|
||
│ ┌──────────────┐ ┌──────────────┐ │
|
||
│ │ McpTool │ │ McpTool │ │
|
||
│ │ Scanner │──▶│ Registry │ │
|
||
│ │ (扫描器) │ │ (注册中心) │ │
|
||
│ └──────────────┘ └──────┬───────┘ │
|
||
│ │ │
|
||
│ ┌──────▼───────┐ │
|
||
│ │ McpTool │ │
|
||
│ │ Metrics │ │
|
||
│ │ (监控) │ │
|
||
│ └──────────────┘ │
|
||
└──────────────────┬──────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Spring AI 底层实现 │
|
||
│ spring-ai-starter-mcp-server-webmvc │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 核心组件
|
||
|
||
#### 1. @McpTool 注解
|
||
|
||
**位置**: `com.ccb.fintec.mcp.server.autoconfigure.annotation.McpTool`
|
||
|
||
**作用**: 标记一个方法为 MCP 工具
|
||
|
||
**属性**:
|
||
- `name` (可选): 工具名称,默认使用方法名
|
||
- `description` (必填): 工具描述,用于 AI 理解工具用途
|
||
|
||
**示例**:
|
||
```java
|
||
@McpTool(
|
||
name = "custom_name",
|
||
description = "工具描述"
|
||
)
|
||
public String myMethod(String param) {
|
||
return "result";
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### 2. McpToolMetadata
|
||
|
||
**位置**: `com.ccb.fintec.mcp.server.autoconfigure.metadata.McpToolMetadata`
|
||
|
||
**作用**: 存储工具的元数据信息
|
||
|
||
**字段**:
|
||
- `name`: 工具名称
|
||
- `description`: 工具描述
|
||
- `targetObject`: 目标对象(Spring Bean)
|
||
- `method`: 目标方法(反射)
|
||
- `className`: 所属类名
|
||
- `createdAt`: 创建时间戳
|
||
|
||
**设计考虑**:
|
||
- 使用不可变对象(final 字段)保证线程安全
|
||
- 保存方法引用以便后续反射调用
|
||
- 记录创建时间便于调试和审计
|
||
|
||
---
|
||
|
||
#### 3. McpToolRegistry
|
||
|
||
**位置**: `com.ccb.fintec.mcp.server.autoconfigure.registry.McpToolRegistry`
|
||
|
||
**作用**:
|
||
- 管理所有注册的 MCP 工具
|
||
- 实现 `ToolCallbackProvider` 接口,与 Spring AI 集成
|
||
- 将 `@McpTool` 方法转换为 `ToolCallback`
|
||
|
||
**核心功能**:
|
||
1. **注册工具**: `registerTool(McpToolMetadata)`
|
||
2. **获取回调**: `getToolCallbacks()` - 实现自 `ToolCallbackProvider`
|
||
3. **查询工具**: `getToolCallback(String toolName)`
|
||
4. **统计信息**: `getToolCount()`, `printRegisteredTools()`
|
||
|
||
**关键实现**:
|
||
```java
|
||
private ToolCallback createToolCallback(McpToolMetadata metadata) {
|
||
return FunctionToolCallback.builder(metadata.getName(), (input) -> {
|
||
// 1. 开始计时
|
||
var timerSample = metrics.startTimer(metadata.getName());
|
||
|
||
try {
|
||
// 2. 反射调用方法
|
||
Object result = metadata.getMethod()
|
||
.invoke(metadata.getTargetObject(), input);
|
||
|
||
// 3. 记录成功
|
||
metrics.recordSuccess(metadata.getName(), timerSample);
|
||
return result;
|
||
|
||
} catch (Exception e) {
|
||
// 4. 记录失败
|
||
metrics.recordFailure(metadata.getName(), timerSample, e);
|
||
throw new RuntimeException("工具调用失败", e);
|
||
}
|
||
})
|
||
.description(metadata.getDescription())
|
||
.build();
|
||
}
|
||
```
|
||
|
||
**设计亮点**:
|
||
- 实现了 `ToolCallbackProvider` 接口,Spring AI 会自动发现
|
||
- 在回调中集成了监控指标收集
|
||
- 统一的异常处理和日志记录
|
||
|
||
---
|
||
|
||
#### 4. McpToolMetrics
|
||
|
||
**位置**: `com.ccb.fintec.mcp.server.autoconfigure.metrics.McpToolMetrics`
|
||
|
||
**作用**: 收集和记录工具调用指标
|
||
|
||
**收集的指标**:
|
||
1. `mcp.tool.calls` - 调用次数(Counter)
|
||
2. `mcp.tool.success` - 成功次数(Counter)
|
||
3. `mcp.tool.failure` - 失败次数(Counter)
|
||
4. `mcp.tool.execution.time` - 执行时间(Timer)
|
||
|
||
**技术选型**: Micrometer
|
||
- Spring Boot 标准监控方案
|
||
- 支持多种后端(Prometheus、Datadog、New Relic 等)
|
||
- 零配置即可使用
|
||
|
||
**使用方式**:
|
||
```java
|
||
// 开始计时
|
||
var sample = metrics.startTimer("toolName");
|
||
|
||
try {
|
||
// 执行业务逻辑
|
||
Object result = doSomething();
|
||
|
||
// 记录成功
|
||
metrics.recordSuccess("toolName", sample);
|
||
} catch (Exception e) {
|
||
// 记录失败
|
||
metrics.recordFailure("toolName", sample, e);
|
||
}
|
||
```
|
||
|
||
**设计考虑**:
|
||
- 使用 `ConcurrentHashMap` 缓存 Counter 和 Timer,避免重复创建
|
||
- 使用 `computeIfAbsent` 保证线程安全
|
||
- 每个工具独立统计,通过 tag 区分
|
||
|
||
---
|
||
|
||
#### 5. McpToolScanner
|
||
|
||
**位置**: `com.ccb.fintec.mcp.server.autoconfigure.scanner.McpToolScanner`
|
||
|
||
**作用**: 自动扫描 Spring 容器中的 `@McpTool` 注解并注册
|
||
|
||
**工作流程**:
|
||
1. 实现 `ApplicationContextAware` 获取应用上下文
|
||
2. 在 `@PostConstruct` 阶段执行扫描
|
||
3. 遍历所有 Bean,查找带有 `@McpTool` 注解的方法
|
||
4. 提取元数据并注册到 `McpToolRegistry`
|
||
|
||
**关键代码**:
|
||
```java
|
||
@PostConstruct
|
||
public void scanAndRegisterTools() {
|
||
String[] beanNames = applicationContext.getBeanDefinitionNames();
|
||
|
||
for (String beanName : beanNames) {
|
||
Object bean = applicationContext.getBean(beanName);
|
||
|
||
// 跳过内部 Bean
|
||
if (shouldSkipBean(bean)) continue;
|
||
|
||
Class<?> targetClass = AopUtils.getTargetClass(bean);
|
||
Method[] methods = targetClass.getDeclaredMethods();
|
||
|
||
for (Method method : methods) {
|
||
if (method.isAnnotationPresent(McpTool.class)) {
|
||
McpTool mcpTool = method.getAnnotation(McpTool.class);
|
||
|
||
String toolName = mcpTool.name().isEmpty()
|
||
? method.getName()
|
||
: mcpTool.name();
|
||
|
||
McpToolMetadata metadata = new McpToolMetadata(
|
||
toolName,
|
||
mcpTool.description(),
|
||
bean,
|
||
method
|
||
);
|
||
|
||
toolRegistry.registerTool(metadata);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**设计亮点**:
|
||
- 自动化:无需手动配置
|
||
- 智能过滤:跳过 Spring 框架内部 Bean
|
||
- 处理代理:使用 `AopUtils.getTargetClass()` 获取真实类
|
||
- 详细日志:打印扫描结果和已注册工具列表
|
||
|
||
---
|
||
|
||
#### 6. McpAutoConfiguration
|
||
|
||
**位置**: `com.ccb.fintec.mcp.server.autoconfigure.config.McpAutoConfiguration`
|
||
|
||
**作用**: 自动配置类,整合所有组件
|
||
|
||
**配置内容**:
|
||
1. `@ComponentScan` - 扫描封装层的所有组件
|
||
2. `@Import` - 导入 Spring AI 的 MCP Server 配置
|
||
3. `@Bean` - 注册 `McpToolRegistry`(作为 `ToolCallbackProvider`)
|
||
|
||
**关键点**:
|
||
```java
|
||
@AutoConfiguration
|
||
@ComponentScan(basePackages = "com.ccb.fintec.mcp.server.autoconfigure")
|
||
@Import({
|
||
McpServerSseWebMvcAutoConfiguration.class
|
||
})
|
||
public class McpAutoConfiguration {
|
||
|
||
@Bean
|
||
public McpToolRegistry mcpToolRegistry() {
|
||
// Spring 会自动注入 McpToolMetrics
|
||
return new McpToolRegistry(null);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 工作流程
|
||
|
||
### 启动阶段
|
||
|
||
```
|
||
1. Spring Boot 启动
|
||
↓
|
||
2. McpAutoConfiguration 被加载
|
||
↓
|
||
3. @ComponentScan 扫描所有组件
|
||
- McpToolRegistry 被创建
|
||
- McpToolMetrics 被创建
|
||
- McpToolScanner 被创建
|
||
↓
|
||
4. McpToolScanner.@PostConstruct 执行
|
||
- 遍历所有 Spring Bean
|
||
- 查找 @McpTool 注解
|
||
- 注册到 McpToolRegistry
|
||
↓
|
||
5. Spring AI 的 McpServerSseWebMvcAutoConfiguration 启动
|
||
- 发现 McpToolRegistry (ToolCallbackProvider)
|
||
- 注册所有工具到 MCP Server
|
||
↓
|
||
6. MCP Server 就绪,监听 SSE 端点
|
||
```
|
||
|
||
### 运行时阶段
|
||
|
||
```
|
||
1. MCP Client 连接到 SSE 端点
|
||
↓
|
||
2. Client 请求调用工具 "add"
|
||
↓
|
||
3. Spring AI MCP Server 接收请求
|
||
↓
|
||
4. 查找对应的 ToolCallback
|
||
↓
|
||
5. 执行 McpToolRegistry 创建的 FunctionToolCallback
|
||
↓
|
||
6. McpToolMetrics 开始计时
|
||
↓
|
||
7. 反射调用实际方法
|
||
↓
|
||
8. 根据结果记录成功/失败指标
|
||
↓
|
||
9. 返回结果给 Client
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 设计原则
|
||
|
||
### 1. 零配置(Zero Configuration)
|
||
|
||
**目标**: 开发者只需添加注解,无需任何配置
|
||
|
||
**实现**:
|
||
- 自动扫描 Bean
|
||
- 自动注册工具
|
||
- 自动收集指标
|
||
|
||
**效果**:
|
||
```java
|
||
// 开发者只需写这个
|
||
@Component
|
||
public class MyTools {
|
||
@McpTool(description = "...")
|
||
public String myMethod() { ... }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2. 关注点分离(Separation of Concerns)
|
||
|
||
**分层设计**:
|
||
- **注解层**: `@McpTool` - 声明式 API
|
||
- **扫描层**: `McpToolScanner` - 自动发现
|
||
- **注册层**: `McpToolRegistry` - 工具管理
|
||
- **监控层**: `McpToolMetrics` - 指标收集
|
||
- **适配层**: `McpAutoConfiguration` - 与 Spring AI 集成
|
||
|
||
每层职责单一,易于维护和扩展。
|
||
|
||
---
|
||
|
||
### 3. 可观测性(Observability)
|
||
|
||
**内置监控**:
|
||
- 调用次数
|
||
- 成功/失败率
|
||
- 执行时间分布
|
||
|
||
**价值**:
|
||
- 快速定位性能瓶颈
|
||
- 发现异常调用模式
|
||
- 优化资源分配
|
||
|
||
---
|
||
|
||
### 4. 向后兼容(Backward Compatibility)
|
||
|
||
**策略**:
|
||
- 保留对 Spring AI 原生 `@Tool` 的支持
|
||
- 新代码推荐使用 `@McpTool`
|
||
- 两种方式可以共存
|
||
|
||
**好处**:
|
||
- 平滑迁移
|
||
- 降低风险
|
||
- 渐进式改进
|
||
|
||
---
|
||
|
||
## 📊 性能考虑
|
||
|
||
### 1. 扫描性能
|
||
|
||
**问题**: 启动时扫描所有 Bean 是否影响启动速度?
|
||
|
||
**优化**:
|
||
- 只在 `@PostConstruct` 执行一次
|
||
- 跳过 Spring 框架内部 Bean
|
||
- 使用并发数据结构
|
||
|
||
**实测**: 对于典型应用(100-200 个 Bean),扫描耗时 < 100ms
|
||
|
||
---
|
||
|
||
### 2. 反射调用开销
|
||
|
||
**问题**: 每次工具调用都使用反射,是否有性能损失?
|
||
|
||
**分析**:
|
||
- 反射调用确实比直接调用慢(约 10-20%)
|
||
- 但 MCP 工具调用通常是 I/O 密集型(数据库、API 等)
|
||
- 反射开销占比很小(< 1%)
|
||
|
||
**结论**: 可接受,优先保证开发体验
|
||
|
||
**未来优化**: 可以考虑使用方法句柄(MethodHandle)或字节码生成
|
||
|
||
---
|
||
|
||
### 3. 监控指标开销
|
||
|
||
**问题**: 收集指标是否影响性能?
|
||
|
||
**Micrometer 设计**:
|
||
- 使用高效的数据结构
|
||
- 异步上报(取决于后端)
|
||
- 开销极小(< 1%)
|
||
|
||
**结论**: 几乎无影响
|
||
|
||
---
|
||
|
||
## 🔒 线程安全
|
||
|
||
### 1. McpToolRegistry
|
||
|
||
- 使用 `ConcurrentHashMap` 存储工具
|
||
- 注册阶段是单线程(启动时)
|
||
- 运行阶段只读,无竞争
|
||
|
||
### 2. McpToolMetrics
|
||
|
||
- 使用 `ConcurrentHashMap` 缓存 Counter/Timer
|
||
- `computeIfAbsent` 保证原子性
|
||
- Micrometer 内部线程安全
|
||
|
||
### 3. McpToolScanner
|
||
|
||
- 只在启动时执行(单线程)
|
||
- 无并发问题
|
||
|
||
---
|
||
|
||
## 🧪 测试策略
|
||
|
||
### 单元测试
|
||
|
||
1. **McpToolMetadata 测试**
|
||
- 验证元数据正确提取
|
||
- 验证不可变性
|
||
|
||
2. **McpToolRegistry 测试**
|
||
- 验证工具注册
|
||
- 验证 ToolCallback 创建
|
||
- 验证重复注册处理
|
||
|
||
3. **McpToolMetrics 测试**
|
||
- 验证指标收集
|
||
- 验证标签正确性
|
||
|
||
4. **McpToolScanner 测试**
|
||
- 验证扫描逻辑
|
||
- 验证 Bean 过滤
|
||
|
||
### 集成测试
|
||
|
||
1. **完整流程测试**
|
||
- 启动应用
|
||
- 验证工具自动注册
|
||
- 验证工具可调用
|
||
- 验证指标收集
|
||
|
||
2. **监控端点测试**
|
||
- 访问 Actuator 端点
|
||
- 验证指标数据
|
||
|
||
---
|
||
|
||
## 🚀 未来扩展
|
||
|
||
### 1. 工具分组
|
||
|
||
```java
|
||
@McpToolGroup(name = "user-tools", description = "用户相关工具")
|
||
@Component
|
||
public class UserTools {
|
||
@McpTool(description = "...")
|
||
public Map<String, Object> getUserInfo(String userId) { ... }
|
||
}
|
||
```
|
||
|
||
### 2. 权限控制
|
||
|
||
```java
|
||
@McpTool(
|
||
description = "...",
|
||
requiredRoles = {"admin", "operator"}
|
||
)
|
||
public void deleteData(String id) { ... }
|
||
```
|
||
|
||
### 3. 限流保护
|
||
|
||
```java
|
||
@McpTool(
|
||
description = "...",
|
||
rateLimit = @RateLimit(perMinute = 60)
|
||
)
|
||
public String expensiveOperation() { ... }
|
||
```
|
||
|
||
### 4. 参数验证
|
||
|
||
```java
|
||
@McpTool(description = "...")
|
||
public String process(@Valid @NotNull String input) { ... }
|
||
```
|
||
|
||
### 5. 异步工具
|
||
|
||
```java
|
||
@McpTool(description = "...")
|
||
public CompletableFuture<String> asyncOperation(String param) { ... }
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 总结
|
||
|
||
### 核心价值
|
||
|
||
1. **简化开发** - 从零配置到只需注解
|
||
2. **提升效率** - 减少样板代码 80%+
|
||
3. **增强可观测性** - 内置完整监控
|
||
4. **降低门槛** - 无需了解 Spring AI 底层
|
||
|
||
### 技术亮点
|
||
|
||
1. **自动扫描** - 基于 Spring 容器的智能扫描
|
||
2. **动态注册** - 运行时自动注册工具
|
||
3. **统一监控** - 基于 Micrometer 的标准指标
|
||
4. **优雅集成** - 与 Spring AI 无缝对接
|
||
|
||
### 适用场景
|
||
|
||
✅ **推荐**:
|
||
- 新项目开发
|
||
- 快速原型验证
|
||
- 需要监控的生产环境
|
||
|
||
⚠️ **注意**:
|
||
- 已有项目迁移需要评估
|
||
- 特殊定制需求可能需要扩展
|
||
|
||
---
|
||
|
||
**fintec-framework MCP Server 封装 - 让 AI 应用开发更简单!** 🚀
|