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 compile
rebar3 eunit
Edit 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.