io.github.amol21p/interactive-terminal
MCP server for real interactive terminal sessions — REPLs, SSH, databases, Docker
Open source Open in the app JSON README (API)
About
MCP server for real interactive terminal sessions — REPLs, SSH, databases, Docker
Details
- Kind
- MCP servers
- Topic
- Cloud & DevOps
- Publisher
- amol21p
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 1.0.7
- Stars
- 18
- Forks
- 6
- Open pull requests
- 1
- Last push
- 2026-02-16T16:30:41Z
- Repository state
- ativo
- Language
- TypeScript
- License
- MIT
- Added
- 2026-08-29 03:02:25
- Updated
- 2026-08-29 03:02:25
- Origin id
io.github.amol21p/interactive-terminal
README
# mcp-interactive-terminal
[](https://www.npmjs.com/package/mcp-interactive-terminal)
[](https://opensource.org/licenses/MIT)
[](https://nodejs.org/)
MCP server that gives AI agents (Claude Code, Cursor, Windsurf, etc.) real interactive terminal sessions. Run REPLs, SSH, database clients, and any interactive CLI — with clean text output, smart completion detection, and 7-layer security.
## Why This Exists
AI coding agents can't handle interactive commands. There's no PTY, no stdin streaming. You can't run `rails console`, `python`, `psql`, `ssh`, or any REPL through them. This MCP server fixes that.
```
AI Agent (Claude Code, Cursor, etc.)
↕ MCP (JSON-RPC over stdio)
mcp-interactive-terminal
↕ node-pty + xterm-headless
Interactive Process (rails console, python, psql, ssh, bash...)
↕
Clean text output (exactly what a human would see)
```
## Install
### Claude Code
```bash
claude mcp add terminal -- npx -y mcp-interactive-terminal
```
That's it. The server is now available. Ask Claude to "open a python REPL and calculate 2**100".
### Cursor
Go to **Settings > MCP Servers**, click **Add Server**, and enter:
```json
{
"mcpServers": {
"terminal": {
"command": "npx",
"args": ["-y", "mcp-interactive-terminal"]
}
}
}
```
### Windsurf
Add to your MCP configuration:
```json
{
"mcpServers": {
"terminal": {
"command": "npx",
"args": ["-y", "mcp-interactive-terminal"]
}
}
}
```
### VS Code (GitHub Copilot)
Add to your `.vscode/mcp.json`:
```json
{
"servers": {
"terminal": {
"command": "npx",
"args": ["-y", "mcp-interactive-terminal"]
}
}
}
```
### Any MCP Client
The server communicates over stdio using the [Model Context Protocol](https://modelcontextprotocol.io/). Any MCP-compatible client can use it with the same `npx -y mcp-interactive-terminal` command.
## Real-World Examples
### Rails Console
```
You: "Open rails console for staging and check the user count"
Agent creates session → bash
Agent sends: cd /path/to/app && rails console -e staging
Agent sends: User.count
Agent returns: 1,847,293
```
### Python REPL
```
You: "Open python and test my sorting algorithm"
Agent creates session → python3
Agent sends: def quicksort(arr): ...
Agent sends: quicksort([3, 1, 4, 1, 5, 9])
Agent returns: [1, 1, 3, 4, 5, 9]
```
### Database Client
```
You: "Connect to postgres and show me the largest tables"
Agent creates session → psql -U myuser mydb
Agent sends: SELECT tablename, pg_size_pretty(pg_total_relation_size(tablename::text)) ...
Agent returns: formatted table of results
```
### SSH
```
You: "SSH into the staging server and check disk usage"
Agent creates session → ssh user@staging.example.com
Agent sends: df -h
Agent returns: disk usage table
```
### Docker
```
You: "Open a shell in my running container and check the logs"
Agent creates session → docker exec -it my-container bash
Agent sends: tail -100 /var/log/app.log
Agent returns: last 100 log lines
```
### Node.js REPL
```
You: "Open node and test the date parsing logic"
Agent creates session → node
Agent sends: new Date('2024-02-29').toISOString()
Agent returns: 2024-02-29T00:00:00.000Z
```
## Tools
The server exposes 7 MCP tools:
### `create_session` — Spawn an interactive process
```json
{ "command": "python3", "name": "my-python", "cwd": "/project" }
→ { "session_id": "a1b2c3d4", "name": "my-python", "pid": 12345 }
```
| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `command` | Yes | — | Command to run (bash, python3, psql, ssh, etc.) |
| `args` | No | `[]` | Command arguments |
| `name` | No | auto | Human-readable session name |
| `cwd` | No | server cwd | Working directory |
| `env` | No | `{}` | Additional environment variables |
| `cols` | No | `120` | Terminal columns |
| `rows` | No | `40` | Terminal rows |
### `send_command` — Send input and get output
```json
{ "session_id": "a1b2c3d4", "input": "1 + 1" }
→ { "output": "2", "is_complete": true, "is_alive": true }
```
| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `session_id` | Yes | — | Target session |
| `input` | Yes | — | Command/input to send (newline appended automatically) |
| `timeout_ms` | No | `5000` | Max wait time for output |
| `max_output_chars` | No | `20000` | Truncate output beyond this |
Dangerous commands (`rm -rf`, `DROP TABLE`, `curl|bash`, etc.) are blocked — the agent must use `confirm_dangerous_command` first.
### `read_output` — Read terminal screen (read-only)
```json
{ "session_id": "a1b2c3d4" }
→ { "output": ">>> ", "is_alive": true }
```
Safe to auto-approve — this only reads, never sends input.
### `list_sessions` — List active sessions (read-only)
```json
→ [{ "session_id": "a1b2c3d4", "name": "my-python", "command": "python3", "pid": 12345, "is_alive": true }]
```
Safe to auto-approve.
### `close_session` — Kill a session
```json
{ "session_id": "a1b2c3d4" }
→ { "success": true }
```
### `send_control` — Send control characters
```json
{ "session_id": "a1b2c3d4", "control": "ctrl+c" }
→ { "output": "^C\n>>>" }
```
Supported: `ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+l`, `ctrl+r`, `tab`, `escape`, `up`, `down`, `left`, `right`, `enter`, `backspace`, `delete`, `home`, `end`, and more.
### `confirm_dangerous_command` — Two-step safety confirmation
```json
{ "session_id": "a1b2c3d4", "input": "rm -rf /tmp/old", "justification": "Cleaning up stale temp files from failed build" }
→ { "output": "...", "is_complete": true, "is_alive": true }
```
Required when `send_command` detects a dangerous pattern. The agent must explain why the command is necessary. This is a **separate tool** — even if `send_command` is auto-approved, this requires its own permission.
## How It Works
### Two Terminal Modes
**PTY mode** (default) — uses `node-pty` + `@xterm/headless` (the same terminal emulator as VS Code):
- Clean output — the AI sees exactly what a human would see on screen
- Cursor positioning, progress bars, `\r` overwrites all render correctly
- Full keyboard: arrow keys, tab completion, ctrl+c/d/z, home/end
- Terminal resize, TUI apps (vim, htop, top), 256-color, 1000-line scrollback
**Pipe mode** (automatic fallback) — activates when node-pty can't load (e.g., in sandboxed environments):
- Interactive sessions still work via `child_process.spawn` with auto-injected flags (`python -u -i`, `bash -i`, etc.)
- ANSI codes stripped, control keys still work
- No terminal emulation, but covers the basics
The mode is selected automatically — PTY is tried first, pipe mode kicks in if it fails.
### What the AI sees: PTY vs Pipe
| Scenario | PTY mode | Pipe mode |
|----------|----------|-----------|
| `printf "\rProgress: 3/3"` | `Progress: 3/3` | `Progress: 1/3Progress: 2/3Progress: 3/3` |
| ANSI colors | Stripped cleanly | Stripped via regex |
| vim, htop, top | Readable screen | Garbled |
| Arrow keys, tab completion | Works | Works |
| Terminal resize | Works | No-op |
### Smart "Command Done" Detection
Instead of blindly waiting a fixed time, the server uses a layered strategy:
1. **Process exit** — if the process died, command is done
2. **Prompt detection** — auto-detects the session's prompt at startup (bash `$`, python `>>>`, psql `#`, etc.), watches for it to reappear
3. **Output settling** — no new output for 300ms = probably done
4. **Timeout** — always returns after `timeout_ms` with `is_complete: false`
## Security
Seven-layer defense-in-depth:
| Layer | What It Does | Default |
|-------|-------------|---------|
| MCP Tool Annotations | `readOnlyHint`/`destructiveHint` on each tool | Always on |
| Confirmation Flow | Dangerous patterns require `confirm_dangerous_command` | Always on |
| Input Pattern Detection | Detect rm -rf, DROP TABLE, curl\|bash, etc. | Always on |
| Command Blocklist/Allowlist | Block/allow specific commands | Configurable |
| OS-Level Sandbox | Kernel-level process sandboxing via `@anthropic-ai/sandbox-runtime` | Off (opt-in) |
| Secret Redaction | Redact AWS keys, tokens, private keys in output | Off (opt-in) |
| Resource Limits | Max sessions, output cap, idle timeout, audit logging | Always on |
### Recommended Permissions
Only auto-approve the read-only tools:
```json
{
"permissions": {
"allow": [
"mcp__terminal__list_sessions",
"mcp__terminal__read_output"
]
}
}
```
This way `send_command`, `create_session`, and especially `confirm_dangerous_command` always require human approval.
## Configuration
All settings via environment variables. Pass them in your MCP config:
```json
{
"mcpServers": {
"terminal": {
"command": "npx",
"args": ["-y", "mcp-interactive-terminal"],
"env": {
"MCP_TERMINAL_ALLOWED_COMMANDS": "bash,python3,node,psql",
"MCP_TERMINAL_REDACT_SECRETS": "true",
"MCP_TERMINAL_IDLE_TIMEOUT": "300000"
}
}
}
}
```
| Variable | Default | Description |
|----------|---------|-------------|
| `MCP_TERMINAL_MAX_SESSIONS` | `10` | Max concurrent sessions |
| `MCP_TERMINAL_MAX_OUTPUT` | `20000` | Max output chars per read |
| `MCP_TERMINAL_DEFAULT_TIMEOUT` | `5000` | Default wait timeout (ms) |
| `MCP_TERMINAL_BLOCKED_COMMANDS` | — | Comma-separated blocklist |
| `MCP_TERMINAL_ALLOWED_COMMANDS` | — | Comma-separated allowlist (if set, only these are allowed) |
| `MCP_TERMINAL_ALLOWED_PATHS` | — | Comma-separated paths sessions can access |
| `MCP_TERMINAL_REDACT_SECRETS` | `false` | Redact AWS keys, tokens, private keys in output |
| `MCP_TERMINAL_LOG_INPUTS` | `false` | Log all inputs to stderr (for debugging) |
| `MCP_TERMINAL_IDLE_TIMEOUT` | `1800000` | Auto-close idle sessions (ms, default 30min, 0 = disabled) |
| `MCP_TERMINAL_DANGER_DETECTION` | `true` | Enable dangerous command confirmation flow |
| `MCP_TERMINAL_AUDIT_LOG` | — | Path to JSON audit log file |
| `MCP_TERMINAL_SANDBOX` | `false` | Enable OS-level kernel sandboxing |
| `MCP_TERMINAL_SANDBOX_ALLOW_WRITE` | `/tmp` | Writable paths in sandbox mode |
| `MCP_TERMINAL_SANDBOX_ALLOW_NETWORK` | `*` | Allowed network domains in sandbox |
## Troubleshooting
### "Tools not showing up" / Server fails silently
MCP servers that fail to start often show no error in the client. Check:
```bash
# Test the server directly:
npx -y mcp-interactive-terminal
# You should see "[mcp-terminal] Starting MCP Interactive Terminal Server" on stderr.
# If you see an error, that's what's failing.
```
### Node.js version too old
The server requires Node.js >= 18. If you see errors about unsupported syntax or missing APIs:
```bash
node --version # Must be >= 18
# If using nvm:
nvm install 18 && nvm use 18
# If using volta:
volta install node@18
```
**For nvm/volta/fnm users**: `npx` may use a different Node version than your shell. Use an absolute path:
```json
{
"mcpServers": {
"terminal": {
"command": "/Users/you/.nvm/versions/node/v22.0.0/bin/npx",
"args": ["-y", "mcp-interactive-terminal"]
}
}
}
```
Find your path with: `which npx`
### node-pty compilation errors
`node-pty` is a native module that requires build tools. If it fails to compile, the server automatically falls back to **pipe mode** — interactive sessions still work, just without terminal emulation.
If you want full PTY support:
```bash
# macOS:
xcode-select --install
# Ubuntu/Debian:
sudo apt-get install -y make python3 build-essential
# RHEL/Fedora:
sudo yum install -y make python3 gcc gcc-c++
```
### Session dies immediately
Some commands need to be run inside a shell rather than directly:
```
# Instead of: create_session({ command: "rails console -e staging" })
# Do this: create_session({ command: "bash" })
# send_command({ input: "rails console -e staging" })
```
This is because `create_session` runs the command directly (like `exec`), not through a shell. Spawning `bash` first gives you a full shell environment.
### Output looks garbled
If output contains escape codes or looks wrong, you're likely in **pipe mode** (node-pty failed to load). Check the server logs for `"falling back to pipe mode"`. Install build tools (see above) to enable PTY mode.
### Timeout too short for long-running commands
Increase the timeout per-command:
```json
{ "session_id": "...", "input": "bundle install", "timeout_ms": 60000 }
```
Or globally via environment variable:
```json
{ "env": { "MCP_TERMINAL_DEFAULT_TIMEOUT": "30000" } }
```
## Comparison with Alternatives
| Feature | mcp-interactive-terminal | App-specific terminal servers | Generic shell MCP servers |
|---------|------------------------|-------------------------------|--------------------------|
| Cross-platform | Yes | Often single-app only | Varies |
| Clean output (xterm-headless) | Yes | No (screen scrape) | No (raw PTY dump) |
| Smart completion detection | 4-layer algorithm | No | Basic timeout |
| Security layers | 7 (confirmation flow, sandbox, redaction, etc.) | None | Basic |
| Dangerous command confirmation | Yes (separate tool) | No | No |
| MCP tool annotations | Yes | No | No |
| Background sessions | Yes | No (uses active tab) | Yes |
| Focused API | 7 tools | 2-3 tools | 15-20+ tools (scope creep) |
| Install | `npx -y` (zero-config) | Requires specific app | Varies |
## Development
```bash
git clone https://github.com/amol21p/mcp-interactive-terminal.git
cd mcp-interactive-terminal
npm install
npm run build
npm test
```
Test with MCP Inspector:
```bash
npx @modelcontextprotocol/inspector dist/index.js
```
## License
MIT