#
tokens: 3493/50000 9/9 files
lines: off (toggle) GitHub
raw markdown copy
# 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 📂

[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/yourusername/FileManagerMcp/graphs/commit-activity)
[![smithery badge](https://smithery.ai/badge/@taha-ghadirian/filemanagermcp)](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();
    }
}
```