# Directory Structure
```
├── .gitignore
├── Dockerfile
├── FileManagerMcp.sln
├── LICENSE
├── README.md
├── smithery.yaml
└── src
├── appsettings.json
├── FileManagerMcp.csproj
├── Login.cs
├── Program.cs
├── ServiceConfiguration.cs
└── Toolkits
└── FtpTool.cs
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Dependencies
2 | node_modules/
3 | packages/
4 | *.nuget/
5 | bin/
6 | obj/
7 | dist/
8 | build/
9 |
10 | # IDE - VSCode
11 | .vscode/*
12 | !.vscode/settings.json
13 | !.vscode/tasks.json
14 | !.vscode/launch.json
15 | !.vscode/extensions.json
16 |
17 | # IDE - Visual Studio / Rider
18 | .vs/
19 | .idea/
20 | *.suo
21 | *.user
22 | *.userosscache
23 | *.sln.docstates
24 | *.userprefs
25 |
26 | # Compiled output
27 | *.dll
28 | *.exe
29 | *.pdb
30 | *.cache
31 |
32 | # Logs and databases
33 | *.log
34 | logs/
35 | *.sqlite
36 | *.db
37 |
38 | # Environment files
39 | .env
40 | .env.local
41 | .env.development.local
42 | .env.test.local
43 | .env.production.local
44 |
45 | # OS generated files
46 | .DS_Store
47 | .DS_Store?
48 | ._*
49 | .Spotlight-V100
50 | .Trashes
51 | ehthumbs.db
52 | Thumbs.db
53 |
54 | # Build results
55 | [Dd]ebug/
56 | [Dd]ebugPublic/
57 | [Rr]elease/
58 | [Rr]eleases/
59 | x64/
60 | x86/
61 | [Aa][Rr][Mm]/
62 | [Aa][Rr][Mm]64/
63 | bld/
64 | [Bb]in/
65 | [Oo]bj/
66 | [Ll]og/
67 | [Ll]ogs/
68 |
69 | # NuGet Packages
70 | *.nupkg
71 | # NuGet Symbol Packages
72 | *.snupkg
73 | # The packages folder can be ignored because of Package Restore
74 | **/[Pp]ackages/*
75 | # except build/, which is used as an MSBuild target.
76 | !**/[Pp]ackages/build/
77 |
78 | # User-specific files
79 | *.rsuser
80 | *.suo
81 | *.user
82 | *.userosscache
83 | *.sln.docstates
84 |
85 | # Test Results
86 | [Tt]est[Rr]esult*/
87 | [Bb]uild[Ll]og.*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # File Manager MCP 📂
2 |
3 | [](https://www.gnu.org/licenses/gpl-3.0)
4 | [](https://github.com/yourusername/FileManagerMcp/graphs/commit-activity)
5 | [](https://smithery.ai/server/@taha-ghadirian/filemanagermcp)
6 |
7 | A powerful and user-friendly File Manager application that provides a modern interface for managing FTP file operations.
8 |
9 | > 🤖 **AI-Powered Development**: This project is a result of vibe coding through AI prompt engineering. The entire codebase was developed by collaborating with AI, showcasing the potential of modern AI-assisted development practices.
10 |
11 | ## 🚀 Features
12 |
13 | - 📁 Browse and manage remote FTP directories
14 | - ⬆️ Upload files and directories
15 | - ⬇️ Download files and directories
16 | - 🗑️ Delete files and directories
17 | - 📝 Create new directories
18 | - 🔄 Recursive file operations support
19 | - 💻 Clean and intuitive user interface
20 |
21 | ## 🚀 Usage
22 |
23 | ### Using Smithery Hosted Service (Recommended)
24 |
25 | 1. Visit [File Manager on smithery](https://smithery.ai/server/@taha-ghadirian/filemanagermcp)
26 |
27 | 2. Create an account or sign in
28 |
29 | 3. Connect using your preferred development environment:
30 | - Visual Studio Code
31 | - Cursor
32 | - Any IDE or tool with MCP integration
33 |
34 | ### Alternative: Local Installation
35 |
36 | If you prefer running the application locally, follow these steps:
37 |
38 | 1. Make sure you have the [.NET 9.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) installed
39 | 2. Follow the installation steps below to build and run the application
40 |
41 | ### 🛠️ Build and Run Locally
42 |
43 | 1. Clone the repository:
44 |
45 | HTTPS:
46 | ```bash
47 | git clone https://github.com/taha-ghadirian/FileManagerMcp.git
48 | ```
49 |
50 | SSH:
51 | ```bash
52 | git clone [email protected]:taha-ghadirian/FileManagerMcp.git
53 | ```
54 |
55 | Then navigate to the project directory:
56 | ```bash
57 | cd FileManagerMcp
58 | ```
59 |
60 | 2. Install dependencies:
61 | ```bash
62 | dotnet restore
63 | ```
64 |
65 | 3. Build the project:
66 | ```bash
67 | dotnet build
68 | ```
69 |
70 | 4. Run the application in inspector:
71 | ```bash
72 | npx @modelcontextprotocol/inspector dotnet run
73 | ```
74 |
75 | ## 🔧 Configuration
76 |
77 | The application uses environment variables for configuration. Here are the required environment variables:
78 |
79 | | Option | Description | Required | Default |
80 | |----------|-------------|----------|---------|
81 | | `ftpHost` | FTP server hostname or IP address | Yes | - |
82 | | `ftpUsername` | FTP account username | Yes | - |
83 | | `ftpPassword` | FTP account password | Yes | - |
84 | | `ftpPort` | FTP server port | No | 21 |
85 |
86 | You can set these environment variables in several ways:
87 |
88 | 1. Setting them inline when running the application:
89 | ```bash
90 | ftpHost=ftp.example.com ftpUsername=myuser ftpPassword=mypassword npx @modelcontextprotocol/inspector dotnet run
91 | ```
92 |
93 | ⚠️ **Security Note**: Never commit sensitive information like passwords to version control. Always use environment variables or secure secrets management for production deployments.
94 |
95 |
96 | ## 🤝 Contributing
97 |
98 | Contributions are welcome! Please feel free to submit a Pull Request.
99 |
100 | 1. Fork the project
101 | 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
102 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
103 | 4. Push to the branch (`git push origin feature/AmazingFeature`)
104 | 5. Open a Pull Request
105 |
106 | ## 📝 License
107 |
108 | This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details. This means:
109 |
110 | - You can freely use, modify, and distribute this software
111 | - If you modify and distribute this software, you must:
112 | - Make your source code available
113 | - License your modifications under GPL v3.0
114 | - Document your changes
115 | - Preserve the original copyright notices
116 |
117 | ## 📞 Support
118 |
119 | If you have any questions or need support, please open an issue in the GitHub repository.
120 |
121 | ## ✨ Acknowledgments
122 |
123 | - Thanks to all contributors who have helped shape this project
124 | - Built with .NET and modern best practices
125 |
126 | ---
127 |
128 | Made with ❤️ by Taha Ghadirian
```
--------------------------------------------------------------------------------
/src/Login.cs:
--------------------------------------------------------------------------------
```csharp
1 | namespace FileManagerMcp;
2 |
3 | public class FtpCredential
4 | {
5 | public int port = 21;
6 | public string host = null!;
7 | public string username = null!;
8 | public string password = null!;
9 | }
```
--------------------------------------------------------------------------------
/src/appsettings.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "FTP": {
10 | "Host": "ftp://example.com",
11 | "User": "username",
12 | "Password": "password",
13 | "Port": 21
14 | }
15 | }
```
--------------------------------------------------------------------------------
/src/Program.cs:
--------------------------------------------------------------------------------
```csharp
1 | using FileManagerMcp;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 | using Microsoft.Extensions.Logging;
5 | using Microsoft.Extensions.Configuration;
6 |
7 |
8 | var builder = Host.CreateApplicationBuilder(args);
9 |
10 | builder.ConfigureServices();
11 |
12 | var loginDetail = builder.Configuration.GetSection("FTP").Get<FtpCredential>();
13 | builder.Services.AddSingleton(loginDetail ?? throw new ArgumentNullException(nameof(loginDetail)));
14 |
15 | await builder.Build().RunAsync();
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2 | # Build stage
3 | FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
4 | WORKDIR /src
5 |
6 | # Copy csproj and restore dependencies
7 | COPY ["src/FileManagerMcp.csproj", "./"]
8 | RUN dotnet restore
9 |
10 | # Copy the rest of the code
11 | COPY src/ ./
12 |
13 | # Build and publish
14 | RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false
15 |
16 | # Runtime stage
17 | FROM mcr.microsoft.com/dotnet/runtime:9.0 AS final
18 | WORKDIR /app
19 |
20 | # Copy the published files from build stage
21 | COPY --from=build /app/publish .
22 |
23 | # Set the entry point
24 | ENTRYPOINT ["dotnet", "FileManagerMcp.dll"]
25 |
```
--------------------------------------------------------------------------------
/src/ServiceConfiguration.cs:
--------------------------------------------------------------------------------
```csharp
1 | // Create a new file to configure services
2 | using Microsoft.Extensions.DependencyInjection;
3 | using Microsoft.Extensions.Hosting;
4 | using Microsoft.Extensions.Logging;
5 | using Microsoft.Extensions.Configuration;
6 | using FileManagerMcp.Toolkits;
7 |
8 | public static class ServiceConfiguration
9 | {
10 | public static HostApplicationBuilder ConfigureServices(this HostApplicationBuilder builder)
11 | {
12 | builder.Configuration
13 | .SetBasePath(Directory.GetCurrentDirectory())
14 | .AddEnvironmentVariables()
15 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
16 | .AddUserSecrets<Program>();
17 |
18 | builder.Logging.AddConsole(consoleLogOptions =>
19 | {
20 | // Configure all logs to go to stderr
21 | consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
22 | });
23 |
24 | builder.Services
25 | .AddMcpServer()
26 | .WithStdioServerTransport()
27 | .WithTools<FtpTool>();
28 |
29 | return builder;
30 | }
31 | }
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | required:
9 | - ftpHost
10 | - ftpUsername
11 | - ftpPassword
12 | properties:
13 | ftpHost:
14 | type: string
15 | description: FTP host address.
16 | ftpUsername:
17 | type: string
18 | description: FTP username.
19 | ftpPassword:
20 | type: string
21 | description: FTP password.
22 | ftpPort:
23 | type: number
24 | default: 21
25 | description: FTP port, default is 21.
26 | commandFunction:
27 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
28 | |-
29 | (config) => ({
30 | command: 'dotnet',
31 | args: ['FileManagerMcp.dll'],
32 | env: {
33 | FTP__HOST: config.ftpHost,
34 | FTP__USERNAME: config.ftpUsername,
35 | FTP__PASSWORD: config.ftpPassword,
36 | FTP__PORT: String(config.ftpPort)
37 | }
38 | })
39 | exampleConfig:
40 | ftpHost: ftp.example.com
41 | ftpUsername: user123
42 | ftpPassword: pass123
43 | ftpPort: 21
44 |
```
--------------------------------------------------------------------------------
/src/Toolkits/FtpTool.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System.ComponentModel;
2 | using FluentFTP;
3 | using ModelContextProtocol.Server;
4 |
5 | namespace FileManagerMcp.Toolkits;
6 |
7 | [McpServerToolType]
8 | public class FtpTool : IDisposable
9 | {
10 | private readonly FtpClient _client;
11 | private readonly FtpCredential _credential;
12 | public FtpTool(FtpCredential credential)
13 | {
14 | _credential = credential;
15 | _client = new FtpClient(credential.host, credential.username, credential.password, credential.port);
16 | _client.Connect();
17 | }
18 |
19 |
20 |
21 | [McpServerTool, Description("Return list of files in the given directory.")]
22 | public string ListFiles(
23 | [Description("The directory to list files from.")] string directory = "/",
24 | [Description("Whether to list files recursively.")] bool recursive = false)
25 | {
26 | if (!_client.DirectoryExists(directory))
27 | {
28 | return $"Error: Directory '{directory}' does not exist";
29 | }
30 |
31 | var items = _client.GetListing(directory, recursive ? FtpListOption.Recursive : FtpListOption.AllFiles);
32 | var fileList = items.Select(item => $"{item.Type},{item.FullName},{item.Size},{item.Modified}").ToList();
33 |
34 | return "Type,Name,Size,Modified\n" + string.Join("\n", fileList);
35 | }
36 |
37 | [McpServerTool, Description("Download a file from the given directory.")]
38 | public string DownloadFile(
39 | [Description("The file path to download. Example: '/path/to/file.txt'")] string filePath,
40 | [Description("The local path to download the file to, example: 'downloads/file.txt'")] string localPath = ".")
41 | {
42 | if (!_client.FileExists(filePath))
43 | {
44 | return $"Error: File '{filePath}' does not exist.";
45 | }
46 |
47 | var directory = Path.GetDirectoryName(localPath) ?? ".";
48 | if (!_client.DirectoryExists(directory))
49 | {
50 | Directory.CreateDirectory(directory);
51 | }
52 |
53 | if (File.Exists(localPath))
54 | {
55 | return $"Error: Local file '{localPath}' already exists, can't overwrite it.";
56 | }
57 |
58 | _client.DownloadFile(filePath, localPath);
59 |
60 | return $"Downloaded {filePath} to {localPath}";
61 | }
62 |
63 | [McpServerTool, Description("Upload a file to the given directory.")]
64 | public string UploadFile(
65 | [Description("The file path to upload. Example: 'downloads/file.txt'")] string filePath,
66 | [Description("The remote path to upload the file to. Example: '/path/to/file.txt'")] string remotePath = "/")
67 | {
68 | if (!File.Exists(filePath))
69 | {
70 | return $"Error: Local file '{filePath}' does not exist";
71 | }
72 |
73 | if (!_client.DirectoryExists(Path.GetDirectoryName(remotePath)))
74 | {
75 | return $"Error: Remote directory '{Path.GetDirectoryName(remotePath)}' does not exist";
76 | }
77 |
78 | if (_client.FileExists(remotePath))
79 | {
80 | return $"Error: Remote file '{remotePath}' already exists";
81 | }
82 |
83 | _client.UploadFile(filePath, remotePath);
84 |
85 | return $"Uploaded {filePath} to {remotePath}";
86 | }
87 |
88 | [McpServerTool, Description("Delete a file from the given directory.")]
89 | public string DeleteFile(
90 | [Description("The file path to delete. Example: '/path/to/file.txt'")] string filePath)
91 | {
92 | if (!_client.FileExists(filePath))
93 | {
94 | return $"Error: File '{filePath}' does not exist.";
95 | }
96 |
97 | _client.DeleteFile(filePath);
98 |
99 | return $"Deleted {filePath}";
100 | }
101 |
102 | [McpServerTool, Description("Delete multiple files from the given paths.")]
103 | public string DeleteFiles(
104 | [Description("The file paths to delete, separated by commas. Example: '/path/file1.txt,/path/file2.txt'")] string filePaths)
105 | {
106 | var paths = filePaths.Split(',').Select(p => p.Trim()).ToList();
107 | var deleted = new List<string>();
108 | var failed = new List<string>();
109 |
110 | foreach (var path in paths)
111 | {
112 | try
113 | {
114 | _client.DeleteFile(path);
115 | deleted.Add(path);
116 | }
117 | catch (Exception)
118 | {
119 | failed.Add(path);
120 | }
121 | }
122 |
123 | var result = $"Deleted {deleted.Count} files: {string.Join(", ", deleted)}";
124 | if (failed.Count != 0)
125 | {
126 | result += $"\nFailed to delete {failed.Count} files: {string.Join(", ", failed)}";
127 | }
128 | return result;
129 | }
130 |
131 | [McpServerTool, Description("Create a directory in the given path.")]
132 | public string CreateDirectory(
133 | [Description("The path to create the directory in. Example: '/path/to/directory'")] string path)
134 | {
135 | if (_client.DirectoryExists(path))
136 | {
137 | return $"Error: Directory '{path}' already exists.";
138 | }
139 |
140 | _client.CreateDirectory(path);
141 |
142 | return $"Created directory {path}";
143 | }
144 |
145 | [McpServerTool, Description("Delete a directory from the given path.")]
146 | public string DeleteDirectory(
147 | [Description("The path to delete the directory from. Example: '/path/to/directory'")] string path)
148 | {
149 | _client.DeleteDirectory(path);
150 |
151 | return $"Deleted directory {path}";
152 | }
153 |
154 | public void Dispose()
155 | {
156 | _client.Disconnect();
157 | }
158 | }
```