/* * File System MCP Server Example * * This example demonstrates how to create an MCP server that provides * file system resources. It shows how to use the built-in file system * resource provider to expose files and directories to MCP clients. * * Compile with: * valac --pkg mcp-vala --pkg json-glib-1.0 --pkg gee-0.8 --pkg posix filesystem-server.vala -o filesystem-server */ using Mcp; using Posix; /** * File system server that provides resources from a local directory. */ public class FileSystemServer : GLib.Object { private Mcp.Core.Server server; private string root_path; /** * Creates a new FileSystemServer. * * @param root_path The root directory to serve files from */ public FileSystemServer (string root_path) { this.root_path = root_path; // Create server information var server_info = new Mcp.Types.Protocol.ServerInfo ( "filesystem-server", "1.0.0" ); server_info.description = "MCP server providing file system resources"; // Create server capabilities with resources enabled var capabilities = new Mcp.Types.Protocol.ServerCapabilities (); capabilities.logging = true; capabilities.resources = new Mcp.Types.Protocol.ResourcesCapabilities (); capabilities.resources.subscribe = true; // Enable resource subscriptions capabilities.resources.list_changed = true; // Enable list change notifications // Create the server server = new Mcp.Core.Server (server_info, capabilities); // Set up file system resource provider setup_filesystem_provider (); } /** * Sets up the file system resource provider. */ private void setup_filesystem_provider () { // Create a custom file system provider var provider = new FileSystemResourceProvider (root_path); // Register the provider with the resource manager server.resource_manager.register_provider ("file", provider); // File system provider registered for path: %s } /** * Runs the server. * * @return Exit code */ public async int run () { try { // Starting file system MCP server... // Start the server bool started = yield server.start (); if (!started) { GLib.stderr.printf ("Failed to start server\n"); return 1; } // File system server started successfully // Serving files from: %s // Waiting for MCP client connections... // Run the main loop var main_loop = new MainLoop (); // Connect shutdown signal server.shutdown.connect (() => { main_loop.quit (); }); main_loop.run (); return 0; } catch (Error e) { GLib.stderr.printf ("Server error: %s\n", e.message); return 1; } } } /** * File system resource provider implementation. */ public class FileSystemResourceProvider : Mcp.Resources.BaseProvider { private string root_path; /** * Creates a new FileSystemResourceProvider. * * @param root_path The root directory to serve files from */ public FileSystemResourceProvider (string root_path) { this.root_path = root_path; } /** * {@inheritDoc} */ public override async Gee.ArrayList list_resources (string? cursor) throws Error { var resources = new Gee.ArrayList (); // List files in root directory var dir = File.new_for_path (root_path); if (!dir.query_exists ()) { throw new Mcp.Core.McpError.RESOURCE_NOT_FOUND ("Directory not found: %s".printf (root_path)); } var enumerator = yield dir.enumerate_children_async ( "standard::name,standard::type,standard::size,standard::content-type", FileQueryInfoFlags.NONE, Priority.DEFAULT, null ); FileInfo? file_info = null; while ((file_info = enumerator.next_file ()) != null) { if (file_info.get_file_type () == FileType.REGULAR) { var file = File.new_for_path (Path.build_filename (root_path, file_info.get_name ())); var resource = new Mcp.Resources.Types.Resource ( file.get_uri (), file_info.get_name () ); resource.description = "File: %s".printf (file_info.get_name ()); // Set MIME type if available string? content_type = file_info.get_content_type (); if (content_type != null) { resource.mime_type = content_type; } else { resource.mime_type = guess_mime_type (file_info.get_name ()); } resource.size = file_info.get_size (); resources.add (resource); } } return resources; } /** * {@inheritDoc} */ public override async Gee.ArrayList read_resource (string uri) throws Error { var file = File.new_for_uri (uri); if (!file.query_exists ()) { throw new Mcp.Core.McpError.RESOURCE_NOT_FOUND ("File not found: %s".printf (uri)); } uint8[] contents; string etag; yield file.load_contents_async (null, out contents, out etag); // Check if it's text content string? mime_type = null; try { var info = yield file.query_info_async ("standard::content-type", FileQueryInfoFlags.NONE); mime_type = info.get_content_type (); } catch (Error e) { // Ignore error and try to guess from URI } var result = new Gee.ArrayList (); if (mime_type != null && mime_type.has_prefix ("text/")) { string text = (string) contents; result.add (new Mcp.Types.Common.TextResourceContents (uri, text)); } else { // Return as blob content result.add (new Mcp.Types.Common.BlobResourceContents (uri, Base64.encode (contents), mime_type ?? "application/octet-stream")); } return result; } /** * Guesses MIME type from filename. * * @param filename The filename * @return The guessed MIME type */ private string guess_mime_type (string filename) { string extension = ""; var last_dot = filename.last_index_of ("."); if (last_dot != -1) { extension = filename.substring (last_dot); } switch (extension) { case ".txt": return "text/plain"; case ".json": return "application/json"; case ".xml": return "application/xml"; case ".html": return "text/html"; case ".css": return "text/css"; case ".js": return "application/javascript"; case ".vala": return "text/x-vala"; case ".c": case ".h": return "text/x-c"; case ".png": return "image/png"; case ".jpg": case ".jpeg": return "image/jpeg"; case ".gif": return "image/gif"; case ".pdf": return "application/pdf"; default: return "application/octet-stream"; } } } /** * Main function. * * @param args Command line arguments * @return Exit code */ public static int main (string[] args) { string root_path = "."; // Parse command line arguments if (args.length > 1) { root_path = args[1]; } // Check if the path exists and is accessible if (!FileUtils.test (root_path, FileTest.EXISTS | FileTest.IS_DIR)) { GLib.stderr.printf ("Error: Path '%s' does not exist or is not a directory\n", root_path); return 1; } // Create and run the server var server = new FileSystemServer (root_path); // Handle Ctrl+C to gracefully shutdown // For now, we'll skip signal handling to get the build working var loop = new MainLoop (); // Run the server server.run.begin ((obj, res) => { loop.quit (); }); // Keep the main loop running loop.run (); return 0; }