1414import java .nio .file .Path ;
1515import java .nio .file .StandardOpenOption ;
1616import java .util .ArrayList ;
17+ import java .util .Base64 ;
1718import java .util .List ;
1819import java .util .concurrent .CompletableFuture ;
1920import 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
0 commit comments