{
  "markdown": "# pty-mcp\n\n[![pty-mcp MCP server](https://glama.ai/mcp/servers/raychao-oao/pty-mcp/badges/score.svg)](https://glama.ai/mcp/servers/raychao-oao/pty-mcp)\n\nAn MCP (Model Context Protocol) server that gives AI agents interactive terminal sessions — local shells, SSH, serial ports, and persistent remote sessions that survive disconnects.\n\nBuilt for **sysadmins and network engineers** who want AI to help with real server and device management, not just code generation.\n\n![AI agent interacting with Telehack BBS via pty-mcp](docs/screenshots/telehack-cowsay.png)\n\n## Why\n\nAI agents run commands in non-interactive shells. They can't:\n- SSH into a server and interact with running processes\n- Connect to routers or switches via serial console\n- Monitor logs and react when a specific event occurs\n- Keep session state across multiple commands\n- Wait for a server to reboot and detect when it's back up\n\npty-mcp solves all of these by providing real PTY sessions over MCP.\n\nWithout pty-mcp, AI agents resort to `sleep 30 && check_status` loops — burning CPU cycles and API calls waiting for things to happen. With `wait_for`, the agent blocks server-side until the event occurs. Less polling, less energy, better for polar bears. 🐻‍❄️\n\n## Use Cases\n\n**Server administration**\n```\n# Reboot a server and wait until it's back online\ncreate_local_session(\"ping myserver\")\nread_output(wait_for: \"bytes from\", timeout: 300)\n→ blocks until server responds after reboot (~80s, one tool call)\n```\n\n**Network device management**\n```\n# Connect to a router via serial console\ncreate_serial_session(port: \"/dev/ttyUSB0\", baud: 9600)\nsend_input(\"show interfaces status\")\nread_output(wait_for: \"\\\\$\")\n```\n\n**Log monitoring and alerting**\n```\n# Watch logs and act when something happens\ncreate_ssh_session(host: \"prod\", user: \"admin\")\nsend_input(\"tail -f /var/log/app.log\")\nread_output(wait_for: \"ERROR|CRITICAL\", timeout: 3600)\n→ returns the error line + context when it appears\n```\n\n**Long-running tasks that survive disconnects**\n```\ncreate_ssh_session(host: \"server\", user: \"admin\", persistent: true)\nsend_input(\"apt upgrade -y\")\ndetach_session()          → close Claude Code, task continues\n# Reconnect later to check result\n```\n\n## Features\n\n| Feature | Description |\n|---------|-------------|\n| **Local terminal** | Interactive bash/python/node sessions on local machine |\n| **SSH sessions** | Connect to remote hosts with key/password auth, SSH config support |\n| **Serial port** | Connect to devices via serial (IoT, embedded, network gear) |\n| **Persistent sessions** | Sessions survive SSH disconnects via `ai-tmux` daemon |\n| **Attach/Detach** | Detach from a running session, reconnect later |\n| **Control keys** | Send ctrl+c, ctrl+d, arrow keys, tab, escape |\n| **Settle detection** | Waits for output to settle before returning (smart timeout) |\n| **Pattern matching** | `wait_for` blocks until a regex pattern appears in output (v0.2.0) |\n| **Bounded memory** | Ring buffer prevents OOM on long-running sessions (v0.2.0) |\n| **Resize terminal** | Resize the PTY window for local, SSH, serial, and persistent remote sessions (v0.10.0) |\n| **Audit log** | Optional voluntary operation log — record `send_input` commands to a collector for review and traceability (v0.8.0) |\n| **Audit redaction** | Credentials, auth headers, and PEM keys are automatically scrubbed before being written to the audit log (v0.10.0) |\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────┐\n│ AI Agent (Claude Code, etc.)                        │\n│                                                     │\n│  MCP Tools: create_local_session, send_input,       │\n│             send_control, read_output, close_session │\n└──────────────────────┬──────────────────────────────┘\n                       │ JSON-RPC stdio\n┌──────────────────────┴──────────────────────────────┐\n│ pty-mcp (MCP Server)                                │\n│                                                     │\n│  Session Manager                                    │\n│  ├── LocalSession  (local PTY via creack/pty)       │\n│  ├── SSHSession    (remote PTY via x/crypto/ssh)    │\n│  ├── SerialSession (serial port via go.bug.st)      │\n│  └── RemoteSession (persistent via ai-tmux)         │\n└─────────────────────────────────────────────────────┘\n\nPersistent mode (ai-tmux):\n\n  pty-mcp ──SSH──▶ ai-tmux client ──Unix socket──▶ ai-tmux server (daemon)\n                                                     ├── PTY: bash\n                                                     ├── PTY: ssh admin@router\n                                                     └── PTY: tail -f /var/log/syslog\n```\n\n## Quick Start\n\n### Claude Code Plugin (recommended)\n\nInstalls the binary automatically and registers the MCP server:\n\n```bash\nclaude plugin marketplace add raychao-oao/pty-mcp\nclaude plugin install pty-mcp@pty-mcp\n```\n\nRestart Claude Code — the binary downloads automatically on session start, then **restart once more** to activate it. No manual `claude mcp add` needed.\n\n**Updating:**\n\n```bash\nclaude plugin marketplace update pty-mcp\nclaude plugin update pty-mcp@pty-mcp\n```\n\nRestart Claude Code — the new binary downloads automatically on session start, then **restart once more** to apply the update.\n\n### Manual install\n\n**One-line install + register** (macOS / Linux / WSL2):\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/raychao-oao/pty-mcp/main/install.sh | sh\nclaude mcp add pty-mcp -- /usr/local/bin/pty-mcp\n```\n\nRestart Claude Code and the tools are available.\n\n<details>\n<summary><b>Other install methods</b></summary>\n\n**Download from GitHub Releases:**\n\nGo to [Releases](https://github.com/raychao-oao/pty-mcp/releases), download the binary for your platform, and make it executable:\n\n| Platform | Binary |\n|----------|--------|\n| macOS (Apple Silicon) | `pty-mcp-darwin-arm64` |\n| macOS (Intel) | `pty-mcp-darwin-amd64` |\n| Linux (x86_64) / WSL2 | `pty-mcp-linux-amd64` |\n| Linux (ARM64) | `pty-mcp-linux-arm64` |\n\n```bash\nchmod +x pty-mcp-*\nsudo mv pty-mcp-* /usr/local/bin/pty-mcp\nclaude mcp add pty-mcp -- /usr/local/bin/pty-mcp\n```\n\n**Build from source** (requires Go 1.25+):\n\n```bash\ngo install github.com/raychao-oao/pty-mcp@latest\nclaude mcp add pty-mcp -- $(go env GOPATH)/bin/pty-mcp\n```\n\n</details>\n\n### WSL2 Notes\n\npty-mcp works in WSL2 out of the box. Use the Linux binary:\n\n```bash\n# Inside WSL2\ncurl -fsSL https://raw.githubusercontent.com/raychao-oao/pty-mcp/main/install.sh | sh\nclaude mcp add pty-mcp -- /usr/local/bin/pty-mcp\n```\n\n### Optional: Install ai-tmux on remote servers\n\nFor persistent sessions that survive SSH disconnects, install `ai-tmux` on your remote server:\n\n```bash\n# Download for your server's architecture\ncurl -fsSL https://raw.githubusercontent.com/raychao-oao/pty-mcp/main/install.sh | sh\n# Or just copy the binary:\nscp /usr/local/bin/ai-tmux your-server:/usr/local/bin/ai-tmux\n```\n\n### Usage Examples\n\nOnce registered, the AI agent can use these MCP tools:\n\n**Local interactive shell:**\n```\ncreate_local_session()                    → {session_id, type: \"local\"}\nsend_input(session_id, \"cd /tmp && ls\")   → {output: \"...\", is_complete: true}\nsend_input(session_id, \"python3\")         → start Python REPL\nsend_input(session_id, \"print('hello')\")  → {output: \"hello\\n>>>\"}\nsend_control(session_id, \"ctrl+d\")        → exit Python\nclose_session(session_id)\n```\n\n**SSH to remote server:**\n```\ncreate_ssh_session(host: \"myserver\", user: \"admin\")\nsend_input(session_id, \"top\")\nsend_control(session_id, \"ctrl+c\")        → stop top\n```\n\n**Wait for pattern (v0.2.0):**\n```\ncreate_local_session(\"ping myserver\")\nread_output(session_id, wait_for: \"bytes from\", timeout: 300)\n→ blocks until server responds or 5 min timeout\n\nsend_input(session_id, \"docker-compose up\")\nread_output(session_id, wait_for: \"ready|error\", timeout: 60, context_lines: 3)\n→ returns matched line + 3 lines of context\n```\n\n**Send secret / password (v0.3.0):**\n```\n# AI detects a password prompt, calls send_secret instead of handling the password itself\ncreate_ssh_session(host: \"router\", user: \"admin\")\nread_output(session_id, wait_for: \"Password:\")   → session is waiting for input\n\nsend_secret(session_id, prompt: \"Router admin password:\")\n→ native GUI dialog appears on the operator's screen (macOS: system dialog,\n   WSL2: Windows Get-Credential, Linux: zenity/kdialog)\n→ operator types password — it is sent directly to the PTY session\n→ AI only sees: {success: true, length: 12}\n→ password never appears in AI context or logs\n```\n\n**Persistent session (survives SSH disconnect):**\n```\ncreate_ssh_session(host: \"server\", user: \"admin\", persistent: true)\nsend_input(session_id, \"make build\")      → start long build\ndetach_session(session_id)                → disconnect, build continues\n\n# Later (even after restart):\nlist_remote_sessions(host: \"server\", user: \"admin\")  → see running sessions\ncreate_ssh_session(host: \"server\", user: \"admin\", session_id: \"abc123\")  → reattach\nsend_input(session_id, \"echo $?\")         → check build result\n```\n\n## MCP Tools\n\n| Tool | Description |\n|------|-------------|\n| `create_local_session` | Start a local interactive terminal (bash, python3, node, etc.) |\n| `create_ssh_session` | SSH to a remote host (supports SSH config aliases) |\n| `create_serial_session` | Connect to a serial port device |\n| `send_input` | Send a command and wait for output to settle |\n| `read_output` | Read output, optionally wait for a pattern (`wait_for`, `timeout`, `context_lines`, `tail_lines`) |\n| `send_control` | Send control keys (ctrl+c, ctrl+d, arrows, tab, etc.) |\n| `send_secret` | Prompt the human operator for a secret via GUI dialog; sends it to the PTY session without exposing it to AI context or logs ¹ |\n| `list_sessions` | List all active sessions |\n| `close_session` | Close a session (terminates remote PTY) |\n| `detach_session` | Disconnect but keep remote PTY running |\n| `resize_session` | Resize the terminal window (rows/cols) for any session type |\n| `list_remote_sessions` | List persistent sessions on a remote host |\n\n> ¹ **`send_secret` platform support**: macOS uses a native password dialog (osascript). WSL2 uses `powershell.exe Get-Credential` (Windows GUI dialog). Linux with a display server uses `zenity` or `kdialog`. Headless Linux falls back to `/dev/tty`. If the operator doesn't respond within 60 seconds, the dialog is dismissed and the call returns a timeout error — it does not fall through to another dialog or wait again. Tool calls now run concurrently, so an unanswered dialog no longer blocks other sessions either way; the 60s bound exists so a `send_secret` call itself doesn't sit open indefinitely, and cancelling it (e.g. pressing ESC in Claude Code) dismisses the dialog immediately instead of waiting out the timeout.\n\n## Audit Log\n\npty-mcp includes an optional audit log feature that records every `send_input` command to a central collector. This lets teams review and trace what AI agents did during a session.\n\n> **Important:** This is a **voluntary, self-reporting** operation log. It relies on operators choosing to enable it and run the collector. Because pty-mcp runs on the operator's own machine, there is no technical mechanism to enforce logging — a non-compliant operator could simply run pty-mcp without audit enabled. This feature provides traceability for teams that *want* it, but **it is not a substitute for system-level audit tools** (e.g., auditd, syslog forwarding, SSH session recording) in environments where audit compliance is required.\n\n### What it records\n\n- Timestamp, operator identity, session ID, session type (local/ssh/serial), target host\n- The exact input sent via `send_input` (including `raw=true` inputs like menu selections)\n- Output snippet (first 2 KB) after each command\n- A `cmd_id` linking the command to its output\n\n`send_secret` is **never logged** — secrets entered via the GUI dialog do not appear in the audit log.\n\nCommands and output snippets are **automatically redacted** before being written. The following patterns are replaced with `[REDACTED]` or `[PRIVATE KEY REDACTED]`:\n- Key-value credentials: `password=`, `passwd:`, `token=`, `api_key=`, `access_key=`, `auth_token=`, `secret=`\n- HTTP Authorization headers: `Authorization: Bearer …`, `Authorization: Basic …`, `Authorization: Token …`\n- PEM private key blocks: `-----BEGIN RSA PRIVATE KEY-----` / `-----BEGIN OPENSSH PRIVATE KEY-----`\n\n### Setup\n\n**Each operator runs once** to create their config and generate a token:\n\n```bash\npty-mcp audit init\n```\n\nThis creates `~/.config/pty-mcp/config` (chmod 600) with a randomly generated token and prints the token to share with the collector admin.\n\n**The collector admin** starts the server (using the token from init output):\n\n```bash\nPTY_MCP_AUDIT_TOKEN=<token-from-init> \\\n  pty-mcp audit serve --port 9099 --log /var/log/pty-mcp-audit.jsonl\n```\n\n**Enable audit** after setting the collector URL in the config:\n\n```bash\n# Edit config and set: audit-url=http://your-collector:9099\npty-mcp audit enable\n# Restart Claude Code to apply\n```\n\nTo temporarily stop logging without losing your config:\n\n```bash\npty-mcp audit disable\n```\n\nOperators without a config file are unaffected — audit is off by default.\n\n### Audit modes\n\n| Mode | Behaviour |\n|------|-----------|\n| `best-effort` (default) | Commands execute regardless of whether the log was written; entries are queued and retried in the background |\n| `strict` | `send_input` is rejected if the audit entry cannot be delivered; use when logging is a team policy requirement |\n\n### Reviewing logs\n\nLogs are stored as JSONL (one JSON object per line), readable with standard tools:\n\n```bash\n# All commands by operator ray\ngrep '\"user\":\"ray\"' /var/log/pty-mcp-audit.jsonl | jq .\n\n# Commands sent to a specific host\njq 'select(.target == \"root@prod01\")' /var/log/pty-mcp-audit.jsonl\n```\n\n## ai-tmux: Persistent Terminal Daemon\n\n`ai-tmux` is a lightweight daemon that runs on remote servers, keeping PTY sessions alive across SSH disconnects. Think of it as tmux designed for AI agents.\n\n### Install on remote server\n\n```bash\n# Cross-compile for Linux\nGOOS=linux GOARCH=amd64 go build -o ai-tmux-linux ./cmd/ai-tmux/\n\n# Copy to server\nscp ai-tmux-linux server:~/ai-tmux\nssh server \"chmod +x ~/ai-tmux && sudo mv ~/ai-tmux /usr/local/bin/ai-tmux\"\n```\n\n### How it works\n\n- `ai-tmux server` — daemon mode, listens on Unix socket, manages PTY sessions\n- `ai-tmux client` — bridge mode, forwards JSON protocol over stdin/stdout (used by pty-mcp over SSH)\n- `ai-tmux list` — list active sessions\n\nThe daemon auto-starts when pty-mcp connects with `persistent: true`. Sessions are reaped after 30 minutes of inactivity.\n\npty-mcp checks `ai-tmux --version` on the remote host before opening a session and returns a clear error if the binary is missing or below the minimum required version — no cryptic connection errors.\n\n## SSH Config Support\n\npty-mcp reads `~/.ssh/config` to resolve host aliases:\n\n```\n# ~/.ssh/config\nHost myserver\n    HostName 192.168.1.100\n    User admin\n    Port 2222\n    IdentityFile ~/.ssh/id_ed25519\n```\n\n```\ncreate_ssh_session(host: \"myserver\", user: \"admin\")\n# Automatically resolves hostname, port, and identity file\n```\n\n## Requirements\n\n- Go 1.25+\n- For serial: appropriate device permissions\n- For persistent sessions: `ai-tmux` binary on remote server\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history.\n\n## License\n\nMIT\n",
  "bytes": 15442,
  "sha": "39b9c715f32c0d0477fb314c5b0ba01ae869cdae2a5d29a24aa8f2e8c28c6d48",
  "repo_slug": "raychao-oao/pty-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_raychao_oao_pty_mcp_pty_mcp_035afb23/readme"
}