# Host Terminal MCP
[](https://pypi.org/project/host-terminal-mcp/)
[](https://opensource.org/licenses/Apache-2.0)
An [MCP](https://modelcontextprotocol.io) server that lets AI assistants run terminal commands on your machine with permission controls. Built for **Claude Desktop / Co-work** and any MCP-compatible client.
## How It Works
```
You (in Co-work) Your Mac
───────────────── ─────────
"run git status"
│
▼
Claude (cloud)
│ MCP tool call:
│ execute_command("git status")
▼
Claude Desktop (local)
│ forwards via stdio pipe
▼
host-terminal-mcp ◄── this project
│ 1. permission check ✅
│ 2. /bin/bash -c "git status"
▼
Terminal output flows back up the chain
```
Claude Desktop spawns `host-terminal-mcp` as a child process and communicates over stdin/stdout using the MCP protocol. There is no network server involved — it's a local pipe.
## Setup for Co-work
### 1. Install
```bash
uv tool install host-terminal-mcp
```
Or with pip:
```bash
pip install host-terminal-mcp
```
### 2. Configure Claude Desktop
Add the MCP server to your Claude Desktop config file:
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
> **Important:** Claude Desktop runs with a minimal PATH (`/usr/local/bin`, `/usr/bin`, `/bin`, `/usr/sbin`, `/sbin`, `/opt/homebrew/bin`). If you installed with `uv tool install` or `pip install --user`, the binary is likely in `~/.local/bin/` which is **not** in Claude Desktop's PATH. Use the **full absolute path** to the binary to avoid "No such file or directory" errors.
Find your binary path:
```bash
which host-terminal-mcp
# Example output: /Users/you/.local/bin/host-terminal-mcp
```
Then use that path in the config:
```json
{
"mcpServers": {
"host-terminal": {
"command": "/Users/you/.local/bin/host-terminal-mcp"
}
}
}
```
To start in `ask` mode (recommended — prompts you before running unlisted commands):
```json
{
"mcpServers": {
"host-terminal": {
"command": "/Users/you/.local/bin/host-terminal-mcp",
"args": ["--mode", "ask"]
}
}
}
```
> **Tip:** If you installed globally to a system path (e.g. `/usr/local/bin/host-terminal-mcp`), you can use just `"command": "host-terminal-mcp"` without the full path.
### 3. Restart Claude Desktop
Quit and reopen Claude Desktop. It will automatically spawn the `host-terminal-mcp` process. You can verify it's running:
```bash
ps aux | grep host-terminal-mcp
```
### 4. Use it
Open a Co-work session in Claude.ai (or use Claude Desktop directly) and ask:
- "List files in my home directory"
- "Show git status in ~/projects/myapp"
- "What's running on port 3000?"
- "Run the tests for this project"
## Why This Tool
**Terminal access with guard rails.** Three permission modes let you choose the right level of access — a locked-down allowlist for read-only commands, an `ask` mode that prompts you for real-time approval using [MCP elicitation](https://modelcontextprotocol.io/specification/draft/client/elicitation), or an unrestricted mode for sandboxed environments.
**Skills that teach the AI how to use the terminal.** The plugin ships with [skills](skills/) — structured guides that Claude reads at runtime. A [codebase explorer](skills/codebase-explorer/SKILL.md) skill teaches project navigation patterns (directory structure, manifest detection, dependency tracing). A [terminal workflows](skills/terminal-workflows/SKILL.md) skill teaches safe shell execution (command chaining, error handling, process management). Claude doesn't just get access — it gets expertise.
**Slash commands and connectors.** Built-in [commands](commands/) like `/shell`, `/git`, and `/permissions` give you direct control. A [connector system](CONNECTORS.md) (`~~terminal`) lets other plugins reference terminal access without being tied to a specific MCP implementation — swap in SSH, Docker, or a cloud shell without changing your workflows.
## Permission Modes
| Mode | Behavior | Safety |
|------|----------|--------|
| `allowlist` (default) | Only pre-approved read-only commands run | Safest |
| `ask` | Prompts **you** for approval on unlisted commands | Recommended for power users |
| `allow_all` | Everything runs except blocked commands | Dangerous |
### How `ask` Mode Works
When Claude tries to run a command not in the allow list, the server uses [MCP elicitation](https://modelcontextprotocol.io/specification/draft/client/elicitation) to prompt **you** (the human) directly in the Claude Desktop UI:
```
┌─────────────────────────────────────────────┐
│ The AI wants to run a command that is not │
│ in the allow list: │
│ │
│ npm install │
│ │
│ Do you approve? │
│ │
│ [✓] Approve this command? │
│ [✓] Add to allowed list permanently? │
│ │
│ [ Cancel ] [ Submit ] │
└─────────────────────────────────────────────┘
```
- **Approve** — runs the command for this session
- **Add to allowed list permanently** — saves the command to your config file so you're never asked again
- **Cancel/Decline** — command is blocked
This is a real human-in-the-loop: Claude cannot approve commands on its own.
> **Note:** Elicitation requires MCP client support. If your client doesn't support it, unlisted commands are rejected with a message telling you to add them to the config file.
Permission check order: **blocked** (always wins) > **allowed** > **session-approved** > **mode decision**
## Default Allowed Commands
These commands (and their arguments) are allowed out of the box:
**File listing & navigation:**
`ls`, `ll`, `la`, `pwd`, `tree`, `find`, `locate`, `which`, `whereis`, `file`
**File viewing:**
`cat`, `head`, `tail`, `less`, `more`, `bat`, `wc`
**Search:**
`grep`, `rg`, `ag`, `ack`, `fzf`
**Git (read-only):**
`git status`, `git log`, `git diff`, `git show`, `git branch`, `git remote`, `git tag`, `git stash list`, `git rev-parse`, `git config --get`, `git config --list`, `git blame`, `git shortlog`, `git describe`
**System info:**
`uname`, `hostname`, `whoami`, `id`, `date`, `uptime`, `df`, `du`, `free`, `top -l 1`, `ps`
**Network (read-only):**
`ping -c`, `curl -I`, `curl --head`, `dig`, `nslookup`, `host`, `ifconfig`, `ip addr`, `netstat`, `ss`
**Package managers (info only):**
`npm list`, `npm ls`, `npm view`, `npm show`, `npm outdated`, `pip list`, `pip show`, `pip freeze`, `brew list`, `brew info`, `apt list`, `dpkg -l`
**Dev tool versions:**
`python --version`, `python3 --version`, `node --version`, `npm --version`, `cargo --version`, `rustc --version`, `go version`, `java --version`, `javac --version`, `ruby --version`, `docker --version`
**Docker (read-only):**
`docker ps`, `docker images`, `docker logs`
**Data processing:**
`jq`, `yq`
**Misc:**
`man`, `help`, `type`, `stat`, `md5sum`, `sha256sum`, `shasum`
## Always Blocked Commands
These are blocked regardless of permission mode:
| Pattern | Reason |
|---------|--------|
| `rm -rf /`, `rm -rf ~`, `rm -rf *` | Recursive delete |
| `mkfs`, `dd` | Format/overwrite disk |
| `find ... -exec` | Arbitrary command execution |
| `:(){` | Fork bomb |
| `> /dev/sd*` | Overwrite disk device |
| `chmod -R 777 /`, `chown -R` | Dangerous permission changes |
| `sudo`, `su`, `doas` | Privilege escalation |
| `reboot`, `shutdown`, `halt`, `poweroff` | System control |
| `kill`, `killall`, `pkill` | Process control |
| `nc -l`, `nmap` | Network attacks |
| `*/.ssh/`, `*/.aws/`, `*/.gnupg/` | Sensitive credential access |
| `/etc/shadow`, `/etc/passwd` | System file access |
| `history -c`, `shred` | History/credential wiping |
## Configuration
Config file: `~/.config/host-terminal-mcp/config.yaml`
```bash
# Generate a default config file
host-terminal-mcp --init-config
```
### Add custom allowed commands
```yaml
allowed_commands:
- pattern: "docker compose logs"
description: "Docker Compose service logs"
- pattern: "docker compose ps"
description: "Docker Compose service status"
- pattern: "npm install"
description: "Install npm packages"
# Use regex for flexible matching
- pattern: "^kubectl get "
description: "Kubernetes get resources"
is_regex: true
```
### Other options
```yaml
permission_mode: allowlist # allowlist | ask | allow_all
timeout_seconds: 300 # Max command execution time
max_output_size: 100000 # Max output chars (truncated beyond this)
shell: /bin/bash # Shell to use
allowed_directories: # Commands restricted to these dirs
- /Users/me
environment_passthrough: # Env vars passed to commands
- PATH
- HOME
- USER
- LANG
- LC_ALL
```
## HTTP Transport
For external services (e.g. a chatbot in Docker) that need to call your machine over the network:
```bash
# Install with HTTP extras
uv tool install 'host-terminal-mcp[http]'
# Start
host-terminal-mcp --http --port 8099
# Or in background
nohup host-terminal-mcp --http --port 8099 --mode ask > /tmp/host-terminal-mcp.log 2>&1 &
```
### Endpoints
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/health` | GET | Health check |
| `/execute` | POST | Run a command |
| `/cd` | POST | Change working directory |
| `/cwd` | GET | Get current directory |
| `/permissions` | GET | Get permission config |
### Example
```bash
curl -X POST http://localhost:8099/execute \
-H "Content-Type: application/json" \
-d '{"command": "docker compose ps", "working_directory": "/path/to/project"}'
```
## Architecture
```
src/host_terminal_mcp/
├── server.py ← MCP stdio server, tool handlers, elicitation
├── http_server.py ← Alternative HTTP/REST transport (FastAPI)
├── config.py ← Permission rules, allowlist/blocklist, YAML config
└── executor.py ← Runs commands via asyncio subprocess
```
**Tools exposed to the AI:**
| Tool | Description |
|------|-------------|
| `execute_command` | Run a shell command (main tool) |
| `change_directory` | Change working directory |
| `get_current_directory` | Get current working directory |
| `get_permission_status` | Inspect current permissions |
| `set_permission_mode` | Change permission mode |
## Development
```bash
git clone https://github.com/ankitaa186/host-terminal-mcp.git
cd host-terminal-mcp
make install # Install all deps (venv auto-created)
make test # Run tests
make lint # Run linters
make format # Format code
make run # Run stdio server (foreground)
make run MODE=ask # Run in ask mode
make inspect # Test with MCP Inspector
make help # Show all targets
```
### From source with Claude Desktop
```json
{
"mcpServers": {
"host-terminal": {
"command": "uv",
"args": ["run", "--directory", "/path/to/host-terminal-mcp", "host-terminal-mcp"]
}
}
}
```
## Disclaimer
This software executes shell commands on your computer as directed by an AI model. AI models can behave unpredictably. Although permission controls (allowlist, blocklist, human-in-the-loop approval) are provided, they are offered on a best-effort basis and **cannot guarantee safety**. In particular, AI models may attempt to bypass permission controls — for example, by calling tool APIs to self-approve commands or by crafting inputs that circumvent the allowlist. The `ask` mode depends on the MCP client correctly presenting approval prompts to a human; not all clients do so, and this project has no control over client behavior. By installing or using this software you acknowledge that: **(1)** you are solely responsible for every command that runs on your system, **(2)** the authors and contributors disclaim all liability for any damage, data loss, security breach, or other harm arising from its use, **(3)** permission controls are a best-effort safeguard, not a security boundary, and **(4)** this software is provided "AS IS" without warranties of any kind, as stated in the [Apache 2.0 License](LICENSE). Do not run this tool on production systems or systems containing sensitive data without understanding the risks.
## License
Apache-2.0 — see [LICENSE](LICENSE) for the full text, including the warranty disclaimer and limitation of liability.