Introducing PTY-MCP — The MCP Server That Makes Terminals Interactive
An open-source MCP server that solves the fundamental limitation of AI agents: they can only run non-interactive commands. PTY-MCP gives AI agents a real PTY session — local shell, SSH, serial port, and persistent sessions that survive disconnects.
What is PTY-MCP?
PTY-MCP is an MCP (Model Context Protocol) server that gives AI agents interactive terminal sessions. Unlike standard shell tools that run a single command and exit, PTY-MCP maintains a live PTY (pseudo-terminal) — the same kind of terminal emulation that makes SSH, Python REPL, and router CLIs work.
When an AI agent needs to do something that requires interaction — typing a password, navigating a menu, watching a long-running process — PTY-MCP is the answer.
MCP Tools
| Tool | Description |
|---|---|
| create_local_session | Start a local interactive shell (bash, python3, node, etc.). Supports log_file, log_max_size, log_max_files for log rotation. |
| create_ssh_session | SSH to a remote host (supports SSH config aliases). Same log options as local session. |
| create_serial_session | Connect to a serial port device (IoT, embedded systems, network gear). Same log options. |
| send_input | Send input to the session. raw=true skips newline (for single-char menus). wait_for/wait_for_timeout combines send+wait in one call (v0.7.0). Returns cursor_start/cursor_end and timed_out. |
| read_output | Read session output. wait_for blocks until a regex pattern appears. since_cursor for incremental reads. max_bytes for chunked reads with has_more. |
| send_secret | Prompts the human operator with a native password dialog. The password is sent directly to the PTY — the AI never sees it. Returns {"success": true, "length": N}. |
| prepare_secret | Pre-stages a password before it's needed. Shows the dialog immediately and buffers the credential in the session. auto_send: true fires when password_prompt is detected. line_ending controls \r/\r\n/\n per device. Never logged. (v0.9.0) |
| get_session_state | Returns session state without reading raw output: state (at_prompt / password_prompt / confirmation / pager / running / unknown), awaiting_secret, last_prompt. |
| resize_session | Resize the terminal window (rows / cols) for any session type. Validated to ≤ 500 rows / ≤ 1000 cols. (v0.10.0) |
| get_credential_bundle | Generate a signed HPKE ConsumerBundle (Ed25519 identity key + ephemeral X25519 session key). Pass this to cred-mcp; it seals the credential and returns a SealedBox. Plaintext never reaches the AI. (v0.11.0) |
| inject_secret | Receive a SealedBox from cred-mcp, HPKE-decrypt locally, write directly to the PTY via WriteRaw, zero memory. Returns {"success":true} — the AI never sees the plaintext. Session keys are single-use. (v0.11.0) |
| send_control | Send control keys: ctrl+c, ctrl+d, arrow keys, tab, escape. |
| list_sessions | List all active sessions. |
| close_session | Close a session and terminate the PTY. |
| detach_session | Disconnect from a session while keeping the remote PTY running. |
| list_remote_sessions | List persistent sessions on a remote host. status param for filtering. |
We Need an Interactive Terminal for AI Agents
All AI coding agents (Claude Code, Cursor, Copilot) run inside a non-interactive shell. This means they cannot:
- Send input to a running process or press ctrl+c
- Use REPLs like python3, node, or psql
- Maintain state across commands (cd, export, alias all reset)
- Reconnect to a remote session after disconnect
PTY-MCP solves all of these by giving the AI agent a real PTY session.
The Conversation That Needs PTY-MCP
Tools, and Why They Exist
send_secret — The AI should not see your password
The problem: the AI needs to type a password (sudo, SSH, enable mode), but you don't want the password in the AI's context or logs. send_secret solves this by popping a native OS password dialog on the operator's screen. The password goes directly into the PTY. The AI only receives {"success": true, "length": 12}.
get_session_state — Ask what the terminal is waiting for
Before sending input, the AI needs to know what the terminal is doing. Parsing raw output is fragile. get_session_state runs a classifier on the last 2KB of output and returns a structured answer: at_prompt, password_prompt, confirmation, pager, running, or unknown.
wait_for — Stop polling, start waiting
Without wait_for, AI agents use sleep 30 && check_status loops — burning CPU cycles and API tokens waiting for things to happen. wait_for blocks server-side until a regex pattern appears in the output. Less polling, less energy, better for polar bears. 🐻❄️
raw=true — For menus that don't want Enter
Some CLIs (Sophos XG, BIOS menus, router selection screens) expect a single character — pressing Enter breaks the flow. send_input with raw=true sends input without appending a newline.
log_file + log rotation — Full record of long-running tasks
For deployments, builds, and audit sessions, every line of PTY output is written to a log file in real time. Add log_max_size and log_max_files to rotate automatically — no unbounded log growth.
audit log — know what your agent did
When an AI agent runs commands on your server, you may need a full record of what was sent and what came back — for compliance, debugging, or simply understanding what happened. v0.8.0 adds a built-in audit log system.
pty-mcp audit serve starts an HTTP log collector on your server. Every send_input call generates two log entries: one before execution (the command), one after (the output). send_secret is never logged — passwords stay out of the audit trail by design.
strict (stop if audit fails — for compliance environments) or best-effort (log when possible, continue either way). Set via --audit-mode flag or config file.
audit log credential redaction — sensitive data stays out of logs (v0.10.0)
Audit logs are only useful if they're safe to store. v0.10.0 adds automatic scrubbing before any command or output is written to the audit log. Patterns like password=, token=, api_key=, Authorization: Bearer/Basic/Token headers, and PEM private key blocks are replaced with [REDACTED] or [PRIVATE KEY REDACTED]. You get a full operation record without credential exposure.
resize_session — make full-screen tools work correctly (v0.10.0)
Tools like vim, htop, tmux, and router CLIs render based on terminal dimensions. If the AI agent opens a session with default dimensions, these tools look broken. resize_session lets the agent set rows and cols to match the actual display, for all session types (local, SSH, serial, persistent).
get_credential_bundle + inject_secret — credentials sealed end-to-end, plaintext never in context (v0.11.0)
The problem: the AI needs to authenticate to a system (SSH password, sudo, enable mode), but send_secret requires a human at the keyboard. What if you want fully automated credential injection — no human in the loop — without the password ever appearing in the LLM context?
v0.11.0 adds a first-class HPKE credential delivery protocol that integrates with cred-mcp. The AI generates a cryptographic identity and a one-time session key, passes the public bundle to cred-mcp, receives back an encrypted SealedBox, and hands that to pty-mcp for local decryption and direct PTY injection. The LLM context only ever contains ciphertext.
Session keys are single-use: a second inject_secret call with the same session_id always errors, preventing replay. Audit events (bundle_generated, secret_injected, inject_failed) are sent to the audit collector — no plaintext, ciphertext, or key material is logged.
persistent + detach — Start a task, come back later
SSH connections drop. Long builds take hours. With persistent: true, PTY-MCP routes through ai-tmux — a daemon on the remote server that keeps the PTY alive after disconnect. Detach, close Claude Code, come back tomorrow.
Installation
Tested on: macOS, WSL, Linux with KDE Plasma. Binaries are published for other platforms, but those are untested on real hardware — the GUI password dialog in particular varies by desktop environment (kdialog on KDE, zenity on GNOME).