package com.claudecode.mcp; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** * MCP 客户端 —— 对应 claude-code 中的 mcp/ 模块。 *
* 负责与单个 MCP 服务器的完整生命周期管理: *
* MCP 协议使用 JSON-RPC 2.0 格式通信。
*
* @see McpTransport
* @see McpManager
*/
public class McpClient implements AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(McpClient.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
/** JSON-RPC 请求 ID 生成器 */
private final AtomicInteger idCounter = new AtomicInteger(1);
/** 服务器名称标识 */
private final String serverName;
/** 底层传输层 */
private final McpTransport transport;
/** 已发现的工具集合:toolName -> McpTool */
private final Map
* 步骤:
*
*
*
* @throws McpException 初始化失败
*/
public void initialize() throws McpException {
log.info("Initializing MCP server '{}'...", serverName);
// 1. 发送 initialize 请求
int initId = nextId();
var initRequest = Map.of(
"jsonrpc", "2.0",
"id", initId,
"method", "initialize",
"params", Map.of(
"protocolVersion", "2024-11-05",
"capabilities", Map.of(),
"clientInfo", Map.of(
"name", "claude-code-java",
"version", "1.0.0"
)
)
);
JsonNode response;
try {
response = transport.sendRequest(MAPPER.writeValueAsString(initRequest));
} catch (Exception e) {
throw new McpException("MCP initialize request failed: " + e.getMessage(), e);
}
// 2. 解析服务器能力
JsonNode result = response.get("result");
if (result != null) {
serverCapabilities = result.get("capabilities");
serverInfo = result.get("serverInfo");
String serverVersion = result.has("protocolVersion")
? result.get("protocolVersion").asText() : "unknown";
log.info("MCP server '{}' protocol version: {}", serverName, serverVersion);
if (serverInfo != null) {
log.info("MCP server info: {}", serverInfo);
}
}
// 3. 发送 initialized 通知
var initializedNotif = Map.of(
"jsonrpc", "2.0",
"method", "notifications/initialized"
);
try {
transport.sendNotification(MAPPER.writeValueAsString(initializedNotif));
} catch (Exception e) {
throw new McpException("Failed to send initialized notification: " + e.getMessage(), e);
}
// 4. 发现工具
discoverTools();
// 5. 发现资源
discoverResources();
initialized = true;
log.info("MCP server '{}' initialization complete: {} tools, {} resources",
serverName, tools.size(), resources.size());
}
/**
* 发现服务器提供的工具 —— 发送 {@code tools/list} 请求。
*/
private void discoverTools() throws McpException {
// 检查服务器是否支持 tools 能力
if (serverCapabilities != null
&& serverCapabilities.has("tools")
&& serverCapabilities.get("tools").isObject()) {
// 服务器声明支持工具
} else if (serverCapabilities != null && !serverCapabilities.has("tools")) {
log.debug("MCP server '{}' did not declare tools capability, attempting discovery", serverName);
}
int id = nextId();
var request = Map.of(
"jsonrpc", "2.0",
"id", id,
"method", "tools/list",
"params", Map.of()
);
try {
JsonNode response = transport.sendRequest(MAPPER.writeValueAsString(request));
JsonNode result = response.get("result");
if (result != null && result.has("tools")) {
JsonNode toolsNode = result.get("tools");
if (toolsNode != null && toolsNode.isArray()) {
for (JsonNode toolNode : toolsNode) {
String name = toolNode.get("name").asText();
String description = toolNode.has("description")
? toolNode.get("description").asText() : "";
JsonNode inputSchema = toolNode.get("inputSchema");
tools.put(name, new McpTool(name, description, inputSchema));
log.debug("Discovered MCP tool: {} - {}", name, description);
}
}
}
} catch (McpException e) {
// tools/list 可能不被支持,记录警告但不中断初始化
if (e.isJsonRpcError() && e.getErrorCode() == -32601) {
log.debug("MCP server '{}' does not support tools/list", serverName);
} else {
log.warn("Failed to discover MCP tools: {}", e.getMessage());
}
} catch (Exception e) {
log.warn("MCP tool discovery serialization exception: {}", e.getMessage());
}
}
/**
* 发现服务器提供的资源 —— 发送 {@code resources/list} 请求。
*/
private void discoverResources() throws McpException {
// 检查服务器是否支持 resources 能力
if (serverCapabilities != null
&& serverCapabilities.has("resources")
&& serverCapabilities.get("resources").isObject()) {
// 服务器声明支持资源
} else if (serverCapabilities != null && !serverCapabilities.has("resources")) {
log.debug("MCP server '{}' did not declare resources capability, skipping discovery", serverName);
return;
}
int id = nextId();
var request = Map.of(
"jsonrpc", "2.0",
"id", id,
"method", "resources/list",
"params", Map.of()
);
try {
JsonNode response = transport.sendRequest(MAPPER.writeValueAsString(request));
JsonNode result = response.get("result");
if (result != null && result.has("resources")) {
JsonNode resourcesNode = result.get("resources");
if (resourcesNode != null && resourcesNode.isArray()) {
for (JsonNode resNode : resourcesNode) {
String uri = resNode.get("uri").asText();
String name = resNode.has("name") ? resNode.get("name").asText() : uri;
String description = resNode.has("description")
? resNode.get("description").asText() : "";
String mimeType = resNode.has("mimeType")
? resNode.get("mimeType").asText() : "text/plain";
resources.put(uri, new McpResource(uri, name, description, mimeType));
log.debug("Discovered MCP resource: {} ({})", name, uri);
}
}
}
} catch (McpException e) {
if (e.isJsonRpcError() && e.getErrorCode() == -32601) {
log.debug("MCP server '{}' does not support resources/list", serverName);
} else {
log.warn("Failed to discover MCP resources: {}", e.getMessage());
}
} catch (Exception e) {
log.warn("MCP resource discovery serialization exception: {}", e.getMessage());
}
}
/**
* 调用 MCP 工具 —— 发送 {@code tools/call} 请求。
*
* @param toolName 工具名称
* @param arguments 工具参数(键值对)
* @return 工具执行结果文本
* @throws McpException 调用失败或工具不存在
*/
public String callTool(String toolName, Map