Skip to content

Commit 49c400b

Browse files
author
Your Name
committed
v1.1.0: Add binary file support and list_directory tool
1 parent aebe932 commit 49c400b

4 files changed

Lines changed: 100 additions & 2 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Imagine having a senior developer sitting inside your server console, ready to e
2828
| `write_file` | Create or edit files. Perfect for config tweaks. | *"Create a new skript file called 'welcome.sk' that greets players."* |
2929
| `list_plugins` | Get a clean list of all installed plugins & versions. | *"Check if WorldGuard is enabled and up to date."* |
3030
| `get_logs` | Fetch the last 100 lines of `latest.log`. | *"Why did the server just lag? Check the logs."* |
31+
| `write_file_base64` | Upload binary files (JARs, images) via base64. | *"Upload this new 'SuperSword.jar' to the plugins folder."* |
32+
| `read_file_base64` | Download binary files as base64 strings. | *"Get the 'world/icon.png' file so I can analyze it."* |
33+
| `list_directory` | List files in a folder with file sizes. | *"List all files in the 'world/region' directory."* |
3134

3235
### 💡 Configuration Examples
3336

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.cursor</groupId>
88
<artifactId>mcp-minecraft</artifactId>
9-
<version>1.0-SNAPSHOT</version>
9+
<version>1.1.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>MCPMinecraft</name>

src/main/java/com/cursor/mcp/tools/ToolHandler.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.nio.file.Path;
1515
import java.nio.file.StandardOpenOption;
1616
import java.util.ArrayList;
17+
import java.util.Base64;
1718
import java.util.List;
1819
import java.util.concurrent.CompletableFuture;
1920
import java.util.concurrent.TimeUnit;
@@ -59,6 +60,30 @@ public ObjectNode listTools() {
5960
addTool(tools, "list_plugins", "List installed plugins", mapper.createObjectNode().put("type", "object"));
6061
addTool(tools, "get_logs", "Get recent log lines", mapper.createObjectNode().put("type", "object"));
6162

63+
// Binary file tools
64+
ObjectNode readBinarySchema = mapper.createObjectNode();
65+
readBinarySchema.put("type", "object");
66+
ObjectNode readBinaryProps = mapper.createObjectNode();
67+
readBinaryProps.set("path", mapper.createObjectNode().put("type", "string"));
68+
readBinarySchema.set("properties", readBinaryProps);
69+
addTool(tools, "read_file_base64", "Read a binary file and return as base64", readBinarySchema);
70+
71+
ObjectNode writeBinarySchema = mapper.createObjectNode();
72+
writeBinarySchema.put("type", "object");
73+
ObjectNode writeBinaryProps = mapper.createObjectNode();
74+
writeBinaryProps.set("path", mapper.createObjectNode().put("type", "string"));
75+
writeBinaryProps.set("content", mapper.createObjectNode().put("type", "string").put("description", "Base64 encoded file content"));
76+
writeBinarySchema.set("properties", writeBinaryProps);
77+
addTool(tools, "write_file_base64", "Write a binary file from base64 encoded content", writeBinarySchema);
78+
79+
// List directory tool
80+
ObjectNode listDirSchema = mapper.createObjectNode();
81+
listDirSchema.put("type", "object");
82+
ObjectNode listDirProps = mapper.createObjectNode();
83+
listDirProps.set("path", mapper.createObjectNode().put("type", "string"));
84+
listDirSchema.set("properties", listDirProps);
85+
addTool(tools, "list_directory", "List files and directories in a path", listDirSchema);
86+
6287
return result;
6388
}
6489

@@ -77,6 +102,12 @@ public Object callTool(String name, JsonNode args) throws Exception {
77102
return readFile(args.get("path").asText());
78103
case "write_file":
79104
return writeFile(args.get("path").asText(), args.get("content").asText());
105+
case "read_file_base64":
106+
return readFileBase64(args.get("path").asText());
107+
case "write_file_base64":
108+
return writeFileBase64(args.get("path").asText(), args.get("content").asText());
109+
case "list_directory":
110+
return listDirectory(args.has("path") ? args.get("path").asText() : ".");
80111
case "list_plugins":
81112
return listPlugins();
82113
case "get_logs":
@@ -172,5 +203,69 @@ private ObjectNode getLogs() throws Exception {
172203
String recentLogs = lines.subList(start, lines.size()).stream().collect(Collectors.joining("\n"));
173204
return createTextResult(recentLogs);
174205
}
206+
207+
private ObjectNode readFileBase64(String pathStr) throws Exception {
208+
Path path = serverRoot.resolve(pathStr).normalize();
209+
if (!path.startsWith(serverRoot)) {
210+
throw new SecurityException("Access denied: Path is outside server root.");
211+
}
212+
if (!Files.exists(path)) {
213+
throw new IllegalArgumentException("File not found: " + pathStr);
214+
}
215+
216+
byte[] bytes = Files.readAllBytes(path);
217+
String base64 = Base64.getEncoder().encodeToString(bytes);
218+
return createTextResult(base64);
219+
}
220+
221+
private ObjectNode writeFileBase64(String pathStr, String base64Content) throws Exception {
222+
Path path = serverRoot.resolve(pathStr).normalize();
223+
if (!path.startsWith(serverRoot)) {
224+
throw new SecurityException("Access denied: Path is outside server root.");
225+
}
226+
227+
byte[] bytes = Base64.getDecoder().decode(base64Content);
228+
Files.createDirectories(path.getParent());
229+
Files.write(path, bytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
230+
return createTextResult("Binary file written successfully to " + pathStr + " (" + bytes.length + " bytes)");
231+
}
232+
233+
private ObjectNode listDirectory(String pathStr) throws Exception {
234+
Path path = serverRoot.resolve(pathStr).normalize();
235+
if (!path.startsWith(serverRoot)) {
236+
throw new SecurityException("Access denied: Path is outside server root.");
237+
}
238+
if (!Files.exists(path)) {
239+
throw new IllegalArgumentException("Directory not found: " + pathStr);
240+
}
241+
if (!Files.isDirectory(path)) {
242+
throw new IllegalArgumentException("Not a directory: " + pathStr);
243+
}
244+
245+
StringBuilder sb = new StringBuilder();
246+
try (var stream = Files.list(path)) {
247+
stream.sorted().forEach(p -> {
248+
String name = p.getFileName().toString();
249+
if (Files.isDirectory(p)) {
250+
sb.append("[DIR] ").append(name).append("/\n");
251+
} else {
252+
try {
253+
long size = Files.size(p);
254+
sb.append("[FILE] ").append(name).append(" (").append(formatSize(size)).append(")\n");
255+
} catch (Exception e) {
256+
sb.append("[FILE] ").append(name).append("\n");
257+
}
258+
}
259+
});
260+
}
261+
return createTextResult(sb.toString());
262+
}
263+
264+
private String formatSize(long bytes) {
265+
if (bytes < 1024) return bytes + " B";
266+
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
267+
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024));
268+
return String.format("%.1f GB", bytes / (1024.0 * 1024 * 1024));
269+
}
175270
}
176271

src/main/resources/plugin.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: MCPMinecraft
2-
version: 1.0-SNAPSHOT
2+
version: 1.1.0
33
main: com.cursor.mcp.McpPlugin
44
api-version: 1.20
55
description: Model Context Protocol Server for Minecraft

0 commit comments

Comments
 (0)