zhou zhou
23 小时以前 82624affb0251b75b62b35567d3eb260c06efe78
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package com.vincent.rsf.server.ai.service.impl.mcp;
 
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.ai.config.AiDefaults;
import com.vincent.rsf.server.ai.entity.AiMcpMount;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.json.jackson.JacksonMcpJsonMapper;
import io.modelcontextprotocol.spec.McpSchema;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
 
import java.time.Duration;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
 
@Slf4j
@Component
@RequiredArgsConstructor
public class McpClientFactory {
 
    private final ObjectMapper objectMapper;
 
    public McpSyncClient createClient(AiMcpMount mount) {
        Duration timeout = Duration.ofMillis(mount.getRequestTimeoutMs() == null
                ? AiDefaults.DEFAULT_TIMEOUT_MS
                : mount.getRequestTimeoutMs());
        JacksonMcpJsonMapper jsonMapper = new JacksonMcpJsonMapper(objectMapper);
        if (AiDefaults.MCP_TRANSPORT_STDIO.equals(mount.getTransportType())) {
            ServerParameters.Builder parametersBuilder = ServerParameters.builder(mount.getCommand());
            List<String> args = readStringList(mount.getArgsJson());
            if (!args.isEmpty()) {
                parametersBuilder.args(args);
            }
            Map<String, String> env = readStringMap(mount.getEnvJson());
            if (!env.isEmpty()) {
                parametersBuilder.env(env);
            }
            StdioClientTransport transport = new StdioClientTransport(parametersBuilder.build(), jsonMapper);
            transport.setStdErrorHandler(message -> log.warn("MCP STDIO stderr [{}]: {}", mount.getName(), message));
            return McpClient.sync(transport)
                    .requestTimeout(timeout)
                    .initializationTimeout(timeout)
                    .clientInfo(new McpSchema.Implementation("rsf-ai-client", "RSF AI Client", "1.0.0"))
                    .build();
        }
        if (!AiDefaults.MCP_TRANSPORT_SSE_HTTP.equals(mount.getTransportType())) {
            throw new CoolException("不支持的 MCP 传输类型: " + mount.getTransportType());
        }
 
        if (!StringUtils.hasText(mount.getServerUrl())) {
            throw new CoolException("MCP 服务地址不能为空");
        }
        HttpClientSseClientTransport.Builder transportBuilder = HttpClientSseClientTransport.builder(mount.getServerUrl())
                .jsonMapper(jsonMapper)
                .connectTimeout(timeout);
        if (StringUtils.hasText(mount.getEndpoint())) {
            transportBuilder.sseEndpoint(mount.getEndpoint());
        }
        Map<String, String> headers = readStringMap(mount.getHeadersJson());
        if (!headers.isEmpty()) {
            transportBuilder.customizeRequest(builder -> headers.forEach(builder::header));
        }
        return McpClient.sync(transportBuilder.build())
                .requestTimeout(timeout)
                .initializationTimeout(timeout)
                .clientInfo(new McpSchema.Implementation("rsf-ai-client", "RSF AI Client", "1.0.0"))
                .build();
    }
 
    private List<String> readStringList(String json) {
        if (!StringUtils.hasText(json)) {
            return Collections.emptyList();
        }
        try {
            return objectMapper.readValue(json, new TypeReference<List<String>>() {
            });
        } catch (Exception e) {
            throw new CoolException("解析 MCP 列表配置失败: " + e.getMessage());
        }
    }
 
    private Map<String, String> readStringMap(String json) {
        if (!StringUtils.hasText(json)) {
            return Collections.emptyMap();
        }
        try {
            Map<String, String> result = objectMapper.readValue(json, new TypeReference<LinkedHashMap<String, String>>() {
            });
            return result == null ? Collections.emptyMap() : result;
        } catch (Exception e) {
            throw new CoolException("解析 MCP Map 配置失败: " + e.getMessage());
        }
    }
}