# PTY MCP Server
An MCP (Model Context Protocol) server that provides interactive PTY (pseudo-terminal) sessions for AI assistants like Claude Code. This enables interaction with programs that require full terminal emulation such as vim, ssh, less, top, interactive REPLs, and more.
## LLM Deployment Instructions
This section provides complete step-by-step instructions for an LLM to deploy this MCP server autonomously.
### Prerequisites
Before starting, ensure the system has:
- Node.js >= 18.0.0 (`node --version` to check)
- npm (comes with Node.js)
- Build tools for native modules
To check/install prerequisites:
```bash
# Check Node.js version
node --version # Should be >= 18.0.0
# If Node.js is missing or outdated, install via nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
source ~/.bashrc
nvm install 20
nvm use 20
# Install build tools (required for node-pty native module)
# Ubuntu/Debian:
sudo apt-get update && sudo apt-get install -y python3 make g++
# macOS:
xcode-select --install
# Fedora/RHEL:
sudo dnf install -y python3 make gcc-c++
```
### Step 1: Clone the Repository
```bash
# Create MCP servers directory if it doesn't exist
mkdir -p ~/mcp-servers
cd ~/mcp-servers
# Clone the repository
git clone https://github.com/dblmca/pty-mcp.git
cd pty-mcp
```
### Step 2: Install Dependencies
```bash
npm install
```
This installs:
- `@modelcontextprotocol/sdk` - The MCP SDK for server implementation
- `node-pty` - Native PTY bindings (requires build tools)
**Troubleshooting `node-pty` build failures:**
```bash
# If npm install fails with node-pty errors, install build dependencies first
sudo apt-get install -y python3 make g++ # Ubuntu/Debian
# Then retry
npm install
```
### Step 3: Register with Claude Code
**CRITICAL**: Claude Code does NOT use `~/.claude/.mcp.json` directly. You MUST use the CLI:
```bash
# Add to user config (available in all projects)
claude mcp add -s user pty-mcp -- node ~/mcp-servers/pty-mcp/server.js
```
If using a different installation path, adjust accordingly:
```bash
claude mcp add -s user pty-mcp -- node /full/path/to/pty-mcp/server.js
```
### Step 4: Verify Installation
```bash
# List registered MCP servers
claude mcp list
# Should show pty-mcp with status
```
### Step 5: Test the Server
After restarting Claude Code, test with:
```
1. Call pty_spawn with no arguments to create a shell session
2. Call pty_read to see the shell prompt
3. Call pty_write with input: "echo hello\r"
4. Call pty_read to see the output
5. Call pty_kill to clean up
```
---
## Tools Reference
### `pty_spawn`
Create a new interactive PTY session.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `command` | string | No | User's shell | Command to run |
| `args` | string[] | No | [] | Command arguments |
| `cwd` | string | No | Current dir | Working directory |
| `cols` | number | No | 120 | Terminal columns (max: 500) |
| `rows` | number | No | 30 | Terminal rows (max: 200) |
| `env` | object | No | {} | Additional environment variables |
**Returns:**
```json
{
"session_id": "abc123def456...",
"pid": 12345,
"command": "/bin/bash",
"cols": 120,
"rows": 30
}
```
### `pty_write`
Send input to a PTY session. Supports escape sequences.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `session_id` | string | Yes | Session ID from `pty_spawn` |
| `input` | string | Yes | Input to send |
**Escape Sequences:**
| Sequence | Key | Use Case |
|----------|-----|----------|
| `\r` | Enter | Execute commands |
| `\n` | Newline | Multi-line input |
| `\t` | Tab | Autocomplete |
| `\x03` | Ctrl-C | Interrupt/cancel |
| `\x04` | Ctrl-D | EOF/exit |
| `\x1b` | Escape | Exit modes (vim, less) |
| `\x1b[A` | Arrow Up | History/navigation |
| `\x1b[B` | Arrow Down | History/navigation |
| `\x1b[C` | Arrow Right | Cursor movement |
| `\x1b[D` | Arrow Left | Cursor movement |
**Returns:**
```json
{ "written": 5 }
```
### `pty_read`
Read buffered output from a PTY session.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `session_id` | string | Yes | - | Session ID |
| `timeout_ms` | number | No | 100 | Wait time for output (max: 5000) |
| `clear_buffer` | boolean | No | true | Clear buffer after reading |
**Returns:**
```json
{
"output": "user@host:~$ ",
"exited": false,
"exit_code": null,
"signal": null
}
```
### `pty_resize`
Resize a PTY terminal for full-screen applications.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `session_id` | string | Yes | Session ID |
| `cols` | number | Yes | New columns (max: 500) |
| `rows` | number | Yes | New rows (max: 200) |
### `pty_kill`
Terminate a PTY session and clean up resources.
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `session_id` | string | Yes | - | Session ID |
| `signal` | string | No | SIGHUP | Signal: SIGHUP, SIGTERM, SIGKILL, SIGINT |
### `pty_list`
List all active PTY sessions. No parameters required.
---
## Usage Examples
### SSH Session
```
1. pty_spawn { command: "ssh", args: ["user@192.168.1.100"] }
2. pty_read { session_id: "...", timeout_ms: 5000 } # Wait for prompt/password
3. pty_write { session_id: "...", input: "password\r" } # If password needed
4. pty_read { session_id: "..." } # Get remote prompt
5. pty_write { session_id: "...", input: "hostname\r" } # Run commands
6. pty_read { session_id: "..." }
7. pty_write { session_id: "...", input: "exit\r" } # Disconnect
8. pty_kill { session_id: "..." } # Cleanup
```
### Python REPL
```
1. pty_spawn { command: "python3" }
2. pty_read { session_id: "..." } # ">>> "
3. pty_write { session_id: "...", input: "import math\r" }
4. pty_write { session_id: "...", input: "print(math.pi)\r" }
5. pty_read { session_id: "..." } # "3.141592653589793\n>>> "
6. pty_write { session_id: "...", input: "\x04" } # Ctrl-D to exit
```
### Vim Editing
```
1. pty_spawn { command: "vim", args: ["file.txt"] }
2. pty_read { session_id: "...", timeout_ms: 1000 } # Wait for vim
3. pty_write { session_id: "...", input: "i" } # Insert mode
4. pty_write { session_id: "...", input: "Hello, World!" }
5. pty_write { session_id: "...", input: "\x1b" } # Escape
6. pty_write { session_id: "...", input: ":wq\r" } # Save and quit
7. pty_kill { session_id: "..." }
```
### Interactive Program (top, htop)
```
1. pty_spawn { command: "top" }
2. pty_read { session_id: "...", timeout_ms: 2000 } # Get display
3. pty_write { session_id: "...", input: "q" } # Quit top
# Or: pty_write { session_id: "...", input: "\x03" } # Ctrl-C
```
---
## Configuration
| Setting | Value | Description |
|---------|-------|-------------|
| Max sessions | 10 | Maximum concurrent PTY sessions |
| Session timeout | 30 minutes | Idle sessions auto-cleanup |
| Buffer size | 1 MB | Max output buffer per session |
| Input size | 1 MB | Max input per write |
| Default terminal | 120x30 | Default cols x rows |
| Max terminal | 500x200 | Maximum cols x rows |
---
## Troubleshooting
### Server not found after registration
```bash
# Verify registration
claude mcp list
# If missing, re-add
claude mcp add -s user pty-mcp -- node ~/mcp-servers/pty-mcp/server.js
# Restart Claude Code
```
### node-pty build fails
```bash
# Install build dependencies
sudo apt-get install -y python3 make g++
# Clear npm cache and retry
rm -rf node_modules package-lock.json
npm install
```
### Session commands not working
1. Ensure you're using the correct `session_id` from `pty_spawn`
2. Check if session exited: `pty_list` or check `exited` field in `pty_read`
3. For password prompts, use longer `timeout_ms` (e.g., 5000)
### Output contains escape codes
Terminal output includes ANSI escape sequences for colors and formatting. This is normal behavior for terminal applications.
---
## Security
- Commands restricted to standard system paths
- Shell metacharacters rejected in command names
- Environment variable names validated
- Input/buffer sizes limited
- Sessions auto-expire after 30 minutes
---
## Claude Code Integration (CLAUDE.md snippet)
Add this to your project's `CLAUDE.md` file to help Claude Code understand how to use this MCP server:
```markdown
### PTY MCP Server (Interactive Terminal)
The `pty-mcp` server provides interactive terminal (PTY) sessions for programs that require full terminal emulation (vim, ssh, less, top, interactive REPLs, etc.).
**Location:** `~/mcp-servers/pty-mcp/`
**When to Use:**
- Interactive programs that need terminal emulation (vim, nano, less, top, htop)
- SSH sessions
- Interactive REPLs (python, node, irb)
- Programs that require Ctrl-C, Ctrl-D, or arrow keys
- Any program that doesn't work well with the standard Bash tool
**MCP Tools:**
- `pty_spawn` - Create a new PTY session (returns session_id)
- `pty_write` - Send input to a session (supports escape sequences)
- `pty_read` - Read buffered output from a session
- `pty_resize` - Resize terminal dimensions
- `pty_kill` - Kill a session
- `pty_list` - List all active sessions
**Escape Sequences for `pty_write`:**
| Sequence | Meaning |
|----------|---------|
| `\r` | Enter/Return |
| `\x03` | Ctrl-C (interrupt) |
| `\x04` | Ctrl-D (EOF) |
| `\x1b` | Escape |
| `\x1b[A` | Arrow Up |
| `\x1b[B` | Arrow Down |
| `\x1b[C` | Arrow Right |
| `\x1b[D` | Arrow Left |
**Usage Examples:**
\`\`\`
# Basic shell session
1. pty_spawn {} → { session_id: "abc123" }
2. pty_read { session_id: "abc123" } → { output: "$ " }
3. pty_write { session_id: "abc123", input: "ls -la\r" }
4. pty_read { session_id: "abc123" } → { output: "..." }
5. pty_kill { session_id: "abc123" }
# Python REPL
1. pty_spawn { command: "python3" }
2. pty_write { session_id: "...", input: "x = 42\r" }
3. pty_write { session_id: "...", input: "print(x * 2)\r" }
4. pty_read { session_id: "..." } → { output: "84\n>>> " }
5. pty_write { session_id: "...", input: "\x04" } # Ctrl-D to exit
# Vim editing
1. pty_spawn { command: "vim", args: ["file.txt"] }
2. pty_write { session_id: "...", input: "i" } # Insert mode
3. pty_write { session_id: "...", input: "Hello!" }
4. pty_write { session_id: "...", input: "\x1b" } # Escape
5. pty_write { session_id: "...", input: ":wq\r" } # Save and quit
\`\`\`
**Configuration:**
- Max 10 concurrent sessions
- 30-minute idle timeout (auto-cleanup)
- 1MB output buffer per session
- Default terminal: 120x30
```
---
## License
MIT