gaspardpetit/mcp-shell
If you are the rightful owner of mcp-shell and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.
mcp-shell is a containerized Model Context Protocol (MCP) server designed to provide a comprehensive set of tools for LLMs within a controlled Docker environment.
mcp-shell
mcp-shell is a containerized Model Context Protocol (MCP) server exposing a rich set of tools an LLM can call. It’s built to be capable (document/code tooling, search/convert, shell) yet contained inside a Docker sandbox you control.
Transports: http (streamable, default) · sse · stdio
Presets: minimal · standard · full
Overview
- Purpose: Let an LLM run commands, inspect/transform files, process documents (Word/Excel/PowerPoint/PDF), and use common CLIs (Python, Node.js, Git, jq/yq, ripgrep, ImageMagick, ffmpeg, Tesseract, Pandoc, Poppler, DuckDB CLI, etc.).
- Primary tools:
shell.exec,python.run,node.run,sh.script.write_and_run, package managers (apt.install,pip.install,npm.install),git.*(clone, status, commit, pull, push, etc.),fs.*(list, stat, read, write, search, hash, etc.),archive.*, text utilities liketext.diffandtext.apply_patch, document helpers such asdoc.convert,pdf.extract_text,spreadsheet.to_csv,doc.metadata, media tools likeimage.convert,video.transcode,ocr.extract, and web tools likehttp.request,web.download,web.search, andmd.fetch, and process tools likeproc.spawn,proc.stdin,proc.wait,proc.kill,proc.list. - Dependencies:
fs.searchrelies on thergbinary (ripgrep); document tools rely onpandoc. Development dependencies are listed inscripts/deps.txtand can be installed viascripts/install-deps.sh. - Function reference: see for supported functions.
- Security model: Execution is confined to a non-root user in a container. You control:
- Host mounts (read-only vs read-write).
- Network egress (enable/disable at run-time).
- Resource limits (CPU, RAM, pids).
- Auditability: Tool calls are JSONL-logged to
/logs/mcp-shell.log(when/logsis mounted). Default caps: timeout 60s; 1 MiB per stream (stdout/stderr). - Observability: Prometheus metrics are exposed at
GET /metrics.
Build
Tool layers are prebuilt and published with a tools- tag prefix. The main
Dockerfile simply adds the compiled server on top of one of these layers:
# Standard image using the latest tool layer
docker build -t mcp-shell:std \
--build-arg BASE_IMAGE=ghcr.io/<owner>/mcp-shell:tools-std-latest .
# Light image
docker build -t mcp-shell:mini \
--build-arg BASE_IMAGE=ghcr.io/<owner>/mcp-shell:tools-light-latest .
# Full image
docker build -t mcp-shell:full \
--build-arg BASE_IMAGE=ghcr.io/<owner>/mcp-shell:tools-full-latest .
To rebuild the underlying tool layer itself (usually only needed when the
tooling changes), use Dockerfile.tools:
docker build -f Dockerfile.tools -t ghcr.io/<owner>/mcp-shell:tools-std \
--build-arg PRESET=standard .
Optional OCR languages or locales (meaningful for standard/full) apply when
building the tool layer:
# Extra OCR languages (English `eng` is always installed)
docker build -f Dockerfile.tools -t ghcr.io/<owner>/mcp-shell:tools-std \
--build-arg PRESET=standard \
--build-arg TESS_LANGS_EXTRA="tesseract-ocr-fra tesseract-ocr-deu" .
# Extra locales (English en_US.UTF-8 is always enabled)
docker build -f Dockerfile.tools -t ghcr.io/<owner>/mcp-shell:tools-std \
--build-arg PRESET=standard \
--build-arg EXTRA_LOCALES="fr_CA.UTF-8 de_DE.UTF-8" .
Run
A) Service mode (HTTP, default)
The image defaults to --transport=http, listening on ${PORT:-3333} and serving /mcp plus /healthz.
docker run --rm -d --name mcp-shell \
--user 10001:10001 \
--read-only \
--tmpfs /tmp:rw,nosuid,nodev,size=512m \
--tmpfs /run:rw,nosuid,nodev,size=64m \
--cap-drop=ALL \
--security-opt no-new-privileges \
--pids-limit=2048 \
--memory=4g --cpus=2 \
--init \
-e EGRESS=1 \
-p 3333:3333 \
-v "$PWD/workspace":/workspace:rw \
-v "$PWD/logs":/logs:rw \
mcp-shell:std
Notes:
- Mount something into
/workspaceif you wantshell.exectolsreal files. EGRESS=1just sets intent for your server/tools; actual network policy is up to how you run Docker.- Package managers (
apt.install,pip.install,npm.install) are disabled unlessEGRESS=1or the server is started with--allow-pkg. SHELL_EXEC_DENYandSHELL_EXEC_ALLOWcan configure block/allow patterns forshell.exec. Global concurrency is capped byMAX_CONCURRENCY; per-tool rate limits useRATE_LIMIT_<TOOL>environment variables (default 5 RPS).
B) Air-gapped mode (STDIO)
If you prefer no network at all, keep a container idling and start MCP sessions via stdio:
# Idle container with no network
docker run -d --name mcp-stdio --rm \
--user 10001:10001 \
--read-only \
--tmpfs /tmp:rw,nosuid,nodev,size=512m \
--tmpfs /run:rw,nosuid,nodev,size=64m \
--cap-drop=ALL \
--security-opt no-new-privileges \
--pids-limit=2048 \
--memory=4g --cpus=2 \
--network=none \
--init \
-e EGRESS=0 \
-v "$PWD/workspace":/workspace:rw \
-v "$PWD/logs":/logs:rw \
mcp-shell:std tail -f /dev/null
# Start a one-off MCP stdio session (single client)
docker exec -i mcp-stdio /app/mcp-server --transport=stdio
Healthcheck
In HTTP mode the container exposes:
GET /healthz(top-level)GET /mcp/health(built-in)
The Docker HEALTHCHECK probes those endpoints internally; no action required.
Quick manual check:
curl -fsS http://127.0.0.1:3333/healthz | jq
Testing (HTTP / streamable)
Streamable HTTP is session-based. First call initialize to get a Mcp-Session-Id, then send notifications/initialized, and reuse the session for subsequent requests.
# 1) Initialize (creates session)
INIT_RES_HEADERS=$(mktemp)
curl -sS -D "$INIT_RES_HEADERS" \
-H 'content-type: application/json' \
-H 'accept: application/json' \
-X POST http://127.0.0.1:3333/mcp/ \
-d '{
"jsonrpc":"2.0",
"id":"1",
"method":"initialize",
"params": { "protocolVersion": "2025-06-18", "capabilities": {} }
}' >/dev/null
# Extract session id from response headers
SID=$(awk -F': ' 'BEGIN{IGNORECASE=1} /^Mcp-Session-Id:/ {print $2}' "$INIT_RES_HEADERS" | tr -d '\r')
echo "Session: $SID"
# 2) Notify ready
curl -sS -H 'content-type: application/json' \
-H "Mcp-Session-Id: $SID" \
-X POST http://127.0.0.1:3333/mcp/ \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
# 3) List tools (expect "shell.exec" and "fs.*" tools)
curl -sS -H 'content-type: application/json' -H "Mcp-Session-Id: $SID" \
-X POST http://127.0.0.1:3333/mcp/ \
-d '{"jsonrpc":"2.0","id":"2","method":"tools/list"}' | jq .
# 4) Run a command
curl -sS -H 'content-type: application/json' -H "Mcp-Session-Id: $SID" \
-X POST http://127.0.0.1:3333/mcp/ \
-d '{
"jsonrpc":"2.0","id":"3","method":"tools/call",
"params":{"name":"shell.exec","arguments":{"cmd":"ls -la","cwd":"/workspace"}}
}' | jq .
SSE variant: start with --transport=sse, open the event stream (GET /mcp/sse), then POST messages to /mcp/message with the same session id.
STDIO smoke test:
docker run -i --rm mcp-shell:std --transport=stdio <<'JSON'
{"jsonrpc":"2.0","id":"1","method":"tools/list"}
{"jsonrpc":"2.0","id":"2","method":"tools/call","params":{"name":"shell.exec","arguments":{"cmd":"ls -la","cwd":"/workspace"}}}
JSON
Project Purpose
A practical LLM toolbox MCP server:
- Capability: broad, pragmatic CLIs and libraries that cover 90% of real tasks.
- Safety: non-root user, read-only rootfs option, least privilege, and explicit mounts.
- Extensibility: add new tools or apt/pip/npm packages as needed.
Disclaimer
Use at your own risk — you decide what mounts, network, and limits to grant. If you enable network access, the agent may reach internal hosts visible to the container. Keep it in a container; don’t run it directly on your host unless you truly know what you’re doing.