概述
MCP(Model Context Protocol)是一个开放协议,允许AI模型通过标准化的方式与外部工具和数据源交互。LangChat Pro实现了MCP客户端和服务端,为AI助手提供强大的扩展能力。协议概述
MCP核心概念
MCP协议定义了模型与工具之间的标准化通信方式:- 服务器(Server): 提供工具和资源的组件
- 客户端(Client): 调用服务器工具的组件
- 工具(Tools): 可被模型调用的功能
- 资源(Resources): 可访问的数据或内容
- 提示词模板(Prompts): 预定义的提示词模板
MCP通信模型
Copy
┌──────────────┐
│ LLM │
│ (模型层) │
└──────┬───────┘
│
▼
┌──────────────┐
│ MCP Client │
│ (客户端) │
└──────┬───────┘
│
▼
┌──────────────┐
│ MCP Server │
│ (服务端) │
└──────────────┘
核心组件
McpClientManager
路径:langchat-mcp/src/main/java/cn/langchat/mcp/factory/McpClientManager.java
职责: 管理MCP客户端连接和生命周期
Copy
@Service
public class McpClientManager {
@Resource
private AigcMcpService mcpService;
/**
* 客户端实例缓存
*/
private final Map<String, McpClient> clientCache = new ConcurrentHashMap<>();
/**
* 获取MCP客户端
*/
public McpClient getClient(String serverId) {
// 检查缓存
if (clientCache.containsKey(serverId)) {
return clientCache.get(serverId);
}
// 创建新客户端
AigcMcp serverConfig = mcpService.getById(serverId);
McpClient client = createClient(serverConfig);
// 缓存客户端
clientCache.put(serverId, client);
return client;
}
/**
* 创建MCP客户端
*/
private McpClient createClient(AigcMcp serverConfig) {
McpClient client = McpClient.newClient()
.serverConfig(serverConfig.getConfig())
.transport(getTransport(serverConfig))
.build();
// 连接服务器
client.connect();
return client;
}
/**
* 获取所有工具
*/
public List<Tool> getTools(String serverId) {
McpClient client = getClient(serverId);
return client.listTools();
}
/**
* 调用工具
*/
public String callTool(String serverId, String toolName, Map<String, Object> args) {
McpClient client = getClient(serverId);
return client.callTool(toolName, args);
}
/**
* 关闭客户端
*/
public void closeClient(String serverId) {
McpClient client = clientCache.remove(serverId);
if (client != null) {
client.close();
}
}
/**
* 关闭所有客户端
*/
public void closeAll() {
clientCache.values().forEach(McpClient::close);
clientCache.clear();
}
}
McpToolFactory
职责
将MCP服务器提供的工具注册到AI Service实现
Copy
@Component
public class McpToolFactory implements DynamicToolFactory {
@Resource
private McpClientManager mcpClientManager;
@Resource
private AigcMcpService mcpService;
@Override
public void dynamicTools(LcChatReq req, AiServices<LangChatAgent> aiServices) {
log.debug("开始构建MCP工具: userId={}, appId={}",
req.getUserId(), req.getAppId());
// 1. 获取启用的MCP服务器
var mcpServers = mcpService.getEnabledServers(req.getUserId(), req.getAppId());
if (CollUtil.isEmpty(mcpServers)) {
log.debug("未启用MCP服务器: userId={}, appId={}",
req.getUserId(), req.getAppId());
return;
}
// 2. 获取所有工具
var tools = new ArrayList<Tool>();
for (var server : mcpServers) {
try {
List<Tool> serverTools = mcpClientManager.getTools(server.getId());
tools.addAll(serverTools);
} catch (Exception e) {
log.error("获取MCP工具失败: serverId={}", server.getId(), e);
}
}
if (CollUtil.isEmpty(tools)) {
log.debug("MCP工具为空: userId={}, appId={}",
req.getUserId(), req.getAppId());
return;
}
// 3. 添加到AiServices
aiServices.tools(tools);
log.info("MCP工具构建完成: userId={}, appId={}, toolCount={}",
req.getUserId(), req.getAppId(), tools.size());
}
}
MCP服务端
FileSystemMcpServer
路径:langchat-mcp-server/mcp-server-filesystem/src/main/java/cn/langchat/mcp/server/fs/FileSystemMcpServer.java
职责: 提供文件系统操作工具
实现示例:
Copy
public class FileSystemMcpServer {
/**
* 读取文件
*/
@McpTool(name = "read_file", description = "读取文件内容")
public String readFile(
@McpParameter(name = "path", description = "文件路径", required = true)
String path
) {
try {
return Files.readString(Paths.get(path));
} catch (IOException e) {
throw new RuntimeException("读取文件失败", e);
}
}
/**
* 写入文件
*/
@McpTool(name = "write_file", description = "写入文件内容")
public String writeFile(
@McpParameter(name = "path", description = "文件路径", required = true)
String path,
@McpParameter(name = "content", description = "文件内容", required = true)
String content
) {
try {
Files.writeString(Paths.get(path), content);
return "写入成功";
} catch (IOException e) {
throw new RuntimeException("写入文件失败", e);
}
}
/**
* 列出目录
*/
@McpTool(name = "list_directory", description = "列出目录内容")
public String listDirectory(
@McpParameter(name = "path", description = "目录路径", required = true)
String path
) {
try (Stream<Path> stream = Files.list(Paths.get(path))) {
return stream
.map(Path::getFileName)
.map(Path::toString)
.collect(Collectors.joining("\n"));
} catch (IOException e) {
throw new RuntimeException("列出目录失败", e);
}
}
/**
* 搜索文件
*/
@McpTool(name = "search_files", description = "搜索文件")
public String searchFiles(
@McpParameter(name = "path", description = "搜索路径", required = true)
String path,
@McpParameter(name = "pattern", description = "文件名模式", required = true)
String pattern
) {
try (Stream<Path> stream = Files.walk(Paths.get(path))) {
return stream
.filter(p -> p.getFileName().toString().matches(pattern))
.map(Path::toString)
.collect(Collectors.joining("\n"));
} catch (IOException e) {
throw new RuntimeException("搜索文件失败", e);
}
}
}
配置模型
AigcMcp
Copy
public class AigcMcp {
private String id;
private String name; // MCP服务器名称
private String description; // 描述
private String type; // 类型: stdio, sse
private String config; // 配置(JSON)
private Boolean enabled; // 是否启用
}
配置示例
Copy
{
"id": "mcp-001",
"name": "filesystem",
"type": "stdio",
"description": "文件系统MCP服务器",
"config": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"PYTHONPATH": "/path/to/dependencies"
}
},
"enabled": true
}
传输方式
1. Stdio(标准输入输出)
适用场景: 本地进程通信Copy
private Transport getStdioTransport(AigcMcp serverConfig) {
McpConfig config = JsonUtil.toBean(serverConfig.getConfig(), McpConfig.class);
return Transport.stdio()
.command(config.getCommand())
.args(config.getArgs())
.env(config.getEnv())
.build();
}
2. SSE(Server-Sent Events)
适用场景: HTTP通信Copy
private Transport getSseTransport(AigcMcp serverConfig) {
McpConfig config = JsonUtil.toBean(serverConfig.getConfig(), McpConfig.class);
return Transport.sse()
.url(config.getUrl())
.headers(config.getHeaders())
.build();
}
MCP调用流程
1. 工具调用
Copy
用户提问:"读取文件 /path/to/file.txt"
↓
LLM分析问题
↓
识别需要调用MCP工具: "read_file"
↓
McpToolFactory提供工具
↓
McpClientManager调用工具
↓
McpServer执行工具
↓
返回文件内容
↓
LLM根据结果生成回答
2. 资源访问
Copy
用户提问:"获取最新文档"
↓
LLM识别需要访问资源
↓
McpClientManager列出资源
↓
读取资源内容
↓
LLM根据资源生成回答
扩展点
1. 自定义MCP服务器
步骤1: 创建MCP服务器Copy
public class CustomMcpServer {
@McpTool(name = "custom_tool", description = "自定义工具")
public String customTool(
@McpParameter(name = "input", description = "输入参数", required = true)
String input
) {
// 自定义逻辑
return "处理结果: " + input;
}
@McpResource(name = "custom_resource", description = "自定义资源")
public String customResource() {
// 返回资源内容
return "资源内容";
}
}
Copy
INSERT INTO aigc_mcp (id, name, type, description, enabled)
VALUES (
'mcp-custom',
'custom_server',
'stdio',
'自定义MCP服务器',
1
);
2. 添加新传输方式
Copy
private Transport getCustomTransport(AigcMcp serverConfig) {
McpConfig config = JsonUtil.toBean(serverConfig.getConfig(), McpConfig.class);
return Transport.custom()
.url(config.getUrl())
.timeout(config.getTimeout())
.build();
}
最佳实践
1. 工具命名
- 使用下划线分隔
- 名称要清晰描述功能
- 示例:
read_file,write_file,search_files
2. 参数定义
- 明确参数类型
- 标注必需参数
- 提供参数说明
- 设置合理的默认值
3. 错误处理
- 捕获所有异常
- 返回友好的错误信息
- 记录详细日志
- 考虑重试机制
4. 性能优化
- 缓存客户端实例
- 使用连接池
- 设置超时时间
- 异步执行耗时操作
5. 安全考虑
- 验证文件路径
- 限制访问范围
- 过滤敏感操作
- 记录调用日志
配置说明
application.yml
Copy
langchat:
mcp:
enabled: true # 是否启用MCP
timeout: 30 # 超时时间(秒)
max-retries: 3 # 最大重试次数
pool-size: 10 # 连接池大小
mcp:
servers:
filesystem:
type: stdio
command: python
args: ["/path/to/server.py"]
custom:
type: sse
url: http://localhost:8080/mcp

