The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the MCP Shell listing page.
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.
Docker (easiest):
From source:
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:
To customize the policy, point to a YAML config:
Secure mode (default) — typed tools only, every path confined to working_directory:
Claude Desktop — add to your MCP config:
For custom config, mount the file and set the env:
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.
| 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 |
working_directory, symlinks followed, and anything that resolves outside
it is rejected.--, refs after
--end-of-options, and a ref starting with - is rejected. Git runs with
GIT_CONFIG_NOSYSTEM=1, GIT_CONFIG_GLOBAL=/dev/null, core.fsmonitor,
core.pager and core.hooksPath neutralised, and --no-ext-diff --no-textconv on log/diff/show/blame.PATH, HOME and LANG,
never the server's own environment or .env secrets.writes_enabled: true exposes the
file/git write tools; a non-empty scripts map exposes run_script. Both
are off by default.MCP_SHELL_ALLOW_UNSAFE=1. The only tool
registered is shell_exec, which runs bash -c with no validation. Fine
for local dev, dangerous otherwise.Threat model, guarantees, and the scope for vulnerability reports live in SECURITY.md. Read it before opening an advisory.
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.
Fork, branch, make fmt test, open a PR.