# 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:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
packages/
*.nuget/
bin/
obj/
dist/
build/
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# IDE - Visual Studio / Rider
.vs/
.idea/
*.suo
*.user
*.userosscache
*.sln.docstates
*.userprefs
# Compiled output
*.dll
*.exe
*.pdb
*.cache
# Logs and databases
*.log
logs/
*.sqlite
*.db
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# Test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# File Manager MCP 📂
[](https://www.gnu.org/licenses/gpl-3.0)
[](https://github.com/yourusername/FileManagerMcp/graphs/commit-activity)
[](https://smithery.ai/server/@taha-ghadirian/filemanagermcp)
A powerful and user-friendly File Manager application that provides a modern interface for managing FTP file operations.
> 🤖 **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.
## 🚀 Features
- 📁 Browse and manage remote FTP directories
- ⬆️ Upload files and directories
- ⬇️ Download files and directories
- 🗑️ Delete files and directories
- 📝 Create new directories
- 🔄 Recursive file operations support
- 💻 Clean and intuitive user interface
## 🚀 Usage
### Using Smithery Hosted Service (Recommended)
1. Visit [File Manager on smithery](https://smithery.ai/server/@taha-ghadirian/filemanagermcp)
2. Create an account or sign in
3. Connect using your preferred development environment:
- Visual Studio Code
- Cursor
- Any IDE or tool with MCP integration
### Alternative: Local Installation
If you prefer running the application locally, follow these steps:
1. Make sure you have the [.NET 9.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) installed
2. Follow the installation steps below to build and run the application
### 🛠️ Build and Run Locally
1. Clone the repository:
HTTPS:
```bash
git clone https://github.com/taha-ghadirian/FileManagerMcp.git
```
SSH:
```bash
git clone [email protected]:taha-ghadirian/FileManagerMcp.git
```
Then navigate to the project directory:
```bash
cd FileManagerMcp
```
2. Install dependencies:
```bash
dotnet restore
```
3. Build the project:
```bash
dotnet build
```
4. Run the application in inspector:
```bash
npx @modelcontextprotocol/inspector dotnet run
```
## 🔧 Configuration
The application uses environment variables for configuration. Here are the required environment variables:
| Option | Description | Required | Default |
|----------|-------------|----------|---------|
| `ftpHost` | FTP server hostname or IP address | Yes | - |
| `ftpUsername` | FTP account username | Yes | - |
| `ftpPassword` | FTP account password | Yes | - |
| `ftpPort` | FTP server port | No | 21 |
You can set these environment variables in several ways:
1. Setting them inline when running the application:
```bash
ftpHost=ftp.example.com ftpUsername=myuser ftpPassword=mypassword npx @modelcontextprotocol/inspector dotnet run
```
⚠️ **Security Note**: Never commit sensitive information like passwords to version control. Always use environment variables or secure secrets management for production deployments.
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the project
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## 📝 License
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details. This means:
- You can freely use, modify, and distribute this software
- If you modify and distribute this software, you must:
- Make your source code available
- License your modifications under GPL v3.0
- Document your changes
- Preserve the original copyright notices
## 📞 Support
If you have any questions or need support, please open an issue in the GitHub repository.
## ✨ Acknowledgments
- Thanks to all contributors who have helped shape this project
- Built with .NET and modern best practices
---
Made with ❤️ by Taha Ghadirian
```
--------------------------------------------------------------------------------
/src/Login.cs:
--------------------------------------------------------------------------------
```csharp
namespace FileManagerMcp;
public class FtpCredential
{
public int port = 21;
public string host = null!;
public string username = null!;
public string password = null!;
}
```
--------------------------------------------------------------------------------
/src/appsettings.json:
--------------------------------------------------------------------------------
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"FTP": {
"Host": "ftp://example.com",
"User": "username",
"Password": "password",
"Port": 21
}
}
```
--------------------------------------------------------------------------------
/src/Program.cs:
--------------------------------------------------------------------------------
```csharp
using FileManagerMcp;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
var builder = Host.CreateApplicationBuilder(args);
builder.ConfigureServices();
var loginDetail = builder.Configuration.GetSection("FTP").Get<FtpCredential>();
builder.Services.AddSingleton(loginDetail ?? throw new ArgumentNullException(nameof(loginDetail)));
await builder.Build().RunAsync();
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
# Copy csproj and restore dependencies
COPY ["src/FileManagerMcp.csproj", "./"]
RUN dotnet restore
# Copy the rest of the code
COPY src/ ./
# Build and publish
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false
# Runtime stage
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS final
WORKDIR /app
# Copy the published files from build stage
COPY --from=build /app/publish .
# Set the entry point
ENTRYPOINT ["dotnet", "FileManagerMcp.dll"]
```
--------------------------------------------------------------------------------
/src/ServiceConfiguration.cs:
--------------------------------------------------------------------------------
```csharp
// Create a new file to configure services
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using FileManagerMcp.Toolkits;
public static class ServiceConfiguration
{
public static HostApplicationBuilder ConfigureServices(this HostApplicationBuilder builder)
{
builder.Configuration
.SetBasePath(Directory.GetCurrentDirectory())
.AddEnvironmentVariables()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddUserSecrets<Program>();
builder.Logging.AddConsole(consoleLogOptions =>
{
// Configure all logs to go to stderr
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithTools<FtpTool>();
return builder;
}
}
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
required:
- ftpHost
- ftpUsername
- ftpPassword
properties:
ftpHost:
type: string
description: FTP host address.
ftpUsername:
type: string
description: FTP username.
ftpPassword:
type: string
description: FTP password.
ftpPort:
type: number
default: 21
description: FTP port, default is 21.
commandFunction:
# A JS function that produces the CLI command based on the given config to start the MCP on stdio.
|-
(config) => ({
command: 'dotnet',
args: ['FileManagerMcp.dll'],
env: {
FTP__HOST: config.ftpHost,
FTP__USERNAME: config.ftpUsername,
FTP__PASSWORD: config.ftpPassword,
FTP__PORT: String(config.ftpPort)
}
})
exampleConfig:
ftpHost: ftp.example.com
ftpUsername: user123
ftpPassword: pass123
ftpPort: 21
```
--------------------------------------------------------------------------------
/src/Toolkits/FtpTool.cs:
--------------------------------------------------------------------------------
```csharp
using System.ComponentModel;
using FluentFTP;
using ModelContextProtocol.Server;
namespace FileManagerMcp.Toolkits;
[McpServerToolType]
public class FtpTool : IDisposable
{
private readonly FtpClient _client;
private readonly FtpCredential _credential;
public FtpTool(FtpCredential credential)
{
_credential = credential;
_client = new FtpClient(credential.host, credential.username, credential.password, credential.port);
_client.Connect();
}
[McpServerTool, Description("Return list of files in the given directory.")]
public string ListFiles(
[Description("The directory to list files from.")] string directory = "/",
[Description("Whether to list files recursively.")] bool recursive = false)
{
if (!_client.DirectoryExists(directory))
{
return $"Error: Directory '{directory}' does not exist";
}
var items = _client.GetListing(directory, recursive ? FtpListOption.Recursive : FtpListOption.AllFiles);
var fileList = items.Select(item => $"{item.Type},{item.FullName},{item.Size},{item.Modified}").ToList();
return "Type,Name,Size,Modified\n" + string.Join("\n", fileList);
}
[McpServerTool, Description("Download a file from the given directory.")]
public string DownloadFile(
[Description("The file path to download. Example: '/path/to/file.txt'")] string filePath,
[Description("The local path to download the file to, example: 'downloads/file.txt'")] string localPath = ".")
{
if (!_client.FileExists(filePath))
{
return $"Error: File '{filePath}' does not exist.";
}
var directory = Path.GetDirectoryName(localPath) ?? ".";
if (!_client.DirectoryExists(directory))
{
Directory.CreateDirectory(directory);
}
if (File.Exists(localPath))
{
return $"Error: Local file '{localPath}' already exists, can't overwrite it.";
}
_client.DownloadFile(filePath, localPath);
return $"Downloaded {filePath} to {localPath}";
}
[McpServerTool, Description("Upload a file to the given directory.")]
public string UploadFile(
[Description("The file path to upload. Example: 'downloads/file.txt'")] string filePath,
[Description("The remote path to upload the file to. Example: '/path/to/file.txt'")] string remotePath = "/")
{
if (!File.Exists(filePath))
{
return $"Error: Local file '{filePath}' does not exist";
}
if (!_client.DirectoryExists(Path.GetDirectoryName(remotePath)))
{
return $"Error: Remote directory '{Path.GetDirectoryName(remotePath)}' does not exist";
}
if (_client.FileExists(remotePath))
{
return $"Error: Remote file '{remotePath}' already exists";
}
_client.UploadFile(filePath, remotePath);
return $"Uploaded {filePath} to {remotePath}";
}
[McpServerTool, Description("Delete a file from the given directory.")]
public string DeleteFile(
[Description("The file path to delete. Example: '/path/to/file.txt'")] string filePath)
{
if (!_client.FileExists(filePath))
{
return $"Error: File '{filePath}' does not exist.";
}
_client.DeleteFile(filePath);
return $"Deleted {filePath}";
}
[McpServerTool, Description("Delete multiple files from the given paths.")]
public string DeleteFiles(
[Description("The file paths to delete, separated by commas. Example: '/path/file1.txt,/path/file2.txt'")] string filePaths)
{
var paths = filePaths.Split(',').Select(p => p.Trim()).ToList();
var deleted = new List<string>();
var failed = new List<string>();
foreach (var path in paths)
{
try
{
_client.DeleteFile(path);
deleted.Add(path);
}
catch (Exception)
{
failed.Add(path);
}
}
var result = $"Deleted {deleted.Count} files: {string.Join(", ", deleted)}";
if (failed.Count != 0)
{
result += $"\nFailed to delete {failed.Count} files: {string.Join(", ", failed)}";
}
return result;
}
[McpServerTool, Description("Create a directory in the given path.")]
public string CreateDirectory(
[Description("The path to create the directory in. Example: '/path/to/directory'")] string path)
{
if (_client.DirectoryExists(path))
{
return $"Error: Directory '{path}' already exists.";
}
_client.CreateDirectory(path);
return $"Created directory {path}";
}
[McpServerTool, Description("Delete a directory from the given path.")]
public string DeleteDirectory(
[Description("The path to delete the directory from. Example: '/path/to/directory'")] string path)
{
_client.DeleteDirectory(path);
return $"Deleted directory {path}";
}
public void Dispose()
{
_client.Disconnect();
}
}
```