Skip to main content
Glama

shell-mcp

Remote shell + file operations MCP server. Core is pure stdlib (fully unit-testable); only server.py depends on mcp / starlette / uvicorn.

Features

  • Streamable HTTP at /mcp (POST). No long-lived connections, safe behind buffered reverse proxies.

  • Bearer token auth (constant-time compare) + optional IP allowlist + /health endpoint.

  • Two execution modes: restricted (command allowlist, no shell operators) / unrestricted (full shell). Windows: unrestricted mode uses cmd.exe or PowerShell; POSIX: $SHELL -lc.

  • Reliable process execution: stdout+stderr merged in real order; output streamed to temp file (bounded memory, no pipe deadlock); children in their own process group; timeout kills the entire process tree (POSIX killpg / Windows taskkill /T).

  • Background tasks: run_command(background="always") for dev servers / builds; task(action="wait"|"output"|"cancel"|"list") to manage.

  • Sync-first by default: run_command waits up to SHELL_MCP_WAIT_SECONDS (30s) then auto-backgrounds. Short commands return results inline.

  • Safe editing: edit_file exact replacement + atomic write + unified diff; apply_patch multi-file atomic; expected_sha256 precondition prevents stale-read conflicts (returns FILE_CHANGED without touching files).

  • Code search: grep (ripgrep preferred, pure-Python fallback) + glob (sort by path or modified time).

  • Encoding consistency: auto-detect (UTF-8 / Windows code pages / GB18030), preserve original encoding on write.

  • Output governance: line+byte caps, tail truncation, full output spilled to OS temp dir (shell-mcp-spool/), auto-pruned.

  • Machine-readable errors: all failures return code (e.g. OLD_TEXT_NOT_FOUND, FILE_NOT_FOUND) + structured fields.

  • Audit log: JSON lines, auto-rotated.

  • Cross-platform: Windows / macOS / Linux. Platform-specific code gated behind os.name == "nt".

Related MCP server: MCP Remote Code

Quick Start

pip install -r requirements.txt
cp .env.example .env   # fill SHELL_MCP_TOKEN (openssl rand -hex 32)
# macOS / Linux:
./run.sh
# Windows:
run.bat

Client config (Streamable HTTP):

{
  "url": "http://127.0.0.1:8000/mcp",
  "headers": { "Authorization": "Bearer <SHELL_MCP_TOKEN>" }
}

Print a ready-to-paste client snippet (with real token + auto-detected Tailscale URL):

python -m shell_mcp.server --print-client

Tools (13)

Tool

Description

run_command

Execute a shell command. background="auto" (default): sync-first, auto-backgrounds after wait_seconds. Optional cwd, timeout, detail level.

task

Unified background task lifecycle. action: wait / output / cancel / list.

grep

Regex search. Structured results: [{path, line, text}]. Ripgrep preferred, pure-Python fallback.

glob

File pattern matching. Sort by path (stable) or modified (newest first).

list_dir

Directory listing with optional depth recursion.

read_file

Read a text file with paging. Returns sha256 + next_start_line. Images (png/jpeg/gif/webp) returned as image content.

read_files

Batch read up to 20 files. Partial failures don't block other files.

write_file

Atomic write. Optional expected_sha256 precondition. append mode supported.

edit_file

Exact string replacement with {old, new, replace_all?}. Returns unified diff.

apply_patch

Multi-file patch (*** Begin Patch envelope): add / update / delete / move. All-or-nothing.

set_workdir

Change working directory (convenience; prefer per-call cwd).

outline

Extract code symbols per language (regex-based, shebang-aware).

review_file

One-shot audit: read file + recent commits + diff (parallel via anyio).

Low-level tools (task_output, kill_task, list_tasks, delete_file, make_dir, rename_file) hidden by default; enable with SHELL_MCP_EXPOSE_LOWLEVEL=true.

Configuration

All via environment variables (see .env.example for full list):

Variable

Default

Description

SHELL_MCP_TOKEN

(required)

Bearer token for auth

SHELL_MCP_HOST

127.0.0.1

Bind address

SHELL_MCP_PORT

8000

Bind port

SHELL_MCP_WORKDIR

./default

Working directory

SHELL_MCP_UNRESTRICTED

false

Full shell vs. allowlist

SHELL_MCP_READONLY

true

Disable write/edit/patch tools

SHELL_MCP_TIMEOUT

120

Default command timeout (seconds)

SHELL_MCP_WAIT_SECONDS

30

Sync-first wait before auto-background

SHELL_MCP_ALLOWLIST

(built-in)

Comma-separated command list (restricted mode)

SHELL_MCP_ALLOWED_IPS

(empty)

Comma-separated IP allowlist

SHELL_MCP_ENCODING

auto

Output/file encoding

SHELL_MCP_LOG

shell-mcp-audit.log

Audit log path

SHELL_MCP_MAX_OUTPUT

51200

Max bytes returned per call

SHELL_MCP_MAX_LINES

2000

Max lines returned per call

SHELL_MCP_MAX_CAPTURE

5242880

Hard cap on in-memory output

SHELL_MCP_SPOOL_MAX_FILES

50

Max spilled output files kept

Exposing via Tailscale

Keep SHELL_MCP_HOST=127.0.0.1 (never bind to public interfaces). Let tailscaled handle TLS:

# Tailnet-only (auto-HTTPS):
tailscale serve --bg 8000

# Public internet (Funnel; requires Funnel enabled in admin console):
tailscale funnel --bg 8000

# Check status / stop:
tailscale serve status
tailscale funnel --bg off 8000

Client URL: https://<machine>.<tailnet>.ts.net/mcp

Security notes:

  • Funnel = anyone on the internet can reach this port. Use a long random token (openssl rand -hex 32). Keep SHELL_MCP_UNRESTRICTED=false.

  • Tailscale serve forwards real client IPs (100.x.y.z). Pin them with SHELL_MCP_ALLOWED_IPS for token+IP dual defense (tailnet only; don't use with Funnel).

  • Funnel only supports ports 443/8443/10000 for HTTPS. TLS terminated by Tailscale.

  • Audit log records commands and paths. Restrict file permissions on the log.

Architecture

shell_mcp/
├── config.py          # env → Config; SessionState
├── errors.py          # ToolFailure → MCP isError
├── paths.py           # Path resolution + sandbox enforcement
├── encoding.py        # Auto-detect encoding (UTF-8 / Windows / GB18030)
├── output.py          # Line+byte truncation, spool spill, pruning
├── process.py         # Command parse/execute: merged output, file capture, tree kill
├── tasks.py           # Background task registry
├── mutation_queue.py  # Per-path serialized write queue
├── editing.py         # Transactional edits: validate → atomic write → diff
├── audit.py           # JSON lines audit log
├── tools/
│   ├── shell.py       # run_command / task
│   ├── files.py       # File tools (read/write/edit/patch)
│   ├── search.py      # grep / glob
│   └── audit.py       # outline / review_file
└── server.py          # FastMCP + auth middleware (only third-party deps)

spool:// Resources

Truncated command output and background task logs are served as spool://<name> MCP resources. Tool results include full_output_resource / log_resource fields. Files live in the OS temp directory (shell-mcp-spool/) and are auto-pruned.

Design: Stale-Read Protection

read_file(path) → {text, sha256}
write_file(path, content, expected_sha256=<from read>)
→ server re-reads file, computes sha256(disk)
→ match? write. mismatch? FILE_CHANGED (file untouched)

No server-side cache. The SHA256 is computed from disk at read time and re-verified at write time. This is optimistic concurrency (ETag/If-Match pattern), not a cache.

Tests

Core is dependency-free; run directly:

python3 -m unittest discover -s tests -v

License

MIT — see LICENSE.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/takereshui/shell-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server