Securely Execute Shell Commands for AI
Go MCP server giving an LLM controlled filesystem and Git access via typed tools by default - no raw shell command unless opted in.
1.1.0Add to Favorites
Why it matters
Bridge AI assistants with your shell environment by providing secure, auditable execution of shell commands. Offers fine-grained control over command execution through allowlists, blocklists, and environment variable configuration.
Outcomes
What it gets done
Execute shell commands with customizable security restrictions.
Generate structured JSON output including stdout, stderr, and exit codes.
Leverage Docker for secure, isolated command execution.
Audit all command executions for security and compliance.
Source
Get it from source
Spark does not host a copy of it.
Open sourceReports
Agent outcome reports
No reports yet
Capabilities
Tools your agent gets
Executes shell commands with customizable security restrictions, returning structured output with stdout, stderr, and exit codes.
Overview
Shell MCP Server
mcp-shell is a Go MCP server that gives an LLM typed, path-confined tools for reading files and inspecting or modifying a Git repository, with an opt-in unrestricted raw-shell mode for local development. Use it when an LLM needs controlled, auditable filesystem and Git access; avoid the unrestricted shell_exec mode outside local development since it runs arbitrary commands with no validation.
What it does
mcp-shell is an MCP server, written in Go on top of mark3labs/mcp-go, that gives an LLM a controlled way to interact with a filesystem and Git repository. It boots in a secure mode by default that exposes only typed, purpose-built tools - no raw shell command - and can optionally be switched to a fully unrestricted mode that hands the model a single shell_exec tool running bash -c with no validation.
When to use - and when NOT to
Use it when you want an LLM to read files, search code, inspect Git history, or, opt-in, write files and make commits, with every action confined to a configured working_directory and audited. Do not enable MCP_SHELL_ALLOW_UNSAFE=1 outside local development: unrestricted mode removes all validation and path confinement, running arbitrary shell commands built by the model itself.
Capabilities
Secure mode always registers read-only tools - read_file, list_dir, glob, grep, stat, diff_files, system_info - plus read-only Git inspection (git_status, git_log, git_diff, git_show, git_blame, git_branches, git_tags, git_rev_parse, git_ls_files, git_stash_list, git_remotes). Setting writes_enabled: true additionally exposes file and Git write tools (write_file, edit_file, mkdir, move, delete, git_add, git_commit, git_switch, git_restore, git_stash), and a non-empty scripts map in the config exposes operator-defined commands through a single run_script tool whose argv the client cannot alter. Every path parameter is resolved against working_directory with symlinks followed, and anything resolving outside it is rejected; Git paths and refs are passed positionally and validated, with a leading - rejected to prevent flag injection, and Git runs with a minimized, sandboxed environment (GIT_CONFIG_NOSYSTEM=1, neutralized hooks, pager, and fsmonitor). There are no network Git tools - push, fetch, and clone are not offered. Child processes only inherit PATH, HOME, and LANG, never the server's own environment or .env secrets.
How to install
Run it with Docker:
docker run -it --rm -v /tmp/mcp-workspace:/tmp/mcp-workspace sonirico/mcp-shell:latest
Or build from source:
git clone https://github.com/sonirico/mcp-shell && cd mcp-shell
make install
mcp-shell
To customize the security policy, point MCP_SHELL_SEC_CONFIG_FILE at a YAML file setting working_directory, max_execution_time, max_output_size, audit_log, writes_enabled, and optional scripts. To run fully unrestricted instead, set MCP_SHELL_ALLOW_UNSAFE=1, which disables secure mode entirely and leaves shell_exec as the only registered tool. The Docker image runs as non-root on Alpine and is recommended for production, ideally paired with OS-level sandboxing (read-only filesystem, dropped capabilities) as defense-in-depth.
Who it's for
Teams building coding agents or automation that need an LLM to read code, search a repository, and inspect or make Git commits under a tightly scoped, auditable security policy - without granting it an open shell.
Source README
mcp-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 nosecurity-legacy.yaml example. If you need raw shell access, setMCP_SHELL_ALLOW_UNSAFE=1 to get shell_exec back; it is no longer
constrained by the security: block at all.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.