A Devin Clone MCP Server
A Model Context Protocol (MCP) server written in Erlang that provides file, directory, and version control operations for software projects.
- new-dir: Create directories with automatic parent creation
 - new-dirs: Create directory structures with multiple children
 - move: Safely move/rename files and directories
 - write: Create/write files with append mode support
 - read: Read file contents with size limits
 - list-files: List directory contents with file types and sizes
 - show-cwd: Display current working directory
 - change-cwd: Navigate to relative directories
 
- git-add: Add files to staging area (
git add <files>) - git-log: Show commit history (
git log [options]) - git-diff: Show differences (no args, 1 arg, or 2 args)
 - git-pull: Pull with rebase (
git pull <remote> <branch> --rebase) - git-checkout-branch: Create and checkout new branch (
git checkout -b <name>) - git-push: Push to remote (
git push <remote>) - git-commit-all: Commit all changes (
git commit -a -m <message>) - git-commit-files: Commit specific files (
git commit <files> -m <message>) - git-clone: Clone repository (
git clone <url>) - git-status: Show repository status (
git status --porcelain) 
- Path Validation: Only relative paths allowed, prevents directory traversal
 - Sandboxing: All operations restricted to base directory
 - File Size Limits: Configurable maximum file sizes
 - Extension Filtering: Optional file type restrictions
 - Safe Error Handling: No information disclosure through errors
 
- devout://status: Real-time service status and configuration
 - devout://help: Comprehensive tool documentation
 - create_project: Intelligent project structure generation (Erlang, web, API, library)
 
- Application: 
devout_app- Standard OTP application callback - Supervisor: 
devout_sup- Manages server process lifecycle - Server: 
devout_server- Main gen_server coordinating with erlmcp - Operations: 
devout_fs- File system operation implementations - Validation: 
devout_path_validator- Security and path validation - Git Operations: 
devout_git- Git command implementations - Entry Point: 
devout_stdio_main- Standalone stdio executable 
βββββββββββββββββββ    ββββββββββββββββββββ    βββββββββββββββββββ
β   Claude AI     βββββΆβ   devout_server  βββββΆβ    devout_fs    β
βββββββββββββββββββ    ββββββββββββββββββββ    βββββββββββββββββββ
                            β      β                    β
                            β      βΌ                    βΌ
                       ββββββββββββββββββββ    βββββββββββββββββββ
                       β erlmcp_stdio     β    β path_validator  β
                       β (MCP Protocol)   β    β (Security)      β
                       ββββββββββββββββββββ    βββββββββββββββββββ
                            β
                            βΌ
                       ββββββββββββββββββββ    βββββββββββββββββββ
                       β    devout_git    βββββΆβ     erlexec     β
                       ββββββββββββββββββββ    β (Proc. Mgmt.)   β
                                               βββββββββββββββββββ
- Erlang/OTP 25+
 - Rebar3
 - Git (for dependencies & Git operations)
 
git clone <repository-url>
cd devout
rebar3 get-deps
rebar3 compilerebar3 eunitEdit claude_desktop_config.json:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
Linux: ~/.config/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "devout": {
        "command": "/Users/oubiwann/lab/erlsci/devout/bin/start.sh",
        "args": []
    }
  }
}User: Create a new project structure called "my_app" with src, test, and docs directories
Claude: I'll create that project structure for you.
[Uses new-dirs tool: {"path": "my_app", "children": ["src", "test", "docs"]}]
Directory structure created successfully:
  - my_app
  - src
  - test
  - docs
User: What files are in the current directory?
Claude: I'll show you the files in the current directory.
[Uses list-files tool: {}]
Contents of .:
[FILE] .DS_Store (6148 bytes)
[DIR] .git
[DIR] .github
[FILE] .gitignore (164 bytes)
[FILE] LICENSE (11358 bytes)
[FILE] Makefile (4612 bytes)
[FILE] README.md (9895 bytes)
[DIR] _build
[DIR] bin
[DIR] config
[DIR] src
[DIR] test
User: Create a configuration file with some JSON content
Claude: I'll create a config file for you.
[Uses write tool: {"path": "my_app/config.json", "content": "{\n  \"port\": 3000,\n  \"debug\": true\n}"}]
Content written to file successfully: my_app/config.json (32 bytes)
User: Show me the current directory and then read the config file
Claude: I'll show the current directory and read the config file.
[Uses show-cwd tool: {}]
Current working directory: /Users/developer/projects
[Uses read tool: {"path": "my_app/config.json"}]
Content of my_app/config.json (32 bytes):
{
  "port": 3000,
  "debug": true
}
User: Use the create_project prompt for an Erlang application
Claude: I'll generate an Erlang project structure prompt.
[Uses create_project prompt: {"project_name": "my_erlang_app", "project_type": "erlang"}]
Create a project structure for 'my_erlang_app' (erlang project). Create an Erlang/OTP project structure with directories: src, include, test, priv, rebar.config. Use the devout tools to create the necessary directories and files.
User: Show me the last 5 commits
Claude: I'll show you the recent commit history.
[Uses git-log tool: {"args": "--oneline -5"}]
Git log:
a1b2c3d Add new feature
e4f5g6h Fix bug in parser
i7j8k9l Update documentation
m1n2o3p Initial commit
User: Create a new feature branch called "user-authentication"
Claude: I'll create and checkout a new branch for you.
[Uses git-checkout-branch tool: {"branch": "user-authentication"}]
Git checkout branch successful:
Switched to a new branch 'user-authentication'
User: Add these files to git and commit them with message "Add user auth"
Claude: I'll stage and commit those files for you.
[Uses git-add tool: {"files": ["src/auth.erl", "test/auth_test.erl"]}]
[Uses git-commit-files tool: {"files": ["src/auth.erl", "test/auth_test.erl"], "message": "Add user auth"}]
Git operations successful!
User: Pull latest changes from origin main and then push my branch
Claude: I'll sync with the remote repository for you.
[Uses git-pull tool: {"remote": "origin", "branch": "main"}]
[Uses git-push tool: {"remote": "origin"}]
Git sync completed successfully!
Edit config/sys.config to customize behavior:
[
    {devout, [
        {max_file_size, 10485760},          % 10MB limit
        {allowed_extensions, all},          % or [<<".txt">>, <<".md">>]
        {enable_recursive_delete, false},   % Safety first
        {allowed_operations, [              % Restrict available operations
            new_dir, new_dirs, move, write, read, list_files, show_cwd, change_cwd
        ]}
    ]},
    {erlexec, [
        {debug, false},                     % Enable for git command debugging
        {verbose, false}
    ]}
].| MCP Tool | Git Command | Description | 
|---|---|---|
git-add | 
git add <files> | 
Stage files for commit | 
git-log | 
git log [options] | 
Show commit history | 
git-diff | 
git diff [ref1] [ref2] | 
Show differences | 
git-pull | 
git pull <remote> <branch> --rebase | 
Pull with rebase | 
git-checkout-branch | 
git checkout -b <name> | 
Create new branch | 
git-push | 
git push <remote> | 
Push to remote | 
git-commit-all | 
git commit -a -m <message> | 
Commit all changes | 
git-commit-files | 
git commit <files> -m <message> | 
Commit specific files | 
git-clone | 
git clone <url> | 
Clone repository | 
git-status | 
git status --porcelain | 
Show repo status | 
{
  "files": ["file1.txt", "file2.txt"]  // Array of files
  // OR
  "file": "single_file.txt"            // Single file
}{
  "args": "--oneline -10"              // Optional: git log arguments
}{
  // No parameters = working directory vs staged
  // OR
  "ref": "HEAD~1"                      // Compare against reference
  // OR
  "ref1": "HEAD~2",                    // Compare two references
  "ref2": "HEAD~1"
}{
  "remote": "origin",                  // Required: remote name
  "branch": "main"                     // Required: branch name
}{
  "remote": "origin"                   // Required: remote name
}- Relative Paths Only: Absolute paths rejected (
/etc/passwdβ) - Traversal Prevention: Parent directory access blocked (
../../../etcβ) - Base Directory Enforcement: Operations confined to working directory
 - Path Normalization: Handles 
./,//, and other edge cases 
- File Size Limits: Configurable maximum file sizes (default: 10MB)
 - Operation Whitelisting: Restrict available operations per deployment
 - Extension Filtering: Optional file type restrictions
 - Memory Protection: Streaming for large files
 
- Information Hiding: Errors don't leak system details
 - Graceful Degradation: Partial failures handled cleanly
 - Audit Logging: All operations logged for security review
 - Input Sanitization: All inputs validated before processing
 
# Full test suite
rebar3 eunit
# Specific test modules
rebar3 eunit --module=devout_test
# With coverage
rebar3 cover# Static analysis
rebar3 dialyzer
# Cross references
rebar3 xref
# Linting
rebar3 lint# Interactive shell
rebar3 shell
# Start stdio server manually
devout_server:start_stdio().- 
Add to allowed_operations in
devout.app.src - 
Implement in devout_fs.erl (if needed):
my_operation(Path) -> case devout_path_validator:validate_path(Path) of {ok, ValidPath} -> % Your operation here ok; {error, Reason} -> {error, Reason} end.
 - 
Register tool in devout_app.erl:
ok = erlmcp_stdio:add_tool( <<"my-operation">>, <<"Description">>, fun devout_server:handle_my_operation/1, SchemaMap ).
 - 
Add handler in devout_server.erl:
handle_my_operation(#{<<"path">> := Path}) -> case devout_fs:my_operation(Path) of ok -> <<"Success message">>; {error, Reason} -> devout_fmt:err(Reason) end.
 
- 
Add function to devout_git.erl:
my_git_operation(Args) -> execute_git(["my-operation"] ++ Args).
 - 
Add MCP handler:
handle_my_git_operation(#{<<"param">> := Value}) -> case my_git_operation([binary_to_list(Value)]) of {ok, Output} -> <<"Success: ", Output/binary>>; {error, Reason} -> devout_fmt:err(Reason) end.
 - 
Register tool in devout_server.erl:
ok = erlmcp_stdio:add_tool( <<"git-my-operation">>, <<"Description of my git operation">>, fun devout_git:handle_my_git_operation/1, Schema ).
 - 
Add tests in devout_git_test.erl
 
# Check if application is running
erl -eval "io:format('~p~n', [application:which_applications()]), halt()."
# Verify processes
erl -eval "io:format('~p~n', [whereis(devout_server)]), halt()."Logs go to stderr to avoid interfering with MCP protocol on stdout:
- Info: Service lifecycle events
 - Warning: Security violations, invalid paths
 - Error: Operation failures, system errors
 
| Issue | Cause | Solution | 
|---|---|---|
| Service not starting | Missing dependencies | rebar3 get-deps && rebar3 compile | 
| Path rejected | Absolute/traversal path | Use relative paths only | 
| File too large | Exceeds size limit | Check max_file_size config | 
| Permission denied | File system permissions | Check directory ownership | 
| Tool not found | Version mismatch | Verify erlmcp compatibility | 
| Git command timeout | Long-running operation | Increase timeout in erlexec config | 
| Git not found | Missing git installation | Install git and ensure it's in PATH | 
| Merge conflicts | Conflicting changes | Resolve conflicts manually | 
| Repository not found | Not in git repository | Initialize with git init first | 
- Memory Usage: ~10MB base + file buffers
 - Throughput: 1000+ operations/second for small files
 - Latency: <10ms for typical operations
 - Scalability: Single-threaded, suitable for interactive use
 
- Fork the repository
 - Create a feature branch
 - Add tests for new functionality
 - Ensure all tests pass: 
rebar3 eunit - Run static analysis: 
rebar3 dialyzer - Submit a pull request
 
- Use OTP principles and gen_server patterns
 - Comprehensive error handling with proper types
 - Security-first design - validate all inputs
 - Document all public functions with edoc
 - Follow Erlang naming conventions
 
- Built on the excellent erlmcp library
 - Inspired by security practices from the Erlang/OTP ecosystem
 - Thanks to the Claude Desktop team for MCP protocol specification
 
Apache License 2.0 - see LICENSE file for details.
