headers = new HashMap<>();
+ if (env != null) {
+ String authToken = env.get("AUTHORIZATION");
+ if (authToken != null) {
+ headers.put("Authorization", authToken);
+ }
+ String apiKey = env.get("API_KEY");
+ if (apiKey != null) {
+ headers.put("X-API-Key", apiKey);
+ }
+ }
+
+ HttpSseTransport transport = new HttpSseTransport(url, headers, null);
+ McpClient client;
+ try {
+ transport.connect();
+ client = new McpClient(name, transport);
+ client.initialize();
+ } catch (Exception e) {
+ try {
+ transport.close();
+ } catch (Exception suppressed) {
+ e.addSuppressed(suppressed);
+ }
+ throw (e instanceof McpException mcp) ? mcp
+ : new McpException("Failed to connect MCP HTTP server '" + name + "': " + e.getMessage(), e);
+ }
+
+ clients.put(name, client);
+ for (McpClient.McpTool tool : client.getTools()) {
+ toolToServer.put(tool.name(), name);
+ }
+
+ log.info("MCP HTTP server '{}' connected successfully", name);
+ return client;
+ }
+
+ /**
+ * 展开字符串中的环境变量引用。
+ * 支持 ${VAR_NAME} 语法,未定义的变量保留原样。
+ *
+ * @param value 包含可能的环境变量引用的字符串
+ * @return 展开后的字符串
+ */
+ static String expandEnvVars(String value) {
+ if (value == null || !value.contains("${")) {
+ return value;
+ }
+
+ StringBuilder result = new StringBuilder();
+ int i = 0;
+ while (i < value.length()) {
+ if (i < value.length() - 2 && value.charAt(i) == '$' && value.charAt(i + 1) == '{') {
+ int end = value.indexOf('}', i + 2);
+ if (end != -1) {
+ String varName = value.substring(i + 2, end);
+ String envVal = System.getenv(varName);
+ if (envVal != null) {
+ result.append(envVal);
+ } else {
+ result.append("${").append(varName).append("}");
+ }
+ i = end + 1;
+ } else {
+ result.append(value.charAt(i));
+ i++;
+ }
+ } else {
+ result.append(value.charAt(i));
+ i++;
+ }
+ }
+ return result.toString();
+ }
}
diff --git a/src/main/java/com/claudecode/tool/impl/ListMcpResourcesTool.java b/src/main/java/com/claudecode/tool/impl/ListMcpResourcesTool.java
new file mode 100644
index 0000000..6368284
--- /dev/null
+++ b/src/main/java/com/claudecode/tool/impl/ListMcpResourcesTool.java
@@ -0,0 +1,109 @@
+package com.claudecode.tool.impl;
+
+import com.claudecode.mcp.McpClient;
+import com.claudecode.mcp.McpManager;
+import com.claudecode.tool.Tool;
+import com.claudecode.tool.ToolContext;
+
+import java.util.Map;
+
+/**
+ * ListMcpResources 工具 —— 列出 MCP 服务器提供的资源。
+ *
+ * 对应 claude-code 中浏览 MCP 资源的功能。
+ * 显示所有已连接 MCP 服务器的资源列表,包括 URI、名称、描述和 MIME 类型。
+ */
+public class ListMcpResourcesTool implements Tool {
+
+ @Override
+ public String name() {
+ return "ListMcpResources";
+ }
+
+ @Override
+ public String description() {
+ return """
+ List resources available from connected MCP (Model Context Protocol) servers.
+ Shows all resources with their URIs, names, descriptions, and MIME types.
+ Use this to discover what data sources are available before reading them.
+ Optionally filter by server name.""";
+ }
+
+ @Override
+ public String inputSchema() {
+ return """
+ {
+ "type": "object",
+ "properties": {
+ "server": {
+ "type": "string",
+ "description": "Optional: filter resources by MCP server name"
+ }
+ }
+ }""";
+ }
+
+ @Override
+ public String execute(Map input, ToolContext context) {
+ McpManager mcpManager = context.getOrDefault("MCP_MANAGER", null);
+ if (mcpManager == null) {
+ return "No MCP servers configured.";
+ }
+
+ String serverFilter = (String) input.getOrDefault("server", null);
+ var clients = mcpManager.getClients();
+
+ if (clients.isEmpty()) {
+ return "No MCP servers connected.";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ int totalResources = 0;
+
+ for (var entry : clients.entrySet()) {
+ String serverName = entry.getKey();
+ McpClient client = entry.getValue();
+
+ if (serverFilter != null && !serverFilter.isBlank()
+ && !serverName.equalsIgnoreCase(serverFilter)) {
+ continue;
+ }
+
+ if (!client.isInitialized() || !client.isConnected()) {
+ sb.append("⚠ Server '").append(serverName).append("': not connected\n");
+ continue;
+ }
+
+ var resources = client.getResources();
+ if (resources.isEmpty()) {
+ sb.append("Server '").append(serverName).append("': no resources\n");
+ continue;
+ }
+
+ sb.append("## ").append(serverName).append(" (").append(resources.size()).append(" resources)\n\n");
+
+ for (var resource : resources) {
+ sb.append("- **").append(resource.name()).append("**\n");
+ sb.append(" URI: `").append(resource.uri()).append("`\n");
+ if (!resource.description().isBlank()) {
+ sb.append(" ").append(resource.description()).append("\n");
+ }
+ sb.append(" Type: ").append(resource.mimeType()).append("\n\n");
+ totalResources++;
+ }
+ }
+
+ if (totalResources == 0) {
+ return serverFilter != null
+ ? "No resources found for server '" + serverFilter + "'."
+ : "No MCP resources available from any connected server.";
+ }
+
+ return sb.toString().stripTrailing();
+ }
+
+ @Override
+ public String activityDescription(Map input) {
+ return "📋 Listing MCP resources";
+ }
+}
diff --git a/src/main/java/com/claudecode/tool/impl/ReadMcpResourceTool.java b/src/main/java/com/claudecode/tool/impl/ReadMcpResourceTool.java
new file mode 100644
index 0000000..377e531
--- /dev/null
+++ b/src/main/java/com/claudecode/tool/impl/ReadMcpResourceTool.java
@@ -0,0 +1,130 @@
+package com.claudecode.tool.impl;
+
+import com.claudecode.mcp.McpClient;
+import com.claudecode.mcp.McpManager;
+import com.claudecode.tool.Tool;
+import com.claudecode.tool.ToolContext;
+
+import java.util.Map;
+
+/**
+ * ReadMcpResource 工具 —— 读取 MCP 服务器的指定资源。
+ *
+ * 对应 claude-code 中读取 MCP 资源的功能。
+ * 通过 URI 从 MCP 服务器读取资源内容。
+ */
+public class ReadMcpResourceTool implements Tool {
+
+ @Override
+ public String name() {
+ return "ReadMcpResource";
+ }
+
+ @Override
+ public String description() {
+ return """
+ Read a specific resource from a connected MCP (Model Context Protocol) server.
+ Provide the resource URI (obtained from ListMcpResources) to fetch its content.
+ The server name is optional — if omitted, all servers are searched for the URI.""";
+ }
+
+ @Override
+ public String inputSchema() {
+ return """
+ {
+ "type": "object",
+ "properties": {
+ "uri": {
+ "type": "string",
+ "description": "The resource URI to read (e.g., 'file:///path' or 'custom://resource')"
+ },
+ "server": {
+ "type": "string",
+ "description": "Optional: the MCP server name that provides this resource"
+ }
+ },
+ "required": ["uri"]
+ }""";
+ }
+
+ @Override
+ public String execute(Map input, ToolContext context) {
+ String uri = (String) input.get("uri");
+ String serverFilter = (String) input.getOrDefault("server", null);
+
+ if (uri == null || uri.isBlank()) {
+ return "Error: 'uri' is required. Use ListMcpResources to discover available resources.";
+ }
+
+ McpManager mcpManager = context.getOrDefault("MCP_MANAGER", null);
+ if (mcpManager == null) {
+ return "Error: No MCP servers configured.";
+ }
+
+ var clients = mcpManager.getClients();
+ if (clients.isEmpty()) {
+ return "Error: No MCP servers connected.";
+ }
+
+ // If server specified, try only that server
+ if (serverFilter != null && !serverFilter.isBlank()) {
+ McpClient client = clients.get(serverFilter);
+ if (client == null) {
+ return "Error: MCP server '" + serverFilter + "' not found. "
+ + "Available servers: " + String.join(", ", clients.keySet());
+ }
+ return readFromClient(client, serverFilter, uri);
+ }
+
+ // Try all connected servers
+ for (var entry : clients.entrySet()) {
+ McpClient client = entry.getValue();
+ if (!client.isInitialized() || !client.isConnected()) continue;
+
+ // Check if this server has the resource
+ boolean hasResource = client.getResources().stream()
+ .anyMatch(r -> r.uri().equals(uri));
+ if (hasResource) {
+ return readFromClient(client, entry.getKey(), uri);
+ }
+ }
+
+ // No server has this resource — try reading anyway (some servers allow arbitrary URIs)
+ for (var entry : clients.entrySet()) {
+ McpClient client = entry.getValue();
+ if (!client.isInitialized() || !client.isConnected()) continue;
+ try {
+ String result = client.readResource(uri);
+ if (result != null && !result.isBlank()) {
+ return result;
+ }
+ } catch (Exception ignored) {
+ // Try next server
+ }
+ }
+
+ return "Error: Resource '" + uri + "' not found on any connected MCP server. "
+ + "Use ListMcpResources to see available resources.";
+ }
+
+ private String readFromClient(McpClient client, String serverName, String uri) {
+ if (!client.isInitialized() || !client.isConnected()) {
+ return "Error: MCP server '" + serverName + "' is not connected.";
+ }
+ try {
+ String content = client.readResource(uri);
+ if (content == null || content.isBlank()) {
+ return "(Resource returned empty content)";
+ }
+ return content;
+ } catch (Exception e) {
+ return "Error reading resource '" + uri + "' from server '" + serverName + "': " + e.getMessage();
+ }
+ }
+
+ @Override
+ public String activityDescription(Map input) {
+ String uri = (String) input.getOrDefault("uri", "?");
+ return "📖 Reading MCP resource: " + uri;
+ }
+}