About mcp-shell 🐚
mcp-shell is an MCP server written in Go that lets an LLM run shell-related operations under explicit control. It starts in secure mode with only typed tools, confined to a configured working directory. It suits users who want to audit and constrain what an AI can execute.
Key features
- 01Secure mode by default with typed tools only
- 02Paths confined to the working_directory
- 03File read, grep, glob and git inspection tools
- 04Write tools and scripts require explicit opt-in
- 05Docker image and YAML security configuration
Use cases
- 01Letting an LLM search code and inspect git history in a fixed directory
- 02Allowing controlled file edits and commits after enabling writes
- 03Running raw shell access in local development with MCP_SHELL_ALLOW_UNSAFE
MCP support
This project is an MCP server that exposes typed file, search and git tools to MCP clients; in unrestricted mode it exposes only the shell_exec tool.
Configuration
Local server config is the same for every client. Copy it into your client’s MCP config file.
Tools
Local servers expose tools only once running on your machine, and the maintainer has not provided a tool list.
Details
From the project READMEmcp-shell
MCP server that runs shell commands. Your LLM gets a tool; you get control over what runs and how.
Built on mark3labs/mcp-go. Written in Go.
Run it
Docker (easiest):
docker run -it --rm -v /tmp/mcp-workspace:/tmp/mcp-workspace sonirico/mcp-shell:latest
From source:
git clone https://github.com/sonirico/mcp-shell && cd mcp-shell
make install
mcp-shell
Configure it
Secure mode is the default. With no config file, mcp-shell boots in secure
mode and registers only typed tools: file reads, grep/glob, git inspection,
and (opt-in) file/git writes and operator-defined scripts. There is no raw
shell command. You only need a config file to change the defaults below. To
run fully unrestricted you must opt in explicitly:
MCP_SHELL_ALLOW_UNSAFE=1 mcp-shell # disables secure mode; the only tool is shell_exec
To customize the policy, point to a YAML config:
export MCP_SHELL_SEC_CONFIG_FILE=/path/to/security.yaml
mcp-shell
Secure mode (default) — typed tools only, every path confined to working_directory:
security:
enabled: true
working_directory: /tmp/mcp-workspace
max_execution_time: 30s
max_output_size: 1048576
run_as_user: ""
audit_log: true
# Expose file and git write tools (write_file, edit_file, mkdir, move,
# delete, git_add, git_commit, git_switch, git_restore, git_stash). Off by
# default.
writes_enabled: false
# Operator-defined scripts exposed through the run_script tool. The client
# picks a name; the argv is yours and cannot be altered.
# scripts:
# test: ["go", "test", "./..."]
# lint: ["golangci-lint", "run"]
Wire it up
Claude Desktop — add to your MCP config:
{
"mcpServers": {
"shell": {
"command": "docker",
"args": ["run", "--rm", "-i", "sonirico/mcp-shell:latest"],
"env": { "MCP_SHELL_LOG_LEVEL": "info" }
}
}
}
For custom config, mount the file and set the env:
{
"command": "docker",
"args": ["run", "--rm", "-i", "-v", "/path/to/security.yaml:/etc/mcp-shell/security.yaml", "-e", "MCP_SHELL_SEC_CONFIG_FILE=/etc/mcp-shell/security.yaml", "sonirico/mcp-shell:latest"]
}
Tools
Secure mode (the default) registers these typed tools. * marks a required
parameter.
| Tool | Parameters | Available |
|---|---|---|
read_file | path*, offset, limit, tail | always |
list_dir | path, depth, include_hidden | always |
glob | pattern*, path, newer_than, max_results | always |
grep | pattern*, path, glob, ignore_case, context, files_only, count, max_results | always |
stat | path* | always |
diff_files | path_a*, path_b* | always |
system_info | always | |
git_status | always | |
git_log | max_count, ref, path, author, grep, since, until, oneline, follow | always |
git_diff | ref, ref_to, staged, path, stat_only, name_only | always |
git_show | ref, path, stat_only | always |
git_blame | path*, ref, line_start, line_end | always |
git_branches | all, merged | always |
git_tags | pattern | always |
git_rev_parse | ref* | always |
git_ls_files | path, untracked | always |
git_stash_list | always | |
git_remotes | always | |
write_file | path*, content*, append | writes_enabled |
edit_file | path*, old_string*, new_string*, replace_all | writes_enabled |
mkdir | path* | writes_enabled |
move | from*, to* | writes_enabled |
delete | path*, recursive | writes_enabled |
git_add | paths, all | writes_enabled |
git_commit | message*, all | writes_enabled |
git_switch | branch*, create | writes_enabled |
git_restore | paths*, staged | writes_enabled |
git_stash | action*, message | writes_enabled |
run_script | name* | scripts |
Every path parameter is resolved against working_directory (symlinks
followed); anything outside it is rejected. Git paths and refs are passed
positionally and validated: a ref starting with - is rejected. There are no
network tools; push, fetch and clone are not offered.
Unrestricted mode: shell_exec exists only with MCP_SHELL_ALLOW_UNSAFE=1, runs the command through bash -c with no validation, by design, and it is the only tool registered in that mode.
Environment variables
| Variable | Description |
|---|---|
MCP_SHELL_SEC_CONFIG_FILE | Path to security YAML (overrides built-in secure defaults) |
MCP_SHELL_ALLOW_UNSAFE | Set 1 (or true) to disable secure mode and expose shell_exec instead of the typed tools (opt-in) |
MCP_SHELL_SERVER_NAME | Server name (default: "mcp-shell 🐚") |
MCP_SHELL_LOG_LEVEL | debug, info, warn, error, fatal |
MCP_SHELL_LOG_FORMAT | json, console |
MCP_SHELL_LOG_OUTPUT | stdout, stderr, file |
Development
make install dev-tools # deps + goimports, golines
make fmt test lint
make docker-build # build image locally
make release # binary + docker image
Security
- Default: Secure mode. The server builds every command's argv itself; the client never supplies a shell string. Only typed tools are registered.
- Path confinement: every path parameter is resolved against
working_directory, symlinks followed, and anything that resolves outside it is rejected. - Git hardening: paths are passed after
--, refs after--end-of-options, and a ref starting with-is rejected. Git runs withGIT_CONFIG_NOSYSTEM=1,GIT_CONFIG_GLOBAL=/dev/null,core.fsmonitor,core.pagerandcore.hooksPathneutralised, and--no-ext-diff --no-textconvon log/diff/show/blame. - Minimal environment: child processes get only
PATH,HOMEandLANG, never the server's own environment or.envsecrets. - Writes and scripts are opt-in:
writes_enabled: trueexposes the file/git write tools; a non-emptyscriptsmap exposesrun_script. Both are off by default. - Unrestricted: only via
MCP_SHELL_ALLOW_UNSAFE=1. The only tool registered isshell_exec, which runsbash -cwith no validation. Fine for local dev, dangerous otherwise. - Docker: Runs as non-root, Alpine-based. Use it in production. Best paired with an OS sandbox (read-only FS, dropped caps) as defense-in-depth.
Threat model, guarantees, and the scope for vulnerability reports live in SECURITY.md. Read it before opening an advisory.
Migrating from 0.x
Secure mode no longer validates a shell_exec command string; it exposes
typed tools instead. A config file's security: block no longer accepts:
| Removed key | Replacement |
|---|---|
use_shell_execution | not needed; typed tools never shell out |
allowed_executables | not needed; each tool runs a fixed, server-built argv |
allowed_commands | not needed; same as above |
blocked_commands | not needed; same as above |
blocked_patterns | not needed; same as above |
Loading a config file that still sets one of these fails at startup with an
error naming the key. There is no more "legacy mode" and no
security-legacy.yaml example. If you need raw shell access, set
MCP_SHELL_ALLOW_UNSAFE=1 to get shell_exec back; it is no longer
constrained by the security: block at all.
Contributing
Fork, branch, make fmt test, open a PR.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.