This server wraps persistent PTY sessions so Claude can interact with SSH connections, database CLIs, REPLs, and TUI applications that the built-in Bash tool can't handle. It exposes tools like session_create, session_send, session_read, and session_interact to manage long-lived processes across multiple tool calls. You get regex-triggered reads with session_wait_for, special key support for arrow keys and control characters, and four read modes including auto-detection of alternate screen buffers for tools like htop or vim. It includes a dangerous command gate that prompts before running destructive operations, OSC 133 shell integration for exit code detection, and configurable output truncation to manage token usage. Reach for this when you need Claude to maintain an interactive session that survives beyond a single command execution, whether that's a multi-line Python REPL or a live MySQL prompt.
Public tool metadata for what this MCP can expose to an agent.
terminal_startStart a terminal session. Auto-detects shell if omitted.6 paramsStart a terminal session. Auto-detects shell if omitted.
cwdstringenvobjectcolsintegernamestringrowsintegershellstringterminal_execRun a command in a session and wait for completion.6 paramsRun a command in a session and wait for completion.
commandstringtimeoutintegermaxLinesintegersessionIdstringquietExitMsintegerminOutputBytesintegerterminal_runRun a binary directly. shell=true for built-ins/pipes/redirects.12 paramsRun a binary directly. shell=true for built-ins/pipes/redirects.
cmdstringcwdstringargsarrayparsebooleanshellbooleansummarybooleantimeoutintegerparseOnlybooleansuccessFilestringmaxOutputBytesintegersuccessExitCodevaluesuccessFilePatternstringterminal_writeWrite raw data to a terminal session.2 paramsWrite raw data to a terminal session.
datastringsessionIdstringterminal_readRead new output from a terminal session.5 paramsRead new output from a terminal session.
sinceintegertimeoutintegermaxLinesintegersessionIdstringidleTimeoutintegerterminal_waitWait for a pattern to appear in terminal output.5 paramsWait for a pattern to appear in terminal output.
patternstringtimeoutintegersessionIdstringtailLinesintegerreturnModestringtail · full · match-onlydefault: tailterminal_stopStop a terminal session.3 paramsStop a terminal session.
sessionIdstringsnapshotLinesintegertranscriptPathstringterminal_listList active terminal sessions.1 paramsList active terminal sessions.
verbosebooleanterminal_extra8 more tools: terminal_run_paged, terminal_get_history, terminal_resize, terminal_send_key, terminal_watch, terminal_retry, terminal_diff, terminal_write_file. list=true for full schemas, or pass tool + args to call.3 params8 more tools: terminal_run_paged, terminal_get_history, terminal_resize, terminal_send_key, terminal_watch, terminal_retry, terminal_diff, terminal_write_file. list=true for full schemas, or pass tool + args to call.
argsobjectlistbooleantoolstring
MCP server for interactive terminal sessions — SSH, REPLs, database CLIs, and TUI apps.
If you've hit any of these limitations with Claude Code, terminal-mcp solves them:
terminal-mcp fills this gap by exposing MCP tools that create and manage real PTY sessions. Each session runs as a persistent child process; you send input, special keys, and control characters and read output across multiple tool calls for as long as the session lives.
session_interact combines send and read, halving LLM round tripssession_wait_for blocks until a pattern matches in output — no more guessing timeoutsrm -rf, DROP TABLE, curl|sh, etc.) and requires confirmationtail (default), head_tail (preserves beginning and end), tail_only (for build logs), nonetail, head_tail, tail_only, none) to prevent context overflow while preserving the most useful outputTERMINAL_MCP_* environment variablespip install terminal-mcp| Client | Status | Install |
|---|---|---|
| Claude Code (CLI) | ✅ Supported | ~/.claude.json or .mcp.json |
| Claude Desktop | ✅ Supported | One-click install |
| VS Code (Copilot Chat) | ✅ Supported | One-click install or .vscode/mcp.json |
| Cursor | ✅ Supported | One-click install or Settings → MCP |
| Windsurf | ✅ Supported | ~/.codeium/windsurf/mcp_config.json |
Recommended — no install needed:
uvx terminal-mcp
Or install via pip:
pip install terminal-mcp
Or from source:
git clone https://github.com/mkpvishnu/terminal-mcp.git
cd terminal-mcp
pip install -e ".[dev]"
Add to ~/.claude.json (or project .mcp.json):
{
"mcpServers": {
"terminal": {
"command": "uvx",
"args": ["terminal-mcp"]
}
}
}
Add to your claude_desktop_config.json:
{
"mcpServers": {
"terminal": {
"command": "uvx",
"args": ["terminal-mcp"]
}
}
}
Click the one-click install badge above, or add to .vscode/mcp.json:
{
"servers": {
"terminal-mcp": {
"command": "uvx",
"args": ["terminal-mcp"]
}
}
}
session_exec exec="echo hello from terminal-mcp"
session_create command="ssh user@myserver.example.com" label="prod-ssh"
session_read session_id="a1b2c3d4" timeout=5.0
session_send session_id="a1b2c3d4" password="mypassword"
session_send session_id="a1b2c3d4" input="df -h"
session_read session_id="a1b2c3d4"
session_close session_id="a1b2c3d4"
session_create command="python3" label="repl"
session_read session_id="e5f6g7h8"
session_send session_id="e5f6g7h8" input="import math"
session_send session_id="e5f6g7h8" input="print(math.sqrt(144))"
session_read session_id="e5f6g7h8"
session_close session_id="e5f6g7h8"
session_create command="python3 -m openclaw configure" label="openclaw"
session_read session_id="x1y2z3w4" timeout=3.0
session_send session_id="x1y2z3w4" key="down"
session_send session_id="x1y2z3w4" key="down"
session_send session_id="x1y2z3w4" key="enter"
session_read session_id="x1y2z3w4"
session_send session_id="x1y2z3w4" key="tab"
session_read session_id="x1y2z3w4"
session_close session_id="x1y2z3w4"
session_create command="htop" label="monitor"
session_read session_id="a1b2c3d4"
→ auto-detects TUI, returns snapshot with mode_used="snapshot", tui_active=true
session_read session_id="a1b2c3d4" mode="diff"
→ returns only changed lines since last read
session_read session_id="a1b2c3d4" mode="diff"
→ returns only lines that changed, minimizing tokens
session_close session_id="a1b2c3d4"
session_exec exec="ls -la /tmp"
session_exec exec="python3 -c 'print(42)'" command="bash" timeout=10.0
session_create command="bash" label="demo"
session_interact session_id="a1b2c3d4" input="ls -la" wait_for="\\$\\s*$" timeout=5.0
session_interact session_id="a1b2c3d4" input="whoami" wait_for="\\$"
session_close session_id="a1b2c3d4"
session_create command="bash" label="build"
session_send session_id="a1b2c3d4" input="npm run build"
session_wait_for session_id="a1b2c3d4" pattern="Build complete|ERROR" timeout=60.0
session_close session_id="a1b2c3d4"
session_send session_id="a1b2c3d4" input="rm -rf /tmp/old"
→ returns: requires_confirmation=true, reason="Matched dangerous pattern: ..."
session_send session_id="a1b2c3d4" input="rm -rf /tmp/old" confirmed=true
→ executes the command
session_send session_id="a1b2c3d4" control_char="c"
session_read session_id="a1b2c3d4"
Spawn a persistent PTY terminal session.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
command | string | Yes | — | Shell command to run (e.g. bash, python3, ssh user@host) |
label | string | No | command name | Human-readable label |
rows | integer | No | 24 | Terminal height |
cols | integer | No | 80 | Terminal width |
idle_timeout | integer | No | 1800 | Seconds before auto-close |
enable_snapshot | boolean | No | true | Deprecated: snapshot is now always enabled |
scrollback_lines | integer | No | 1000 | Scrollback history lines |
Returns: session_id, label, pid, created_at, snapshot_available
Send input text, a control character, or a special key to an active session. Only one of input, control_char, key, or password may be provided per call.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
session_id | string | Yes | — | Session ID from session_create |
input | string | No | — | Text to send |
press_enter | boolean | No | true | Append carriage return after input |
control_char | string | No | — | Control character: c d z l ] |
key | string | No | — | Special key (see table below) |
password | string | No | — | Password or secret (not logged) |
confirmed | boolean | No | false | Bypass dangerous command gate |
Returns: bytes_sent — or requires_confirmation, reason if the command matches a dangerous pattern
| Key | Description | Key | Description |
|---|---|---|---|
up | Arrow up | f1–f12 | Function keys |
down | Arrow down | home | Home |
left | Arrow left | end | End |
right | Arrow right | page-up | Page Up |
tab | Tab | page-down | Page Down |
shift-tab | Shift+Tab | insert | Insert |
escape | Escape | delete | Delete |
enter | Enter | backspace | Backspace |
| Char | Signal | Description |
|---|---|---|
c | SIGINT | Interrupt (Ctrl-C) |
d | EOF | End of file / logout (Ctrl-D) |
z | SIGTSTP | Suspend (Ctrl-Z) |
l | — | Clear screen (Ctrl-L) |
] | — | Telnet escape |
Resize the terminal window of an active session.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
session_id | string | Yes | — | Session ID |
rows | integer | Yes | — | New terminal height |
cols | integer | Yes | — | New terminal width |
Returns: rows, cols
Read output from a session.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
session_id | string | Yes | — | Session ID |
mode | string | No | auto | auto, stream, snapshot, or diff |
timeout | number | No | 2.0 | Settle timeout in seconds (stream/auto mode) |
strip_ansi | boolean | No | true | Strip ANSI escape sequences |
scrollback | integer | No | — | Lines of scrollback history (snapshot mode) |
truncation | string | No | config default | Truncation mode: tail, head_tail, tail_only, none |
Returns: output, bytes_read, prompt_detected, is_alive, truncated, tui_active, snapshot_available, mode_used, changed_lines (diff mode), is_first_read (diff mode), total_lines (scrollback), osc133, command_state, exit_code, command_complete (shell integration)
Terminate a session gracefully (EOF → SIGHUP → SIGKILL).
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | Session ID to close |
Returns: exit_status — or already_closed: true if the session was already terminated (idempotent)
Execute a command in a temporary session and return output. The session is automatically cleaned up.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
exec | string | Yes | — | Command to execute |
command | string | No | bash | Shell to use |
timeout | number | No | 5.0 | Seconds to wait for output |
rows | integer | No | 24 | Terminal height |
cols | integer | No | 80 | Terminal width |
truncation | string | No | config default | Truncation mode: tail, head_tail, tail_only, none |
Returns: output, bytes_read, session_id, truncated
Send input and read output in a single call. Combines session_send + session_read to halve round trips. Optionally waits for a regex pattern in the output.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
session_id | string | Yes | — | Session ID |
input | string | No | — | Text to send |
press_enter | boolean | No | true | Append carriage return after input |
control_char | string | No | — | Control character: c d z l ] |
key | string | No | — | Special key (see session_send) |
password | string | No | — | Password or secret (not logged) |
wait_for | string | No | — | Regex pattern to wait for in output |
timeout | number | No | 5.0 | Seconds to wait for output |
strip_ansi | boolean | No | true | Strip ANSI escape sequences |
confirmed | boolean | No | false | Bypass dangerous command gate |
read_mode | string | No | stream | Read mode: auto, stream, snapshot, diff |
truncation | string | No | config default | Truncation mode: tail, head_tail, tail_only, none |
Returns: output, bytes_read, bytes_sent, matched (when wait_for used), prompt_detected, is_alive, truncated, tui_active, mode_used, snapshot_available
Read output from a session until a regex pattern matches or timeout expires. Use this instead of session_read when you know what output to expect.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
session_id | string | Yes | — | Session ID |
pattern | string | Yes | — | Regex pattern to wait for in output |
timeout | number | No | 30.0 | Max seconds to wait |
strip_ansi | boolean | No | true | Strip ANSI escape sequences |
truncation | string | No | config default | Truncation mode: tail, head_tail, tail_only, none |
Returns: output, bytes_read, matched, prompt_detected, is_alive, truncated
List all active sessions with their status and idle time.
Returns: sessions (array with tui_active, snapshot_available per session), count
flowchart LR
Client[AI Client] -->|MCP JSON-RPC| Server[terminal-mcp]
Server --> SM[Session Manager]
SM --> S1[PTY 1: bash]
SM --> S2[PTY 2: python3]
SM --> S3[PTY 3: ssh user@host]
S1 & S2 & S3 -.->|PTY output| Reader[Reader Thread]
Reader -.->|buffer| Server
stateDiagram-v2
[*] --> Active : session_create
state Active {
Idle --> Sending : session_send
Sending --> Idle
Idle --> Reading : session_read
Reading --> Idle
Idle --> Resizing : session_resize
Resizing --> Idle
}
Active --> [*] : session_close
Active --> [*] : idle_timeout
Each session is backed by a real PTY allocated via pexpect.spawn. The design has four main parts:
Background reader thread. A daemon thread continuously reads from the PTY file descriptor in 4096-byte chunks and appends bytes to an in-memory buffer. The thread is lock-protected and dies automatically when the child process exits.
Output settling (stream mode). session_read in stream mode polls the buffer until no new bytes have arrived for timeout seconds (default 2s), then returns everything written since the last read call. A hard ceiling of timeout + 10s prevents infinite blocking.
Snapshot mode (always on). All PTY output is fed into a pyte virtual screen buffer. In auto mode (the default), session_read automatically detects TUI applications via alternate screen buffer sequences and returns a rendered snapshot. The diff mode returns only changed lines since the last read, minimizing tokens for TUI monitoring.
Idle cleanup. SessionManager runs a background cleanup loop (every 60s by default) that closes sessions idle longer than their idle_timeout. The default timeout is 30 minutes. Concurrent sessions are capped at 10 by default.
All settings can be overridden via environment variables prefixed with TERMINAL_MCP_:
| Setting | Env Var | Default | Description |
|---|---|---|---|
max_sessions | TERMINAL_MCP_MAX_SESSIONS | 10 | Maximum concurrent sessions |
idle_timeout | TERMINAL_MCP_IDLE_TIMEOUT | 1800 | Seconds before auto-close |
default_rows | TERMINAL_MCP_DEFAULT_ROWS | 24 | Default terminal height |
default_cols | TERMINAL_MCP_DEFAULT_COLS | 80 | Default terminal width |
read_settle_timeout | TERMINAL_MCP_READ_SETTLE_TIMEOUT | 2.0 | Output settle timeout |
max_output_bytes | TERMINAL_MCP_MAX_OUTPUT_BYTES | 100000 | Max bytes per read |
cleanup_interval | TERMINAL_MCP_CLEANUP_INTERVAL | 60 | Seconds between cleanup |
max_buffer_bytes | TERMINAL_MCP_MAX_BUFFER_BYTES | 1000000 | Per-session PTY buffer cap in bytes |
safety_gate | TERMINAL_MCP_SAFETY_GATE | on | Dangerous command gate (off to disable) |
dangerous_patterns | TERMINAL_MCP_DANGEROUS_PATTERNS | built-in | Extra patterns (semicolon-separated regexes) |
truncation_mode | TERMINAL_MCP_TRUNCATION_MODE | tail | Default truncation strategy (tail, head_tail, tail_only, none) |
Per-session parameters: rows, cols, idle_timeout, and scrollback_lines can be set per session via session_create. There is no global env var for scrollback_lines — it defaults to 1000 lines per session. Set scrollback_lines=0 to disable scrollback history.
Since terminal-mcp runs as a subprocess of your MCP client, environment variables must be configured in the client's MCP server configuration — not in your shell profile (.bashrc, .zshrc, etc.).
Claude Code (~/.claude.json or project .mcp.json):
{
"mcpServers": {
"terminal": {
"command": "uvx",
"args": ["terminal-mcp"],
"env": {
"TERMINAL_MCP_MAX_SESSIONS": "20",
"TERMINAL_MCP_IDLE_TIMEOUT": "3600",
"TERMINAL_MCP_SAFETY_GATE": "off",
"TERMINAL_MCP_TRUNCATION_MODE": "head_tail"
}
}
}
}
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"terminal": {
"command": "uvx",
"args": ["terminal-mcp"],
"env": {
"TERMINAL_MCP_MAX_SESSIONS": "20",
"TERMINAL_MCP_IDLE_TIMEOUT": "3600"
}
}
}
}
VS Code / Cursor (.vscode/mcp.json):
{
"servers": {
"terminal-mcp": {
"command": "uvx",
"args": ["terminal-mcp"],
"env": {
"TERMINAL_MCP_MAX_SESSIONS": "20",
"TERMINAL_MCP_IDLE_TIMEOUT": "3600"
}
}
}
}
Windsurf (~/.codeium/windsurf/mcp_config.json):
{
"mcpServers": {
"terminal": {
"command": "uvx",
"args": ["terminal-mcp"],
"env": {
"TERMINAL_MCP_MAX_SESSIONS": "20"
}
}
}
}
You can also set env vars directly in your shell when running the server manually for testing:
TERMINAL_MCP_MAX_SESSIONS=20 TERMINAL_MCP_SAFETY_GATE=off uvx terminal-mcp
wait_for matching command echo — session_interact with wait_for no longer matches the echoed input text. Pattern matching now starts from a buffer position anchored before the send, so only genuinely new output is matched (fixes #6)wait_for matching stale buffer content — session_interact with wait_for no longer matches pre-existing unread buffer content (startup banners, previous command output). Uses a monotonic absolute byte counter that survives buffer trims (fixes #7)session_close — closing an already-closed or naturally-exited session now returns success: true, already_closed: true instead of a not_found error. Safe for finally-style cleanup (fixes #8)strip_ansi now handles Kitty keyboard protocol sequences, application keypad mode (ESC=/ESC>), DCS sequences, secondary DA responses, and tilde-terminated CSI sequences that leaked through during TUI exit transitions (fixes #9)is_alive race on Linux — is_alive property now uses exception-safe _is_alive() internally, preventing PtyProcessError when child processes exit before waitpid can reap them. Fixes flaky CI failures on Linux runnersESC[?1049h, ESC[?47h, ESC[?1047h) and switches session_read to snapshot mode. New mode="auto" is now the defaultsession_read with mode="diff" returns only changed screen lines since last read, with 1-indexed line numbers and is_first_read flagtruncate_output_smart() with four strategies: tail (keep beginning), head_tail (keep first 30% + last 70% with line-count marker), tail_only (keep end, ideal for build logs), none (disable truncation). Configurable via truncation parameter on all read tools or TERMINAL_MCP_TRUNCATION_MODE env varenable_snapshot=true). enable_snapshot parameter deprecatedread_mode on session_interact — choose how output is read back: auto, stream, snapshot, or diffread_snapshot() and read_diff() now acquire buffer lock before reading pyte screentui_active, snapshot_available, mode_used added to read responses; snapshot_available added to create and list responsessession_interact tool — send input and read output in a single MCP call, halving round trips. Supports all input types (text, keys, control chars, passwords) with optional wait_for regex patternsession_wait_for tool — block until a regex pattern matches in session output or timeout expires. Replaces fragile timeout-based reads when you know what to expectrm -rf, DROP TABLE, curl|sh, chmod 777, etc.) and returns requires_confirmation instead of executing. Resend with confirmed=true to proceed. 17 built-in patterns, extensible via TERMINAL_MCP_DANGEROUS_PATTERNS env var, disable with TERMINAL_MCP_SAFETY_GATE=offcommand_state, exit_code, and command_complete fields for precise command completion trackingTERMINAL_MCP_MAX_BUFFER_BYTES), prevents unbounded memory growth on long-running sessionsasyncio.to_thread(), unblocking the event loop for concurrent MCP requestsstrip_ansi parameter now correctly applied in snapshot and scrollback read modessession_exec now applies max_output_bytes truncation to prevent context overflowkill, systemd)close() now handles pexpect exceptions when child process is already reapedimport atexit from server.pymcp-name marker and server.json for official MCP registrymax_output_bytes (100KB default)TERMINAL_MCP_* env varspassword parameter on session_send for credentials (redacted from logs)pyte.HistoryScreen with configurable history depth; scrollback param on session_readpip install terminal-mcp via trusted publishing workflowkey parameter on session_sendinput, control_char, and key are now validated as mutually exclusivepip install -e ".[dev]"
pytest tests/ -v
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
pytest tests/ -vTERMINAL_MCP_MAX_SESSIONSMaximum concurrent sessions
TERMINAL_MCP_IDLE_TIMEOUTSeconds before auto-close (default 1800)
hovecapital/read-only-local-postgres-mcp-server
cocaxcode/database-mcp
io.github.infoinlet-marketplace/mcp-mysql
io.github.cybeleri/database-admin
io.github.yash-0620/postgres-mcp-secured